# SPDX-License-Identifier: GPL-2.0-or-later # authors: dudecon, jambay # This module contains the UI definition, display, # and processing (create mesh) functions. # The routines to generate the vertices for the wall # are found in the "Blocks" module. import bpy from bpy.types import Operator from bpy.props import ( BoolProperty, FloatProperty, StringProperty, ) from .Blocks import ( NOTZERO, PI, dims, settings, shelfSpecs, stepSpecs, createWall, radialized, slope, openingSpecs, bigBlock, shelfExt, stepMod, stepLeft, shelfBack, stepOnly, stepBack, ) from bpy_extras import object_utils class add_mesh_wallb(Operator, object_utils.AddObjectHelper): bl_idname = "mesh.wall_add" bl_label = "Add a Masonry Wall" bl_description = "Create a block (masonry) wall mesh" bl_options = {'REGISTER', 'UNDO'} # UI items - API for properties - User accessible variables... # not all options are via UI, and some operations just don't work yet Wall : BoolProperty(name = "Wall", default = True, description = "Wall") #### change properties name : StringProperty(name = "Name", description = "Name") change : BoolProperty(name = "Change", default = False, description = "change Wall") # only create object when True # False allows modifying several parameters without creating object ConstructTog: BoolProperty( name="Construct", description="Generate the object", default=True ) # need to modify so radial makes a tower (normal); # want "flat" setting to make disk (alternate) # make the wall circular - if not sloped it's a flat disc RadialTog: BoolProperty( name="Radial", description="Make masonry radial", default=False ) # curve the wall - if radial creates dome. SlopeTog: BoolProperty( name="Curved", description="Make masonry sloped, or curved", default=False ) # need to review defaults and limits for all of these UI objects # wall area/size WallStart: FloatProperty( name="Start", description="Left side, or start angle", default=-10.0, min=-100, max=100.0 ) WallEnd: FloatProperty( name="End", description="Right side, or end angle", default=10.0, min=0.0, max=100.0 ) WallBottom: FloatProperty( name="Bottom", description="Lower height or radius", default=0.0, min=-100, max=100 ) WallTop: FloatProperty( name="Top", description="Upper height or radius", default=15.0, min=0.0, max=100.0 ) EdgeOffset: FloatProperty( name="Edging", description="Block staggering on wall sides", default=0.6, min=0.0, max=100.0 ) # block sizing Width: FloatProperty( name="Width", description="Average width of each block", default=1.5, min=0.01, max=100.0 ) WidthVariance: FloatProperty( name="Variance", description="Random variance of block width", default=0.5, min=0.0, max=100.0 ) WidthMinimum: FloatProperty( name="Minimum", description="Absolute minimum block width", default=0.5, min=0.01, max=100.0 ) Height: FloatProperty( name="Height", description="Average Height of each block", default=0.7, min=0.01, max=100.0 ) HeightVariance: FloatProperty( name="Variance", description="Random variance of block Height", default=0.3, min=0.0, max=100.0 ) HeightMinimum: FloatProperty( name="Minimum", description="Absolute minimum block Height", default=0.25, min=0.01, max=100.0 ) Depth: FloatProperty( name="Depth", description="Average Depth of each block", default=2.0, min=0.01, max=100.0 ) DepthVariance: FloatProperty( name="Variance", description="Random variance of block Depth", default=0.1, min=0.0, max=100.0 ) DepthMinimum: FloatProperty( name="Minimum", description="Absolute minimum block Depth", default=1.0, min=0.01, max=100.0 ) MergeBlock: BoolProperty( name="Merge Blocks", description="Make big blocks (merge closely adjoining blocks)", default=False ) # edging for blocks Grout: FloatProperty( name="Thickness", description="Distance between blocks", default=0.1, min=-10.0, max=10.0 ) GroutVariance: FloatProperty( name="Variance", description="Random variance of block Grout", default=0.03, min=0.0, max=100.0 ) GroutDepth: FloatProperty( name="Depth", description="Grout Depth from the face of the blocks", default=0.1, min=0.0001, max=10.0 ) GroutDepthVariance: FloatProperty( name="Variance", description="Random variance of block Grout Depth", default=0.03, min=0.0, max=100.0 ) GroutEdge: BoolProperty( name="Edging", description="Grout perimiter", default=False ) # properties for openings Opening1Tog: BoolProperty( name="Opening(s)", description="Make windows or doors", default=True ) Opening1Width: FloatProperty( name="Width", description="The Width of the first opening", default=2.5, min=0.01, max=100.0 ) Opening1Height: FloatProperty( name="Height", description="The Height of the first opening", default=3.5, min=0.01, max=100.0 ) Opening1X: FloatProperty( name="Indent", description="The x position or spacing of the first opening", default=5.0, min=-100, max=100.0 ) Opening1Z: FloatProperty( name="Bottom", description="The z position of the First opening", default=5.0, min=-100, max=100.0 ) Opening1Repeat: BoolProperty( name="Repeat", description="make multiple openings, with spacing X1", default=False ) Opening1TopArchTog: BoolProperty( name="Top Arch", description="Add an arch to the top of the first opening", default=True ) Opening1TopArch: FloatProperty( name="Curve", description="Height of the arch on the top of the opening", default=2.5, min=0.001, max=100.0 ) Opening1TopArchThickness: FloatProperty( name="Thickness", description="Thickness of the arch on the top of the opening", default=0.75, min=0.001, max=100.0 ) Opening1BtmArchTog: BoolProperty( name="Bottom Arch", description="Add an arch to the bottom of opening 1", default=False ) Opening1BtmArch: FloatProperty( name="Curve", description="Height of the arch on the bottom of the opening", default=1.0, min=0.01, max=100.0 ) Opening1BtmArchThickness: FloatProperty( name="Thickness", description="Thickness of the arch on the bottom of the opening", default=0.5, min=0.01, max=100.0 ) Opening1Bevel: FloatProperty( name="Bevel", description="Angle block face", default=0.25, min=-10.0, max=10.0 ) # openings on top of wall CrenelTog: BoolProperty( name="Crenels", description="Make openings along top of wall", default=False ) CrenelXP: FloatProperty( name="Width", description="Gap width in wall based the percentage of wall width", default=0.25, min=0.10, max=1.0, subtype="PERCENTAGE" ) CrenelZP: FloatProperty( name="Height", description="Crenel Height as the percentage of wall height", default=0.10, min=0.10, max=1.0, subtype="PERCENTAGE" ) # narrow openings in wall. # need to prevent overlap with arch openings - though inversion is an interesting effect. SlotTog: BoolProperty( name="Slots", description="Make narrow openings in wall", default=False ) SlotRpt: BoolProperty( name="Repeat", description="Repeat slots along wall", default=False ) SlotWdg: BoolProperty( name="Wedged (n/a)", description="Bevel edges of slots", default=False ) SlotX: FloatProperty( name="Indent", description="The x position or spacing of slots", default=0.0, min=-100, max=100.0 ) SlotGap: FloatProperty( name="Opening", description="The opening size of slots", default=0.5, min=0.10, max=100.0 ) SlotV: BoolProperty( name="Vertical", description="Vertical slots", default=True ) SlotVH: FloatProperty( name="Height", description="Height of vertical slot", default=3.5, min=0.10, max=100.0 ) SlotVBtm: FloatProperty( name="Bottom", description="Z position for slot", default=5.00, min=-100.0, max=100.0 ) SlotH: BoolProperty( name="Horizontal", description="Horizontal slots", default=False ) SlotHW: FloatProperty( name="Width", description="Width of horizontal slot", default=2.5, min=0.10, max=100.0 ) # this should offset from VBtm... maybe make a % like crenels? SlotHBtm: FloatProperty( name="Bottom", description="Z position for horizontal slot", default=5.50, min=-100.0, max=100.0 ) # properties for shelf (extend blocks in area) ShelfTog: BoolProperty( name="Shelf", description="Add blocks in area by depth to make shelf/platform", default=False ) ShelfX: FloatProperty( name="Left", description="The x position of Shelf", default=-5.00, min=-100, max=100.0 ) ShelfZ: FloatProperty( name="Bottom", description="The z position of Shelf", default=10.0, min=-100, max=100.0 ) ShelfH: FloatProperty( name="Height", description="The Height of Shelf area", default=1.0, min=0.01, max=100.0 ) ShelfW: FloatProperty( name="Width", description="The Width of shelf area", default=5.0, min=0.01, max=100.0 ) ShelfD: FloatProperty( name="Depth", description="Depth of each block for shelf (from cursor + 1/2 wall depth)", default=2.0, min=0.01, max=100.0 ) ShelfBack: BoolProperty( name="Backside", description="Shelf on backside of wall", default=False ) # properties for steps (extend blocks in area, progressive width) StepTog: BoolProperty( name="Steps", description="Add blocks in area by depth with progressive width to make steps", default=False ) StepX: FloatProperty( name="Left", description="The x position of steps", default=-9.00, min=-100, max=100.0 ) StepZ: FloatProperty( name="Bottom", description="The z position of steps", default=0.0, min=-100, max=100.0 ) StepH: FloatProperty( name="Height", description="The Height of step area", default=10.0, min=0.01, max=100.0 ) StepW: FloatProperty( name="Width", description="The Width of step area", default=8.0, min=0.01, max=100.0 ) StepD: FloatProperty( name="Depth", description="Depth of each block for steps (from cursor + 1/2 wall depth)", default=1.0, min=0.01, max=100.0 ) StepV: FloatProperty( name="Riser", description="Height of each step", default=0.70, min=0.01, max=100.0 ) StepT: FloatProperty( name="Tread", description="Width of each step", default=1.0, min=0.01, max=100.0 ) StepLeft: BoolProperty( name="Direction", description="If checked, flip steps direction towards the -X axis", default=False ) StepOnly: BoolProperty( name="Steps Only", description="Steps only, no supporting blocks", default=False ) StepBack: BoolProperty( name="Backside", description="Steps on backside of wall", default=False ) # Display the toolbox options def draw(self, context): layout = self.layout box = layout.box() box.prop(self, 'ConstructTog') # Wall area (size/position) box = layout.box() box.label(text="Wall Size (area)") col = box.column(align=True) col.prop(self, "WallStart") col.prop(self, "WallEnd") col = box.column(align=True) col.prop(self, "WallBottom") col.prop(self, "WallTop") box.prop(self, "EdgeOffset") # Wall block sizing box = layout.box() box.label(text="Block Sizing") box.prop(self, "MergeBlock") # add checkbox for "fixed" sizing (ignore variance) a.k.a. bricks col = box.column(align=True) col.prop(self, "Width") col.prop(self, "WidthVariance") col.prop(self, "WidthMinimum") col = box.column(align=True) col.prop(self, "Height") col.prop(self, "HeightVariance") col.prop(self, "HeightMinimum") col = box.column(align=True) col.prop(self, "Depth") col.prop(self, "DepthVariance") col.prop(self, "DepthMinimum") # grout settings box = layout.box() box.label(text="Grout") col = box.column(align=True) col.prop(self, "Grout") col.prop(self, "GroutVariance") col = box.column(align=True) col.prop(self, "GroutDepth") col.prop(self, "GroutDepthVariance") # Wall shape modifiers box = layout.box() box.label(text="Wall Shape") row = box.row(align=True) row.prop(self, "RadialTog", toggle=True) row.prop(self, "SlopeTog", toggle=True) # Openings (doors, windows; arched) box = layout.box() box.prop(self, 'Opening1Tog') if self.Opening1Tog: col = box.column(align=True) col.prop(self, "Opening1Width") col.prop(self, "Opening1Height") col.prop(self, "Opening1X") col.prop(self, "Opening1Z") col.prop(self, "Opening1Bevel") box.prop(self, "Opening1Repeat", toggle=True) sub_box = box.box() sub_box.prop(self, "Opening1TopArchTog") if self.Opening1TopArchTog: col = sub_box.column(align=True) col.prop(self, "Opening1TopArch") col.prop(self, "Opening1TopArchThickness") sub_box = box.box() sub_box.prop(self, "Opening1BtmArchTog") if self.Opening1BtmArchTog: col = sub_box.column(align=True) col.prop(self, "Opening1BtmArch") col.prop(self, "Opening1BtmArchThickness") # Slots (narrow openings) box = layout.box() box.prop(self, "SlotTog") if self.SlotTog: col = box.column(align=True) col.prop(self, "SlotX") col.prop(self, "SlotGap") box.prop(self, "SlotRpt", toggle=True) sub_box = box.box() sub_box.prop(self, "SlotV") if self.SlotV: col = sub_box.column(align=True) col.prop(self, "SlotVH") col.prop(self, "SlotVBtm") sub_box = box.box() sub_box.prop(self, "SlotH") if self.SlotH: col = sub_box.column(align=True) col.prop(self, "SlotHW") col.prop(self, "SlotHBtm") # Crenels, gaps in top of wall box = layout.box() box.prop(self, "CrenelTog") if self.CrenelTog: col = box.column(align=True) col.prop(self, "CrenelXP") col.prop(self, "CrenelZP") # Shelfing (protrusions) box = layout.box() box.prop(self, 'ShelfTog') if self.ShelfTog: col = box.column(align=True) col.prop(self, "ShelfX") col.prop(self, "ShelfZ") col = box.column(align=True) col.prop(self, "ShelfW") col.prop(self, "ShelfH") col.prop(self, "ShelfD") box.prop(self, "ShelfBack") # Steps box = layout.box() box.prop(self, 'StepTog') if self.StepTog: col = box.column(align=True) col.prop(self, "StepX") col.prop(self, "StepZ") col = box.column(align=True) col.prop(self, "StepH") col.prop(self, "StepW") col.prop(self, "StepD") col = box.column(align=True) col.prop(self, "StepV") col.prop(self, "StepT") col = box.column(align=True) row = col.row(align=True) row.prop(self, "StepLeft", toggle=True) row.prop(self, "StepOnly", toggle=True) col.prop(self, "StepBack", toggle=True) if self.change == False: # generic transform props box = layout.box() box.prop(self, 'align', expand=True) box.prop(self, 'location', expand=True) box.prop(self, 'rotation', expand=True) # Respond to UI - get the properties set by user. # Check and process UI settings to generate masonry def execute(self, context): global radialized global slope global openingSpecs global bigBlock global shelfExt global stepMod global stepLeft global shelfBack global stepOnly global stepBack # Create the wall when enabled (skip regen iterations when off) if not self.ConstructTog: return {'FINISHED'} # turn off 'Enter Edit Mode' use_enter_edit_mode = bpy.context.preferences.edit.use_enter_edit_mode bpy.context.preferences.edit.use_enter_edit_mode = False # enter the settings for the wall dimensions (area) # start can't be zero - min/max don't matter [if max less than end] but zero don't workie. # start can't exceed end. if not self.WallStart or self.WallStart >= self.WallEnd: self.WallStart = NOTZERO # Reset UI if input out of bounds... dims['s'] = self.WallStart dims['e'] = self.WallEnd dims['b'] = self.WallBottom dims['t'] = self.WallTop settings['eoff'] = self.EdgeOffset # retrieve the settings for the wall block properties settings['w'] = self.Width settings['wv'] = self.WidthVariance settings['wm'] = self.WidthMinimum if not radialized: settings['sdv'] = settings['w'] else: settings['sdv'] = 0.12 settings['h'] = self.Height settings['hv'] = self.HeightVariance settings['hm'] = self.HeightMinimum settings['d'] = self.Depth settings['dv'] = self.DepthVariance settings['dm'] = self.DepthMinimum if self.MergeBlock: bigBlock = 1 else: bigBlock = 0 settings['g'] = self.Grout settings['gv'] = self.GroutVariance settings['gd'] = self.GroutDepth settings['gdv'] = self.GroutDepthVariance if self.GroutEdge: settings['ge'] = 1 else: settings['ge'] = 0 # set wall shape modifiers if self.RadialTog: radialized = 1 # eliminate to allow user control for start/completion? dims['s'] = 0.0 # complete radial if dims['e'] > PI * 2: dims['e'] = PI * 2 # max end for circle if dims['b'] < settings['g']: dims['b'] = settings['g'] # min bottom for grout extension else: radialized = 0 if self.SlopeTog: slope = 1 else: slope = 0 shelfExt = 0 shelfBack = 0 # Add shelf if enabled if self.ShelfTog: shelfExt = 1 shelfSpecs['h'] = self.ShelfH shelfSpecs['w'] = self.ShelfW shelfSpecs['d'] = self.ShelfD shelfSpecs['x'] = self.ShelfX shelfSpecs['z'] = self.ShelfZ if self.ShelfBack: shelfBack = 1 stepMod = 0 stepLeft = 0 stepOnly = 0 stepBack = 0 # Make steps if enabled if self.StepTog: stepMod = 1 stepSpecs['x'] = self.StepX stepSpecs['z'] = self.StepZ stepSpecs['h'] = self.StepH stepSpecs['w'] = self.StepW stepSpecs['d'] = self.StepD stepSpecs['v'] = self.StepV stepSpecs['t'] = self.StepT if self.StepLeft: stepLeft = 1 if self.StepOnly: stepOnly = 1 if self.StepBack: stepBack = 1 # enter the settings for the openings # when openings overlap they create inverse stonework - interesting but not the desired effect :) # if opening width == indent * 2 the edge blocks fail (row of blocks cross opening) - bug. openingSpecs = [] openingIdx = 0 # track opening array references for multiple uses # general openings with arch options - can be windows or doors. if self.Opening1Tog: # set defaults... openingSpecs += [{'w': 0.5, 'h': 0.5, 'x': 0.8, 'z': 2.7, 'rp': 1, 'b': 0.0, 'v': 0, 'vl': 0, 't': 0, 'tl': 0}] openingSpecs[openingIdx]['w'] = self.Opening1Width openingSpecs[openingIdx]['h'] = self.Opening1Height openingSpecs[openingIdx]['x'] = self.Opening1X openingSpecs[openingIdx]['z'] = self.Opening1Z openingSpecs[openingIdx]['rp'] = self.Opening1Repeat if self.Opening1TopArchTog: openingSpecs[openingIdx]['v'] = self.Opening1TopArch openingSpecs[openingIdx]['t'] = self.Opening1TopArchThickness if self.Opening1BtmArchTog: openingSpecs[openingIdx]['vl'] = self.Opening1BtmArch openingSpecs[openingIdx]['tl'] = self.Opening1BtmArchThickness openingSpecs[openingIdx]['b'] = self.Opening1Bevel openingIdx += 1 # count window/door/arch openings # Slots (narrow openings) if self.SlotTog: if self.SlotV: # vertical slots # set defaults... openingSpecs += [{'w': 0.5, 'h': 0.5, 'x': 0.0, 'z': 2.7, 'rp': 0, 'b': 0.0, 'v': 0, 'vl': 0, 't': 0, 'tl': 0}] openingSpecs[openingIdx]['w'] = self.SlotGap openingSpecs[openingIdx]['h'] = self.SlotVH openingSpecs[openingIdx]['x'] = self.SlotX openingSpecs[openingIdx]['z'] = self.SlotVBtm openingSpecs[openingIdx]['rp'] = self.SlotRpt # make them pointy... openingSpecs[openingIdx]['v'] = self.SlotGap openingSpecs[openingIdx]['t'] = self.SlotGap / 2 openingSpecs[openingIdx]['vl'] = self.SlotGap openingSpecs[openingIdx]['tl'] = self.SlotGap / 2 openingIdx += 1 # count vertical slot openings # need to handle overlap of H and V slots... if self.SlotH: # Horizontal slots # set defaults... openingSpecs += [{'w': 0.5, 'h': 0.5, 'x': 0.0, 'z': 2.7, 'rp': 0, 'b': 0.0, 'v': 0, 'vl': 0, 't': 0, 'tl': 0}] openingSpecs[openingIdx]['w'] = self.SlotHW openingSpecs[openingIdx]['h'] = self.SlotGap openingSpecs[openingIdx]['x'] = self.SlotX openingSpecs[openingIdx]['z'] = self.SlotHBtm # horizontal repeat isn't same spacing as vertical... openingSpecs[openingIdx]['rp'] = self.SlotRpt # make them pointy... openingIdx += 1 # count horizontal slot openings # Crenellations (top row openings) if self.CrenelTog: # add bottom arch option? # perhaps a repeat toggle... # if crenel opening overlaps with arch opening it fills with blocks... # set defaults... openingSpecs += [{'w': 0.5, 'h': 0.5, 'x': 0.0, 'z': 2.7, 'rp': 1, 'b': 0.0, 'v': 0, 'vl': 0, 't': 0, 'tl': 0}] wallW = self.WallEnd - self.WallStart crenelW = wallW * self.CrenelXP # Width % opening. wallH = self.WallTop - self.WallBottom crenelH = wallH * self.CrenelZP # % proportional height. openingSpecs[openingIdx]['w'] = crenelW openingSpecs[openingIdx]['h'] = crenelH # calculate the spacing between openings. # this isn't the absolute start (left), # it's opening center offset relative to cursor (space between openings)... openingSpecs[openingIdx]['x'] = crenelW * 2 - 1 # assume standard spacing if not radialized: # normal wall? # set indent 0 (center) if opening is 50% or more of wall width, no repeat. if crenelW * 2 >= wallW: openingSpecs[openingIdx]['x'] = 0 openingSpecs[openingIdx]['rp'] = 0 # set bottom of opening (center of hole) openingSpecs[openingIdx]['z'] = self.WallTop - (crenelH / 2) openingIdx += 1 # count crenel openings # Process the user settings to generate a wall # generate the list of vertices for the wall... verts_array, faces_array = createWall( radialized, slope, openingSpecs, bigBlock, shelfExt, shelfBack, stepMod, stepLeft, stepOnly, stepBack ) if bpy.context.mode == "OBJECT": if context.selected_objects != [] and context.active_object and \ (context.active_object.data is not None) and ('Wall' in context.active_object.data.keys()) and \ (self.change == True): obj = context.active_object oldmesh = obj.data oldmeshname = obj.data.name mesh = bpy.data.meshes.new("Wall") mesh.from_pydata(verts_array, [], faces_array) obj.data = mesh for material in oldmesh.materials: obj.data.materials.append(material) bpy.data.meshes.remove(oldmesh) obj.data.name = oldmeshname else: mesh = bpy.data.meshes.new("Wall") mesh.from_pydata(verts_array, [], faces_array) obj = object_utils.object_data_add(context, mesh, operator=self) mesh.update() obj.data["Wall"] = True obj.data["change"] = False for prm in WallParameters(): obj.data[prm] = getattr(self, prm) if bpy.context.mode == "EDIT_MESH": active_object = context.active_object name_active_object = active_object.name bpy.ops.object.mode_set(mode='OBJECT') mesh = bpy.data.meshes.new("TMP") mesh.from_pydata(verts_array, [], faces_array) obj = object_utils.object_data_add(context, mesh, operator=self) obj.select_set(True) active_object.select_set(True) bpy.context.view_layer.objects.active = active_object bpy.ops.object.join() context.active_object.name = name_active_object bpy.ops.object.mode_set(mode='EDIT') if use_enter_edit_mode: bpy.ops.object.mode_set(mode = 'EDIT') # restore pre operator state bpy.context.preferences.edit.use_enter_edit_mode = use_enter_edit_mode return {'FINISHED'} def WallParameters(): WallParameters = [ "ConstructTog", "RadialTog", "SlopeTog", "WallStart", "WallEnd", "WallBottom", "WallTop", "EdgeOffset", "Width", "WidthVariance", "WidthMinimum", "Height", "HeightVariance", "HeightMinimum", "Depth", "DepthVariance", "DepthMinimum", "MergeBlock", "Grout", "GroutVariance", "GroutDepth", "GroutDepthVariance", "GroutEdge", "Opening1Tog", "Opening1Width", "Opening1Height", "Opening1X", "Opening1Z", "Opening1Repeat", "Opening1TopArchTog", "Opening1TopArch", "Opening1TopArchThickness", "Opening1BtmArchTog", "Opening1BtmArch", "Opening1BtmArchThickness", "CrenelTog", "CrenelXP", "CrenelZP", "SlotTog", "SlotRpt", "SlotWdg", "SlotX", "SlotGap", "SlotV", "SlotVH", "SlotVBtm", "SlotH", "SlotHW", "SlotHBtm", "ShelfTog", "ShelfX", "ShelfZ", "ShelfH", "ShelfW", "ShelfD", "ShelfBack", "StepTog", "StepX", "StepZ", "StepH", "StepW", "StepD", "StepV", "StepT", "StepLeft", "StepOnly", "StepBack", ] return WallParameters