Merge branch 'blender-v4.0-release'
[blender-addons.git] / add_mesh_extra_objects / add_mesh_twisted_torus.py
blobcdd73bb10b2011b56e112a2fb51ca83dcd2d8eed
1 # SPDX-FileCopyrightText: 2010-2022 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
5 # Author: Paulo_Gomes
7 import bpy
8 from mathutils import Quaternion, Vector
9 from math import cos, sin, pi
10 from bpy.props import (
11 FloatProperty,
12 IntProperty,
13 BoolProperty,
14 StringProperty,
16 from bpy_extras import object_utils
19 # Create a new mesh (object) from verts/edges/faces
20 # verts/edges/faces ... List of vertices/edges/faces for the
21 # new mesh (as used in from_pydata)
22 # name ... Name of the new mesh (& object)
24 def create_mesh_object(context, verts, edges, faces, name):
26 # Create new mesh
27 mesh = bpy.data.meshes.new(name)
29 # Make a mesh from a list of verts/edges/faces
30 mesh.from_pydata(verts, edges, faces)
32 # Update mesh geometry after adding stuff
33 mesh.update()
35 from bpy_extras import object_utils
36 return object_utils.object_data_add(context, mesh, operator=None)
39 # A very simple "bridge" tool
41 def createFaces(vertIdx1, vertIdx2, closed=False, flipped=False):
42 faces = []
44 if not vertIdx1 or not vertIdx2:
45 return None
47 if len(vertIdx1) < 2 and len(vertIdx2) < 2:
48 return None
50 fan = False
51 if (len(vertIdx1) != len(vertIdx2)):
52 if (len(vertIdx1) == 1 and len(vertIdx2) > 1):
53 fan = True
54 else:
55 return None
57 total = len(vertIdx2)
59 if closed:
60 # Bridge the start with the end
61 if flipped:
62 face = [
63 vertIdx1[0],
64 vertIdx2[0],
65 vertIdx2[total - 1]]
66 if not fan:
67 face.append(vertIdx1[total - 1])
68 faces.append(face)
70 else:
71 face = [vertIdx2[0], vertIdx1[0]]
72 if not fan:
73 face.append(vertIdx1[total - 1])
74 face.append(vertIdx2[total - 1])
75 faces.append(face)
77 # Bridge the rest of the faces
78 for num in range(total - 1):
79 if flipped:
80 if fan:
81 face = [vertIdx2[num], vertIdx1[0], vertIdx2[num + 1]]
82 else:
83 face = [vertIdx2[num], vertIdx1[num],
84 vertIdx1[num + 1], vertIdx2[num + 1]]
85 faces.append(face)
86 else:
87 if fan:
88 face = [vertIdx1[0], vertIdx2[num], vertIdx2[num + 1]]
89 else:
90 face = [vertIdx1[num], vertIdx2[num],
91 vertIdx2[num + 1], vertIdx1[num + 1]]
92 faces.append(face)
94 return faces
97 def add_twisted_torus(major_rad, minor_rad, major_seg, minor_seg, twists):
98 PI_2 = pi * 2.0
99 z_axis = (0.0, 0.0, 1.0)
101 verts = []
102 faces = []
104 edgeloop_prev = []
105 for major_index in range(major_seg):
106 quat = Quaternion(z_axis, (major_index / major_seg) * PI_2)
107 rot_twists = PI_2 * major_index / major_seg * twists
109 edgeloop = []
111 # Create section ring
112 for minor_index in range(minor_seg):
113 angle = (PI_2 * minor_index / minor_seg) + rot_twists
115 vec = Vector((
116 major_rad + (cos(angle) * minor_rad),
117 0.0,
118 sin(angle) * minor_rad))
119 vec = quat @ vec
121 edgeloop.append(len(verts))
122 verts.append(vec)
124 # Remember very first edgeloop
125 if major_index == 0:
126 edgeloop_first = edgeloop
128 # Bridge last with current ring
129 if edgeloop_prev:
130 f = createFaces(edgeloop_prev, edgeloop, closed=True)
131 faces.extend(f)
133 edgeloop_prev = edgeloop
135 # Bridge first and last ring
136 f = createFaces(edgeloop_prev, edgeloop_first, closed=True)
137 faces.extend(f)
139 return verts, faces
142 class AddTwistedTorus(bpy.types.Operator, object_utils.AddObjectHelper):
143 bl_idname = "mesh.primitive_twisted_torus_add"
144 bl_label = "Add Twisted Torus"
145 bl_description = "Construct a twisted torus mesh"
146 bl_options = {'REGISTER', 'UNDO', 'PRESET'}
148 TwistedTorus : BoolProperty(name = "TwistedTorus",
149 default = True,
150 description = "TwistedTorus")
151 change : BoolProperty(name = "Change",
152 default = False,
153 description = "change TwistedTorus")
155 major_radius: FloatProperty(
156 name="Major Radius",
157 description="Radius from the origin to the"
158 " center of the cross section",
159 min=0.01,
160 max=100.0,
161 default=1.0
163 minor_radius: FloatProperty(
164 name="Minor Radius",
165 description="Radius of the torus' cross section",
166 min=0.01,
167 max=100.0,
168 default=0.25
170 major_segments: IntProperty(
171 name="Major Segments",
172 description="Number of segments for the main ring of the torus",
173 min=3,
174 max=256,
175 default=48
177 minor_segments: IntProperty(
178 name="Minor Segments",
179 description="Number of segments for the minor ring of the torus",
180 min=3,
181 max=256,
182 default=12
184 twists: IntProperty(
185 name="Twists",
186 description="Number of twists of the torus",
187 min=0,
188 max=256,
189 default=1
191 use_abso: BoolProperty(
192 name="Use Int/Ext Controls",
193 description="Use the Int/Ext controls for torus dimensions",
194 default=False
196 abso_major_rad: FloatProperty(
197 name="Exterior Radius",
198 description="Total Exterior Radius of the torus",
199 min=0.01,
200 max=100.0,
201 default=1.0
203 abso_minor_rad: FloatProperty(
204 name="Inside Radius",
205 description="Total Interior Radius of the torus",
206 min=0.01,
207 max=100.0,
208 default=0.5
211 def draw(self, context):
212 layout = self.layout
214 layout.prop(self, 'major_radius', expand=True)
215 layout.prop(self, 'minor_radius', expand=True)
216 layout.prop(self, 'major_segments', expand=True)
217 layout.prop(self, 'minor_segments', expand=True)
218 layout.prop(self, 'twists', expand=True)
219 layout.prop(self, 'use_abso', expand=True)
220 layout.prop(self, 'abso_major_rad', expand=True)
221 layout.prop(self, 'abso_minor_rad', expand=True)
223 if self.change == False:
224 col = layout.column(align=True)
225 col.prop(self, 'align', expand=True)
226 col = layout.column(align=True)
227 col.prop(self, 'location', expand=True)
228 col = layout.column(align=True)
229 col.prop(self, 'rotation', expand=True)
231 def execute(self, context):
232 # turn off 'Enter Edit Mode'
233 use_enter_edit_mode = bpy.context.preferences.edit.use_enter_edit_mode
234 bpy.context.preferences.edit.use_enter_edit_mode = False
236 if self.use_abso is True:
237 extra_helper = (self.abso_major_rad - self.abso_minor_rad) * 0.5
238 self.major_radius = self.abso_minor_rad + extra_helper
239 self.minor_radius = extra_helper
241 if bpy.context.mode == "OBJECT":
242 if context.selected_objects != [] and context.active_object and \
243 (context.active_object.data is not None) and ('TwistedTorus' in context.active_object.data.keys()) and \
244 (self.change == True):
245 obj = context.active_object
246 oldmesh = obj.data
247 oldmeshname = obj.data.name
248 verts, faces = add_twisted_torus(
249 self.major_radius,
250 self.minor_radius,
251 self.major_segments,
252 self.minor_segments,
253 self.twists
255 mesh = bpy.data.meshes.new('TwistedTorus')
256 mesh.from_pydata(verts, [], faces)
257 obj.data = mesh
258 for material in oldmesh.materials:
259 obj.data.materials.append(material)
260 bpy.data.meshes.remove(oldmesh)
261 obj.data.name = oldmeshname
262 else:
263 verts, faces = add_twisted_torus(
264 self.major_radius,
265 self.minor_radius,
266 self.major_segments,
267 self.minor_segments,
268 self.twists
270 mesh = bpy.data.meshes.new('TwistedTorus')
271 mesh.from_pydata(verts, [], faces)
272 obj = object_utils.object_data_add(context, mesh, operator=self)
274 obj.data["TwistedTorus"] = True
275 obj.data["change"] = False
276 for prm in TwistedTorusParameters():
277 obj.data[prm] = getattr(self, prm)
279 if bpy.context.mode == "EDIT_MESH":
280 active_object = context.active_object
281 name_active_object = active_object.name
282 bpy.ops.object.mode_set(mode='OBJECT')
283 verts, faces = add_twisted_torus(
284 self.major_radius,
285 self.minor_radius,
286 self.major_segments,
287 self.minor_segments,
288 self.twists
290 mesh = bpy.data.meshes.new('TwistedTorus')
291 mesh.from_pydata(verts, [], faces)
292 obj = object_utils.object_data_add(context, mesh, operator=self)
293 obj.select_set(True)
294 active_object.select_set(True)
295 bpy.context.view_layer.objects.active = active_object
296 bpy.ops.object.join()
297 context.active_object.name = name_active_object
298 bpy.ops.object.mode_set(mode='EDIT')
300 if use_enter_edit_mode:
301 bpy.ops.object.mode_set(mode = 'EDIT')
303 # restore pre operator state
304 bpy.context.preferences.edit.use_enter_edit_mode = use_enter_edit_mode
306 return {'FINISHED'}
308 def TwistedTorusParameters():
309 TwistedTorusParameters = [
310 "major_radius",
311 "minor_radius",
312 "major_segments",
313 "minor_segments",
314 "twists",
315 "use_abso",
316 "abso_major_rad",
317 "abso_minor_rad",
319 return TwistedTorusParameters