Cleanup: trailing space
[blender-addons.git] / add_mesh_extra_objects / add_mesh_gears.py
blobb74ac93655800b8d8d56bc6fa5db6b6a3d56fd68
1 # SPDX-License-Identifier: GPL-2.0-or-later
2 # Copyright 2009-2010 Michel J. Anders (varkenvarken)
4 import bpy
5 from bpy.types import Operator
6 from math import (
7 atan, asin, cos,
8 sin, tan, pi,
9 radians,
11 from bpy.props import (
12 FloatProperty,
13 IntProperty,
14 BoolProperty,
15 StringProperty,
16 FloatVectorProperty
18 from mathutils import (
19 Vector,
20 Matrix,
22 from bpy_extras import object_utils
24 # A very simple "bridge" tool.
25 # Connects two equally long vertex rows with faces.
26 # Returns a list of the new faces (list of lists)
28 # vertIdx1 ... First vertex list (list of vertex indices)
29 # vertIdx2 ... Second vertex list (list of vertex indices)
30 # closed ... Creates a loop (first & last are closed)
31 # flipped ... Invert the normal of the face(s)
33 # Note: You can set vertIdx1 to a single vertex index to create
34 # a fan/star of faces
35 # Note: If both vertex idx list are the same length they have
36 # to have at least 2 vertices
38 def createFaces(vertIdx1, vertIdx2, closed=False, flipped=False):
39 faces = []
41 if not vertIdx1 or not vertIdx2:
42 return None
44 if len(vertIdx1) < 2 and len(vertIdx2) < 2:
45 return None
47 fan = False
48 if (len(vertIdx1) != len(vertIdx2)):
49 if (len(vertIdx1) == 1 and len(vertIdx2) > 1):
50 fan = True
51 else:
52 return None
54 total = len(vertIdx2)
56 if closed:
57 # Bridge the start with the end.
58 if flipped:
59 face = [
60 vertIdx1[0],
61 vertIdx2[0],
62 vertIdx2[total - 1]]
63 if not fan:
64 face.append(vertIdx1[total - 1])
65 faces.append(face)
67 else:
68 face = [vertIdx2[0], vertIdx1[0]]
69 if not fan:
70 face.append(vertIdx1[total - 1])
71 face.append(vertIdx2[total - 1])
72 faces.append(face)
74 # Bridge the rest of the faces.
75 for num in range(total - 1):
76 if flipped:
77 if fan:
78 face = [vertIdx2[num], vertIdx1[0], vertIdx2[num + 1]]
79 else:
80 face = [vertIdx2[num], vertIdx1[num],
81 vertIdx1[num + 1], vertIdx2[num + 1]]
82 faces.append(face)
83 else:
84 if fan:
85 face = [vertIdx1[0], vertIdx2[num], vertIdx2[num + 1]]
86 else:
87 face = [vertIdx1[num], vertIdx2[num],
88 vertIdx2[num + 1], vertIdx1[num + 1]]
89 faces.append(face)
91 return faces
94 # Calculate the vertex coordinates for a single
95 # section of a gear tooth.
96 # Returns 4 lists of vertex coords (list of tuples):
97 # *-*---*---* (1.) verts_inner_base
98 # | | | |
99 # *-*---*---* (2.) verts_outer_base
100 # | | |
101 # *---*---* (3.) verts_middle_tooth
102 # \ | /
103 # *-*-* (4.) verts_tip_tooth
108 # radius
109 # Ad
110 # De
111 # base
112 # p_angle
113 # rack
114 # crown
116 def add_tooth(a, t, d, radius, Ad, De, base, p_angle, rack=0, crown=0.0):
117 A = [a, a + t / 4, a + t / 2, a + 3 * t / 4]
118 C = [cos(i) for i in A]
119 S = [sin(i) for i in A]
121 Ra = radius + Ad
122 Rd = radius - De
123 Rb = Rd - base
125 # Pressure angle calc
126 O = Ad * tan(p_angle)
127 if Ra != 0:
128 p_angle = atan(O / Ra)
129 else:
130 p_angle = atan(O)
132 if radius < 0:
133 p_angle = -p_angle
135 if rack:
136 S = [sin(t / 4) * I for I in range(-2, 3)]
137 Sp = [0, sin(-t / 4 + p_angle), 0, sin(t / 4 - p_angle)]
139 verts_inner_base = [(Rb, radius * S[I], d) for I in range(4)]
140 verts_outer_base = [(Rd, radius * S[I], d) for I in range(4)]
141 verts_middle_tooth = [(radius, radius * S[I], d) for I in range(1, 4)]
142 verts_tip_tooth = [(Ra, radius * Sp[I], d) for I in range(1, 4)]
144 else:
145 Cp = [
147 cos(a + t / 4 + p_angle),
148 cos(a + t / 2),
149 cos(a + 3 * t / 4 - p_angle)]
150 Sp = [0,
151 sin(a + t / 4 + p_angle),
152 sin(a + t / 2),
153 sin(a + 3 * t / 4 - p_angle)]
155 verts_inner_base = [(Rb * C[I], Rb * S[I], d)
156 for I in range(4)]
157 verts_outer_base = [(Rd * C[I], Rd * S[I], d)
158 for I in range(4)]
159 verts_middle_tooth = [(radius * C[I], radius * S[I], d + crown / 3)
160 for I in range(1, 4)]
161 verts_tip_tooth = [(Ra * Cp[I], Ra * Sp[I], d + crown)
162 for I in range(1, 4)]
164 return (verts_inner_base, verts_outer_base,
165 verts_middle_tooth, verts_tip_tooth)
168 # EXPERIMENTAL Calculate the vertex coordinates for a single
169 # section of a gearspoke.
170 # Returns them as a list of tuples
175 # radius
176 # De
177 # base
181 # gap
182 # width
184 # @todo Finish this.
186 def add_spoke(a, t, d, radius, De, base, s, w, l, gap=0, width=19):
187 Rd = radius - De
188 Rb = Rd - base
190 verts = []
191 edgefaces = []
192 edgefaces2 = []
193 sf = []
195 if not gap:
196 for N in range(width, 1, -2):
197 edgefaces.append(len(verts))
198 ts = t / 4
199 tm = a + 2 * ts
200 te = asin(w / Rb)
201 td = te - ts
202 t4 = ts + td * (width - N) / (width - 3.0)
203 A = [tm + (i - int(N / 2)) * t4 for i in range(N)]
204 C = [cos(i) for i in A]
205 S = [sin(i) for i in A]
207 verts.extend((Rb * I, Rb * J, d) for (I, J) in zip(C, S))
208 edgefaces2.append(len(verts) - 1)
210 Rb = Rb - s
212 n = 0
213 for N in range(width, 3, -2):
214 sf.extend([(i + n, i + 1 + n, i + 2 + n, i + N + n)
215 for i in range(0, N - 1, 2)])
216 sf.extend([(i + 2 + n, i + N + n, i + N + 1 + n, i + N + 2 + n)
217 for i in range(0, N - 3, 2)])
219 n = n + N
221 return verts, edgefaces, edgefaces2, sf
224 # Create gear geometry.
225 # Returns:
226 # * A list of vertices (list of tuples)
227 # * A list of faces (list of lists)
228 # * A list (group) of vertices of the tip (list of vertex indices)
229 # * A list (group) of vertices of the valley (list of vertex indices)
231 # teethNum ... Number of teeth on the gear
232 # radius ... Radius of the gear, negative for crown gear
233 # Ad ... Addendum, extent of tooth above radius
234 # De ... Dedendum, extent of tooth below radius
235 # base ... Base, extent of gear below radius
236 # p_angle ... Pressure angle. Skewness of tooth tip. (radiant)
237 # width ... Width, thickness of gear
238 # skew ... Skew of teeth. (radiant)
239 # conangle ... Conical angle of gear. (radiant)
240 # rack
241 # crown ... Inward pointing extend of crown teeth
243 # inner radius = radius - (De + base)
245 def add_gear(teethNum, radius, Ad, De, base, p_angle,
246 width=1, skew=0, conangle=0, rack=0, crown=0.0):
248 if teethNum < 2:
249 return None, None, None, None
251 t = 2 * pi / teethNum
253 if rack:
254 teethNum = 1
256 #print(radius, width, conangle)
257 if radius != 0:
258 scale = (radius - 2 * width * tan(conangle)) / radius
259 else:
260 scale = radius - 2 * width * tan(conangle)
262 verts = []
263 faces = []
264 vgroup_top = [] # Vertex group of top/tip? vertices.
265 vgroup_valley = [] # Vertex group of valley vertices
267 verts_bridge_prev = []
268 for toothCnt in range(teethNum):
269 a = toothCnt * t
271 verts_bridge_start = []
272 verts_bridge_end = []
274 verts_outside_top = []
275 verts_outside_bottom = []
276 for (s, d, c, top) \
277 in [(0, -width, 1, True), (skew, width, scale, False)]:
279 verts1, verts2, verts3, verts4 = add_tooth(a + s, t, d,
280 radius * c, Ad * c, De * c, base * c, p_angle,
281 rack, crown)
283 vertsIdx1 = list(range(len(verts), len(verts) + len(verts1)))
284 verts.extend(verts1)
285 vertsIdx2 = list(range(len(verts), len(verts) + len(verts2)))
286 verts.extend(verts2)
287 vertsIdx3 = list(range(len(verts), len(verts) + len(verts3)))
288 verts.extend(verts3)
289 vertsIdx4 = list(range(len(verts), len(verts) + len(verts4)))
290 verts.extend(verts4)
292 verts_outside = []
293 verts_outside.extend(vertsIdx2[:2])
294 verts_outside.append(vertsIdx3[0])
295 verts_outside.extend(vertsIdx4)
296 verts_outside.append(vertsIdx3[-1])
297 verts_outside.append(vertsIdx2[-1])
299 if top:
300 # verts_inside_top = vertsIdx1
301 verts_outside_top = verts_outside
303 verts_bridge_start.append(vertsIdx1[0])
304 verts_bridge_start.append(vertsIdx2[0])
305 verts_bridge_end.append(vertsIdx1[-1])
306 verts_bridge_end.append(vertsIdx2[-1])
308 else:
309 # verts_inside_bottom = vertsIdx1
310 verts_outside_bottom = verts_outside
312 verts_bridge_start.append(vertsIdx2[0])
313 verts_bridge_start.append(vertsIdx1[0])
314 verts_bridge_end.append(vertsIdx2[-1])
315 verts_bridge_end.append(vertsIdx1[-1])
317 # Valley = first 2 vertices of outer base:
318 vgroup_valley.extend(vertsIdx2[:1])
319 # Top/tip vertices:
320 vgroup_top.extend(vertsIdx4)
322 faces_tooth_middle_top = createFaces(vertsIdx2[1:], vertsIdx3,
323 flipped=top)
324 faces_tooth_outer_top = createFaces(vertsIdx3, vertsIdx4,
325 flipped=top)
327 faces_base_top = createFaces(vertsIdx1, vertsIdx2, flipped=top)
328 faces.extend(faces_base_top)
330 faces.extend(faces_tooth_middle_top)
331 faces.extend(faces_tooth_outer_top)
333 # faces_inside = createFaces(verts_inside_top, verts_inside_bottom)
334 # faces.extend(faces_inside)
336 faces_outside = createFaces(verts_outside_top, verts_outside_bottom,
337 flipped=True)
338 faces.extend(faces_outside)
340 if toothCnt == 0:
341 verts_bridge_first = verts_bridge_start
343 # Bridge one tooth to the next
344 if verts_bridge_prev:
345 faces_bridge = createFaces(verts_bridge_prev, verts_bridge_start)
346 faces.extend(faces_bridge)
348 # Remember "end" vertices for next tooth.
349 verts_bridge_prev = verts_bridge_end
351 # Bridge the first to the last tooth.
352 faces_bridge_f_l = createFaces(verts_bridge_prev, verts_bridge_first)
353 faces.extend(faces_bridge_f_l)
355 return verts, faces, vgroup_top, vgroup_valley
358 # Create spokes geometry
359 # Returns:
360 # * A list of vertices (list of tuples)
361 # * A list of faces (list of lists)
363 # teethNum ... Number of teeth on the gear.
364 # radius ... Radius of the gear, negative for crown gear
365 # De ... Dedendum, extent of tooth below radius
366 # base ... Base, extent of gear below radius
367 # width ... Width, thickness of gear
368 # conangle ... Conical angle of gear. (radiant)
369 # rack
370 # spoke
371 # spbevel
372 # spwidth
373 # splength
374 # spresol
376 # @todo Finish this
377 # @todo Create a function that takes a "Gear" and creates a
378 # matching "Gear Spokes" object
380 def add_spokes(teethNum, radius, De, base, width=1, conangle=0, rack=0,
381 spoke=3, spbevel=0.1, spwidth=0.2, splength=1.0, spresol=9):
383 if teethNum < 2:
384 return None, None, None, None
386 if spoke < 2:
387 return None, None, None, None
389 t = 2 * pi / teethNum
391 if rack:
392 teethNum = 1
394 scale = (radius - 2 * width * tan(conangle)) / radius
396 verts = []
397 faces = []
399 c = scale # debug
401 fl = len(verts)
402 for toothCnt in range(teethNum):
403 a = toothCnt * t
404 s = 0 # For test
406 if toothCnt % spoke == 0:
407 for d in (-width, width):
408 sv, edgefaces, edgefaces2, sf = add_spoke(a + s, t, d,
409 radius * c, De * c, base * c,
410 spbevel, spwidth, splength, 0, spresol)
411 verts.extend(sv)
412 faces.extend([j + fl for j in i] for i in sf)
413 fl += len(sv)
415 d1 = fl - len(sv)
416 d2 = fl - 2 * len(sv)
418 faces.extend([(i + d2, j + d2, j + d1, i + d1)
419 for (i, j) in zip(edgefaces[:-1], edgefaces[1:])])
420 faces.extend([(i + d2, j + d2, j + d1, i + d1)
421 for (i, j) in zip(edgefaces2[:-1], edgefaces2[1:])])
423 else:
424 for d in (-width, width):
425 sv, edgefaces, edgefaces2, sf = add_spoke(a + s, t, d,
426 radius * c, De * c, base * c,
427 spbevel, spwidth, splength, 1, spresol)
429 verts.extend(sv)
430 fl += len(sv)
432 d1 = fl - len(sv)
433 d2 = fl - 2 * len(sv)
435 faces.extend([[i + d2, i + 1 + d2, i + 1 + d1, i + d1]
436 for (i) in range(0, 3)])
437 faces.extend([[i + d2, i + 1 + d2, i + 1 + d1, i + d1]
438 for (i) in range(5, 8)])
440 return verts, faces
443 # Create worm geometry.
444 # Returns:
445 # * A list of vertices
446 # * A list of faces
447 # * A list (group) of vertices of the tip
448 # * A list (group) of vertices of the valley
450 # teethNum ... Number of teeth on the worm
451 # radius ... Radius of the gear, negative for crown gear
452 # Ad ... Addendum, extent of tooth above radius
453 # De ... Dedendum, extent of tooth below radius
454 # p_angle ... Pressure angle. Skewness of tooth tip. (radiant)
455 # width ... Width, thickness of gear
456 # crown ... Inward pointing extend of crown teeth
458 # @todo: Fix teethNum. Some numbers are not possible yet
459 # @todo: Create start & end geometry (closing faces)
461 def add_worm(teethNum, rowNum, radius, Ad, De, p_angle,
462 width=1, skew=radians(11.25), crown=0.0):
464 worm = teethNum
465 teethNum = 24
467 t = 2 * pi / teethNum
469 verts = []
470 faces = []
471 vgroup_top = [] # Vertex group of top/tip? vertices.
472 vgroup_valley = [] # Vertex group of valley vertices
474 # width = width / 2.0
476 edgeloop_prev = []
477 for Row in range(rowNum):
478 edgeloop = []
480 for toothCnt in range(teethNum):
481 a = toothCnt * t
483 s = Row * skew
484 d = Row * width
485 c = 1
487 isTooth = False
488 if toothCnt % (teethNum / worm) != 0:
489 # Flat
490 verts1, verts2, verts3, verts4 = add_tooth(a + s, t, d,
491 radius - De, 0.0, 0.0, 0, p_angle)
493 # Ignore other verts than the "other base".
494 verts1 = verts3 = verts4 = []
496 else:
497 # Tooth
498 isTooth = True
499 verts1, verts2, verts3, verts4 = add_tooth(a + s, t, d,
500 radius * c, Ad * c, De * c, 0 * c, p_angle, 0, crown)
502 # Remove various unneeded verts (if we are "inside" the tooth)
503 del(verts2[2]) # Central vertex in the base of the tooth.
504 del(verts3[1]) # Central vertex in the middle of the tooth.
506 vertsIdx2 = list(range(len(verts), len(verts) + len(verts2)))
507 verts.extend(verts2)
508 vertsIdx3 = list(range(len(verts), len(verts) + len(verts3)))
509 verts.extend(verts3)
510 vertsIdx4 = list(range(len(verts), len(verts) + len(verts4)))
511 verts.extend(verts4)
513 if isTooth:
514 verts_current = []
515 verts_current.extend(vertsIdx2[:2])
516 verts_current.append(vertsIdx3[0])
517 verts_current.extend(vertsIdx4)
518 verts_current.append(vertsIdx3[-1])
519 verts_current.append(vertsIdx2[-1])
521 # Valley = first 2 vertices of outer base:
522 vgroup_valley.extend(vertsIdx2[:1])
523 # Top/tip vertices:
524 vgroup_top.extend(vertsIdx4)
526 else:
527 # Flat
528 verts_current = vertsIdx2
530 # Valley - all of them.
531 vgroup_valley.extend(vertsIdx2)
533 edgeloop.extend(verts_current)
535 # Create faces between rings/rows.
536 if edgeloop_prev:
537 faces_row = createFaces(edgeloop, edgeloop_prev, closed=True)
538 faces.extend(faces_row)
540 # Remember last ring/row of vertices for next ring/row iteration.
541 edgeloop_prev = edgeloop
543 return verts, faces, vgroup_top, vgroup_valley
545 def AddGearMesh(self, context):
547 verts, faces, verts_tip, verts_valley = add_gear(
548 self.number_of_teeth,
549 self.radius,
550 self.addendum,
551 self.dedendum,
552 self.base,
553 self.angle,
554 width=self.width,
555 skew=self.skew,
556 conangle=self.conangle,
557 crown=self.crown
560 mesh = bpy.data.meshes.new("Gear")
561 mesh.from_pydata(verts, [], faces)
563 return mesh, verts_tip, verts_valley
566 class AddGear(Operator, object_utils.AddObjectHelper):
567 bl_idname = "mesh.primitive_gear"
568 bl_label = "Add Gear"
569 bl_description = "Construct a gear mesh"
570 bl_options = {'REGISTER', 'UNDO', 'PRESET'}
572 Gear : BoolProperty(name = "Gear",
573 default = True,
574 description = "Gear")
576 #### change properties
577 name : StringProperty(name = "Name",
578 description = "Name")
580 change : BoolProperty(name = "Change",
581 default = False,
582 description = "change Gear")
584 number_of_teeth: IntProperty(name="Number of Teeth",
585 description="Number of teeth on the gear",
586 min=2,
587 soft_max=1000,
588 default=12
590 radius: FloatProperty(name="Radius",
591 description="Radius of the gear, negative for crown gear",
592 soft_min=-1000.0,
593 soft_max=1000.0,
594 unit='LENGTH',
595 default=1.0
597 addendum: FloatProperty(name="Addendum",
598 description="Addendum, extent of tooth above radius",
599 soft_min=-1000.0,
600 soft_max=1000.0,
601 unit='LENGTH',
602 default=0.1
604 dedendum: FloatProperty(name="Dedendum",
605 description="Dedendum, extent of tooth below radius",
606 soft_min=-1000.0,
607 soft_max=1000.0,
608 unit='LENGTH',
609 default=0.1
611 angle: FloatProperty(name="Pressure Angle",
612 description="Pressure angle, skewness of tooth tip",
613 soft_min=radians(-45.0),
614 soft_max=radians(45.0),
615 unit='ROTATION',
616 default=radians(20.0)
618 base: FloatProperty(name="Base",
619 description="Base, extent of gear below radius",
620 soft_min=-1000.0,
621 soft_max=1000.0,
622 unit='LENGTH',
623 default=0.2
625 width: FloatProperty(name="Width",
626 description="Width, thickness of gear",
627 soft_min=-1000.0,
628 soft_max=1000.0,
629 unit='LENGTH',
630 default=0.2
632 skew: FloatProperty(name="Skewness",
633 description="Skew of teeth",
634 soft_min=radians(-360.0),
635 soft_max=radians(360.0),
636 unit='ROTATION',
637 default=radians(0.0)
639 conangle: FloatProperty(name="Conical angle",
640 description="Conical angle of gear",
641 soft_min=radians(-360.0),
642 soft_max=radians(360.0),
643 unit='ROTATION',
644 default=radians(0.0)
646 crown: FloatProperty(name="Crown",
647 description="Inward pointing extend of crown teeth",
648 soft_min=-1000.0,
649 soft_max=1000.0,
650 unit='LENGTH',
651 default=0.0
654 def draw(self, context):
655 layout = self.layout
657 box = layout.box()
658 box.prop(self, 'number_of_teeth')
660 box = layout.box()
661 box.prop(self, 'radius')
662 box.prop(self, 'width')
663 box.prop(self, 'base')
665 box = layout.box()
666 box.prop(self, 'dedendum')
667 box.prop(self, 'addendum')
669 box = layout.box()
670 box.prop(self, 'angle')
671 box.prop(self, 'skew')
672 box.prop(self, 'conangle')
673 box.prop(self, 'crown')
675 if self.change == False:
676 # generic transform props
677 box = layout.box()
678 box.prop(self, 'align', expand=True)
679 box.prop(self, 'location', expand=True)
680 box.prop(self, 'rotation', expand=True)
682 @classmethod
683 def poll(cls, context):
684 return context.scene is not None
686 def execute(self, context):
687 # turn off 'Enter Edit Mode'
688 use_enter_edit_mode = bpy.context.preferences.edit.use_enter_edit_mode
689 bpy.context.preferences.edit.use_enter_edit_mode = False
691 if bpy.context.mode == "OBJECT":
692 if context.selected_objects != [] and context.active_object and \
693 (context.active_object.data is not None) and ('Gear' in context.active_object.data.keys()) and \
694 (self.change == True):
695 obj = context.active_object
696 oldmesh = obj.data
697 oldmeshname = obj.data.name
698 mesh, verts_tip, verts_valley = AddGearMesh(self, context)
699 obj.data = mesh
700 try:
701 bpy.ops.object.vertex_group_remove(all=True)
702 except:
703 pass
705 for material in oldmesh.materials:
706 obj.data.materials.append(material)
708 bpy.data.meshes.remove(oldmesh)
709 obj.data.name = oldmeshname
710 else:
711 mesh, verts_tip, verts_valley = AddGearMesh(self, context)
712 obj = object_utils.object_data_add(context, mesh, operator=self)
714 # Create vertex groups from stored vertices.
715 tipGroup = obj.vertex_groups.new(name='Tips')
716 tipGroup.add(verts_tip, 1.0, 'ADD')
718 valleyGroup = obj.vertex_groups.new(name='Valleys')
719 valleyGroup.add(verts_valley, 1.0, 'ADD')
721 obj.data["Gear"] = True
722 obj.data["change"] = False
723 for prm in GearParameters():
724 obj.data[prm] = getattr(self, prm)
726 if bpy.context.mode == "EDIT_MESH":
727 active_object = context.active_object
728 name_active_object = active_object.name
729 bpy.ops.object.mode_set(mode='OBJECT')
730 mesh, verts_tip, verts_valley = AddGearMesh(self, context)
731 obj = object_utils.object_data_add(context, mesh, operator=self)
733 # Create vertex groups from stored vertices.
734 tipGroup = obj.vertex_groups.new(name='Tips')
735 tipGroup.add(verts_tip, 1.0, 'ADD')
737 valleyGroup = obj.vertex_groups.new(name='Valleys')
738 valleyGroup.add(verts_valley, 1.0, 'ADD')
740 obj.select_set(True)
741 active_object.select_set(True)
742 bpy.context.view_layer.objects.active = active_object
743 bpy.ops.object.join()
744 context.active_object.name = name_active_object
745 bpy.ops.object.mode_set(mode='EDIT')
747 if use_enter_edit_mode:
748 bpy.ops.object.mode_set(mode = 'EDIT')
750 # restore pre operator state
751 bpy.context.preferences.edit.use_enter_edit_mode = use_enter_edit_mode
753 return {'FINISHED'}
755 def invoke(self, context, event):
756 self.execute(context)
758 return {'FINISHED'}
760 def GearParameters():
761 GearParameters = [
762 "number_of_teeth",
763 "radius",
764 "addendum",
765 "dedendum",
766 "base",
767 "angle",
768 "width",
769 "skew",
770 "conangle",
771 "crown",
773 return GearParameters
775 def AddWormGearMesh(self, context):
777 verts, faces, verts_tip, verts_valley = add_worm(
778 self.number_of_teeth,
779 self.number_of_rows,
780 self.radius,
781 self.addendum,
782 self.dedendum,
783 self.angle,
784 width=self.row_height,
785 skew=self.skew,
786 crown=self.crown
789 mesh = bpy.data.meshes.new("Worm Gear")
790 mesh.from_pydata(verts, [], faces)
792 return mesh, verts_tip, verts_valley
795 class AddWormGear(Operator, object_utils.AddObjectHelper):
796 bl_idname = "mesh.primitive_worm_gear"
797 bl_label = "Add Worm Gear"
798 bl_description = "Construct a worm gear mesh"
799 bl_options = {'REGISTER', 'UNDO', 'PRESET'}
801 WormGear : BoolProperty(name = "WormGear",
802 default = True,
803 description = "WormGear")
805 #### change properties
806 name : StringProperty(name = "Name",
807 description = "Name")
809 change : BoolProperty(name = "Change",
810 default = False,
811 description = "change WormGear")
813 number_of_teeth: IntProperty(
814 name="Number of Teeth",
815 description="Number of teeth on the gear",
816 min=1,
817 soft_max=1000,
818 default=12
820 number_of_rows: IntProperty(
821 name="Number of Rows",
822 description="Number of rows on the worm gear",
823 min=0,
824 soft_max=1000,
825 default=32
827 radius: FloatProperty(
828 name="Radius",
829 description="Radius of the gear, negative for crown gear",
830 soft_min=-1000.0,
831 soft_max=1000.0,
832 unit='LENGTH',
833 default=1.0
835 addendum: FloatProperty(
836 name="Addendum",
837 description="Addendum, extent of tooth above radius",
838 soft_min=-1000.0,
839 soft_max=1000.0,
840 unit='LENGTH',
841 default=0.1
843 dedendum: FloatProperty(
844 name="Dedendum",
845 description="Dedendum, extent of tooth below radius",
846 soft_min=-1000.0,
847 soft_max=1000.0,
848 unit='LENGTH',
849 default=0.1
851 angle: FloatProperty(
852 name="Pressure Angle",
853 description="Pressure angle, skewness of tooth tip",
854 soft_min=radians(-45.0),
855 soft_max=radians(45.0),
856 default=radians(20.0),
857 unit='ROTATION'
859 row_height: FloatProperty(
860 name="Row Height",
861 description="Height of each Row",
862 soft_min=-1000.0,
863 soft_max=1000.0,
864 unit='LENGTH',
865 default=0.2
867 skew: FloatProperty(
868 name="Skewness per Row",
869 description="Skew of each row",
870 soft_min=radians(-360.0),
871 soft_max=radians(360.0),
872 default=radians(11.25),
873 unit='ROTATION'
875 crown: FloatProperty(
876 name="Crown",
877 description="Inward pointing extend of crown teeth",
878 soft_min=-1000.0,
879 soft_max=1000.0,
880 unit='LENGTH',
881 default=0.0
884 def draw(self, context):
885 layout = self.layout
886 box = layout.box()
887 box.prop(self, "number_of_teeth")
888 box.prop(self, "number_of_rows")
889 box.prop(self, "radius")
890 box.prop(self, "row_height")
892 box = layout.box()
893 box.prop(self, "addendum")
894 box.prop(self, "dedendum")
896 box = layout.box()
897 box.prop(self, "angle")
898 box.prop(self, "skew")
899 box.prop(self, "crown")
901 if self.change == False:
902 # generic transform props
903 box = layout.box()
904 box.prop(self, 'align', expand=True)
905 box.prop(self, 'location', expand=True)
906 box.prop(self, 'rotation', expand=True)
908 def execute(self, context):
909 # turn off 'Enter Edit Mode'
910 use_enter_edit_mode = bpy.context.preferences.edit.use_enter_edit_mode
911 bpy.context.preferences.edit.use_enter_edit_mode = False
913 if bpy.context.mode == "OBJECT":
914 if context.selected_objects != [] and context.active_object and \
915 (context.active_object.data is not None) and ('WormGear' in context.active_object.data.keys()) and \
916 (self.change == True):
917 obj = context.active_object
918 oldmesh = obj.data
919 oldmeshname = obj.data.name
921 mesh, verts_tip, verts_valley = AddWormGearMesh(self, context)
922 obj.data = mesh
923 try:
924 bpy.ops.object.vertex_group_remove(all=True)
925 except:
926 pass
928 for material in oldmesh.materials:
929 obj.data.materials.append(material)
931 bpy.data.meshes.remove(oldmesh)
932 obj.data.name = oldmeshname
933 else:
934 mesh, verts_tip, verts_valley = AddWormGearMesh(self, context)
935 obj = object_utils.object_data_add(context, mesh, operator=self)
937 # Create vertex groups from stored vertices.
938 tipGroup = obj.vertex_groups.new(name = 'Tips')
939 tipGroup.add(verts_tip, 1.0, 'ADD')
941 valleyGroup = obj.vertex_groups.new(name = 'Valleys')
942 valleyGroup.add(verts_valley, 1.0, 'ADD')
944 obj.data["WormGear"] = True
945 obj.data["change"] = False
946 for prm in WormGearParameters():
947 obj.data[prm] = getattr(self, prm)
949 if bpy.context.mode == "EDIT_MESH":
950 active_object = context.active_object
951 name_active_object = active_object.name
952 bpy.ops.object.mode_set(mode='OBJECT')
953 mesh, verts_tip, verts_valley = AddWormGearMesh(self, context)
954 obj = object_utils.object_data_add(context, mesh, operator=self)
956 # Create vertex groups from stored vertices.
957 tipGroup = obj.vertex_groups.new(name = 'Tips')
958 tipGroup.add(verts_tip, 1.0, 'ADD')
960 valleyGroup = obj.vertex_groups.new(name = 'Valleys')
961 valleyGroup.add(verts_valley, 1.0, 'ADD')
963 obj.select_set(True)
964 active_object.select_set(True)
965 bpy.context.view_layer.objects.active = active_object
966 bpy.ops.object.join()
967 context.active_object.name = name_active_object
968 bpy.ops.object.mode_set(mode='EDIT')
970 if use_enter_edit_mode:
971 bpy.ops.object.mode_set(mode = 'EDIT')
973 # restore pre operator state
974 bpy.context.preferences.edit.use_enter_edit_mode = use_enter_edit_mode
976 return {'FINISHED'}
978 def WormGearParameters():
979 WormGearParameters = [
980 "number_of_teeth",
981 "number_of_rows",
982 "radius",
983 "addendum",
984 "dedendum",
985 "angle",
986 "row_height",
987 "skew",
988 "crown",
990 return WormGearParameters