diff options
author | Martin Buerbaum <martin.buerbaum@gmx.at> | 2010-04-15 23:17:37 +0400 |
---|---|---|
committer | Martin Buerbaum <martin.buerbaum@gmx.at> | 2010-04-15 23:17:37 +0400 |
commit | 4a9fab8f97052dec3f354a0949f46d139ffdcc95 (patch) | |
tree | 316673f0961e534470b5e0b1fec2fe5d4ebb4aff /add_mesh_gears.py | |
parent | 2525a55a6eae3a58a0d6b6cecf8b0cbbe3487a3e (diff) |
* Version 2.2 - Reformatted and made PEP8 compatible
* Added _lots_ of "telling" variable names ... far from done :-/
* Support "recall" operator.
* @todo: This script definitly needs some comments on what's going on.
* @todo: Lots of hardcoded stuff (also deepcopy) - is there a better way?
* @todo: Do not create "double" vertices in the first place.
* @todo: Is it really a good idea to mix gears & worm in the same operator?
Diffstat (limited to 'add_mesh_gears.py')
-rw-r--r-- | add_mesh_gears.py | 787 |
1 files changed, 507 insertions, 280 deletions
diff --git a/add_mesh_gears.py b/add_mesh_gears.py index 0b69e41d..79364b28 100644 --- a/add_mesh_gears.py +++ b/add_mesh_gears.py @@ -1,195 +1,355 @@ # add_mesh_gear.py (c) 2009, 2010 Michel J. Anders (varkenvarken) # -# add gears/cogwheels to the blender 2.50 add->mesh menu +# ***** BEGIN GPL LICENSE BLOCK ***** # -# tested with the official blender 2.50 alpha 0 32-bit windows -# also tested with trunk svn 26208 on 32-bit windows # -# ##### BEGIN GPL LICENSE BLOCK ##### +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. # -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. # -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ##### END GPL LICENSE BLOCK ##### +# ***** END GPL LICENCE BLOCK ***** bl_addon_info = { 'name': 'Add Mesh: Gears', 'author': 'varkenvarken', - 'version': '2.1', + 'version': '2.2', 'blender': (2, 5, 3), 'location': 'View3D > Add > Mesh ', 'description': 'Adds a mesh Gear to the Add Mesh menu', 'url': 'http://wiki.blender.org/index.php/Extensions:2.5/Py/' \ - 'Scripts/Add_Mesh/Add_Gear', + 'Scripts/Add_Mesh/Add_Gear', 'category': 'Add Mesh'} """ What was needed to port it from 2.49 -> 2.50 alpha 0? -The basic functions that calculate the geometry (verts and faces) are unchanged -( add_tooth(), add_spoke2(), add_gear() ) - -These functions were designed to return lists of tuples (x,y,z) (for the vertices) and -lists of lists [i,k,l,m] (for the faces). Because the Blender 2.50 API does not provide -facilties to alter individual elements of the the verts and faces attributes of a mesh -directly we have to add the calculated vertices and faces in bulk by using the -mesh.add_geometry(nverts,nedges,nfaces) methodfolowed by -mesh.verts.foreach_set("co", verts_loc) and mesh.faces.foreach_set("verts_raw", faces). - -Both the foreach_set() methods take flattened lists as arguments, not lists of tuples, so we -added a simple function to flatten a list of lists or tuples. +The basic functions that calculate the geometry (verts and faces) are mostly +unchanged (add_tooth, add_spoke2, add_gear) -Also, the vertex group API is changed a little bit but the concepts are the same: -vertexgroup = ob.add_vertex_group('NAME_OF_VERTEXGROUP') # add a vertex group +Also, the vertex group API is changed a little bit but the concepts +are the same: +========= +vertexgroup = ob.add_vertex_group('NAME_OF_VERTEXGROUP') for i in vertexgroup_vertex_indices: ob.add_vertex_to_group(i, vertexgroup, weight, 'ADD') +========= Now for some reason the name does not 'stick' and we have to set it this way: vertexgroup.name = 'NAME_OF_VERTEXGROUP' -Conversion to 2.50 also meant we could simply do away with our crude user interface. -Just definining the appropriate properties in the AddGear() operator will display the -properties in the Blender GUI with the added benefit of making it interactive: changing -a property will redo the AddGear() operator providing the user with instant feedback. +Conversion to 2.50 also meant we could simply do away with our crude user +interface. +Just definining the appropriate properties in the AddGear() operator will +display the properties in the Blender GUI with the added benefit of making +it interactive: changing a property will redo the AddGear() operator providing +the user with instant feedback. -FInally we had to convert/throw away some print statements to print functions as Blender -nows uses Python 3.x +Finally we had to convert/throw away some print statements to print functions +as Blender nows uses Python 3.x -The most puzzling issue was that the built in Python zip() function changed its behavior. -In 3.x it returns a zip object (that can be iterated over) instead of a list of tuples. This meant -we could no longer use deepcopy(zip(...)) but had to convert the zip object to a list of tuples -first. +The most puzzling issue was that the built in Python zip() +function changed its behavior. +In 3.x it returns a zip object (that can be iterated over) +instead of a list of tuples. +This meant we could no longer use deepcopy(zip(...)) but had to convert the +zip object to a list of tuples first. -The code to actually implement the AddGear() function is mostly copied from add_mesh_torus() -(distributed with Blender). +The code to actually implement the AddGear() function is mostly copied from +add_mesh_torus() (distributed with Blender). Unresolved issues: -- removing doubles: - the code that produces the teeth of the gears produces some duplicate vertices. The original - script just called remove_doubles() but if we do that in 2.50 we have a problem. To apply - the bpy.ops.mesh.remove_doubles() operator we have to change to edit mode. The moment - we do that we loose to possibilty to interactively change the properties. Also changing back - to object mode raises a strange exception (to investigate). So for now, removing doubles is left - to the user once satisfied with the chosen setting for a gear, - -- no suitable icon: - a rather minor point but I reused the torus icon for the add->mesh->gear menu entry as there - doesn't seem to be a generic mesh icon or a way to add custom icons. Too bad, but as this is - just eye candy it's no big deal. - +- Removing doubles: + The algorithm should not generate a full section to begin with. + At least the last 4 vertices don't need to be created, because the + next section will have them as their first 4 vertices anyway. + OLD COMMENT ON DOUBLES: + "The code that produces the teeth of the gears produces some duplicate + vertices. The original script just called remove_doubles() but if we + do that in 2.50 we have a problem. + To apply the bpy.ops.mesh.remove_doubles() operator we have to change + to edit mode. The moment we do that we loose to possibilty to + interactively change the properties. + Also changing back to object mode raises a strange exception (to + investigate). So for now, removing doubles is left to the user + once satisfied with the chosen setting for a gear." + +- No suitable icon: + A rather minor point but I reused the torus icon for the add->mesh->gear + menu entry as there doesn't seem to be a generic mesh icon or a way to + add custom icons. Too bad, but as this is just eye candy it's no big deal. """ import bpy import mathutils -from math import cos, sin, tan, atan, asin, pi,radians as rad -from copy import deepcopy as dc - -def flatten(alist): - "Flatten a list of lists or tuples." - return sum([list(a) for a in alist],[]) - -#constants -faces=[[0, 5, 6, 1], - [1, 6, 7, 2], - [2, 7, 8, 3], - [3, 8, 9, 4], - [6, 10, 11, 7], - [7, 11, 12, 8], - [10, 13, 14, 11], - [11, 14, 15, 12]] - -L = 16 # number of vertices -#edgefaces -ef = [5, 6, 10, 13, 14, 15, 12, 8, 9] -ef2 = [i + L for i in ef] -# in python 3, zip() returns a zip object so we have to force the result into a list of lists to keep -# deepcopy happy later on in the script. -efc = [[i, j, k, l] for i, j, k, l in zip(ef[:-1], ef2[:-1], ef2[1:], ef[1:])] -vv = [5, 6, 8, 9, 21,22, 24, 25] #vertices in a valley -tv = [13, 14, 15, 29, 30, 31] #vertices on a tooth - -spokefaces=((0, 1, 2, 5), - (2, 3, 4, 7), - (5, 2, 7, 6), - (5, 6, 9, 8), - (6, 7, 10, 9), - (11, 8, 13, 12), - (8, 9, 10, 13), - (13, 10, 15, 14)) - - -def add_tooth(a, t, d, r, Ad, De, b, p, rack=0, crown=0.0): +from math import * +from copy import deepcopy +from bpy.props import * + +# Constants +FACES = [ + [0, 5, 6, 1], + [1, 6, 7, 2], + [2, 7, 8, 3], + [3, 8, 9, 4], + [6, 10, 11, 7], + [7, 11, 12, 8], + [10, 13, 14, 11], + [11, 14, 15, 12]] + +VERT_NUM = 16 # Number of vertices + +# Edgefaces +EDGEFACES = [5, 6, 10, 13, 14, 15, 12, 8, 9] +EDGEFACES2 = [i + VERT_NUM for i in EDGEFACES] # i.e. Indices are offset by 16 + +# In python 3, zip() returns a zip object so we have to force the +# result into a list of lists to keep deepcopy happy later on in the script. +EFC = [[i, j, k, l] for i, j, k, l + in zip(EDGEFACES[:-1], EDGEFACES2[:-1], EDGEFACES2[1:], EDGEFACES[1:])] +VERTS_TOOTH = [13, 14, 15, 29, 30, 31] # Vertices on a tooth +VERTS_VALLEY = [5, 6, 8, 9, 21, 22, 24, 25] # Vertices in a valley + +#SPOKEFACES = ( +# (0, 1, 2, 5), +# (2, 3, 4, 7), +# (5, 2, 7, 6), +# (5, 6, 9, 8), +# (6, 7, 10, 9), +# (11, 8, 13, 12), +# (8, 9, 10, 13), +# (13, 10, 15, 14)) + + +# Stores the values of a list of properties and the +# operator id in a property group ('recall_op') inside the object. +# Could (in theory) be used for non-objects. +# Note: Replaces any existing property group with the same name! +# ob ... Object to store the properties in. +# op ... The operator that should be used. +# op_args ... A dictionary with valid Blender +# properties (operator arguments/parameters). +def store_recall_properties(ob, op, op_args): + if ob and op and op_args: + recall_properties = {} + + # Add the operator identifier and op parameters to the properties. + recall_properties['op'] = op.bl_idname + recall_properties['args'] = op_args + + # Store new recall properties. + ob['recall'] = recall_properties + + +# Apply view rotation to objects if "Align To" for +# new objects was set to "VIEW" in the User Preference. +def apply_object_align(context, ob): + obj_align = bpy.context.user_preferences.edit.object_align + + if (context.space_data.type == 'VIEW_3D' + and obj_align == 'VIEW'): + view3d = context.space_data + region = view3d.region_3d + viewMatrix = region.view_matrix + rot = viewMatrix.rotation_part() + ob.rotation_euler = rot.invert().to_euler() + + +# Create a new mesh (object) from verts/edges/faces. +# verts/edges/faces ... List of vertices/edges/faces for the +# new mesh (as used in from_pydata). +# name ... Name of the new mesh (& object). +# edit ... Replace existing mesh data. +# Note: Using "edit" will destroy/delete existing mesh data. +def create_mesh_object(context, verts, edges, faces, name, edit): + scene = context.scene + obj_act = scene.objects.active + + # Can't edit anything, unless we have an active obj. + if edit and not obj_act: + return None + + # Create new mesh + mesh = bpy.data.meshes.new(name) + + # Make a mesh from a list of verts/edges/faces. + mesh.from_pydata(verts, edges, faces) + + # Update mesh geometry after adding stuff. + mesh.update() + + # Deselect all objects. + bpy.ops.object.select_all(action='DESELECT') + + if edit: + # Replace geometry of existing object + + # Use the active obj and select it. + ob_new = obj_act + ob_new.selected = True + + if obj_act.mode == 'OBJECT': + # Get existing mesh datablock. + old_mesh = ob_new.data + + # Set object data to nothing + ob_new.data = None + + # Clear users of existing mesh datablock. + old_mesh.user_clear() + + # Remove old mesh datablock if no users are left. + if (old_mesh.users == 0): + bpy.data.meshes.remove(old_mesh) + + # Assign new mesh datablock. + ob_new.data = mesh + + else: + # Create new object + ob_new = bpy.data.objects.new(name, mesh) + + # Link new object to the given scene and select it. + scene.objects.link(ob_new) + ob_new.selected = True + + # Place the object at the 3D cursor location. + ob_new.location = scene.cursor_location + + apply_object_align(context, ob_new) + + if obj_act and obj_act.mode == 'EDIT': + if not edit: + # We are in EditMode, switch to ObjectMode. + bpy.ops.object.mode_set(mode='OBJECT') + + # Select the active object as well. + obj_act.selected = True + + # Apply location of new object. + scene.update() + + # Join new object into the active. + bpy.ops.object.join() + + # Switching back to EditMode. + bpy.ops.object.mode_set(mode='EDIT') + + ob_new = obj_act + + else: + # We are in ObjectMode. + # Make the new object the active one. + scene.objects.active = ob_new + + return ob_new + + +# a +# t +# d +# radius +# Ad +# De +# base +# p_angle +# rack +# crown +def add_tooth(a, t, d, radius, Ad, De, base, p_angle, rack=0, crown=0.0): """ private function: calculate the vertex coords for a single side - section of a gear tooth. returns them as a list of lists. + section of a gear tooth. + Returns them as a list of tuples. """ A = [a, a + t / 4, a + t / 2, a + 3 * t / 4, a + t] C = [cos(i) for i in A] S = [sin(i) for i in A] - Ra= r + Ad - Rd= r - De - Rb= Rd - b - - #Pressure angle calc - O = Ad * tan(p) - p = atan(O/Ra) - if r < 0: - p = -p + Ra = radius + Ad + Rd = radius - De + Rb = Rd - base - if rack : - S =[sin(t/4)*I for I in range(-2,3)] - Sp=[0,sin(-t/4+p),0,sin(t/4-p)] + # Pressure angle calc + O = Ad * tan(p_angle) + p_angle = atan(O / Ra) - v = [(Rb, r * S[I], d) for I in range(5)] - v.extend([(Rd, r * S[I], d) for I in range(5)]) - v.extend([(r, r * S[I], d) for I in range(1, 4)]) - v.extend([(Ra, r * Sp[I], d) for I in range(1, 4)]) + if radius < 0: + p_angle = -p_angle - else: - Cp = [0, cos(a + t / 4 + p), cos(a + t / 2), cos(a + 3 * t / 4 - p)] - Sp = [0, sin(a + t / 4 + p), sin(a + t / 2), sin(a + 3 * t / 4 - p)] - - v = [(Rb * C[I], Rb * S[I], d) for I in range(5)] - v.extend([(Rd * C[I], Rd * S[I], d) for I in range(5)]) - v.extend([(r * C[I],r * S[I],d + crown / 3) for I in range(1, 4)]) - v.extend([(Ra * Cp[I], Ra * Sp[I], d + crown) for I in range(1, 4)]) + if rack: + S = [sin(t / 4) * I for I in range(-2, 3)] + Sp = [0, sin(-t / 4 + p_angle), 0, sin(t / 4 - p_angle)] - return v + verts = [(Rb, radius * S[I], d) for I in range(5)] + verts.extend([(Rd, radius * S[I], d) for I in range(5)]) + verts.extend([(radius, radius * S[I], d) for I in range(1, 4)]) + verts.extend([(Ra, radius * Sp[I], d) for I in range(1, 4)]) -def add_spoke2(a, t, d, r, De, b, s, w, l, gap=0, width=19): + else: + Cp = [ + 0, + cos(a + t / 4 + p_angle), + cos(a + t / 2), + cos(a + 3 * t / 4 - p_angle)] + Sp = [0, + sin(a + t / 4 + p_angle), + sin(a + t / 2), + sin(a + 3 * t / 4 - p_angle)] + + verts = [(Rb * C[I], Rb * S[I], d) + for I in range(5)] + verts.extend([(Rd * C[I], Rd * S[I], d) for I in range(5)]) + verts.extend([(radius * C[I], radius * S[I], d + crown / 3) + for I in range(1, 4)]) + verts.extend([(Ra * Cp[I], Ra * Sp[I], d + crown) + for I in range(1, 4)]) + + return verts + + +# a +# t +# d +# radius +# De +# base +# s +# w +# l +# gap +# width +def add_spoke2(a, t, d, radius, De, base, s, w, l, gap=0, width=19): """ - EXPERIMENTAL private function: calculate the vertex coords for a single side - section of a gearspoke. returns them as a list of lists. + EXPERIMENTAL private function: calculate the vertex coords for + a single side section of a gearspoke. + Returns them as a list of lists. """ - Rd=r - De - Rb=Rd - b - Rl=Rb + Rd = radius - De + Rb = Rd - base + Rl = Rb - v = [] - ef = [] - ef2 = [] + verts = [] + edgefaces = [] + edgefaces2 = [] sf = [] - if not gap : - for N in range(width, 1, -2) : - ef.append(len(v)) + if not gap: + for N in range(width, 1, -2): + edgefaces.append(len(v)) ts = t / 4 tm = a + 2 * ts te = asin(w / Rb) @@ -198,93 +358,156 @@ def add_spoke2(a, t, d, r, De, b, s, w, l, gap=0, width=19): A = [tm + (i - int(N / 2)) * t4 for i in range(N)] C = [cos(i) for i in A] S = [sin(i) for i in A] - v.extend([ (Rb * I, Rb * J, d) for (I, J) in zip(C,S)]) - ef2.append(len(v) - 1) - Rb= Rb - s - n=0 + + verts.extend([(Rb * I, Rb * J, d) for (I, J) in zip(C, S)]) + edgefaces2.append(len(v) - 1) + + Rb = Rb - s + + n = 0 for N in range(width, 3, -2): - sf.extend([(i + n,i + 1 + n,i + 2 + n,i + N + n) for i in range(0, N - 1, 2)]) - sf.extend([(i + 2 + n,i + N + n,i + N + 1 + n,i + N + 2 + n) for i in range(0,N - 3,2)]) + sf.extend([(i + n, i + 1 + n, i + 2 + n, i + N + n) + for i in range(0, N - 1, 2)]) + sf.extend([(i + 2 + n, i + N + n, i + N + 1 + n, i + N + 2 + n) + for i in range(0, N - 3, 2)]) + n = n + N - return v, ef, ef2, sf + return v, edgefaces, edgefaces2, sf -def add_gear(N, r, Ad, De, b, p, D=1, skew=0, conangle=0, rack=0, crown=0.0, spoke=0, spbevel=0.1, spwidth=0.2, splength=1.0, spresol=9): - """ - """ - worm =0 - if N < 5: - worm, N = N, 24 +# Create gear geometry. +# Returns: +# * A list of vertices +# * A list of faces +# * A list (group) of vertices of the tip +# * A list (group) of vertices of the valley +# +# teethNum ... Number of teeth on the gear. Set to 4 for "worm" +# radius ... Radius of the gear, negative for crown gear +# Ad ... Addendum, extent of tooth above radius. +# De ... Dedendum, extent of tooth below radius. +# base ... Base, extent of gear below radius. +# p_angle ... Pressure angle. Skewness of tooth tip. (radiant) +# width ... Width, thickness of gear. +# skew ... Skew of teeth. (radiant) +# conangle ... Conical angle of gear. (radiant) +# rack +# crown ... Inward pointing extend of crown teeth. +# spoke +# spbevel +# spwidth +# splength +# spresol +def add_gear(teethNum, radius, Ad, De, base, p_angle, + width=1, skew=0, conangle=0, rack=0, crown=0.0, spoke=0, + spbevel=0.1, spwidth=0.2, splength=1.0, spresol=9): + worm = 0 + + if teethNum < 5: + worm = teethNum + teethNum = 24 + + t = 2 * pi / teethNum - t = 2 * pi / N if rack: - N=1 - p = rad(p) - conangle = rad(conangle) - skew = rad(skew) - scale = (r - 2 * D * tan(conangle)) / r + teethNum = 1 - f = [] - v = [] - tg= [] #vertexgroup of top vertices. - vg= [] #vertexgroup of valley vertices + scale = (radius - 2 * width * tan(conangle)) / radius + verts = [] + faces = [] + vgroup_top = [] # Vertex group of top/tip? vertices. + vgroup_val = [] # Vertex group of valley vertices M = [0] if worm: - M, skew, D = (range(32), rad(11.25), D / 2) + M = range(32) + skew = radians(11.25) + width = width / 2.0 for W in M: - fl = W * N * L * 2 - l = 0 #number of vertices - for I in range(int(N)): - a = I * t - for (s, d, c, first) in ((W * skew, W * 2 * D - D, 1, 1),((W + 1) * skew, W * 2 * D + D, scale, 0)): - if worm and I % (int(N) / worm) != 0: - v.extend(add_tooth(a + s, t, d, r - De, 0.0, 0.0, b, p)) + fl = W * teethNum * VERT_NUM * 2 + vertNum = 0 # Number of vertices + + for toothCnt in range(teethNum): + a = toothCnt * t + + for (s, d, c, first) \ + in [(W * skew, W * 2 * width - width, 1, 1), \ + ((W + 1) * skew, W * 2 * width + width, scale, 0)]: + if worm and toothCnt % (teethNum / worm) != 0: + verts_tooth = add_tooth(a + s, t, d, + radius - De, 0.0, 0.0, base, p_angle) + verts.extend(verts_tooth) + else: - v.extend(add_tooth(a + s, t, d, r * c, Ad * c, De * c, b * c, p, rack, crown)) - if not worm or (W == 0 and first) or (W == (len(M) - 1) and not first) : - f.extend([ [j + l + fl for j in i] for i in dc(faces)]) - l += L - - #print (len(f)) - #print (dc(efc)) - f.extend([[j + I * L * 2 + fl for j in i] for i in dc(efc)]) - #print (len(f)) - tg.extend([i + I * L * 2 for i in tv]) - vg.extend([i + I * L * 2 for i in vv]) + verts_tooth = add_tooth(a + s, t, d, + radius * c, Ad * c, De * c, base * c, p_angle, + rack, crown) + verts.extend(verts_tooth) + + if (not worm + or (W == 0 and first) + or (W == (len(M) - 1) and not first)): + faces.extend([[j + vertNum + fl for j in i] + for i in deepcopy(FACES)]) + + vertNum += len(verts_tooth) + + faces.extend([[j + toothCnt * VERT_NUM * 2 + fl for j in i] + for i in deepcopy(EFC)]) + + vgroup_top.extend([i + toothCnt * VERT_NUM * 2 + for i in VERTS_TOOTH]) + vgroup_val.extend([i + toothCnt * VERT_NUM * 2 + for i in VERTS_VALLEY]) + + if toothCnt == 2: + break + # EXPERIMENTAL: add spokes if not worm and spoke > 0: - fl=len(v) - for I in range(int(N)): - a = I * t - s = 0 # for test - if I % spoke == 0 : - for d in (-D, D) : - sv, ef, ef2, sf = add_spoke2(a + s, t, d, r * c, De * c, b * c, spbevel, spwidth, splength, 0, spresol) - v.extend(sv) - f.extend([[j + fl for j in i] for i in sf]) + fl = len(v) + for toothCnt in range(teethNum): + a = toothCnt * t + s = 0 # For test + + if toothCnt % spoke == 0: + for d in (-width, width): + sv, edgefaces, edgefaces2, sf = add_spoke2(a + s, t, d, + radius * c, De * c, base * c, + spbevel, spwidth, splength, 0, spresol) + verts.extend(sv) + faces.extend([[j + fl for j in i] for i in sf]) fl += len(sv) + d1 = fl - len(sv) d2 = fl - 2 * len(sv) - f.extend([(i + d2, j + d2, j + d1, i + d1) for (i, j) in zip(ef[:-1], ef[1:])]) - f.extend([(i + d2, j + d2, j + d1, i + d1) for (i, j) in zip(ef2[:-1], ef2[1:])]) + + faces.extend([(i + d2, j + d2, j + d1, i + d1) + for (i, j) in zip(edgefaces[:-1], edgefaces[1:])]) + faces.extend([(i + d2, j + d2, j + d1, i + d1) + for (i, j) in zip(edgefaces2[:-1], edgefaces2[1:])]) + else: - for d in (-D, D) : - (sv,ef,ef2,sf) = add_spoke2(a + s, t, d, r * c, De * c, b * c, spbevel, spwidth, splength, 1, spresol) - v.extend(sv) + for d in (-width, width): + sv, edgefaces, edgefaces2, sf = add_spoke2(a + s, t, d, + radius * c, De * c, base * c, + spbevel, spwidth, splength, 1, spresol) + + verts.extend(sv) fl += len(sv) + d1 = fl - len(sv) d2 = fl - 2 * len(sv) - #f.extend([(i+d2,i+1+d2,i+1+d1,i+d1) for (i) in (0,1,2,3)]) - #f.extend([(i+d2,i+1+d2,i+1+d1,i+d1) for (i) in (5,6,7,8)]) - return flatten(v), flatten(f), tg, vg + #faces.extend([(i+d2, i+1+d2, i+1+d1, i+d1) + # for (i) in (0, 1, 2, 3)]) + #faces.extend([(i+d2, i+1+d2, i+1+d1, i+d1) + # for (i) in (5, 6, 7, 8)]) - -from bpy.props import * + return verts, faces, vgroup_top, vgroup_val class AddGear(bpy.types.Operator): @@ -293,119 +516,123 @@ class AddGear(bpy.types.Operator): bl_label = "Add Gear" bl_options = {'REGISTER', 'UNDO'} + # edit - Whether to add or update. + edit = BoolProperty(name="", + description="", + default=False, + options={'HIDDEN'}) number_of_teeth = IntProperty(name="Number of Teeth", - description="Number of teeth on the gear", - default=12, min=4, max=200) + description="Number of teeth on the gear", + min=4, + max=200, + default=12) radius = FloatProperty(name="Radius", - description="Radius of the gear, negative for crown gear", - default=1.0, min=-100.0, max=100.0) + description="Radius of the gear, negative for crown gear", + min=-100.0, + max=100.0, + default=1.0) addendum = FloatProperty(name="Addendum", - description="Addendum, extent of tooth above radius", - default=0.1, min=0.01, max=100.0) + description="Addendum, extent of tooth above radius", + min=0.01, + max=100.0, + default=0.1) dedendum = FloatProperty(name="Dedendum", - description="Dedendum, extent of tooth below radius", - default=0.1, min=0.0, max=100.0) + description="Dedendum, extent of tooth below radius", + min=0.0, + max=100.0, + default=0.1) angle = FloatProperty(name="Pressure Angle", - description="Pressure angle, skewness of tooth tip (degrees)", - default=20.0, min=0.0, max=45.0) + description="Pressure angle, skewness of tooth tip (degrees)", + min=0.0, + max=45.0, + default=20.0) base = FloatProperty(name="Base", - description="Base, extent of gear below radius", - default=0.2, min=0.0, max=100.0) + description="Base, extent of gear below radius", + min=0.0, + max=100.0, + default=0.2) width = FloatProperty(name="Width", - description="Width, thickness of gear", - default=0.2, min=0.05, max=100.0) + description="Width, thickness of gear", + min=0.05, + max=100.0, + default=0.2) skew = FloatProperty(name="Skewness", - description="Skew of teeth (degrees)", - default=0.0, min=-90.0, max=90.0) + description="Skew of teeth (degrees)", + min=-90.0, + max=90.0, + default=0.0) conangle = FloatProperty(name="Conical angle", - description="Conical angle of gear (degrees)", - default=0.0, min=0.0, max=90.0) + description="Conical angle of gear (degrees)", + min=0.0, + max=90.0, + default=0.0) crown = FloatProperty(name="Crown", - description="Inward pointing extend of crown teeth", - default=0.0, min=0.0, max=100.0) + description="Inward pointing extend of crown teeth", + min=0.0, + max=100.0, + default=0.0) def execute(self, context): - - verts_loc, faces, tip_vertices, valley_vertices = add_gear(self.properties.number_of_teeth, - self.properties.radius, - self.properties.addendum, - self.properties.dedendum, - self.properties.base, - self.properties.angle, - self.properties.width, - skew=self.properties.skew, - conangle=self.properties.conangle, - crown=self.properties.crown) - - #print(len(verts_loc)/3,faces) - - mesh = bpy.data.meshes.new("Gear") - - mesh.add_geometry(int(len(verts_loc) / 3), 0, int(len(faces) / 4)) - mesh.verts.foreach_set("co", verts_loc) - mesh.faces.foreach_set("verts_raw", faces) - mesh.faces.foreach_set("smooth", [False] * len(mesh.faces)) - - scene = context.scene - - # ugh (to quote the author of add_mesh_torus :-) - for ob in scene.objects: - ob.selected = False - - mesh.update() - - ob_new = bpy.data.objects.new('Gear', mesh) - - tipgroup = ob_new.add_vertex_group('Tips') - # for some reason the name does not 'stick' and we have to set it this way: - tipgroup.name = 'Tips' - for i in tip_vertices: - ob_new.add_vertex_to_group(i, tipgroup, 1.0, 'ADD') - - valleygroup = ob_new.add_vertex_group('Valleys') - # for some reason the name does not 'stick' and we have to set it this way: - valleygroup.name = 'Valleys' - for i in valley_vertices: - ob_new.add_vertex_to_group(i, valleygroup, 1.0, 'ADD') - - scene.objects.link(ob_new) - scene.objects.active = ob_new - ob_new.selected = True - - #print(1,bpy.context.mode) - #bpy.ops.object.mode_set(mode='EDIT') - #print(2,bpy.context.mode) - #bpy.ops.mesh.remove_doubles() - #print(3,bpy.context.mode) - # unfortunately the next line wont get us back to object mode but bombs - #bpy.ops.object.mode_set('OBJECT') - #print(4,bpy.context.mode) - - ob_new.location = tuple(context.scene.cursor_location) + props = self.properties + + verts, faces, verts_tip, verts_valley = add_gear( + props.number_of_teeth, + props.radius, + props.addendum, + props.dedendum, + props.base, + radians(props.angle), + props.width, + skew=radians(props.skew), + conangle=radians(props.conangle), + crown=props.crown) + + # Actually create the mesh object from this geometry data. + obj = create_mesh_object(context, verts, [], faces, "Gear", props.edit) + + # Store 'recall' properties in the object. + recall_args_list = { + "edit": True, + "number_of_teeth": props.number_of_teeth, + "radius": props.radius, + "addendum": props.addendum, + "dedendum": props.dedendum, + "angle": props.angle, + "base": props.base, + "width": props.width, + "skew": props.skew, + "conangle": props.conangle, + "crown": props.crown} + store_recall_properties(obj, self, recall_args_list) + + # Create vertex groups from stored vertices. + tipGroup = obj.add_vertex_group('Tips') + for vert in verts_tip: + obj.add_vertex_to_group(vert, tipGroup, 1.0, 'ADD') + + valleyGroup = obj.add_vertex_group('Valleys') + for vert in verts_valley: + obj.add_vertex_to_group(vert, valleyGroup, 1.0, 'ADD') return {'FINISHED'} -# Add to a menu, reuse an icon used elsewhere that happens to have fitting name -# unfortunately, the icon shown is the one I expected from looking at the -# blenderbuttons file from the release/datafiles directory - menu_func = (lambda self, context: self.layout.operator(AddGear.bl_idname, text="Gear", icon='PLUGIN')) def register(): bpy.types.register(AddGear) + + # Add "Gears" entry to the "Add Mesh" menu. bpy.types.INFO_MT_mesh_add.append(menu_func) def unregister(): bpy.types.unregister(AddGear) - bpy.types.INFO_MT_mesh_add.remove(menu_func) - # Remove "Gears" menu from the "Add Mesh" menu. - #space_info.INFO_MT_mesh_add.remove(menu_func) + # Remove "Gears" entry from the "Add Mesh" menu. + bpy.types.INFO_MT_mesh_add.remove(menu_func) if __name__ == "__main__": register() - |