1 # SPDX-FileCopyrightText: 2011-2022 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
8 from bpy
.props
import (
14 from math
import pi
, cos
, sin
15 from mathutils
import Vector
16 from bpy_extras
import object_utils
19 # A very simple "bridge" tool
21 def createFaces(vertIdx1
, vertIdx2
, closed
=False, flipped
=False):
24 if not vertIdx1
or not vertIdx2
:
27 if len(vertIdx1
) < 2 and len(vertIdx2
) < 2:
31 if (len(vertIdx1
) != len(vertIdx2
)):
32 if (len(vertIdx1
) == 1 and len(vertIdx2
) > 1):
40 # Bridge the start with the end.
47 face
.append(vertIdx1
[total
- 1])
51 face
= [vertIdx2
[0], vertIdx1
[0]]
53 face
.append(vertIdx1
[total
- 1])
54 face
.append(vertIdx2
[total
- 1])
57 # Bridge the rest of the faces.
58 for num
in range(total
- 1):
61 face
= [vertIdx2
[num
], vertIdx1
[0], vertIdx2
[num
+ 1]]
63 face
= [vertIdx2
[num
], vertIdx1
[num
],
64 vertIdx1
[num
+ 1], vertIdx2
[num
+ 1]]
68 face
= [vertIdx1
[0], vertIdx2
[num
], vertIdx2
[num
+ 1]]
70 face
= [vertIdx1
[num
], vertIdx2
[num
],
71 vertIdx2
[num
+ 1], vertIdx1
[num
+ 1]]
83 def supertoroid(R
, r
, u
, v
, n1
, n2
):
87 u = lateral segmentation
88 v = radial segmentation
89 n1 = value determines the shape of the torus
90 n2 = value determines the shape of the cross-section
92 # create the necessary constants
99 # create each cross-section by calculating each vector on the
101 # x = (cos(theta) ** n1)*(R + r * (cos(phi) ** n2))
102 # y = (sin(theta) ** n1)*(R + r * (cos(phi) ** n2))
103 # z = (r * sin(phi) ** n2)
104 # with theta and phi ranging from 0 to 2pi
107 s
= power(sin(i
* a
), n1
)
108 c
= power(cos(i
* a
), n1
)
110 c2
= R
+ r
* power(cos(j
* b
), n2
)
111 s2
= r
* power(sin(j
* b
), n2
)
112 verts
.append(Vector((c
* c2
, s
* c2
, s2
)))
114 # bridge the last circle with the previous circle
115 if i
> 0: # but not for the first circle, 'cus there's no previous before the first
116 f
= createFaces(range((i
- 1) * v
, i
* v
), range(i
* v
, (i
+ 1) * v
), closed
=True)
118 # bridge the last circle with the first
119 f
= createFaces(range((u
- 1) * v
, u
* v
), range(v
), closed
=True)
125 class add_supertoroid(bpy
.types
.Operator
, object_utils
.AddObjectHelper
):
126 bl_idname
= "mesh.primitive_supertoroid_add"
127 bl_label
= "Add SuperToroid"
128 bl_description
= "Construct a supertoroid mesh"
129 bl_options
= {'REGISTER', 'UNDO', 'PRESET'}
131 SuperToroid
: BoolProperty(name
= "SuperToroid",
133 description
= "SuperToroid")
134 change
: BoolProperty(name
= "Change",
136 description
= "change SuperToroid")
140 description
="The radius inside the tube",
146 description
="The radius of the tube",
152 description
="Radial segmentation",
158 description
="Lateral segmentation",
163 name
="Ring manipulator",
164 description
="Manipulates the shape of the Ring",
169 name
="Cross manipulator",
170 description
="Manipulates the shape of the cross-section",
175 name
="Use Int. and Ext. radii",
176 description
="Use internal and external radii",
186 def draw(self
, context
):
189 layout
.prop(self
, 'R', expand
=True)
190 layout
.prop(self
, 'r', expand
=True)
191 layout
.prop(self
, 'u', expand
=True)
192 layout
.prop(self
, 'v', expand
=True)
193 layout
.prop(self
, 'n1', expand
=True)
194 layout
.prop(self
, 'n2', expand
=True)
195 layout
.prop(self
, 'ie', expand
=True)
197 if self
.change
== False:
198 col
= layout
.column(align
=True)
199 col
.prop(self
, 'align', expand
=True)
200 col
= layout
.column(align
=True)
201 col
.prop(self
, 'location', expand
=True)
202 col
= layout
.column(align
=True)
203 col
.prop(self
, 'rotation', expand
=True)
205 def execute(self
, context
):
206 # turn off 'Enter Edit Mode'
207 use_enter_edit_mode
= bpy
.context
.preferences
.edit
.use_enter_edit_mode
208 bpy
.context
.preferences
.edit
.use_enter_edit_mode
= False
210 props
= self
.properties
212 # check how the radii properties must be used
214 rad1
= (props
.R
+ props
.r
) / 2
215 rad2
= (props
.R
- props
.r
) / 2
216 # for consistency in the mesh, ie no crossing faces, make the largest of the two
219 [rad1
, rad2
] = [rad2
, rad1
]
223 # again for consistency, make the radius in the tube,
224 # at least as big as the radius of the tube
228 if bpy
.context
.mode
== "OBJECT":
229 if context
.selected_objects
!= [] and context
.active_object
and \
230 (context
.active_object
.data
is not None) and ('SuperToroid' in context
.active_object
.data
.keys()) and \
231 (self
.change
== True):
232 obj
= context
.active_object
234 oldmeshname
= obj
.data
.name
235 verts
, faces
= supertoroid(rad1
,
242 mesh
= bpy
.data
.meshes
.new('SuperToroid')
243 mesh
.from_pydata(verts
, [], faces
)
245 for material
in oldmesh
.materials
:
246 obj
.data
.materials
.append(material
)
247 bpy
.data
.meshes
.remove(oldmesh
)
248 obj
.data
.name
= oldmeshname
250 verts
, faces
= supertoroid(rad1
,
257 mesh
= bpy
.data
.meshes
.new('SuperToroid')
258 mesh
.from_pydata(verts
, [], faces
)
259 obj
= object_utils
.object_data_add(context
, mesh
, operator
=self
)
261 obj
.data
["SuperToroid"] = True
262 obj
.data
["change"] = False
263 for prm
in SuperToroidParameters():
264 obj
.data
[prm
] = getattr(self
, prm
)
266 if bpy
.context
.mode
== "EDIT_MESH":
267 active_object
= context
.active_object
268 name_active_object
= active_object
.name
269 bpy
.ops
.object.mode_set(mode
='OBJECT')
270 verts
, faces
= supertoroid(rad1
,
277 mesh
= bpy
.data
.meshes
.new('SuperToroid')
278 mesh
.from_pydata(verts
, [], faces
)
279 obj
= object_utils
.object_data_add(context
, mesh
, operator
=self
)
281 active_object
.select_set(True)
282 bpy
.context
.view_layer
.objects
.active
= active_object
283 bpy
.ops
.object.join()
284 context
.active_object
.name
= name_active_object
285 bpy
.ops
.object.mode_set(mode
='EDIT')
287 if use_enter_edit_mode
:
288 bpy
.ops
.object.mode_set(mode
= 'EDIT')
290 # restore pre operator state
291 bpy
.context
.preferences
.edit
.use_enter_edit_mode
= use_enter_edit_mode
295 def SuperToroidParameters():
296 SuperToroidParameters
= [
306 return SuperToroidParameters