1 # SPDX-FileCopyrightText: 2016-2022 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
5 # authors: dudecon, jambay
7 # This module contains the UI definition, display,
8 # and processing (create mesh) functions.
9 # The routines to generate the vertices for the wall
10 # are found in the "Blocks" module.
14 from bpy
.types
import Operator
15 from bpy
.props
import (
38 from bpy_extras
import object_utils
40 class add_mesh_wallb(Operator
, object_utils
.AddObjectHelper
):
41 bl_idname
= "mesh.wall_add"
42 bl_label
= "Add a Masonry Wall"
43 bl_description
= "Create a block (masonry) wall mesh"
44 bl_options
= {'REGISTER', 'UNDO'}
46 # UI items - API for properties - User accessible variables...
47 # not all options are via UI, and some operations just don't work yet
49 Wall
: BoolProperty(name
= "Wall",
53 #### change properties
54 name
: StringProperty(name
= "Name",
57 change
: BoolProperty(name
= "Change",
59 description
= "change Wall")
61 # only create object when True
62 # False allows modifying several parameters without creating object
63 ConstructTog
: BoolProperty(
65 description
="Generate the object",
68 # need to modify so radial makes a tower (normal);
69 # want "flat" setting to make disk (alternate)
70 # make the wall circular - if not sloped it's a flat disc
71 RadialTog
: BoolProperty(
73 description
="Make masonry radial",
76 # curve the wall - if radial creates dome.
77 SlopeTog
: BoolProperty(
79 description
="Make masonry sloped, or curved",
82 # need to review defaults and limits for all of these UI objects
85 WallStart
: FloatProperty(
87 description
="Left side, or start angle",
91 WallEnd
: FloatProperty(
93 description
="Right side, or end angle",
97 WallBottom
: FloatProperty(
99 description
="Lower height or radius",
103 WallTop
: FloatProperty(
105 description
="Upper height or radius",
109 EdgeOffset
: FloatProperty(
111 description
="Block staggering on wall sides",
112 default
=0.6, min=0.0, max=100.0
115 Width
: FloatProperty(
117 description
="Average width of each block",
121 WidthVariance
: FloatProperty(
123 description
="Random variance of block width",
127 WidthMinimum
: FloatProperty(
129 description
="Absolute minimum block width",
133 Height
: FloatProperty(
135 description
="Average Height of each block",
139 HeightVariance
: FloatProperty(
141 description
="Random variance of block Height",
145 HeightMinimum
: FloatProperty(
147 description
="Absolute minimum block Height",
151 Depth
: FloatProperty(
153 description
="Average Depth of each block",
157 DepthVariance
: FloatProperty(
159 description
="Random variance of block Depth",
163 DepthMinimum
: FloatProperty(
165 description
="Absolute minimum block Depth",
169 MergeBlock
: BoolProperty(
171 description
="Make big blocks (merge closely adjoining blocks)",
175 Grout
: FloatProperty(
177 description
="Distance between blocks",
181 GroutVariance
: FloatProperty(
183 description
="Random variance of block Grout",
187 GroutDepth
: FloatProperty(
189 description
="Grout Depth from the face of the blocks",
193 GroutDepthVariance
: FloatProperty(
195 description
="Random variance of block Grout Depth",
199 GroutEdge
: BoolProperty(
201 description
="Grout perimiter",
204 # properties for openings
205 Opening1Tog
: BoolProperty(
207 description
="Make windows or doors",
210 Opening1Width
: FloatProperty(
212 description
="The Width of the first opening",
216 Opening1Height
: FloatProperty(
218 description
="The Height of the first opening",
222 Opening1X
: FloatProperty(
224 description
="The x position or spacing of the first opening",
228 Opening1Z
: FloatProperty(
230 description
="The z position of the First opening",
234 Opening1Repeat
: BoolProperty(
236 description
="make multiple openings, with spacing X1",
239 Opening1TopArchTog
: BoolProperty(
241 description
="Add an arch to the top of the first opening",
244 Opening1TopArch
: FloatProperty(
246 description
="Height of the arch on the top of the opening",
250 Opening1TopArchThickness
: FloatProperty(
252 description
="Thickness of the arch on the top of the opening",
256 Opening1BtmArchTog
: BoolProperty(
258 description
="Add an arch to the bottom of opening 1",
261 Opening1BtmArch
: FloatProperty(
263 description
="Height of the arch on the bottom of the opening",
267 Opening1BtmArchThickness
: FloatProperty(
269 description
="Thickness of the arch on the bottom of the opening",
273 Opening1Bevel
: FloatProperty(
275 description
="Angle block face",
279 # openings on top of wall
280 CrenelTog
: BoolProperty(
282 description
="Make openings along top of wall",
285 CrenelXP
: FloatProperty(
287 description
="Gap width in wall based the percentage of wall width",
292 CrenelZP
: FloatProperty(
294 description
="Crenel Height as the percentage of wall height",
299 # narrow openings in wall.
300 # need to prevent overlap with arch openings - though inversion is an interesting effect.
301 SlotTog
: BoolProperty(
303 description
="Make narrow openings in wall",
306 SlotRpt
: BoolProperty(
308 description
="Repeat slots along wall",
311 SlotWdg
: BoolProperty(
313 description
="Bevel edges of slots",
316 SlotX
: FloatProperty(
318 description
="The x position or spacing of slots",
319 default
=0.0, min=-100, max=100.0
321 SlotGap
: FloatProperty(
323 description
="The opening size of slots",
324 default
=0.5, min=0.10, max=100.0
328 description
="Vertical slots",
331 SlotVH
: FloatProperty(
333 description
="Height of vertical slot",
337 SlotVBtm
: FloatProperty(
339 description
="Z position for slot",
341 min=-100.0, max=100.0
345 description
="Horizontal slots",
348 SlotHW
: FloatProperty(
350 description
="Width of horizontal slot",
354 # this should offset from VBtm... maybe make a % like crenels?
355 SlotHBtm
: FloatProperty(
357 description
="Z position for horizontal slot",
359 min=-100.0, max=100.0
361 # properties for shelf (extend blocks in area)
362 ShelfTog
: BoolProperty(
364 description
="Add blocks in area by depth to make shelf/platform",
367 ShelfX
: FloatProperty(
369 description
="The x position of Shelf",
373 ShelfZ
: FloatProperty(
375 description
="The z position of Shelf",
379 ShelfH
: FloatProperty(
381 description
="The Height of Shelf area",
385 ShelfW
: FloatProperty(
387 description
="The Width of shelf area",
391 ShelfD
: FloatProperty(
393 description
="Depth of each block for shelf (from cursor + 1/2 wall depth)",
397 ShelfBack
: BoolProperty(
399 description
="Shelf on backside of wall",
402 # properties for steps (extend blocks in area, progressive width)
403 StepTog
: BoolProperty(
405 description
="Add blocks in area by depth with progressive width to make steps",
408 StepX
: FloatProperty(
410 description
="The x position of steps",
414 StepZ
: FloatProperty(
416 description
="The z position of steps",
420 StepH
: FloatProperty(
422 description
="The Height of step area",
426 StepW
: FloatProperty(
428 description
="The Width of step area",
432 StepD
: FloatProperty(
434 description
="Depth of each block for steps (from cursor + 1/2 wall depth)",
438 StepV
: FloatProperty(
440 description
="Height of each step",
444 StepT
: FloatProperty(
446 description
="Width of each step",
450 StepLeft
: BoolProperty(
452 description
="If checked, flip steps direction towards the -X axis",
455 StepOnly
: BoolProperty(
457 description
="Steps only, no supporting blocks",
460 StepBack
: BoolProperty(
462 description
="Steps on backside of wall",
466 # Display the toolbox options
467 def draw(self
, context
):
471 box
.prop(self
, 'ConstructTog')
473 # Wall area (size/position)
475 box
.label(text
="Wall Size (area)")
477 col
= box
.column(align
=True)
478 col
.prop(self
, "WallStart")
479 col
.prop(self
, "WallEnd")
481 col
= box
.column(align
=True)
482 col
.prop(self
, "WallBottom")
483 col
.prop(self
, "WallTop")
484 box
.prop(self
, "EdgeOffset")
488 box
.label(text
="Block Sizing")
489 box
.prop(self
, "MergeBlock")
491 # add checkbox for "fixed" sizing (ignore variance) a.k.a. bricks
492 col
= box
.column(align
=True)
493 col
.prop(self
, "Width")
494 col
.prop(self
, "WidthVariance")
495 col
.prop(self
, "WidthMinimum")
497 col
= box
.column(align
=True)
498 col
.prop(self
, "Height")
499 col
.prop(self
, "HeightVariance")
500 col
.prop(self
, "HeightMinimum")
502 col
= box
.column(align
=True)
503 col
.prop(self
, "Depth")
504 col
.prop(self
, "DepthVariance")
505 col
.prop(self
, "DepthMinimum")
509 box
.label(text
="Grout")
511 col
= box
.column(align
=True)
512 col
.prop(self
, "Grout")
513 col
.prop(self
, "GroutVariance")
515 col
= box
.column(align
=True)
516 col
.prop(self
, "GroutDepth")
517 col
.prop(self
, "GroutDepthVariance")
519 # Wall shape modifiers
521 box
.label(text
="Wall Shape")
522 row
= box
.row(align
=True)
523 row
.prop(self
, "RadialTog", toggle
=True)
524 row
.prop(self
, "SlopeTog", toggle
=True)
526 # Openings (doors, windows; arched)
528 box
.prop(self
, 'Opening1Tog')
530 col
= box
.column(align
=True)
531 col
.prop(self
, "Opening1Width")
532 col
.prop(self
, "Opening1Height")
533 col
.prop(self
, "Opening1X")
534 col
.prop(self
, "Opening1Z")
535 col
.prop(self
, "Opening1Bevel")
537 box
.prop(self
, "Opening1Repeat", toggle
=True)
540 sub_box
.prop(self
, "Opening1TopArchTog")
541 if self
.Opening1TopArchTog
:
542 col
= sub_box
.column(align
=True)
543 col
.prop(self
, "Opening1TopArch")
544 col
.prop(self
, "Opening1TopArchThickness")
547 sub_box
.prop(self
, "Opening1BtmArchTog")
548 if self
.Opening1BtmArchTog
:
549 col
= sub_box
.column(align
=True)
550 col
.prop(self
, "Opening1BtmArch")
551 col
.prop(self
, "Opening1BtmArchThickness")
553 # Slots (narrow openings)
555 box
.prop(self
, "SlotTog")
557 col
= box
.column(align
=True)
558 col
.prop(self
, "SlotX")
559 col
.prop(self
, "SlotGap")
561 box
.prop(self
, "SlotRpt", toggle
=True)
564 sub_box
.prop(self
, "SlotV")
566 col
= sub_box
.column(align
=True)
567 col
.prop(self
, "SlotVH")
568 col
.prop(self
, "SlotVBtm")
571 sub_box
.prop(self
, "SlotH")
573 col
= sub_box
.column(align
=True)
574 col
.prop(self
, "SlotHW")
575 col
.prop(self
, "SlotHBtm")
577 # Crenels, gaps in top of wall
579 box
.prop(self
, "CrenelTog")
581 col
= box
.column(align
=True)
582 col
.prop(self
, "CrenelXP")
583 col
.prop(self
, "CrenelZP")
585 # Shelfing (protrusions)
587 box
.prop(self
, 'ShelfTog')
589 col
= box
.column(align
=True)
590 col
.prop(self
, "ShelfX")
591 col
.prop(self
, "ShelfZ")
593 col
= box
.column(align
=True)
594 col
.prop(self
, "ShelfW")
595 col
.prop(self
, "ShelfH")
596 col
.prop(self
, "ShelfD")
598 box
.prop(self
, "ShelfBack")
602 box
.prop(self
, 'StepTog')
604 col
= box
.column(align
=True)
605 col
.prop(self
, "StepX")
606 col
.prop(self
, "StepZ")
608 col
= box
.column(align
=True)
609 col
.prop(self
, "StepH")
610 col
.prop(self
, "StepW")
611 col
.prop(self
, "StepD")
613 col
= box
.column(align
=True)
614 col
.prop(self
, "StepV")
615 col
.prop(self
, "StepT")
617 col
= box
.column(align
=True)
618 row
= col
.row(align
=True)
619 row
.prop(self
, "StepLeft", toggle
=True)
620 row
.prop(self
, "StepOnly", toggle
=True)
621 col
.prop(self
, "StepBack", toggle
=True)
623 if self
.change
== False:
624 # generic transform props
626 box
.prop(self
, 'align', expand
=True)
627 box
.prop(self
, 'location', expand
=True)
628 box
.prop(self
, 'rotation', expand
=True)
630 # Respond to UI - get the properties set by user.
631 # Check and process UI settings to generate masonry
633 def execute(self
, context
):
645 # Create the wall when enabled (skip regen iterations when off)
646 if not self
.ConstructTog
:
649 # turn off 'Enter Edit Mode'
650 use_enter_edit_mode
= bpy
.context
.preferences
.edit
.use_enter_edit_mode
651 bpy
.context
.preferences
.edit
.use_enter_edit_mode
= False
653 # enter the settings for the wall dimensions (area)
654 # start can't be zero - min/max don't matter [if max less than end] but zero don't workie.
655 # start can't exceed end.
656 if not self
.WallStart
or self
.WallStart
>= self
.WallEnd
:
657 self
.WallStart
= NOTZERO
# Reset UI if input out of bounds...
659 dims
['s'] = self
.WallStart
660 dims
['e'] = self
.WallEnd
661 dims
['b'] = self
.WallBottom
662 dims
['t'] = self
.WallTop
664 settings
['eoff'] = self
.EdgeOffset
666 # retrieve the settings for the wall block properties
667 settings
['w'] = self
.Width
668 settings
['wv'] = self
.WidthVariance
669 settings
['wm'] = self
.WidthMinimum
672 settings
['sdv'] = settings
['w']
674 settings
['sdv'] = 0.12
676 settings
['h'] = self
.Height
677 settings
['hv'] = self
.HeightVariance
678 settings
['hm'] = self
.HeightMinimum
680 settings
['d'] = self
.Depth
681 settings
['dv'] = self
.DepthVariance
682 settings
['dm'] = self
.DepthMinimum
689 settings
['g'] = self
.Grout
690 settings
['gv'] = self
.GroutVariance
691 settings
['gd'] = self
.GroutDepth
692 settings
['gdv'] = self
.GroutDepthVariance
699 # set wall shape modifiers
702 # eliminate to allow user control for start/completion?
703 dims
['s'] = 0.0 # complete radial
704 if dims
['e'] > PI
* 2:
705 dims
['e'] = PI
* 2 # max end for circle
706 if dims
['b'] < settings
['g']:
707 dims
['b'] = settings
['g'] # min bottom for grout extension
719 # Add shelf if enabled
722 shelfSpecs
['h'] = self
.ShelfH
723 shelfSpecs
['w'] = self
.ShelfW
724 shelfSpecs
['d'] = self
.ShelfD
725 shelfSpecs
['x'] = self
.ShelfX
726 shelfSpecs
['z'] = self
.ShelfZ
735 # Make steps if enabled
738 stepSpecs
['x'] = self
.StepX
739 stepSpecs
['z'] = self
.StepZ
740 stepSpecs
['h'] = self
.StepH
741 stepSpecs
['w'] = self
.StepW
742 stepSpecs
['d'] = self
.StepD
743 stepSpecs
['v'] = self
.StepV
744 stepSpecs
['t'] = self
.StepT
755 # enter the settings for the openings
756 # when openings overlap they create inverse stonework - interesting but not the desired effect :)
757 # if opening width == indent * 2 the edge blocks fail (row of blocks cross opening) - bug.
759 openingIdx
= 0 # track opening array references for multiple uses
761 # general openings with arch options - can be windows or doors.
764 openingSpecs
+= [{'w': 0.5, 'h': 0.5, 'x': 0.8, 'z': 2.7, 'rp': 1,
765 'b': 0.0, 'v': 0, 'vl': 0, 't': 0, 'tl': 0}]
767 openingSpecs
[openingIdx
]['w'] = self
.Opening1Width
768 openingSpecs
[openingIdx
]['h'] = self
.Opening1Height
769 openingSpecs
[openingIdx
]['x'] = self
.Opening1X
770 openingSpecs
[openingIdx
]['z'] = self
.Opening1Z
771 openingSpecs
[openingIdx
]['rp'] = self
.Opening1Repeat
773 if self
.Opening1TopArchTog
:
774 openingSpecs
[openingIdx
]['v'] = self
.Opening1TopArch
775 openingSpecs
[openingIdx
]['t'] = self
.Opening1TopArchThickness
777 if self
.Opening1BtmArchTog
:
778 openingSpecs
[openingIdx
]['vl'] = self
.Opening1BtmArch
779 openingSpecs
[openingIdx
]['tl'] = self
.Opening1BtmArchThickness
781 openingSpecs
[openingIdx
]['b'] = self
.Opening1Bevel
783 openingIdx
+= 1 # count window/door/arch openings
785 # Slots (narrow openings)
788 if self
.SlotV
: # vertical slots
790 openingSpecs
+= [{'w': 0.5, 'h': 0.5, 'x': 0.0, 'z': 2.7, 'rp': 0,
791 'b': 0.0, 'v': 0, 'vl': 0, 't': 0, 'tl': 0}]
793 openingSpecs
[openingIdx
]['w'] = self
.SlotGap
794 openingSpecs
[openingIdx
]['h'] = self
.SlotVH
795 openingSpecs
[openingIdx
]['x'] = self
.SlotX
796 openingSpecs
[openingIdx
]['z'] = self
.SlotVBtm
797 openingSpecs
[openingIdx
]['rp'] = self
.SlotRpt
799 # make them pointy...
800 openingSpecs
[openingIdx
]['v'] = self
.SlotGap
801 openingSpecs
[openingIdx
]['t'] = self
.SlotGap
/ 2
802 openingSpecs
[openingIdx
]['vl'] = self
.SlotGap
803 openingSpecs
[openingIdx
]['tl'] = self
.SlotGap
/ 2
805 openingIdx
+= 1 # count vertical slot openings
807 # need to handle overlap of H and V slots...
809 if self
.SlotH
: # Horizontal slots
811 openingSpecs
+= [{'w': 0.5, 'h': 0.5, 'x': 0.0, 'z': 2.7, 'rp': 0,
812 'b': 0.0, 'v': 0, 'vl': 0, 't': 0, 'tl': 0}]
814 openingSpecs
[openingIdx
]['w'] = self
.SlotHW
815 openingSpecs
[openingIdx
]['h'] = self
.SlotGap
816 openingSpecs
[openingIdx
]['x'] = self
.SlotX
817 openingSpecs
[openingIdx
]['z'] = self
.SlotHBtm
818 # horizontal repeat isn't same spacing as vertical...
819 openingSpecs
[openingIdx
]['rp'] = self
.SlotRpt
821 # make them pointy...
822 openingIdx
+= 1 # count horizontal slot openings
824 # Crenellations (top row openings)
827 # add bottom arch option?
828 # perhaps a repeat toggle...
829 # if crenel opening overlaps with arch opening it fills with blocks...
832 openingSpecs
+= [{'w': 0.5, 'h': 0.5, 'x': 0.0, 'z': 2.7, 'rp': 1,
833 'b': 0.0, 'v': 0, 'vl': 0, 't': 0, 'tl': 0}]
835 wallW
= self
.WallEnd
- self
.WallStart
836 crenelW
= wallW
* self
.CrenelXP
# Width % opening.
838 wallH
= self
.WallTop
- self
.WallBottom
839 crenelH
= wallH
* self
.CrenelZP
# % proportional height.
841 openingSpecs
[openingIdx
]['w'] = crenelW
842 openingSpecs
[openingIdx
]['h'] = crenelH
844 # calculate the spacing between openings.
845 # this isn't the absolute start (left),
846 # it's opening center offset relative to cursor (space between openings)...
847 openingSpecs
[openingIdx
]['x'] = crenelW
* 2 - 1 # assume standard spacing
849 if not radialized
: # normal wall?
850 # set indent 0 (center) if opening is 50% or more of wall width, no repeat.
851 if crenelW
* 2 >= wallW
:
852 openingSpecs
[openingIdx
]['x'] = 0
853 openingSpecs
[openingIdx
]['rp'] = 0
854 # set bottom of opening (center of hole)
855 openingSpecs
[openingIdx
]['z'] = self
.WallTop
- (crenelH
/ 2)
857 openingIdx
+= 1 # count crenel openings
859 # Process the user settings to generate a wall
860 # generate the list of vertices for the wall...
861 verts_array
, faces_array
= createWall(
862 radialized
, slope
, openingSpecs
, bigBlock
,
863 shelfExt
, shelfBack
, stepMod
, stepLeft
, stepOnly
,
867 if bpy
.context
.mode
== "OBJECT":
868 if context
.selected_objects
!= [] and context
.active_object
and \
869 (context
.active_object
.data
is not None) and ('Wall' in context
.active_object
.data
.keys()) and \
870 (self
.change
== True):
871 obj
= context
.active_object
873 oldmeshname
= obj
.data
.name
874 mesh
= bpy
.data
.meshes
.new("Wall")
875 mesh
.from_pydata(verts_array
, [], faces_array
)
877 for material
in oldmesh
.materials
:
878 obj
.data
.materials
.append(material
)
879 bpy
.data
.meshes
.remove(oldmesh
)
880 obj
.data
.name
= oldmeshname
882 mesh
= bpy
.data
.meshes
.new("Wall")
883 mesh
.from_pydata(verts_array
, [], faces_array
)
884 obj
= object_utils
.object_data_add(context
, mesh
, operator
=self
)
888 obj
.data
["Wall"] = True
889 obj
.data
["change"] = False
890 for prm
in WallParameters():
891 obj
.data
[prm
] = getattr(self
, prm
)
893 if bpy
.context
.mode
== "EDIT_MESH":
894 active_object
= context
.active_object
895 name_active_object
= active_object
.name
896 bpy
.ops
.object.mode_set(mode
='OBJECT')
897 mesh
= bpy
.data
.meshes
.new("TMP")
898 mesh
.from_pydata(verts_array
, [], faces_array
)
899 obj
= object_utils
.object_data_add(context
, mesh
, operator
=self
)
901 active_object
.select_set(True)
902 bpy
.context
.view_layer
.objects
.active
= active_object
903 bpy
.ops
.object.join()
904 context
.active_object
.name
= name_active_object
905 bpy
.ops
.object.mode_set(mode
='EDIT')
907 if use_enter_edit_mode
:
908 bpy
.ops
.object.mode_set(mode
= 'EDIT')
910 # restore pre operator state
911 bpy
.context
.preferences
.edit
.use_enter_edit_mode
= use_enter_edit_mode
915 def WallParameters():
938 "GroutDepthVariance",
946 "Opening1TopArchTog",
948 "Opening1TopArchThickness",
949 "Opening1BtmArchTog",
951 "Opening1BtmArchThickness",
985 return WallParameters