1 # SPDX-License-Identifier: GPL-2.0-or-later
6 from bpy
.props
import (
12 from math
import pi
, cos
, sin
13 from mathutils
import Vector
14 from bpy_extras
import object_utils
17 # A very simple "bridge" tool
19 def createFaces(vertIdx1
, vertIdx2
, closed
=False, flipped
=False):
22 if not vertIdx1
or not vertIdx2
:
25 if len(vertIdx1
) < 2 and len(vertIdx2
) < 2:
29 if (len(vertIdx1
) != len(vertIdx2
)):
30 if (len(vertIdx1
) == 1 and len(vertIdx2
) > 1):
38 # Bridge the start with the end.
45 face
.append(vertIdx1
[total
- 1])
49 face
= [vertIdx2
[0], vertIdx1
[0]]
51 face
.append(vertIdx1
[total
- 1])
52 face
.append(vertIdx2
[total
- 1])
55 # Bridge the rest of the faces.
56 for num
in range(total
- 1):
59 face
= [vertIdx2
[num
], vertIdx1
[0], vertIdx2
[num
+ 1]]
61 face
= [vertIdx2
[num
], vertIdx1
[num
],
62 vertIdx1
[num
+ 1], vertIdx2
[num
+ 1]]
66 face
= [vertIdx1
[0], vertIdx2
[num
], vertIdx2
[num
+ 1]]
68 face
= [vertIdx1
[num
], vertIdx2
[num
],
69 vertIdx2
[num
+ 1], vertIdx1
[num
+ 1]]
81 def supertoroid(R
, r
, u
, v
, n1
, n2
):
85 u = lateral segmentation
86 v = radial segmentation
87 n1 = value determines the shape of the torus
88 n2 = value determines the shape of the cross-section
90 # create the necessary constants
97 # create each cross-section by calculating each vector on the
99 # x = (cos(theta) ** n1)*(R + r * (cos(phi) ** n2))
100 # y = (sin(theta) ** n1)*(R + r * (cos(phi) ** n2))
101 # z = (r * sin(phi) ** n2)
102 # with theta and phi rangeing from 0 to 2pi
105 s
= power(sin(i
* a
), n1
)
106 c
= power(cos(i
* a
), n1
)
108 c2
= R
+ r
* power(cos(j
* b
), n2
)
109 s2
= r
* power(sin(j
* b
), n2
)
110 verts
.append(Vector((c
* c2
, s
* c2
, s2
)))
112 # bridge the last circle with the previous circle
113 if i
> 0: # but not for the first circle, 'cus there's no previous before the first
114 f
= createFaces(range((i
- 1) * v
, i
* v
), range(i
* v
, (i
+ 1) * v
), closed
=True)
116 # bridge the last circle with the first
117 f
= createFaces(range((u
- 1) * v
, u
* v
), range(v
), closed
=True)
123 class add_supertoroid(bpy
.types
.Operator
, object_utils
.AddObjectHelper
):
124 bl_idname
= "mesh.primitive_supertoroid_add"
125 bl_label
= "Add SuperToroid"
126 bl_description
= "Construct a supertoroid mesh"
127 bl_options
= {'REGISTER', 'UNDO', 'PRESET'}
129 SuperToroid
: BoolProperty(name
= "SuperToroid",
131 description
= "SuperToroid")
132 change
: BoolProperty(name
= "Change",
134 description
= "change SuperToroid")
138 description
="The radius inside the tube",
144 description
="The radius of the tube",
150 description
="Radial segmentation",
156 description
="Lateral segmentation",
161 name
="Ring manipulator",
162 description
="Manipulates the shape of the Ring",
167 name
="Cross manipulator",
168 description
="Manipulates the shape of the cross-section",
173 name
="Use Int. and Ext. radii",
174 description
="Use internal and external radii",
184 def draw(self
, context
):
187 layout
.prop(self
, 'R', expand
=True)
188 layout
.prop(self
, 'r', expand
=True)
189 layout
.prop(self
, 'u', expand
=True)
190 layout
.prop(self
, 'v', expand
=True)
191 layout
.prop(self
, 'n1', expand
=True)
192 layout
.prop(self
, 'n2', expand
=True)
193 layout
.prop(self
, 'ie', expand
=True)
195 if self
.change
== False:
196 col
= layout
.column(align
=True)
197 col
.prop(self
, 'align', expand
=True)
198 col
= layout
.column(align
=True)
199 col
.prop(self
, 'location', expand
=True)
200 col
= layout
.column(align
=True)
201 col
.prop(self
, 'rotation', expand
=True)
203 def execute(self
, context
):
204 # turn off 'Enter Edit Mode'
205 use_enter_edit_mode
= bpy
.context
.preferences
.edit
.use_enter_edit_mode
206 bpy
.context
.preferences
.edit
.use_enter_edit_mode
= False
208 props
= self
.properties
210 # check how the radii properties must be used
212 rad1
= (props
.R
+ props
.r
) / 2
213 rad2
= (props
.R
- props
.r
) / 2
214 # for consistency in the mesh, ie no crossing faces, make the largest of the two
217 [rad1
, rad2
] = [rad2
, rad1
]
221 # again for consistency, make the radius in the tube,
222 # at least as big as the radius of the tube
226 if bpy
.context
.mode
== "OBJECT":
227 if context
.selected_objects
!= [] and context
.active_object
and \
228 (context
.active_object
.data
is not None) and ('SuperToroid' in context
.active_object
.data
.keys()) and \
229 (self
.change
== True):
230 obj
= context
.active_object
232 oldmeshname
= obj
.data
.name
233 verts
, faces
= supertoroid(rad1
,
240 mesh
= bpy
.data
.meshes
.new('SuperToroid')
241 mesh
.from_pydata(verts
, [], faces
)
243 for material
in oldmesh
.materials
:
244 obj
.data
.materials
.append(material
)
245 bpy
.data
.meshes
.remove(oldmesh
)
246 obj
.data
.name
= oldmeshname
248 verts
, faces
= supertoroid(rad1
,
255 mesh
= bpy
.data
.meshes
.new('SuperToroid')
256 mesh
.from_pydata(verts
, [], faces
)
257 obj
= object_utils
.object_data_add(context
, mesh
, operator
=self
)
259 obj
.data
["SuperToroid"] = True
260 obj
.data
["change"] = False
261 for prm
in SuperToroidParameters():
262 obj
.data
[prm
] = getattr(self
, prm
)
264 if bpy
.context
.mode
== "EDIT_MESH":
265 active_object
= context
.active_object
266 name_active_object
= active_object
.name
267 bpy
.ops
.object.mode_set(mode
='OBJECT')
268 verts
, faces
= supertoroid(rad1
,
275 mesh
= bpy
.data
.meshes
.new('SuperToroid')
276 mesh
.from_pydata(verts
, [], faces
)
277 obj
= object_utils
.object_data_add(context
, mesh
, operator
=self
)
279 active_object
.select_set(True)
280 bpy
.context
.view_layer
.objects
.active
= active_object
281 bpy
.ops
.object.join()
282 context
.active_object
.name
= name_active_object
283 bpy
.ops
.object.mode_set(mode
='EDIT')
285 if use_enter_edit_mode
:
286 bpy
.ops
.object.mode_set(mode
= 'EDIT')
288 # restore pre operator state
289 bpy
.context
.preferences
.edit
.use_enter_edit_mode
= use_enter_edit_mode
293 def SuperToroidParameters():
294 SuperToroidParameters
= [
304 return SuperToroidParameters