Cleanup: trailing space
[blender-addons.git] / add_mesh_extra_objects / add_mesh_twisted_torus.py
blob1a2b47f77e240fe75d0e9e1a709d75e5aec94b51
1 # SPDX-License-Identifier: GPL-2.0-or-later
3 # Author: Paulo_Gomes
5 import bpy
6 from mathutils import Quaternion, Vector
7 from math import cos, sin, pi
8 from bpy.props import (
9 FloatProperty,
10 IntProperty,
11 BoolProperty,
12 StringProperty,
14 from bpy_extras import object_utils
17 # Create a new mesh (object) from verts/edges/faces
18 # verts/edges/faces ... List of vertices/edges/faces for the
19 # new mesh (as used in from_pydata)
20 # name ... Name of the new mesh (& object)
22 def create_mesh_object(context, verts, edges, faces, name):
24 # Create new mesh
25 mesh = bpy.data.meshes.new(name)
27 # Make a mesh from a list of verts/edges/faces
28 mesh.from_pydata(verts, edges, faces)
30 # Update mesh geometry after adding stuff
31 mesh.update()
33 from bpy_extras import object_utils
34 return object_utils.object_data_add(context, mesh, operator=None)
37 # A very simple "bridge" tool
39 def createFaces(vertIdx1, vertIdx2, closed=False, flipped=False):
40 faces = []
42 if not vertIdx1 or not vertIdx2:
43 return None
45 if len(vertIdx1) < 2 and len(vertIdx2) < 2:
46 return None
48 fan = False
49 if (len(vertIdx1) != len(vertIdx2)):
50 if (len(vertIdx1) == 1 and len(vertIdx2) > 1):
51 fan = True
52 else:
53 return None
55 total = len(vertIdx2)
57 if closed:
58 # Bridge the start with the end
59 if flipped:
60 face = [
61 vertIdx1[0],
62 vertIdx2[0],
63 vertIdx2[total - 1]]
64 if not fan:
65 face.append(vertIdx1[total - 1])
66 faces.append(face)
68 else:
69 face = [vertIdx2[0], vertIdx1[0]]
70 if not fan:
71 face.append(vertIdx1[total - 1])
72 face.append(vertIdx2[total - 1])
73 faces.append(face)
75 # Bridge the rest of the faces
76 for num in range(total - 1):
77 if flipped:
78 if fan:
79 face = [vertIdx2[num], vertIdx1[0], vertIdx2[num + 1]]
80 else:
81 face = [vertIdx2[num], vertIdx1[num],
82 vertIdx1[num + 1], vertIdx2[num + 1]]
83 faces.append(face)
84 else:
85 if fan:
86 face = [vertIdx1[0], vertIdx2[num], vertIdx2[num + 1]]
87 else:
88 face = [vertIdx1[num], vertIdx2[num],
89 vertIdx2[num + 1], vertIdx1[num + 1]]
90 faces.append(face)
92 return faces
95 def add_twisted_torus(major_rad, minor_rad, major_seg, minor_seg, twists):
96 PI_2 = pi * 2.0
97 z_axis = (0.0, 0.0, 1.0)
99 verts = []
100 faces = []
102 edgeloop_prev = []
103 for major_index in range(major_seg):
104 quat = Quaternion(z_axis, (major_index / major_seg) * PI_2)
105 rot_twists = PI_2 * major_index / major_seg * twists
107 edgeloop = []
109 # Create section ring
110 for minor_index in range(minor_seg):
111 angle = (PI_2 * minor_index / minor_seg) + rot_twists
113 vec = Vector((
114 major_rad + (cos(angle) * minor_rad),
115 0.0,
116 sin(angle) * minor_rad))
117 vec = quat @ vec
119 edgeloop.append(len(verts))
120 verts.append(vec)
122 # Remember very first edgeloop
123 if major_index == 0:
124 edgeloop_first = edgeloop
126 # Bridge last with current ring
127 if edgeloop_prev:
128 f = createFaces(edgeloop_prev, edgeloop, closed=True)
129 faces.extend(f)
131 edgeloop_prev = edgeloop
133 # Bridge first and last ring
134 f = createFaces(edgeloop_prev, edgeloop_first, closed=True)
135 faces.extend(f)
137 return verts, faces
140 class AddTwistedTorus(bpy.types.Operator, object_utils.AddObjectHelper):
141 bl_idname = "mesh.primitive_twisted_torus_add"
142 bl_label = "Add Twisted Torus"
143 bl_description = "Construct a twisted torus mesh"
144 bl_options = {'REGISTER', 'UNDO', 'PRESET'}
146 TwistedTorus : BoolProperty(name = "TwistedTorus",
147 default = True,
148 description = "TwistedTorus")
149 change : BoolProperty(name = "Change",
150 default = False,
151 description = "change TwistedTorus")
153 major_radius: FloatProperty(
154 name="Major Radius",
155 description="Radius from the origin to the"
156 " center of the cross section",
157 min=0.01,
158 max=100.0,
159 default=1.0
161 minor_radius: FloatProperty(
162 name="Minor Radius",
163 description="Radius of the torus' cross section",
164 min=0.01,
165 max=100.0,
166 default=0.25
168 major_segments: IntProperty(
169 name="Major Segments",
170 description="Number of segments for the main ring of the torus",
171 min=3,
172 max=256,
173 default=48
175 minor_segments: IntProperty(
176 name="Minor Segments",
177 description="Number of segments for the minor ring of the torus",
178 min=3,
179 max=256,
180 default=12
182 twists: IntProperty(
183 name="Twists",
184 description="Number of twists of the torus",
185 min=0,
186 max=256,
187 default=1
189 use_abso: BoolProperty(
190 name="Use Int/Ext Controls",
191 description="Use the Int/Ext controls for torus dimensions",
192 default=False
194 abso_major_rad: FloatProperty(
195 name="Exterior Radius",
196 description="Total Exterior Radius of the torus",
197 min=0.01,
198 max=100.0,
199 default=1.0
201 abso_minor_rad: FloatProperty(
202 name="Inside Radius",
203 description="Total Interior Radius of the torus",
204 min=0.01,
205 max=100.0,
206 default=0.5
209 def draw(self, context):
210 layout = self.layout
212 layout.prop(self, 'major_radius', expand=True)
213 layout.prop(self, 'minor_radius', expand=True)
214 layout.prop(self, 'major_segments', expand=True)
215 layout.prop(self, 'minor_segments', expand=True)
216 layout.prop(self, 'twists', expand=True)
217 layout.prop(self, 'use_abso', expand=True)
218 layout.prop(self, 'abso_major_rad', expand=True)
219 layout.prop(self, 'abso_minor_rad', expand=True)
221 if self.change == False:
222 col = layout.column(align=True)
223 col.prop(self, 'align', expand=True)
224 col = layout.column(align=True)
225 col.prop(self, 'location', expand=True)
226 col = layout.column(align=True)
227 col.prop(self, 'rotation', expand=True)
229 def execute(self, context):
230 # turn off 'Enter Edit Mode'
231 use_enter_edit_mode = bpy.context.preferences.edit.use_enter_edit_mode
232 bpy.context.preferences.edit.use_enter_edit_mode = False
234 if self.use_abso is True:
235 extra_helper = (self.abso_major_rad - self.abso_minor_rad) * 0.5
236 self.major_radius = self.abso_minor_rad + extra_helper
237 self.minor_radius = extra_helper
239 if bpy.context.mode == "OBJECT":
240 if context.selected_objects != [] and context.active_object and \
241 (context.active_object.data is not None) and ('TwistedTorus' in context.active_object.data.keys()) and \
242 (self.change == True):
243 obj = context.active_object
244 oldmesh = obj.data
245 oldmeshname = obj.data.name
246 verts, faces = add_twisted_torus(
247 self.major_radius,
248 self.minor_radius,
249 self.major_segments,
250 self.minor_segments,
251 self.twists
253 mesh = bpy.data.meshes.new('TwistedTorus')
254 mesh.from_pydata(verts, [], faces)
255 obj.data = mesh
256 for material in oldmesh.materials:
257 obj.data.materials.append(material)
258 bpy.data.meshes.remove(oldmesh)
259 obj.data.name = oldmeshname
260 else:
261 verts, faces = add_twisted_torus(
262 self.major_radius,
263 self.minor_radius,
264 self.major_segments,
265 self.minor_segments,
266 self.twists
268 mesh = bpy.data.meshes.new('TwistedTorus')
269 mesh.from_pydata(verts, [], faces)
270 obj = object_utils.object_data_add(context, mesh, operator=self)
272 obj.data["TwistedTorus"] = True
273 obj.data["change"] = False
274 for prm in TwistedTorusParameters():
275 obj.data[prm] = getattr(self, prm)
277 if bpy.context.mode == "EDIT_MESH":
278 active_object = context.active_object
279 name_active_object = active_object.name
280 bpy.ops.object.mode_set(mode='OBJECT')
281 verts, faces = add_twisted_torus(
282 self.major_radius,
283 self.minor_radius,
284 self.major_segments,
285 self.minor_segments,
286 self.twists
288 mesh = bpy.data.meshes.new('TwistedTorus')
289 mesh.from_pydata(verts, [], faces)
290 obj = object_utils.object_data_add(context, mesh, operator=self)
291 obj.select_set(True)
292 active_object.select_set(True)
293 bpy.context.view_layer.objects.active = active_object
294 bpy.ops.object.join()
295 context.active_object.name = name_active_object
296 bpy.ops.object.mode_set(mode='EDIT')
298 if use_enter_edit_mode:
299 bpy.ops.object.mode_set(mode = 'EDIT')
301 # restore pre operator state
302 bpy.context.preferences.edit.use_enter_edit_mode = use_enter_edit_mode
304 return {'FINISHED'}
306 def TwistedTorusParameters():
307 TwistedTorusParameters = [
308 "major_radius",
309 "minor_radius",
310 "major_segments",
311 "minor_segments",
312 "twists",
313 "use_abso",
314 "abso_major_rad",
315 "abso_minor_rad",
317 return TwistedTorusParameters