1 # SPDX-License-Identifier: GPL-2.0-or-later
3 # authors: dudecon, jambay
5 # This module contains the UI definition, display,
6 # and processing (create mesh) functions.
7 # The routines to generate the vertices for the wall
8 # are found in the "Blocks" module.
12 from bpy
.types
import Operator
13 from bpy
.props
import (
36 from bpy_extras
import object_utils
38 class add_mesh_wallb(Operator
, object_utils
.AddObjectHelper
):
39 bl_idname
= "mesh.wall_add"
40 bl_label
= "Add a Masonry Wall"
41 bl_description
= "Create a block (masonry) wall mesh"
42 bl_options
= {'REGISTER', 'UNDO'}
44 # UI items - API for properties - User accessible variables...
45 # not all options are via UI, and some operations just don't work yet
47 Wall
: BoolProperty(name
= "Wall",
51 #### change properties
52 name
: StringProperty(name
= "Name",
55 change
: BoolProperty(name
= "Change",
57 description
= "change Wall")
59 # only create object when True
60 # False allows modifying several parameters without creating object
61 ConstructTog
: BoolProperty(
63 description
="Generate the object",
66 # need to modify so radial makes a tower (normal);
67 # want "flat" setting to make disk (alternate)
68 # make the wall circular - if not sloped it's a flat disc
69 RadialTog
: BoolProperty(
71 description
="Make masonry radial",
74 # curve the wall - if radial creates dome.
75 SlopeTog
: BoolProperty(
77 description
="Make masonry sloped, or curved",
80 # need to review defaults and limits for all of these UI objects
83 WallStart
: FloatProperty(
85 description
="Left side, or start angle",
89 WallEnd
: FloatProperty(
91 description
="Right side, or end angle",
95 WallBottom
: FloatProperty(
97 description
="Lower height or radius",
101 WallTop
: FloatProperty(
103 description
="Upper height or radius",
107 EdgeOffset
: FloatProperty(
109 description
="Block staggering on wall sides",
110 default
=0.6, min=0.0, max=100.0
113 Width
: FloatProperty(
115 description
="Average width of each block",
119 WidthVariance
: FloatProperty(
121 description
="Random variance of block width",
125 WidthMinimum
: FloatProperty(
127 description
="Absolute minimum block width",
131 Height
: FloatProperty(
133 description
="Average Height of each block",
137 HeightVariance
: FloatProperty(
139 description
="Random variance of block Height",
143 HeightMinimum
: FloatProperty(
145 description
="Absolute minimum block Height",
149 Depth
: FloatProperty(
151 description
="Average Depth of each block",
155 DepthVariance
: FloatProperty(
157 description
="Random variance of block Depth",
161 DepthMinimum
: FloatProperty(
163 description
="Absolute minimum block Depth",
167 MergeBlock
: BoolProperty(
169 description
="Make big blocks (merge closely adjoining blocks)",
173 Grout
: FloatProperty(
175 description
="Distance between blocks",
179 GroutVariance
: FloatProperty(
181 description
="Random variance of block Grout",
185 GroutDepth
: FloatProperty(
187 description
="Grout Depth from the face of the blocks",
191 GroutDepthVariance
: FloatProperty(
193 description
="Random variance of block Grout Depth",
197 GroutEdge
: BoolProperty(
199 description
="Grout perimiter",
202 # properties for openings
203 Opening1Tog
: BoolProperty(
205 description
="Make windows or doors",
208 Opening1Width
: FloatProperty(
210 description
="The Width of the first opening",
214 Opening1Height
: FloatProperty(
216 description
="The Height of the first opening",
220 Opening1X
: FloatProperty(
222 description
="The x position or spacing of the first opening",
226 Opening1Z
: FloatProperty(
228 description
="The z position of the First opening",
232 Opening1Repeat
: BoolProperty(
234 description
="make multiple openings, with spacing X1",
237 Opening1TopArchTog
: BoolProperty(
239 description
="Add an arch to the top of the first opening",
242 Opening1TopArch
: FloatProperty(
244 description
="Height of the arch on the top of the opening",
248 Opening1TopArchThickness
: FloatProperty(
250 description
="Thickness of the arch on the top of the opening",
254 Opening1BtmArchTog
: BoolProperty(
256 description
="Add an arch to the bottom of opening 1",
259 Opening1BtmArch
: FloatProperty(
261 description
="Height of the arch on the bottom of the opening",
265 Opening1BtmArchThickness
: FloatProperty(
267 description
="Thickness of the arch on the bottom of the opening",
271 Opening1Bevel
: FloatProperty(
273 description
="Angle block face",
277 # openings on top of wall
278 CrenelTog
: BoolProperty(
280 description
="Make openings along top of wall",
283 CrenelXP
: FloatProperty(
285 description
="Gap width in wall based the percentage of wall width",
290 CrenelZP
: FloatProperty(
292 description
="Crenel Height as the percentage of wall height",
297 # narrow openings in wall.
298 # need to prevent overlap with arch openings - though inversion is an interesting effect.
299 SlotTog
: BoolProperty(
301 description
="Make narrow openings in wall",
304 SlotRpt
: BoolProperty(
306 description
="Repeat slots along wall",
309 SlotWdg
: BoolProperty(
311 description
="Bevel edges of slots",
314 SlotX
: FloatProperty(
316 description
="The x position or spacing of slots",
317 default
=0.0, min=-100, max=100.0
319 SlotGap
: FloatProperty(
321 description
="The opening size of slots",
322 default
=0.5, min=0.10, max=100.0
326 description
="Vertical slots",
329 SlotVH
: FloatProperty(
331 description
="Height of vertical slot",
335 SlotVBtm
: FloatProperty(
337 description
="Z position for slot",
339 min=-100.0, max=100.0
343 description
="Horizontal slots",
346 SlotHW
: FloatProperty(
348 description
="Width of horizontal slot",
352 # this should offset from VBtm... maybe make a % like crenels?
353 SlotHBtm
: FloatProperty(
355 description
="Z position for horizontal slot",
357 min=-100.0, max=100.0
359 # properties for shelf (extend blocks in area)
360 ShelfTog
: BoolProperty(
362 description
="Add blocks in area by depth to make shelf/platform",
365 ShelfX
: FloatProperty(
367 description
="The x position of Shelf",
371 ShelfZ
: FloatProperty(
373 description
="The z position of Shelf",
377 ShelfH
: FloatProperty(
379 description
="The Height of Shelf area",
383 ShelfW
: FloatProperty(
385 description
="The Width of shelf area",
389 ShelfD
: FloatProperty(
391 description
="Depth of each block for shelf (from cursor + 1/2 wall depth)",
395 ShelfBack
: BoolProperty(
397 description
="Shelf on backside of wall",
400 # properties for steps (extend blocks in area, progressive width)
401 StepTog
: BoolProperty(
403 description
="Add blocks in area by depth with progressive width to make steps",
406 StepX
: FloatProperty(
408 description
="The x position of steps",
412 StepZ
: FloatProperty(
414 description
="The z position of steps",
418 StepH
: FloatProperty(
420 description
="The Height of step area",
424 StepW
: FloatProperty(
426 description
="The Width of step area",
430 StepD
: FloatProperty(
432 description
="Depth of each block for steps (from cursor + 1/2 wall depth)",
436 StepV
: FloatProperty(
438 description
="Height of each step",
442 StepT
: FloatProperty(
444 description
="Width of each step",
448 StepLeft
: BoolProperty(
450 description
="If checked, flip steps direction towards the -X axis",
453 StepOnly
: BoolProperty(
455 description
="Steps only, no supporting blocks",
458 StepBack
: BoolProperty(
460 description
="Steps on backside of wall",
464 # Display the toolbox options
465 def draw(self
, context
):
469 box
.prop(self
, 'ConstructTog')
471 # Wall area (size/position)
473 box
.label(text
="Wall Size (area)")
475 col
= box
.column(align
=True)
476 col
.prop(self
, "WallStart")
477 col
.prop(self
, "WallEnd")
479 col
= box
.column(align
=True)
480 col
.prop(self
, "WallBottom")
481 col
.prop(self
, "WallTop")
482 box
.prop(self
, "EdgeOffset")
486 box
.label(text
="Block Sizing")
487 box
.prop(self
, "MergeBlock")
489 # add checkbox for "fixed" sizing (ignore variance) a.k.a. bricks
490 col
= box
.column(align
=True)
491 col
.prop(self
, "Width")
492 col
.prop(self
, "WidthVariance")
493 col
.prop(self
, "WidthMinimum")
495 col
= box
.column(align
=True)
496 col
.prop(self
, "Height")
497 col
.prop(self
, "HeightVariance")
498 col
.prop(self
, "HeightMinimum")
500 col
= box
.column(align
=True)
501 col
.prop(self
, "Depth")
502 col
.prop(self
, "DepthVariance")
503 col
.prop(self
, "DepthMinimum")
507 box
.label(text
="Grout")
509 col
= box
.column(align
=True)
510 col
.prop(self
, "Grout")
511 col
.prop(self
, "GroutVariance")
513 col
= box
.column(align
=True)
514 col
.prop(self
, "GroutDepth")
515 col
.prop(self
, "GroutDepthVariance")
517 # Wall shape modifiers
519 box
.label(text
="Wall Shape")
520 row
= box
.row(align
=True)
521 row
.prop(self
, "RadialTog", toggle
=True)
522 row
.prop(self
, "SlopeTog", toggle
=True)
524 # Openings (doors, windows; arched)
526 box
.prop(self
, 'Opening1Tog')
528 col
= box
.column(align
=True)
529 col
.prop(self
, "Opening1Width")
530 col
.prop(self
, "Opening1Height")
531 col
.prop(self
, "Opening1X")
532 col
.prop(self
, "Opening1Z")
533 col
.prop(self
, "Opening1Bevel")
535 box
.prop(self
, "Opening1Repeat", toggle
=True)
538 sub_box
.prop(self
, "Opening1TopArchTog")
539 if self
.Opening1TopArchTog
:
540 col
= sub_box
.column(align
=True)
541 col
.prop(self
, "Opening1TopArch")
542 col
.prop(self
, "Opening1TopArchThickness")
545 sub_box
.prop(self
, "Opening1BtmArchTog")
546 if self
.Opening1BtmArchTog
:
547 col
= sub_box
.column(align
=True)
548 col
.prop(self
, "Opening1BtmArch")
549 col
.prop(self
, "Opening1BtmArchThickness")
551 # Slots (narrow openings)
553 box
.prop(self
, "SlotTog")
555 col
= box
.column(align
=True)
556 col
.prop(self
, "SlotX")
557 col
.prop(self
, "SlotGap")
559 box
.prop(self
, "SlotRpt", toggle
=True)
562 sub_box
.prop(self
, "SlotV")
564 col
= sub_box
.column(align
=True)
565 col
.prop(self
, "SlotVH")
566 col
.prop(self
, "SlotVBtm")
569 sub_box
.prop(self
, "SlotH")
571 col
= sub_box
.column(align
=True)
572 col
.prop(self
, "SlotHW")
573 col
.prop(self
, "SlotHBtm")
575 # Crenels, gaps in top of wall
577 box
.prop(self
, "CrenelTog")
579 col
= box
.column(align
=True)
580 col
.prop(self
, "CrenelXP")
581 col
.prop(self
, "CrenelZP")
583 # Shelfing (protrusions)
585 box
.prop(self
, 'ShelfTog')
587 col
= box
.column(align
=True)
588 col
.prop(self
, "ShelfX")
589 col
.prop(self
, "ShelfZ")
591 col
= box
.column(align
=True)
592 col
.prop(self
, "ShelfW")
593 col
.prop(self
, "ShelfH")
594 col
.prop(self
, "ShelfD")
596 box
.prop(self
, "ShelfBack")
600 box
.prop(self
, 'StepTog')
602 col
= box
.column(align
=True)
603 col
.prop(self
, "StepX")
604 col
.prop(self
, "StepZ")
606 col
= box
.column(align
=True)
607 col
.prop(self
, "StepH")
608 col
.prop(self
, "StepW")
609 col
.prop(self
, "StepD")
611 col
= box
.column(align
=True)
612 col
.prop(self
, "StepV")
613 col
.prop(self
, "StepT")
615 col
= box
.column(align
=True)
616 row
= col
.row(align
=True)
617 row
.prop(self
, "StepLeft", toggle
=True)
618 row
.prop(self
, "StepOnly", toggle
=True)
619 col
.prop(self
, "StepBack", toggle
=True)
621 if self
.change
== False:
622 # generic transform props
624 box
.prop(self
, 'align', expand
=True)
625 box
.prop(self
, 'location', expand
=True)
626 box
.prop(self
, 'rotation', expand
=True)
628 # Respond to UI - get the properties set by user.
629 # Check and process UI settings to generate masonry
631 def execute(self
, context
):
643 # Create the wall when enabled (skip regen iterations when off)
644 if not self
.ConstructTog
:
647 # turn off 'Enter Edit Mode'
648 use_enter_edit_mode
= bpy
.context
.preferences
.edit
.use_enter_edit_mode
649 bpy
.context
.preferences
.edit
.use_enter_edit_mode
= False
651 # enter the settings for the wall dimensions (area)
652 # start can't be zero - min/max don't matter [if max less than end] but zero don't workie.
653 # start can't exceed end.
654 if not self
.WallStart
or self
.WallStart
>= self
.WallEnd
:
655 self
.WallStart
= NOTZERO
# Reset UI if input out of bounds...
657 dims
['s'] = self
.WallStart
658 dims
['e'] = self
.WallEnd
659 dims
['b'] = self
.WallBottom
660 dims
['t'] = self
.WallTop
662 settings
['eoff'] = self
.EdgeOffset
664 # retrieve the settings for the wall block properties
665 settings
['w'] = self
.Width
666 settings
['wv'] = self
.WidthVariance
667 settings
['wm'] = self
.WidthMinimum
670 settings
['sdv'] = settings
['w']
672 settings
['sdv'] = 0.12
674 settings
['h'] = self
.Height
675 settings
['hv'] = self
.HeightVariance
676 settings
['hm'] = self
.HeightMinimum
678 settings
['d'] = self
.Depth
679 settings
['dv'] = self
.DepthVariance
680 settings
['dm'] = self
.DepthMinimum
687 settings
['g'] = self
.Grout
688 settings
['gv'] = self
.GroutVariance
689 settings
['gd'] = self
.GroutDepth
690 settings
['gdv'] = self
.GroutDepthVariance
697 # set wall shape modifiers
700 # eliminate to allow user control for start/completion?
701 dims
['s'] = 0.0 # complete radial
702 if dims
['e'] > PI
* 2:
703 dims
['e'] = PI
* 2 # max end for circle
704 if dims
['b'] < settings
['g']:
705 dims
['b'] = settings
['g'] # min bottom for grout extension
717 # Add shelf if enabled
720 shelfSpecs
['h'] = self
.ShelfH
721 shelfSpecs
['w'] = self
.ShelfW
722 shelfSpecs
['d'] = self
.ShelfD
723 shelfSpecs
['x'] = self
.ShelfX
724 shelfSpecs
['z'] = self
.ShelfZ
733 # Make steps if enabled
736 stepSpecs
['x'] = self
.StepX
737 stepSpecs
['z'] = self
.StepZ
738 stepSpecs
['h'] = self
.StepH
739 stepSpecs
['w'] = self
.StepW
740 stepSpecs
['d'] = self
.StepD
741 stepSpecs
['v'] = self
.StepV
742 stepSpecs
['t'] = self
.StepT
753 # enter the settings for the openings
754 # when openings overlap they create inverse stonework - interesting but not the desired effect :)
755 # if opening width == indent * 2 the edge blocks fail (row of blocks cross opening) - bug.
757 openingIdx
= 0 # track opening array references for multiple uses
759 # general openings with arch options - can be windows or doors.
762 openingSpecs
+= [{'w': 0.5, 'h': 0.5, 'x': 0.8, 'z': 2.7, 'rp': 1,
763 'b': 0.0, 'v': 0, 'vl': 0, 't': 0, 'tl': 0}]
765 openingSpecs
[openingIdx
]['w'] = self
.Opening1Width
766 openingSpecs
[openingIdx
]['h'] = self
.Opening1Height
767 openingSpecs
[openingIdx
]['x'] = self
.Opening1X
768 openingSpecs
[openingIdx
]['z'] = self
.Opening1Z
769 openingSpecs
[openingIdx
]['rp'] = self
.Opening1Repeat
771 if self
.Opening1TopArchTog
:
772 openingSpecs
[openingIdx
]['v'] = self
.Opening1TopArch
773 openingSpecs
[openingIdx
]['t'] = self
.Opening1TopArchThickness
775 if self
.Opening1BtmArchTog
:
776 openingSpecs
[openingIdx
]['vl'] = self
.Opening1BtmArch
777 openingSpecs
[openingIdx
]['tl'] = self
.Opening1BtmArchThickness
779 openingSpecs
[openingIdx
]['b'] = self
.Opening1Bevel
781 openingIdx
+= 1 # count window/door/arch openings
783 # Slots (narrow openings)
786 if self
.SlotV
: # vertical slots
788 openingSpecs
+= [{'w': 0.5, 'h': 0.5, 'x': 0.0, 'z': 2.7, 'rp': 0,
789 'b': 0.0, 'v': 0, 'vl': 0, 't': 0, 'tl': 0}]
791 openingSpecs
[openingIdx
]['w'] = self
.SlotGap
792 openingSpecs
[openingIdx
]['h'] = self
.SlotVH
793 openingSpecs
[openingIdx
]['x'] = self
.SlotX
794 openingSpecs
[openingIdx
]['z'] = self
.SlotVBtm
795 openingSpecs
[openingIdx
]['rp'] = self
.SlotRpt
797 # make them pointy...
798 openingSpecs
[openingIdx
]['v'] = self
.SlotGap
799 openingSpecs
[openingIdx
]['t'] = self
.SlotGap
/ 2
800 openingSpecs
[openingIdx
]['vl'] = self
.SlotGap
801 openingSpecs
[openingIdx
]['tl'] = self
.SlotGap
/ 2
803 openingIdx
+= 1 # count vertical slot openings
805 # need to handle overlap of H and V slots...
807 if self
.SlotH
: # Horizontal slots
809 openingSpecs
+= [{'w': 0.5, 'h': 0.5, 'x': 0.0, 'z': 2.7, 'rp': 0,
810 'b': 0.0, 'v': 0, 'vl': 0, 't': 0, 'tl': 0}]
812 openingSpecs
[openingIdx
]['w'] = self
.SlotHW
813 openingSpecs
[openingIdx
]['h'] = self
.SlotGap
814 openingSpecs
[openingIdx
]['x'] = self
.SlotX
815 openingSpecs
[openingIdx
]['z'] = self
.SlotHBtm
816 # horizontal repeat isn't same spacing as vertical...
817 openingSpecs
[openingIdx
]['rp'] = self
.SlotRpt
819 # make them pointy...
820 openingIdx
+= 1 # count horizontal slot openings
822 # Crenellations (top row openings)
825 # add bottom arch option?
826 # perhaps a repeat toggle...
827 # if crenel opening overlaps with arch opening it fills with blocks...
830 openingSpecs
+= [{'w': 0.5, 'h': 0.5, 'x': 0.0, 'z': 2.7, 'rp': 1,
831 'b': 0.0, 'v': 0, 'vl': 0, 't': 0, 'tl': 0}]
833 wallW
= self
.WallEnd
- self
.WallStart
834 crenelW
= wallW
* self
.CrenelXP
# Width % opening.
836 wallH
= self
.WallTop
- self
.WallBottom
837 crenelH
= wallH
* self
.CrenelZP
# % proportional height.
839 openingSpecs
[openingIdx
]['w'] = crenelW
840 openingSpecs
[openingIdx
]['h'] = crenelH
842 # calculate the spacing between openings.
843 # this isn't the absolute start (left),
844 # it's opening center offset relative to cursor (space between openings)...
845 openingSpecs
[openingIdx
]['x'] = crenelW
* 2 - 1 # assume standard spacing
847 if not radialized
: # normal wall?
848 # set indent 0 (center) if opening is 50% or more of wall width, no repeat.
849 if crenelW
* 2 >= wallW
:
850 openingSpecs
[openingIdx
]['x'] = 0
851 openingSpecs
[openingIdx
]['rp'] = 0
852 # set bottom of opening (center of hole)
853 openingSpecs
[openingIdx
]['z'] = self
.WallTop
- (crenelH
/ 2)
855 openingIdx
+= 1 # count crenel openings
857 # Process the user settings to generate a wall
858 # generate the list of vertices for the wall...
859 verts_array
, faces_array
= createWall(
860 radialized
, slope
, openingSpecs
, bigBlock
,
861 shelfExt
, shelfBack
, stepMod
, stepLeft
, stepOnly
,
865 if bpy
.context
.mode
== "OBJECT":
866 if context
.selected_objects
!= [] and context
.active_object
and \
867 (context
.active_object
.data
is not None) and ('Wall' in context
.active_object
.data
.keys()) and \
868 (self
.change
== True):
869 obj
= context
.active_object
871 oldmeshname
= obj
.data
.name
872 mesh
= bpy
.data
.meshes
.new("Wall")
873 mesh
.from_pydata(verts_array
, [], faces_array
)
875 for material
in oldmesh
.materials
:
876 obj
.data
.materials
.append(material
)
877 bpy
.data
.meshes
.remove(oldmesh
)
878 obj
.data
.name
= oldmeshname
880 mesh
= bpy
.data
.meshes
.new("Wall")
881 mesh
.from_pydata(verts_array
, [], faces_array
)
882 obj
= object_utils
.object_data_add(context
, mesh
, operator
=self
)
886 obj
.data
["Wall"] = True
887 obj
.data
["change"] = False
888 for prm
in WallParameters():
889 obj
.data
[prm
] = getattr(self
, prm
)
891 if bpy
.context
.mode
== "EDIT_MESH":
892 active_object
= context
.active_object
893 name_active_object
= active_object
.name
894 bpy
.ops
.object.mode_set(mode
='OBJECT')
895 mesh
= bpy
.data
.meshes
.new("TMP")
896 mesh
.from_pydata(verts_array
, [], faces_array
)
897 obj
= object_utils
.object_data_add(context
, mesh
, operator
=self
)
899 active_object
.select_set(True)
900 bpy
.context
.view_layer
.objects
.active
= active_object
901 bpy
.ops
.object.join()
902 context
.active_object
.name
= name_active_object
903 bpy
.ops
.object.mode_set(mode
='EDIT')
905 if use_enter_edit_mode
:
906 bpy
.ops
.object.mode_set(mode
= 'EDIT')
908 # restore pre operator state
909 bpy
.context
.preferences
.edit
.use_enter_edit_mode
= use_enter_edit_mode
913 def WallParameters():
936 "GroutDepthVariance",
944 "Opening1TopArchTog",
946 "Opening1TopArchThickness",
947 "Opening1BtmArchTog",
949 "Opening1BtmArchThickness",
983 return WallParameters