# SPDX-License-Identifier: GPL-2.0-or-later # ---------------------------------------------------------- # Automatic generation of columns # Author: Antonio Vazquez (antonioya) # # ---------------------------------------------------------- # noinspection PyUnresolvedReferences import bpy from math import cos, sin, radians, atan, sqrt from bpy.props import BoolProperty, IntProperty, FloatProperty, FloatVectorProperty from .achm_tools import * # ------------------------------------------------------------------ # Define UI class # Columns # ------------------------------------------------------------------ class ARCHIMESH_OT_Column(bpy.types.Operator): bl_idname = "mesh.archimesh_column" bl_label = "Column" bl_description = "Columns Generator" bl_category = 'View' bl_options = {'REGISTER', 'UNDO'} # Define properties model: bpy.props.EnumProperty( name="Model", items=( ('1', "Circular", ""), ('2', "Rectangular", ""), ), description="Type of column", ) keep_size: BoolProperty( name="Keep radius equal", description="Keep all radius (top, mid and bottom) to the same size", default=True, ) rad_top: FloatProperty( name='Top radius', min=0.001, max=10, default=0.15, precision=3, description='Radius of the column in the top', ) rad_mid: FloatProperty( name='Middle radius', min=0.001, max=10, default=0.15, precision=3, description='Radius of the column in the middle', ) shift: FloatProperty( name='', min=-1, max=1, default=0, precision=3, description='Middle displacement', ) rad_bottom: FloatProperty( name='Bottom radius', min=0.001, max=10, default=0.15, precision=3, description='Radius of the column in the bottom', ) col_height: FloatProperty( name='Total height', min=0.001, max=10, default=2.4, precision=3, description='Total height of column, including bases and tops', ) col_sx: FloatProperty( name='X size', min=0.001, max=10, default=0.30, precision=3, description='Column size for x axis', ) col_sy: FloatProperty( name='Y size', min=0.001, max=10, default=0.30, precision=3, description='Column size for y axis', ) cir_base: BoolProperty( name="Include circular base", description="Include a base with circular form", default=False, ) cir_base_r: FloatProperty( name='Radio', min=0.001, max=10, default=0.08, precision=3, description='Rise up radio of base', ) cir_base_z: FloatProperty( name='Height', min=0.001, max=10, default=0.05, precision=3, description='Size for z axis', ) cir_top: BoolProperty( name="Include circular top", description="Include a top with circular form", default=False, ) cir_top_r: FloatProperty( name='Radio', min=0.001, max=10, default=0.08, precision=3, description='Rise up radio of top', ) cir_top_z: FloatProperty( name='Height', min=0.001, max=10, default=0.05, precision=3, description='Size for z axis', ) box_base: BoolProperty( name="Include rectangular base", description="Include a base with rectangular form", default=True, ) box_base_x: FloatProperty( name='X size', min=0.001, max=10, default=0.40, precision=3, description='Size for x axis', ) box_base_y: FloatProperty( name='Y size', min=0.001, max=10, default=0.40, precision=3, description='Size for y axis', ) box_base_z: FloatProperty( name='Height', min=0.001, max=10, default=0.05, precision=3, description='Size for z axis', ) box_top: BoolProperty( name="Include rectangular top", description="Include a top with rectangular form", default=True, ) box_top_x: FloatProperty( name='X size', min=0.001, max=10, default=0.40, precision=3, description='Size for x axis', ) box_top_y: FloatProperty( name='Y size', min=0.001, max=10, default=0.40, precision=3, description='Size for y axis', ) box_top_z: FloatProperty( name='Height', min=0.001, max=10, default=0.05, precision=3, description='Size for z axis', ) arc_top: BoolProperty( name="Create top arch", description="Include an arch in the top of the column", default=False, ) arc_radio: FloatProperty( name='Arc Radio', min=0.001, max=10, default=1, precision=1, description='Radio of the arch', ) arc_width: FloatProperty( name='Thickness', min=0.01, max=10, default=0.15, precision=2, description='Thickness of the arch wall', ) arc_gap: FloatProperty( name='Arc gap', min=0.01, max=10, default=0.25, precision=2, description='Size of the gap in the arch sides', ) crt_mat: BoolProperty( name="Create default Cycles materials", description="Create default materials for Cycles render", default=True, ) crt_array: BoolProperty( name="Create array of elements", description="Create a modifier array for all elemnst", default=False, ) array_num_x: IntProperty( name='Count X', min=0, max=100, default=3, description='Number of elements in array', ) array_space_x: FloatProperty( name='Distance X', min=0.000, max=10, default=1, precision=3, description='Distance between elements (only arc disabled)', ) array_num_y: IntProperty( name='Count Y', min=0, max=100, default=0, description='Number of elements in array', ) array_space_y: FloatProperty( name='Distance Y', min=0.000, max=10, default=1, precision=3, description='Distance between elements (only arc disabled)', ) array_space_z: FloatProperty( name='Distance Z', min=-10, max=10, default=0, precision=3, description='Combined X/Z distance between elements (only arc disabled)', ) ramp: BoolProperty( name="Deform", description="Deform top base with Z displacement", default=True, ) array_space_factor: FloatProperty( name='Move Y center', min=0.00, max=1, default=0.0, precision=3, description='Move the center of the arch in Y axis. (0 centered)', ) # ----------------------------------------------------- # Draw (create UI interface) # ----------------------------------------------------- # noinspection PyUnusedLocal def draw(self, context): layout = self.layout space = bpy.context.space_data if not space.local_view: # Imperial units warning if bpy.context.scene.unit_settings.system == "IMPERIAL": row = layout.row() row.label(text="Warning: Imperial units not supported", icon='COLOR_RED') box = layout.box() box.prop(self, 'model') # Circular if self.model == "1": box.prop(self, 'keep_size') box.prop(self, 'rad_top') if self.keep_size is False: row = box.row() row.prop(self, 'rad_mid') row.prop(self, 'shift') box.prop(self, 'rad_bottom') # Rectangular if self.model == "2": box.prop(self, 'col_sx') box.prop(self, 'col_sy') box.prop(self, 'col_height') box = layout.box() box.prop(self, 'box_base') if self.box_base is True: row = box.row() row.prop(self, 'box_base_x') row.prop(self, 'box_base_y') row.prop(self, 'box_base_z') box = layout.box() box.prop(self, 'box_top') if self.box_top is True: row = box.row() row.prop(self, 'box_top_x') row.prop(self, 'box_top_y') row.prop(self, 'box_top_z') box = layout.box() box.prop(self, 'cir_base') if self.cir_base is True: row = box.row() row.prop(self, 'cir_base_r') row.prop(self, 'cir_base_z') box = layout.box() box.prop(self, 'cir_top') if self.cir_top is True: row = box.row() row.prop(self, 'cir_top_r') row.prop(self, 'cir_top_z') box = layout.box() box.prop(self, 'arc_top') if self.arc_top is True: row = box.row() row.prop(self, 'arc_radio') row.prop(self, 'arc_width') row = box.row() row.prop(self, 'arc_gap') row.prop(self, 'array_space_factor') box = layout.box() box.prop(self, 'crt_array') if self.crt_array is True: row = box.row() row.prop(self, 'array_num_x') row.prop(self, 'array_num_y') if self.arc_top is True: box.label(text="Use arch radio and thickness to set distances") if self.arc_top is False: row = box.row() row.prop(self, 'array_space_x') row.prop(self, 'array_space_y') row = box.row() row.prop(self, 'array_space_z') row.prop(self, 'ramp') box = layout.box() if not context.scene.render.engine in {'CYCLES', 'BLENDER_EEVEE'}: box.enabled = False box.prop(self, 'crt_mat') else: row = layout.row() row.label(text="Warning: Operator does not work in local view mode", icon='ERROR') # ----------------------------------------------------- # Execute # ----------------------------------------------------- # noinspection PyUnusedLocal def execute(self, context): if bpy.context.mode == "OBJECT": create_column_mesh(self) return {'FINISHED'} else: self.report({'WARNING'}, "Archimesh: Option only valid in Object mode") return {'CANCELLED'} # ------------------------------------------------------------------------------ # Generate mesh data # All custom values are passed using self container (self.myvariable) # ------------------------------------------------------------------------------ def create_column_mesh(self): myarc = None cir_top = None cir_bottom = None box_top = None box_bottom = None mycolumn = None # deactivate others for o in bpy.data.objects: if o.select_get() is True: o.select_set(False) bpy.ops.object.select_all(action='DESELECT') radio_top = self.rad_top if self.keep_size is True: radio_mid = radio_top radio_bottom = radio_top else: radio_mid = self.rad_mid radio_bottom = self.rad_bottom # Calculate height height = self.col_height if self.box_base: height = height - self.box_base_z if self.box_top: height = height - self.box_top_z # ------------------------ # Create circular column # ------------------------ if self.model == "1": bpy.ops.object.select_all(action='DESELECT') mycolumn = create_circular_column(self, "Column", radio_top, radio_mid, radio_bottom, height) mycolumn.select_set(True) bpy.context.view_layer.objects.active = mycolumn # Subsurf set_smooth(mycolumn) set_modifier_subsurf(mycolumn) # ------------------------ # Create rectangular column # ------------------------ if self.model == "2": mycolumn = create_rectangular_base(self, "Column", self.col_sx, self.col_sy, height) bpy.ops.object.select_all(action='DESELECT') mycolumn.select_set(True) bpy.context.view_layer.objects.active = mycolumn set_normals(mycolumn) # ------------------------ # Circular base # ------------------------ if self.cir_base is True: cir_bottom = create_torus("Column_cir_bottom", radio_bottom, self.cir_base_r, self.cir_base_z) bpy.ops.object.select_all(action='DESELECT') cir_bottom.select_set(True) bpy.context.view_layer.objects.active = cir_bottom set_modifier_subsurf(cir_bottom) set_smooth(cir_bottom) cir_bottom.location.x = 0.0 cir_bottom.location.y = 0.0 cir_bottom.location.z = self.cir_base_z / 2 cir_bottom.parent = mycolumn # ------------------------ # Rectangular base # ------------------------ if self.box_base is True: box_bottom = create_rectangular_base(self, "Column_box_bottom", self.box_base_x, self.box_base_y, self.box_base_z) bpy.ops.object.select_all(action='DESELECT') box_bottom.select_set(True) bpy.context.view_layer.objects.active = box_bottom box_bottom.parent = mycolumn set_normals(box_bottom) box_bottom.location.x = 0.0 box_bottom.location.y = 0.0 box_bottom.location.z = - self.box_base_z # move column mycolumn.location.z += self.box_base_z # ------------------------ # Circular top # ------------------------ if self.cir_top is True: cir_top = create_torus("Column_cir_top", radio_top, self.cir_top_r, self.cir_top_z) bpy.ops.object.select_all(action='DESELECT') cir_top.select_set(True) bpy.context.view_layer.objects.active = cir_top set_modifier_subsurf(cir_top) set_smooth(cir_top) cir_top.parent = mycolumn cir_top.location.x = 0.0 cir_top.location.y = 0.0 cir_top.location.z = height - self.cir_top_z / 2 # ------------------------ # Rectangular top # ------------------------ if self.box_top is True: box_top = create_rectangular_base(self, "Column_box_top", self.box_top_x, self.box_top_y, self.box_top_z, self.ramp) bpy.ops.object.select_all(action='DESELECT') box_top.select_set(True) bpy.context.view_layer.objects.active = box_top set_normals(box_top) box_top.parent = mycolumn box_top.location.x = 0.0 box_top.location.y = 0.0 box_top.location.z = height # ------------------------ # Create arc # ------------------------ if self.arc_top: myarc = create_arc("Column_arch", self.arc_radio, self.arc_gap, self.arc_width, self.array_space_factor) myarc.parent = mycolumn bpy.ops.object.select_all(action='DESELECT') myarc.select_set(True) bpy.context.view_layer.objects.active = myarc set_normals(myarc) set_modifier_mirror(myarc, "X") myarc.location.x = self.arc_radio + self.arc_gap myarc.location.y = 0.0 if self.box_top is True: myarc.location.z = height + self.box_top_z else: myarc.location.z = height # ------------------------ # Create Array X # ------------------------ if self.array_num_x > 0: if self.arc_top: distance = ((self.arc_radio + self.arc_gap) * 2) zmove = 0 else: distance = self.array_space_x zmove = self.array_space_z if self.crt_array: set_modifier_array(mycolumn, "X", 0, self.array_num_x, True, distance, zmove) if self.box_base is True: set_modifier_array(box_bottom, "X", 0, self.array_num_x, True, distance, zmove) if self.box_top is True: set_modifier_array(box_top, "X", 0, self.array_num_x, True, distance, zmove) if self.cir_base is True: set_modifier_array(cir_bottom, "X", 0, self.array_num_x, True, distance, zmove) if self.cir_top is True: set_modifier_array(cir_top, "X", 0, self.array_num_x, True, distance, zmove) if self.arc_top: if self.array_num_x > 1: set_modifier_array(myarc, "X", 1, self.array_num_x - 1) # one arc minus # ------------------------ # Create Array Y # ------------------------ if self.array_num_y > 0: if self.arc_top: distance = self.arc_width else: distance = self.array_space_y if self.crt_array: set_modifier_array(mycolumn, "Y", 0, self.array_num_y, True, distance) if self.box_base is True: set_modifier_array(box_bottom, "Y", 0, self.array_num_y, True, distance) if self.box_top is True: set_modifier_array(box_top, "Y", 0, self.array_num_y, True, distance) if self.cir_base is True: set_modifier_array(cir_bottom, "Y", 0, self.array_num_y, True, distance) if self.cir_top is True: set_modifier_array(cir_top, "Y", 0, self.array_num_y, True, distance) if self.arc_top: if self.array_num_y > 1: set_modifier_array(myarc, "Y", 1, self.array_num_y - 1) # one less # ------------------------ # Create materials # ------------------------ if self.crt_mat and bpy.context.scene.render.engine in {'CYCLES', 'BLENDER_EEVEE'}: # Column material mat = create_diffuse_material("Column_material", False, 0.748, 0.734, 0.392, 0.573, 0.581, 0.318) set_material(mycolumn, mat) if self.box_base is True or self.box_top is True: mat = create_diffuse_material("Column_rect", False, 0.56, 0.56, 0.56, 0.56, 0.56, 0.56) if self.box_base is True: set_material(box_bottom, mat) if self.box_top is True: set_material(box_top, mat) if self.cir_base is True or self.cir_top is True: mat = create_diffuse_material("Column_cir", False, 0.65, 0.65, 0.65, 0.65, 0.65, 0.65) if self.cir_base is True: set_material(cir_bottom, mat) if self.cir_top is True: set_material(cir_top, mat) if self.arc_top: mat = create_diffuse_material("Column_arch", False, 0.8, 0.8, 0.8) set_material(myarc, mat) bpy.ops.object.select_all(action='DESELECT') mycolumn.select_set(True) bpy.context.view_layer.objects.active = mycolumn return # ------------------------------------------------------------------------------ # Create Column # ------------------------------------------------------------------------------ def create_circular_column(self, objname, radio_top, radio_mid, radio_bottom, height): myvertex = [] myfaces = [] pies = [0, 30, 60, 90, 120, 150, 180, 210, 240, 270, 300, 330] # circle # Add bottom circle for pie in pies: x = cos(radians(pie)) * radio_bottom y = sin(radians(pie)) * radio_bottom mypoint = [(x, y, 0.0)] myvertex.extend(mypoint) # Add middle circle for pie in pies: x = cos(radians(pie)) * radio_mid y = sin(radians(pie)) * radio_mid mypoint = [(x, y, (height / 2) + ((height / 2) * self.shift))] myvertex.extend(mypoint) # Add top circle for pie in pies: x = cos(radians(pie)) * radio_top y = sin(radians(pie)) * radio_top mypoint = [(x, y, height)] myvertex.extend(mypoint) # ------------------------------------- # Faces # ------------------------------------- t = 1 for n in range(0, len(pies) * 2): t += 1 if t > len(pies): t = 1 myface = [(n, n - len(pies) + 1, n + 1, n + len(pies))] myfaces.extend(myface) else: myface = [(n, n + 1, n + len(pies) + 1, n + len(pies))] myfaces.extend(myface) mesh = bpy.data.meshes.new(objname) myobject = bpy.data.objects.new(objname, mesh) myobject.location = bpy.context.scene.cursor.location bpy.context.collection.objects.link(myobject) mesh.from_pydata(myvertex, [], myfaces) mesh.update(calc_edges=True) return myobject # ------------------------------------------------------------------------------ # Create Torus # ------------------------------------------------------------------------------ def create_torus(objname, radio_inside, radio_outside, height): myvertex = [] myfaces = [] pies = [0, 30, 60, 90, 120, 150, 180, 210, 240, 270, 300, 330] # circle segments = [80, 60, 30, 0, 330, 300, 280] # section radio_mid = radio_outside + radio_inside - (height / 2) # Add internal circles Top for pie in pies: x = cos(radians(pie)) * radio_inside y = sin(radians(pie)) * radio_inside mypoint = [(x, y, height / 2)] myvertex.extend(mypoint) # Add external circles Top for pie in pies: x = cos(radians(pie)) * radio_mid y = sin(radians(pie)) * radio_mid mypoint = [(x, y, height / 2)] myvertex.extend(mypoint) # Add Intermediate lines for segment in segments: for pie in pies: radio_externo = radio_mid + (height * cos(radians(segment))) x = cos(radians(pie)) * radio_externo y = sin(radians(pie)) * radio_externo z = sin(radians(segment)) * (height / 2) mypoint = [(x, y, z)] myvertex.extend(mypoint) # Add internal circles Bottom for pie in pies: x = cos(radians(pie)) * radio_inside y = sin(radians(pie)) * radio_inside mypoint = [(x, y, height / 2 * -1)] myvertex.extend(mypoint) # Add external circles bottom for pie in pies: x = cos(radians(pie)) * radio_mid y = sin(radians(pie)) * radio_mid mypoint = [(x, y, height / 2 * -1)] myvertex.extend(mypoint) # ------------------------------------- # Faces # ------------------------------------- t = 1 for n in range(0, len(pies) * len(segments) + (len(pies) * 2)): t += 1 if t > len(pies): t = 1 myface = [(n, n - len(pies) + 1, n + 1, n + len(pies))] myfaces.extend(myface) else: myface = [(n, n + 1, n + len(pies) + 1, n + len(pies))] myfaces.extend(myface) mesh = bpy.data.meshes.new(objname) myobject = bpy.data.objects.new(objname, mesh) myobject.location = bpy.context.scene.cursor.location bpy.context.collection.objects.link(myobject) mesh.from_pydata(myvertex, [], myfaces) mesh.update(calc_edges=True) return myobject # ------------------------------------------------------------------------------ # Create rectangular base # ------------------------------------------------------------------------------ def create_rectangular_base(self, objname, x, y, z, ramp=False): elements = self.array_num_x - 1 height = self.array_space_z * elements width = self.array_space_x * elements if width > 0: angle = atan(height / width) else: angle = 0 radio = sqrt((x * x) + (self.array_space_z * self.array_space_z)) disp = radio * sin(angle) if ramp is False or self.arc_top: addz1 = 0 addz2 = 0 else: if self.array_space_z >= 0: addz1 = 0 addz2 = disp else: addz1 = disp * -1 addz2 = 0 myvertex = [(-x / 2, -y / 2, 0.0), (-x / 2, y / 2, 0.0), (x / 2, y / 2, 0.0), (x / 2, -y / 2, 0.0), (-x / 2, -y / 2, z + addz1), (-x / 2, y / 2, z + addz1), (x / 2, y / 2, z + addz2), (x / 2, -y / 2, z + addz2)] myfaces = [(0, 1, 2, 3), (0, 1, 5, 4), (1, 2, 6, 5), (2, 6, 7, 3), (5, 6, 7, 4), (0, 4, 7, 3)] mesh = bpy.data.meshes.new(objname) myobject = bpy.data.objects.new(objname, mesh) myobject.location = bpy.context.scene.cursor.location bpy.context.collection.objects.link(myobject) mesh.from_pydata(myvertex, [], myfaces) mesh.update(calc_edges=True) return myobject # ------------------------------------------------------------------------------ # Create arc # ------------------------------------------------------------------------------ def create_arc(objname, radio, gap, thickness, center): myvertex = [] half = (thickness / 2) move = half * center listdata = [half + move, -half + move] for pos_y in listdata: # -------------------------------- # First vertices # -------------------------------- myvertex.extend([(-radio - gap, pos_y, radio + radio / 10)]) # Flat cuts angle = 13 * (180 / 16) for i in range(1, 4): z = sin(radians(angle)) * radio mypoint = [(-radio - gap, pos_y, z)] myvertex.extend(mypoint) angle += 180 / 16 myvertex.extend([(-radio - gap, pos_y, 0.0)]) # -------------------------------- # Arc points # -------------------------------- angle = 180 for i in range(0, 9): x = cos(radians(angle)) * radio z = sin(radians(angle)) * radio mypoint = [(x, pos_y, z)] myvertex.extend(mypoint) angle -= 180 / 16 # -------------------------------- # vertical cut points # -------------------------------- angle = 8 * (180 / 16) for i in range(1, 5): x = cos(radians(angle)) * radio mypoint = [(x, pos_y, radio + radio / 10)] myvertex.extend(mypoint) angle += 180 / 16 myfaces = [(23, 24, 21, 22), (24, 25, 20, 21), (25, 26, 19, 20), (27, 18, 19, 26), (18, 27, 28, 35), (28, 29, 34, 35), (29, 30, 33, 34), (30, 31, 32, 33), (12, 13, 31, 30), (29, 11, 12, 30), (11, 29, 28, 10), (10, 28, 27, 9), (9, 27, 26, 8), (25, 7, 8, 26), (24, 6, 7, 25), (23, 5, 6, 24), (22, 4, 5, 23), (5, 4, 3, 6), (6, 3, 2, 7), (7, 2, 1, 8), (8, 1, 0, 9), (9, 0, 17, 10), (10, 17, 16, 11), (11, 16, 15, 12), (14, 13, 12, 15), (21, 3, 4, 22), (20, 2, 3, 21), (19, 1, 2, 20), (1, 19, 18, 0), (0, 18, 35, 17), (17, 35, 34, 16), (33, 15, 16, 34), (32, 14, 15, 33)] mesh = bpy.data.meshes.new(objname) myobject = bpy.data.objects.new(objname, mesh) myobject.location = bpy.context.scene.cursor.location bpy.context.collection.objects.link(myobject) mesh.from_pydata(myvertex, [], myfaces) mesh.update(calc_edges=True) return myobject