1 # add_mesh_gear.py (c) 2009, 2010 Michel J. Anders (varkenvarken)
3 # ***** BEGIN GPL LICENSE BLOCK *****
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; either version 2
9 # of the License, or (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software Foundation,
18 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 # ***** END GPL LICENCE BLOCK *****
24 "author": "Michel J. Anders (varkenvarken)",
26 "blender": (2, 57, 0),
27 "location": "View3D > Add > Mesh > Gears ",
28 "description": "Adds a mesh Gear to the Add Mesh menu",
30 "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.5/Py/"
31 "Scripts/Add_Mesh/Add_Gear",
32 "category": "Add Mesh",
38 from bpy
.props
import *
40 # Create a new mesh (object) from verts/edges/faces.
41 # verts/edges/faces ... List of vertices/edges/faces for the
42 # new mesh (as used in from_pydata).
43 # name ... Name of the new mesh (& object).
44 def create_mesh_object(context
, verts
, edges
, faces
, name
):
46 mesh
= bpy
.data
.meshes
.new(name
)
48 # Make a mesh from a list of verts/edges/faces.
49 mesh
.from_pydata(verts
, edges
, faces
)
51 # Update mesh geometry after adding stuff.
54 from bpy_extras
import object_utils
55 return object_utils
.object_data_add(context
, mesh
, operator
=None)
58 # A very simple "bridge" tool.
59 # Connects two equally long vertex rows with faces.
60 # Returns a list of the new faces (list of lists)
62 # vertIdx1 ... First vertex list (list of vertex indices).
63 # vertIdx2 ... Second vertex list (list of vertex indices).
64 # closed ... Creates a loop (first & last are closed).
65 # flipped ... Invert the normal of the face(s).
67 # Note: You can set vertIdx1 to a single vertex index to create
68 # a fan/star of faces.
69 # Note: If both vertex idx list are the same length they have
70 # to have at least 2 vertices.
71 def createFaces(vertIdx1
, vertIdx2
, closed
=False, flipped
=False):
74 if not vertIdx1
or not vertIdx2
:
77 if len(vertIdx1
) < 2 and len(vertIdx2
) < 2:
81 if (len(vertIdx1
) != len(vertIdx2
)):
82 if (len(vertIdx1
) == 1 and len(vertIdx2
) > 1):
90 # Bridge the start with the end.
97 face
.append(vertIdx1
[total
- 1])
101 face
= [vertIdx2
[0], vertIdx1
[0]]
103 face
.append(vertIdx1
[total
- 1])
104 face
.append(vertIdx2
[total
- 1])
107 # Bridge the rest of the faces.
108 for num
in range(total
- 1):
111 face
= [vertIdx2
[num
], vertIdx1
[0], vertIdx2
[num
+ 1]]
113 face
= [vertIdx2
[num
], vertIdx1
[num
],
114 vertIdx1
[num
+ 1], vertIdx2
[num
+ 1]]
118 face
= [vertIdx1
[0], vertIdx2
[num
], vertIdx2
[num
+ 1]]
120 face
= [vertIdx1
[num
], vertIdx2
[num
],
121 vertIdx2
[num
+ 1], vertIdx1
[num
+ 1]]
127 # Calculate the vertex coordinates for a single
128 # section of a gear tooth.
129 # Returns 4 lists of vertex coords (list of tuples):
130 # *-*---*---* (1.) verts_inner_base
132 # *-*---*---* (2.) verts_outer_base
134 # *---*---* (3.) verts_middle_tooth
136 # *-*-* (4.) verts_tip_tooth
148 def add_tooth(a
, t
, d
, radius
, Ad
, De
, base
, p_angle
, rack
=0, crown
=0.0):
149 A
= [a
, a
+ t
/ 4, a
+ t
/ 2, a
+ 3 * t
/ 4]
150 C
= [cos(i
) for i
in A
]
151 S
= [sin(i
) for i
in A
]
157 # Pressure angle calc
158 O
= Ad
* tan(p_angle
)
159 p_angle
= atan(O
/ Ra
)
165 S
= [sin(t
/ 4) * I
for I
in range(-2, 3)]
166 Sp
= [0, sin(-t
/ 4 + p_angle
), 0, sin(t
/ 4 - p_angle
)]
168 verts_inner_base
= [(Rb
, radius
* S
[I
], d
) for I
in range(4)]
169 verts_outer_base
= [(Rd
, radius
* S
[I
], d
) for I
in range(4)]
170 verts_middle_tooth
= [(radius
, radius
* S
[I
], d
) for I
in range(1, 4)]
171 verts_tip_tooth
= [(Ra
, radius
* Sp
[I
], d
) for I
in range(1, 4)]
176 cos(a
+ t
/ 4 + p_angle
),
178 cos(a
+ 3 * t
/ 4 - p_angle
)]
180 sin(a
+ t
/ 4 + p_angle
),
182 sin(a
+ 3 * t
/ 4 - p_angle
)]
184 verts_inner_base
= [(Rb
* C
[I
], Rb
* S
[I
], d
)
186 verts_outer_base
= [(Rd
* C
[I
], Rd
* S
[I
], d
)
188 verts_middle_tooth
= [(radius
* C
[I
], radius
* S
[I
], d
+ crown
/ 3)
189 for I
in range(1, 4)]
190 verts_tip_tooth
= [(Ra
* Cp
[I
], Ra
* Sp
[I
], d
+ crown
)
191 for I
in range(1, 4)]
193 return (verts_inner_base
, verts_outer_base
,
194 verts_middle_tooth
, verts_tip_tooth
)
197 # EXPERIMENTAL Calculate the vertex coordinates for a single
198 # section of a gearspoke.
199 # Returns them as a list of tuples.
214 def add_spoke(a
, t
, d
, radius
, De
, base
, s
, w
, l
, gap
=0, width
=19):
225 for N
in range(width
, 1, -2):
226 edgefaces
.append(len(verts
))
231 t4
= ts
+ td
* (width
- N
) / (width
- 3.0)
232 A
= [tm
+ (i
- int(N
/ 2)) * t4
for i
in range(N
)]
233 C
= [cos(i
) for i
in A
]
234 S
= [sin(i
) for i
in A
]
236 verts
.extend((Rb
* I
, Rb
* J
, d
) for (I
, J
) in zip(C
, S
))
237 edgefaces2
.append(len(verts
) - 1)
242 for N
in range(width
, 3, -2):
243 sf
.extend([(i
+ n
, i
+ 1 + n
, i
+ 2 + n
, i
+ N
+ n
)
244 for i
in range(0, N
- 1, 2)])
245 sf
.extend([(i
+ 2 + n
, i
+ N
+ n
, i
+ N
+ 1 + n
, i
+ N
+ 2 + n
)
246 for i
in range(0, N
- 3, 2)])
250 return verts
, edgefaces
, edgefaces2
, sf
253 # Create gear geometry.
255 # * A list of vertices (list of tuples)
256 # * A list of faces (list of lists)
257 # * A list (group) of vertices of the tip (list of vertex indices).
258 # * A list (group) of vertices of the valley (list of vertex indices).
260 # teethNum ... Number of teeth on the gear.
261 # radius ... Radius of the gear, negative for crown gear
262 # Ad ... Addendum, extent of tooth above radius.
263 # De ... Dedendum, extent of tooth below radius.
264 # base ... Base, extent of gear below radius.
265 # p_angle ... Pressure angle. Skewness of tooth tip. (radiant)
266 # width ... Width, thickness of gear.
267 # skew ... Skew of teeth. (radiant)
268 # conangle ... Conical angle of gear. (radiant)
270 # crown ... Inward pointing extend of crown teeth.
272 # inner radius = radius - (De + base)
273 def add_gear(teethNum
, radius
, Ad
, De
, base
, p_angle
,
274 width
=1, skew
=0, conangle
=0, rack
=0, crown
=0.0):
277 return None, None, None, None
279 t
= 2 * pi
/ teethNum
284 print(radius
, width
, conangle
)
285 scale
= (radius
- 2 * width
* tan(conangle
)) / radius
289 vgroup_top
= [] # Vertex group of top/tip? vertices.
290 vgroup_valley
= [] # Vertex group of valley vertices
292 verts_bridge_prev
= []
293 for toothCnt
in range(teethNum
):
296 verts_bridge_start
= []
297 verts_bridge_end
= []
299 verts_outside_top
= []
300 verts_outside_bottom
= []
302 in [(0, -width
, 1, True), \
303 (skew
, width
, scale
, False)]:
305 verts1
, verts2
, verts3
, verts4
= add_tooth(a
+ s
, t
, d
,
306 radius
* c
, Ad
* c
, De
* c
, base
* c
, p_angle
,
309 vertsIdx1
= list(range(len(verts
), len(verts
) + len(verts1
)))
311 vertsIdx2
= list(range(len(verts
), len(verts
) + len(verts2
)))
313 vertsIdx3
= list(range(len(verts
), len(verts
) + len(verts3
)))
315 vertsIdx4
= list(range(len(verts
), len(verts
) + len(verts4
)))
319 verts_outside
.extend(vertsIdx2
[:2])
320 verts_outside
.append(vertsIdx3
[0])
321 verts_outside
.extend(vertsIdx4
)
322 verts_outside
.append(vertsIdx3
[-1])
323 verts_outside
.append(vertsIdx2
[-1])
326 #verts_inside_top = vertsIdx1
327 verts_outside_top
= verts_outside
329 verts_bridge_start
.append(vertsIdx1
[0])
330 verts_bridge_start
.append(vertsIdx2
[0])
331 verts_bridge_end
.append(vertsIdx1
[-1])
332 verts_bridge_end
.append(vertsIdx2
[-1])
335 #verts_inside_bottom = vertsIdx1
336 verts_outside_bottom
= verts_outside
338 verts_bridge_start
.append(vertsIdx2
[0])
339 verts_bridge_start
.append(vertsIdx1
[0])
340 verts_bridge_end
.append(vertsIdx2
[-1])
341 verts_bridge_end
.append(vertsIdx1
[-1])
343 # Valley = first 2 vertices of outer base:
344 vgroup_valley
.extend(vertsIdx2
[:1])
346 vgroup_top
.extend(vertsIdx4
)
348 faces_tooth_middle_top
= createFaces(vertsIdx2
[1:], vertsIdx3
,
350 faces_tooth_outer_top
= createFaces(vertsIdx3
, vertsIdx4
,
353 faces_base_top
= createFaces(vertsIdx1
, vertsIdx2
, flipped
=top
)
354 faces
.extend(faces_base_top
)
356 faces
.extend(faces_tooth_middle_top
)
357 faces
.extend(faces_tooth_outer_top
)
359 #faces_inside = createFaces(verts_inside_top, verts_inside_bottom)
360 #faces.extend(faces_inside)
362 faces_outside
= createFaces(verts_outside_top
, verts_outside_bottom
,
364 faces
.extend(faces_outside
)
367 verts_bridge_first
= verts_bridge_start
369 # Bridge one tooth to the next
370 if verts_bridge_prev
:
371 faces_bridge
= createFaces(verts_bridge_prev
, verts_bridge_start
)
372 #, closed=True (for "inside" faces)
373 faces
.extend(faces_bridge
)
375 # Remember "end" vertices for next tooth.
376 verts_bridge_prev
= verts_bridge_end
378 # Bridge the first to the last tooth.
379 faces_bridge_f_l
= createFaces(verts_bridge_prev
, verts_bridge_first
)
380 #, closed=True (for "inside" faces)
381 faces
.extend(faces_bridge_f_l
)
383 return verts
, faces
, vgroup_top
, vgroup_valley
386 # Create spokes geometry.
388 # * A list of vertices (list of tuples)
389 # * A list of faces (list of lists)
391 # teethNum ... Number of teeth on the gear.
392 # radius ... Radius of the gear, negative for crown gear
393 # De ... Dedendum, extent of tooth below radius.
394 # base ... Base, extent of gear below radius.
395 # width ... Width, thickness of gear.
396 # conangle ... Conical angle of gear. (radiant)
405 # @todo Create a function that takes a "Gear" and creates a
406 # matching "Gear Spokes" object.
407 def add_spokes(teethNum
, radius
, De
, base
, width
=1, conangle
=0, rack
=0,
408 spoke
=3, spbevel
=0.1, spwidth
=0.2, splength
=1.0, spresol
=9):
411 return None, None, None, None
414 return None, None, None, None
416 t
= 2 * pi
/ teethNum
421 scale
= (radius
- 2 * width
* tan(conangle
)) / radius
429 for toothCnt
in range(teethNum
):
433 if toothCnt
% spoke
== 0:
434 for d
in (-width
, width
):
435 sv
, edgefaces
, edgefaces2
, sf
= add_spoke(a
+ s
, t
, d
,
436 radius
* c
, De
* c
, base
* c
,
437 spbevel
, spwidth
, splength
, 0, spresol
)
439 faces
.extend([j
+ fl
for j
in i
] for i
in sf
)
443 d2
= fl
- 2 * len(sv
)
445 faces
.extend([(i
+ d2
, j
+ d2
, j
+ d1
, i
+ d1
)
446 for (i
, j
) in zip(edgefaces
[:-1], edgefaces
[1:])])
447 faces
.extend([(i
+ d2
, j
+ d2
, j
+ d1
, i
+ d1
)
448 for (i
, j
) in zip(edgefaces2
[:-1], edgefaces2
[1:])])
451 for d
in (-width
, width
):
452 sv
, edgefaces
, edgefaces2
, sf
= add_spoke(a
+ s
, t
, d
,
453 radius
* c
, De
* c
, base
* c
,
454 spbevel
, spwidth
, splength
, 1, spresol
)
460 d2
= fl
- 2 * len(sv
)
462 faces
.extend([[i
+ d2
, i
+ 1 + d2
, i
+ 1 + d1
, i
+ d1
]
463 for (i
) in range(0, 3)])
464 faces
.extend([[i
+ d2
, i
+ 1 + d2
, i
+ 1 + d1
, i
+ d1
]
465 for (i
) in range(5, 8)])
470 # Create worm geometry.
472 # * A list of vertices
474 # * A list (group) of vertices of the tip
475 # * A list (group) of vertices of the valley
477 # teethNum ... Number of teeth on the worm
478 # radius ... Radius of the gear, negative for crown gear
479 # Ad ... Addendum, extent of tooth above radius.
480 # De ... Dedendum, extent of tooth below radius.
481 # p_angle ... Pressure angle. Skewness of tooth tip. (radiant)
482 # width ... Width, thickness of gear.
483 # crown ... Inward pointing extend of crown teeth.
485 # @todo: Fix teethNum. Some numbers are not possible yet.
486 # @todo: Create start & end geoemtry (closing faces)
487 def add_worm(teethNum
, rowNum
, radius
, Ad
, De
, p_angle
,
488 width
=1, skew
=radians(11.25), crown
=0.0):
493 t
= 2 * pi
/ teethNum
497 vgroup_top
= [] # Vertex group of top/tip? vertices.
498 vgroup_valley
= [] # Vertex group of valley vertices
503 for Row
in range(rowNum
):
506 for toothCnt
in range(teethNum
):
514 if toothCnt
% (teethNum
/ worm
) != 0:
516 verts1
, verts2
, verts3
, verts4
= add_tooth(a
+ s
, t
, d
,
517 radius
- De
, 0.0, 0.0, 0, p_angle
)
519 # Ignore other verts than the "other base".
520 verts1
= verts3
= verts4
= []
525 verts1
, verts2
, verts3
, verts4
= add_tooth(a
+ s
, t
, d
,
526 radius
* c
, Ad
* c
, De
* c
, 0 * c
, p_angle
, 0, crown
)
528 # Remove various unneeded verts (if we are "inside" the tooth)
529 del(verts2
[2]) # Central vertex in the base of the tooth.
530 del(verts3
[1]) # Central vertex in the middle of the tooth.
532 vertsIdx2
= list(range(len(verts
), len(verts
) + len(verts2
)))
534 vertsIdx3
= list(range(len(verts
), len(verts
) + len(verts3
)))
536 vertsIdx4
= list(range(len(verts
), len(verts
) + len(verts4
)))
541 verts_current
.extend(vertsIdx2
[:2])
542 verts_current
.append(vertsIdx3
[0])
543 verts_current
.extend(vertsIdx4
)
544 verts_current
.append(vertsIdx3
[-1])
545 verts_current
.append(vertsIdx2
[-1])
547 # Valley = first 2 vertices of outer base:
548 vgroup_valley
.extend(vertsIdx2
[:1])
550 vgroup_top
.extend(vertsIdx4
)
554 verts_current
= vertsIdx2
556 # Valley - all of them.
557 vgroup_valley
.extend(vertsIdx2
)
559 edgeloop
.extend(verts_current
)
561 # Create faces between rings/rows.
563 faces_row
= createFaces(edgeloop
, edgeloop_prev
, closed
=True)
564 faces
.extend(faces_row
)
566 # Remember last ring/row of vertices for next ring/row iteration.
567 edgeloop_prev
= edgeloop
569 return verts
, faces
, vgroup_top
, vgroup_valley
572 class AddGear(bpy
.types
.Operator
):
573 """Add a gear mesh"""
574 bl_idname
= "mesh.primitive_gear"
575 bl_label
= "Add Gear"
576 bl_options
= {'REGISTER', 'UNDO', 'PRESET'}
578 number_of_teeth
= IntProperty(name
="Number of Teeth",
579 description
="Number of teeth on the gear",
583 radius
= FloatProperty(name
="Radius",
584 description
="Radius of the gear, negative for crown gear",
589 addendum
= FloatProperty(name
="Addendum",
590 description
="Addendum, extent of tooth above radius",
595 dedendum
= FloatProperty(name
="Dedendum",
596 description
="Dedendum, extent of tooth below radius",
601 angle
= FloatProperty(name
="Pressure Angle",
602 description
="Pressure angle, skewness of tooth tip",
606 default
=radians(20.0))
607 base
= FloatProperty(name
="Base",
608 description
="Base, extent of gear below radius",
613 width
= FloatProperty(name
="Width",
614 description
="Width, thickness of gear",
619 skew
= FloatProperty(name
="Skewness",
620 description
="Skew of teeth",
624 default
=radians(0.0))
625 conangle
= FloatProperty(name
="Conical angle",
626 description
="Conical angle of gear",
630 default
=radians(0.0))
631 crown
= FloatProperty(name
="Crown",
632 description
="Inward pointing extend of crown teeth",
638 def draw(self
, context
):
642 box
.prop(self
, 'number_of_teeth')
645 box
.prop(self
, 'radius')
646 box
.prop(self
, 'width')
647 box
.prop(self
, 'base')
650 box
.prop(self
, 'dedendum')
651 box
.prop(self
, 'addendum')
654 box
.prop(self
, 'angle')
655 box
.prop(self
, 'skew')
656 box
.prop(self
, 'conangle')
657 box
.prop(self
, 'crown')
660 def execute(self
, context
):
661 verts
, faces
, verts_tip
, verts_valley
= add_gear(
662 self
.number_of_teeth
,
670 conangle
=self
.conangle
,
673 # Actually create the mesh object from this geometry data.
674 base
= create_mesh_object(context
, verts
, [], faces
, "Gear")
677 # XXX, supporting adding in editmode is move involved
678 if obj
.mode
!= 'EDIT':
679 # Create vertex groups from stored vertices.
680 tipGroup
= obj
.vertex_groups
.new('Tips')
681 tipGroup
.add(verts_tip
, 1.0, 'ADD')
683 valleyGroup
= obj
.vertex_groups
.new('Valleys')
684 valleyGroup
.add(verts_valley
, 1.0, 'ADD')
689 class AddWormGear(bpy
.types
.Operator
):
690 """Add a worm gear mesh"""
691 bl_idname
= "mesh.primitive_worm_gear"
692 bl_label
= "Add Worm Gear"
693 bl_options
= {'REGISTER', 'UNDO', 'PRESET'}
695 number_of_teeth
= IntProperty(name
="Number of Teeth",
696 description
="Number of teeth on the gear",
700 number_of_rows
= IntProperty(name
="Number of Rows",
701 description
="Number of rows on the worm gear",
705 radius
= FloatProperty(name
="Radius",
706 description
="Radius of the gear, negative for crown gear",
711 addendum
= FloatProperty(name
="Addendum",
712 description
="Addendum, extent of tooth above radius",
717 dedendum
= FloatProperty(name
="Dedendum",
718 description
="Dedendum, extent of tooth below radius",
723 angle
= FloatProperty(name
="Pressure Angle",
724 description
="Pressure angle, skewness of tooth tip",
727 default
=radians(20.0),
729 row_height
= FloatProperty(name
="Row Height",
730 description
="Height of each Row",
735 skew
= FloatProperty(name
="Skewness per Row",
736 description
="Skew of each row",
739 default
=radians(11.25),
741 crown
= FloatProperty(name
="Crown",
742 description
="Inward pointing extend of crown teeth",
748 def draw(self
, context
):
751 box
.prop(self
, 'number_of_teeth')
752 box
.prop(self
, 'number_of_rows')
753 box
.prop(self
, 'radius')
754 box
.prop(self
, 'row_height')
756 box
.prop(self
, 'addendum')
757 box
.prop(self
, 'dedendum')
759 box
.prop(self
, 'angle')
760 box
.prop(self
, 'skew')
761 box
.prop(self
, 'crown')
763 def execute(self
, context
):
765 verts
, faces
, verts_tip
, verts_valley
= add_worm(
766 self
.number_of_teeth
,
772 width
=self
.row_height
,
776 # Actually create the mesh object from this geometry data.
777 base
= create_mesh_object(context
, verts
, [], faces
, "Worm Gear")
780 # XXX, supporting adding in editmode is move involved
781 if obj
.mode
!= 'EDIT':
782 # Create vertex groups from stored vertices.
783 tipGroup
= obj
.vertex_groups
.new('Tips')
784 tipGroup
.add(verts_tip
, 1.0, 'ADD')
786 valleyGroup
= obj
.vertex_groups
.new('Valleys')
787 valleyGroup
.add(verts_valley
, 1.0, 'ADD')