Fix #105067: Node Wrangler: cannot preview multi-user material group
[blender-addons.git] / add_mesh_extra_objects / add_mesh_supertoroid.py
blobe0491e7ca57e6364c905fffd5ec4852a279f1382
1 # SPDX-FileCopyrightText: 2011-2022 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
5 # Author: DreamPainter
7 import bpy
8 from bpy.props import (
9 FloatProperty,
10 BoolProperty,
11 IntProperty,
12 StringProperty,
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):
22 faces = []
24 if not vertIdx1 or not vertIdx2:
25 return None
27 if len(vertIdx1) < 2 and len(vertIdx2) < 2:
28 return None
30 fan = False
31 if (len(vertIdx1) != len(vertIdx2)):
32 if (len(vertIdx1) == 1 and len(vertIdx2) > 1):
33 fan = True
34 else:
35 return None
37 total = len(vertIdx2)
39 if closed:
40 # Bridge the start with the end.
41 if flipped:
42 face = [
43 vertIdx1[0],
44 vertIdx2[0],
45 vertIdx2[total - 1]]
46 if not fan:
47 face.append(vertIdx1[total - 1])
48 faces.append(face)
50 else:
51 face = [vertIdx2[0], vertIdx1[0]]
52 if not fan:
53 face.append(vertIdx1[total - 1])
54 face.append(vertIdx2[total - 1])
55 faces.append(face)
57 # Bridge the rest of the faces.
58 for num in range(total - 1):
59 if flipped:
60 if fan:
61 face = [vertIdx2[num], vertIdx1[0], vertIdx2[num + 1]]
62 else:
63 face = [vertIdx2[num], vertIdx1[num],
64 vertIdx1[num + 1], vertIdx2[num + 1]]
65 faces.append(face)
66 else:
67 if fan:
68 face = [vertIdx1[0], vertIdx2[num], vertIdx2[num + 1]]
69 else:
70 face = [vertIdx1[num], vertIdx2[num],
71 vertIdx2[num + 1], vertIdx1[num + 1]]
72 faces.append(face)
74 return faces
77 def power(a, b):
78 if a < 0:
79 return -((-a) ** b)
80 return a ** b
83 def supertoroid(R, r, u, v, n1, n2):
84 """
85 R = big radius
86 r = small radius
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
91 """
92 # create the necessary constants
93 a = 2 * pi / u
94 b = 2 * pi / v
96 verts = []
97 faces = []
99 # create each cross-section by calculating each vector on the
100 # the wannabe circle
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
106 for i in range(u):
107 s = power(sin(i * a), n1)
108 c = power(cos(i * a), n1)
109 for j in range(v):
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)
117 faces.extend(f)
118 # bridge the last circle with the first
119 f = createFaces(range((u - 1) * v, u * v), range(v), closed=True)
120 faces.extend(f)
122 return verts, faces
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",
132 default = True,
133 description = "SuperToroid")
134 change : BoolProperty(name = "Change",
135 default = False,
136 description = "change SuperToroid")
138 R: FloatProperty(
139 name="Big radius",
140 description="The radius inside the tube",
141 default=1.0,
142 min=0.01, max=100.0
144 r: FloatProperty(
145 name="Small radius",
146 description="The radius of the tube",
147 default=0.3,
148 min=0.01, max=100.0
150 u: IntProperty(
151 name="U-segments",
152 description="Radial segmentation",
153 default=16,
154 min=3, max=265
156 v: IntProperty(
157 name="V-segments",
158 description="Lateral segmentation",
159 default=8,
160 min=3, max=265
162 n1: FloatProperty(
163 name="Ring manipulator",
164 description="Manipulates the shape of the Ring",
165 default=1.0,
166 min=0.01, max=100.0
168 n2: FloatProperty(
169 name="Cross manipulator",
170 description="Manipulates the shape of the cross-section",
171 default=1.0,
172 min=0.01, max=100.0
174 ie: BoolProperty(
175 name="Use Int. and Ext. radii",
176 description="Use internal and external radii",
177 default=False
179 edit: BoolProperty(
180 name="",
181 description="",
182 default=False,
183 options={'HIDDEN'}
186 def draw(self, context):
187 layout = self.layout
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
213 if props.ie:
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
217 # the outer radius
218 if rad2 > rad1:
219 [rad1, rad2] = [rad2, rad1]
220 else:
221 rad1 = props.R
222 rad2 = props.r
223 # again for consistency, make the radius in the tube,
224 # at least as big as the radius of the tube
225 if rad2 > rad1:
226 rad1 = rad2
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
233 oldmesh = obj.data
234 oldmeshname = obj.data.name
235 verts, faces = supertoroid(rad1,
236 rad2,
237 props.u,
238 props.v,
239 props.n1,
240 props.n2
242 mesh = bpy.data.meshes.new('SuperToroid')
243 mesh.from_pydata(verts, [], faces)
244 obj.data = mesh
245 for material in oldmesh.materials:
246 obj.data.materials.append(material)
247 bpy.data.meshes.remove(oldmesh)
248 obj.data.name = oldmeshname
249 else:
250 verts, faces = supertoroid(rad1,
251 rad2,
252 props.u,
253 props.v,
254 props.n1,
255 props.n2
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,
271 rad2,
272 props.u,
273 props.v,
274 props.n1,
275 props.n2
277 mesh = bpy.data.meshes.new('SuperToroid')
278 mesh.from_pydata(verts, [], faces)
279 obj = object_utils.object_data_add(context, mesh, operator=self)
280 obj.select_set(True)
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
293 return {'FINISHED'}
295 def SuperToroidParameters():
296 SuperToroidParameters = [
297 "R",
298 "r",
299 "u",
300 "v",
301 "n1",
302 "n2",
303 "ie",
304 "edit",
306 return SuperToroidParameters