1 # SPDX-FileCopyrightText: 2011-2022 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
5 # Author: Phil Cote, cotejrp1, (http://www.blenderaddons.com)
9 from bpy
.props
import (
16 from mathutils
import (
20 from bpy_extras
import object_utils
23 def create_step(width
, base_level
, step_height
, num_sides
):
29 quat_angles
= [(cur_side
/ num_sides
) * PI2
30 for cur_side
in range(num_sides
)]
32 quaternions
= [Quaternion(axis
, quat_angle
)
33 for quat_angle
in quat_angles
]
35 init_vectors
= [Vector([rad
, 0, base_level
])] * len(quaternions
)
37 quat_vector_pairs
= list(zip(quaternions
, init_vectors
))
38 vectors
= [quaternion
@ vec
for quaternion
, vec
in quat_vector_pairs
]
39 bottom_list
= [(vec
.x
, vec
.y
, vec
.z
) for vec
in vectors
]
40 top_list
= [(vec
.x
, vec
.y
, vec
.z
+ step_height
) for vec
in vectors
]
41 full_list
= bottom_list
+ top_list
48 split the blocks up. Credit to oremj for this one.
49 http://stackoverflow.com/questions/312443/how-do-you-split-a-list-into-evenly-sized-chunks-in-python
52 returned_list
= [l
[i
: i
+ n
] for i
in range(0, len(l
), n
)]
56 def get_connector_pairs(lst
, n_sides
):
57 # chop off the verts that get used for the base and top
60 lst
= split_list(lst
, n_sides
)
64 def pyramid_mesh(self
, context
):
68 cur_width
= self
.width
70 for i
in range(self
.num_steps
):
71 verts_loc
= create_step(cur_width
, height_offset
, self
.height
,
73 height_offset
+= self
.height
74 cur_width
-= self
.reduce_by
75 all_verts
.extend(verts_loc
)
77 mesh
= bpy
.data
.meshes
.new("Pyramid")
80 for v_co
in all_verts
:
83 def add_faces(n
, block_vert_sets
):
84 for bvs
in block_vert_sets
:
85 for i
in range(self
.num_sides
- 1):
86 bm
.faces
.new([bvs
[i
], bvs
[i
+ n
], bvs
[i
+ n
+ 1], bvs
[i
+ 1]])
87 bm
.faces
.new([bvs
[n
- 1], bvs
[(n
* 2) - 1], bvs
[n
], bvs
[0]])
89 # get the base and cap faces done.
90 bm
.faces
.new(bm
.verts
[0:self
.num_sides
])
91 bm
.faces
.new(reversed(bm
.verts
[-self
.num_sides
:])) # otherwise normal faces intern... T44619.
94 block_vert_sets
= split_list(bm
.verts
, self
.num_sides
)
95 add_faces(self
.num_sides
, block_vert_sets
)
97 # connector faces between faces and faces of the block above it.
98 connector_pairs
= get_connector_pairs(bm
.verts
, self
.num_sides
)
99 add_faces(self
.num_sides
, connector_pairs
)
107 class AddPyramid(bpy
.types
.Operator
, object_utils
.AddObjectHelper
):
108 bl_idname
= "mesh.primitive_steppyramid_add"
110 bl_description
= "Construct a step pyramid mesh"
111 bl_options
= {'REGISTER', 'UNDO', 'PRESET'}
113 Pyramid
: BoolProperty(name
= "Pyramid",
115 description
= "Pyramid")
116 change
: BoolProperty(name
= "Change",
118 description
= "change Pyramid")
120 num_sides
: IntProperty(
122 description
="How many sides each step will have",
126 num_steps
: IntProperty(
127 name
="Number of Steps",
128 description
="How many steps for the overall pyramid",
132 width
: FloatProperty(
133 name
="Initial Width",
134 description
="Initial base step width",
138 height
: FloatProperty(
140 description
="How tall each step will be",
144 reduce_by
: FloatProperty(
145 name
="Reduce Step By",
146 description
="How much to reduce each succeeding step by",
151 def draw(self
, context
):
154 layout
.prop(self
, 'num_sides', expand
=True)
155 layout
.prop(self
, 'num_steps', expand
=True)
156 layout
.prop(self
, 'width', expand
=True)
157 layout
.prop(self
, 'height', expand
=True)
158 layout
.prop(self
, 'reduce_by', expand
=True)
160 if self
.change
== False:
161 col
= layout
.column(align
=True)
162 col
.prop(self
, 'align', expand
=True)
163 col
= layout
.column(align
=True)
164 col
.prop(self
, 'location', expand
=True)
165 col
= layout
.column(align
=True)
166 col
.prop(self
, 'rotation', expand
=True)
168 def execute(self
, context
):
169 # turn off 'Enter Edit Mode'
170 use_enter_edit_mode
= bpy
.context
.preferences
.edit
.use_enter_edit_mode
171 bpy
.context
.preferences
.edit
.use_enter_edit_mode
= False
173 if bpy
.context
.mode
== "OBJECT":
174 if context
.selected_objects
!= [] and context
.active_object
and \
175 (context
.active_object
.data
is not None) and ('Pyramid' in context
.active_object
.data
.keys()) and \
176 (self
.change
== True):
177 obj
= context
.active_object
179 oldmeshname
= obj
.data
.name
180 obj
.data
= pyramid_mesh(self
, context
)
181 for material
in oldmesh
.materials
:
182 obj
.data
.materials
.append(material
)
183 bpy
.data
.meshes
.remove(oldmesh
)
184 obj
.data
.name
= oldmeshname
186 mesh
= pyramid_mesh(self
, context
)
187 obj
= object_utils
.object_data_add(context
, mesh
, operator
=self
)
189 obj
.data
["Pyramid"] = True
190 obj
.data
["change"] = False
191 for prm
in PyramidParameters():
192 obj
.data
[prm
] = getattr(self
, prm
)
194 if bpy
.context
.mode
== "EDIT_MESH":
195 active_object
= context
.active_object
196 name_active_object
= active_object
.name
197 bpy
.ops
.object.mode_set(mode
='OBJECT')
198 mesh
= pyramid_mesh(self
, context
)
199 obj
= object_utils
.object_data_add(context
, mesh
, operator
=self
)
201 active_object
.select_set(True)
202 bpy
.context
.view_layer
.objects
.active
= active_object
203 bpy
.ops
.object.join()
204 context
.active_object
.name
= name_active_object
205 bpy
.ops
.object.mode_set(mode
='EDIT')
207 if use_enter_edit_mode
:
208 bpy
.ops
.object.mode_set(mode
= 'EDIT')
210 # restore pre operator state
211 bpy
.context
.preferences
.edit
.use_enter_edit_mode
= use_enter_edit_mode
215 def PyramidParameters():
216 PyramidParameters
= [
223 return PyramidParameters