diff options
Diffstat (limited to 'add_curve_extra_objects/add_curve_torus_knots.py')
-rw-r--r-- | add_curve_extra_objects/add_curve_torus_knots.py | 297 |
1 files changed, 156 insertions, 141 deletions
diff --git a/add_curve_extra_objects/add_curve_torus_knots.py b/add_curve_extra_objects/add_curve_torus_knots.py index e6499de5..e093257a 100644 --- a/add_curve_extra_objects/add_curve_torus_knots.py +++ b/add_curve_extra_objects/add_curve_torus_knots.py @@ -16,7 +16,7 @@ # # ##### END GPL LICENSE BLOCK ##### -''' +""" bl_info = { "name": "Torus Knots", "author": "Marius Giurgi (DolphinDream), testscreenings", @@ -25,14 +25,11 @@ bl_info = { "location": "View3D > Add > Curve", "description": "Adds many types of (torus) knots", "warning": "", - "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/" + "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/Py/" "Scripts/Curve/Torus_Knot", "category": "Add Curve"} -''' +""" -# ------------------------------------------------------------------------------ - -#### import modules import bpy from bpy.props import ( BoolProperty, @@ -41,18 +38,21 @@ from bpy.props import ( IntProperty ) from math import ( - sin, - cos, - pi, - sqrt + sin, cos, + pi, sqrt + ) +from mathutils import ( + Vector, + Matrix, ) -from mathutils import * -from bpy_extras.object_utils import AddObjectHelper, object_data_add +from bpy_extras.object_utils import AddObjectHelper from random import random from bpy.types import Operator +# Globals: DEBUG = False + # greatest common denominator def gcd(a, b): if b == 0: @@ -61,9 +61,9 @@ def gcd(a, b): return gcd(b, a % b) -######################################################################## -####################### Knot Definitions ############################### -######################################################################## +# ####################################################################### +# ###################### Knot Definitions ############################### +# ####################################################################### def Torus_Knot(self, linkIndex=0): p = self.torus_p # revolution count (around the torus center) q = self.torus_q # spin count (around the torus tube) @@ -85,19 +85,19 @@ def Torus_Knot(self, linkIndex=0): R = self.torus_R * s # major radius (scaled) r = self.torus_r * s # minor radius (scaled) -# number of decoupled links when (p,q) are NOT co-primes + # number of decoupled links when (p,q) are NOT co-primes links = gcd(p, q) # = 1 when (p,q) are co-primes -# parametrized angle increment (cached outside of the loop for performance) -# NOTE: the total angle is divided by number of decoupled links to ensure -# the curve does not overlap with itself when (p,q) are not co-primes - da = 2*pi/links/(N-1) + # parametrized angle increment (cached outside of the loop for performance) + # NOTE: the total angle is divided by number of decoupled links to ensure + # the curve does not overlap with itself when (p,q) are not co-primes + da = 2 * pi / links / (N - 1) -# link phase : each decoupled link is phased equally around the torus center -# NOTE: linkIndex value is in [0, links-1] - linkPhase = 2*pi/q * linkIndex # = 0 when there is just ONE link + # link phase : each decoupled link is phased equally around the torus center + # NOTE: linkIndex value is in [0, links-1] + linkPhase = 2 * pi / q * linkIndex # = 0 when there is just ONE link -# user defined phasing + # user defined phasing if self.options_plus: rPhase = self.torus_rP # user defined revolution phase sPhase = self.torus_sP # user defined spin phase @@ -113,35 +113,37 @@ def Torus_Knot(self, linkIndex=0): print("gcd = %i" % links) print("p = %i" % p) print("q = %i" % q) - print("link phase = %.2f deg" % (linkPhase * 180/pi)) + print("link phase = %.2f deg" % (linkPhase * 180 / pi)) print("link phase = %.2f rad" % linkPhase) -# flip directions ? NOTE: flipping both is equivalent to no flip + # flip directions ? NOTE: flipping both is equivalent to no flip if self.flip_p: p *= -1 if self.flip_q: q *= -1 -# create the 3D point array for the current link + # create the 3D point array for the current link newPoints = [] - for n in range(N-1): - # t = 2*pi / links * n/(N-1) with: da = 2*pi/links/(N-1) => t = n * da + for n in range(N - 1): + # t = 2 * pi / links * n/(N-1) with: da = 2*pi/links/(N-1) => t = n * da t = n * da - theta = p*t*u + rPhase # revolution angle - phi = q*t*v + sPhase # spin angle + theta = p * t * u + rPhase # revolution angle + phi = q * t * v + sPhase # spin angle - x = (R + r*cos(phi)) * cos(theta) - y = (R + r*cos(phi)) * sin(theta) - z = r*sin(phi) * h + x = (R + r * cos(phi)) * cos(theta) + y = (R + r * cos(phi)) * sin(theta) + z = r * sin(phi) * h -# append 3D point -# NOTE : the array is adjusted later as needed to 4D for POLY and NURBS + # append 3D point + # NOTE : the array is adjusted later as needed to 4D for POLY and NURBS newPoints.append([x, y, z]) return newPoints + # ------------------------------------------------------------------------------ # Calculate the align matrix for the new object (based on user preferences) + def align_matrix(self, context): if self.absolute_location: loc = Matrix.Translation(Vector((0, 0, 0))) @@ -161,8 +163,10 @@ def align_matrix(self, context): align_matrix = userLoc * loc * rot * userRot return align_matrix + # ------------------------------------------------------------------------------ # Set curve BEZIER handles to auto + def setBezierHandles(obj, mode='AUTOMATIC'): scene = bpy.context.scene if obj.type != 'CURVE': @@ -173,18 +177,20 @@ def setBezierHandles(obj, mode='AUTOMATIC'): bpy.ops.curve.handle_type_set(type=mode) bpy.ops.object.mode_set(mode='OBJECT', toggle=True) + # ------------------------------------------------------------------------------ # Convert array of vert coordinates to points according to spline type + def vertsToPoints(Verts, splineType): # main vars vertArray = [] -# array for BEZIER spline output (V3) + # array for BEZIER spline output (V3) if splineType == 'BEZIER': for v in Verts: vertArray += v -# array for non-BEZIER output (V4) + # array for non-BEZIER output (V4) else: for v in Verts: vertArray += v @@ -195,20 +201,22 @@ def vertsToPoints(Verts, splineType): return vertArray + # ------------------------------------------------------------------------------ # Create the Torus Knot curve and object and add it to the scene + def create_torus_knot(self, context): # pick a name based on (p,q) parameters aName = "Torus Knot %i x %i" % (self.torus_p, self.torus_q) -# create curve + # create curve curve_data = bpy.data.curves.new(name=aName, type='CURVE') -# setup materials to be used for the TK links + # setup materials to be used for the TK links if self.use_colors: addLinkColors(self, curve_data) -# create torus knot link(s) + # create torus knot link(s) if self.multiple_links: links = gcd(self.torus_p, self.torus_q) else: @@ -218,34 +226,34 @@ def create_torus_knot(self, context): # get vertices for the current link verts = Torus_Knot(self, l) -# output splineType 'POLY' 'NURBS' or 'BEZIER' + # output splineType 'POLY' 'NURBS' or 'BEZIER' splineType = self.outputType -# turn verts into proper array (based on spline type) + # turn verts into proper array (based on spline type) vertArray = vertsToPoints(verts, splineType) -# create spline from vertArray (based on spline type) + # create spline from vertArray (based on spline type) spline = curve_data.splines.new(type=splineType) if splineType == 'BEZIER': - spline.bezier_points.add(int(len(vertArray)*1.0/3-1)) + spline.bezier_points.add(int(len(vertArray) * 1.0 / 3 - 1)) spline.bezier_points.foreach_set('co', vertArray) else: - spline.points.add(int(len(vertArray)*1.0/4 - 1)) + spline.points.add(int(len(vertArray) * 1.0 / 4 - 1)) spline.points.foreach_set('co', vertArray) spline.use_endpoint_u = True -# set curve options + # set curve options spline.use_cyclic_u = True spline.order_u = 4 -# set a color per link + # set a color per link if self.use_colors: spline.material_index = l curve_data.dimensions = '3D' curve_data.resolution_u = self.segment_res -# create surface ? + # create surface ? if self.geo_surface: curve_data.fill_mode = 'FULL' curve_data.bevel_depth = self.geo_bDepth @@ -255,21 +263,23 @@ def create_torus_knot(self, context): new_obj = bpy.data.objects.new(aName, curve_data) -# set object in the scene + # set object in the scene scene = bpy.context.scene scene.objects.link(new_obj) # place in active scene new_obj.select = True # set as selected scene.objects.active = new_obj # set as active new_obj.matrix_world = self.align_matrix # apply matrix -# set BEZIER handles + # set BEZIER handles if splineType == 'BEZIER': setBezierHandles(new_obj, self.handleType) return + # ------------------------------------------------------------------------------ # Create materials to be assigned to each TK link + def addLinkColors(self, curveData): # some predefined colors for the torus knot links colors = [] @@ -295,13 +305,12 @@ def addLinkColors(self, curveData): colors += [[1.0, 0.0, 0.0]] me = curveData - mat_offset = len(me.materials) links = gcd(self.torus_p, self.torus_q) - mats = [] + for i in range(links): matName = "TorusKnot-Link-%i" % i matListNames = bpy.data.materials.keys() -# create the material + # create the material if matName not in matListNames: if DEBUG: print("Creating new material : %s" % matName) @@ -311,7 +320,7 @@ def addLinkColors(self, curveData): print("Material %s already exists" % matName) mat = bpy.data.materials[matName] -# set material color + # set material color if self.options_plus and self.random_colors: mat.diffuse_color = random(), random(), random() else: @@ -325,10 +334,11 @@ def addLinkColors(self, curveData): me.materials.append(mat) + # ------------------------------------------------------------------------------ # Main Torus Knot class + class torus_knot_plus(Operator, AddObjectHelper): - """""" bl_idname = "curve.torus_knot_plus" bl_label = "Torus Knot +" bl_options = {'REGISTER', 'UNDO', 'PRESET'} @@ -341,10 +351,10 @@ class torus_knot_plus(Operator, AddObjectHelper): self.torus_eR = self.torus_R + self.torus_r self.torus_iR = self.torus_R - self.torus_r -# align_matrix for the invoke + # align_matrix for the invoke align_matrix = None -# GENERAL options + # GENERAL options options_plus = BoolProperty( name="Extra Options", default=False, @@ -355,8 +365,7 @@ class torus_knot_plus(Operator, AddObjectHelper): default=False, description="Set absolute location instead of relative to 3D cursor", ) - -# COLOR options + # COLOR options use_colors = BoolProperty( name="Use Colors", default=False, @@ -364,8 +373,8 @@ class torus_knot_plus(Operator, AddObjectHelper): ) colorSet = EnumProperty( name="Color Set", - items=(('1', 'RGBish', 'RGBsish ordered colors'), - ('2', 'Rainbow', 'Rainbow ordered colors')), + items=(('1', "RGBish", "RGBsish ordered colors"), + ('2', "Rainbow", "Rainbow ordered colors")), ) random_colors = BoolProperty( name="Randomize Colors", @@ -378,8 +387,7 @@ class torus_knot_plus(Operator, AddObjectHelper): min=0.0, max=1.0, description="Color saturation", ) - -# SURFACE Options + # SURFACE Options geo_surface = BoolProperty( name="Surface", default=True, @@ -410,46 +418,45 @@ class torus_knot_plus(Operator, AddObjectHelper): min=0, soft_min=0, description="Offset the surface relative to the curve" ) - -# TORUS KNOT Options + # TORUS KNOT Options torus_p = IntProperty( name="p", default=2, min=1, soft_min=1, - description="Number of REVOLUTIONs around the torus hole before closing the knot" + description="Number of Revolutions around the torus hole before closing the knot" ) torus_q = IntProperty( name="q", default=3, min=1, soft_min=1, - description="Number of SPINs through the torus hole before closing the knot" + description="Number of Spins through the torus hole before closing the knot" ) flip_p = BoolProperty( name="Flip p", default=False, - description="Flip REVOLUTION direction" + description="Flip Revolution direction" ) flip_q = BoolProperty( name="Flip q", default=False, - description="Flip SPIN direction" + description="Flip Spin direction" ) multiple_links = BoolProperty( name="Multiple Links", default=True, - description="Generate ALL links or just ONE link when q and q are not co-primes" + description="Generate all links or just one link when q and q are not co-primes" ) torus_u = IntProperty( - name="p multiplier", + name="Rev. Multiplier", default=1, min=1, soft_min=1, - description="p multiplier" + description="Revolutions Multiplier" ) torus_v = IntProperty( - name="q multiplier", + name="Spin Multiplier", default=1, min=1, soft_min=1, - description="q multiplier" + description="Spin multiplier" ) torus_rP = FloatProperty( name="Revolution Phase", @@ -463,8 +470,7 @@ class torus_knot_plus(Operator, AddObjectHelper): min=0.0, soft_min=0.0, description="Phase spins by this radian amount" ) - -# TORUS DIMENSIONS options + # TORUS DIMENSIONS options mode = EnumProperty( name="Torus Dimensions", items=(("MAJOR_MINOR", "Major/Minor", @@ -517,8 +523,7 @@ class torus_knot_plus(Operator, AddObjectHelper): min=0.0, max=100.0, description="Scale along the local Z axis" ) - -# CURVE options + # CURVE options torus_res = IntProperty( name="Curve Resolution", default=100, @@ -532,9 +537,9 @@ class torus_knot_plus(Operator, AddObjectHelper): description="Curve subdivisions per segment" ) SplineTypes = [ - ('POLY', 'Poly', 'POLY'), - ('NURBS', 'Nurbs', 'NURBS'), - ('BEZIER', 'Bezier', 'BEZIER')] + ('POLY', "Poly", "Poly type"), + ('NURBS', "Nurbs", "Nurbs type"), + ('BEZIER', "Bezier", "Bezier type")] outputType = EnumProperty( name="Output splines", default='BEZIER', @@ -542,8 +547,8 @@ class torus_knot_plus(Operator, AddObjectHelper): items=SplineTypes, ) bezierHandles = [ - ('VECTOR', 'Vector', 'VECTOR'), - ('AUTOMATIC', 'Auto', 'AUTOMATIC'), + ('VECTOR', "Vector", "Bezier Hanles type - Vector"), + ('AUTOMATIC', "Auto", "Bezier Hanles type - Automatic"), ] handleType = EnumProperty( name="Handle type", @@ -557,122 +562,127 @@ class torus_knot_plus(Operator, AddObjectHelper): description="Auto adjust curve resolution based on TK length", ) -##### DRAW ##### def draw(self, context): layout = self.layout -# extra parameters toggle - layout.prop(self, 'options_plus') + # extra parameters toggle + layout.prop(self, "options_plus") -# TORUS KNOT Parameters + # TORUS KNOT Parameters col = layout.column() col.label(text="Torus Knot Parameters:") + box = layout.box() - row = box.row() - row.column().prop(self, 'torus_p') - row.column().prop(self, 'flip_p') - row = box.row() - row.column().prop(self, 'torus_q') - row.column().prop(self, 'flip_q') + split = box.split(percentage=0.85, align=True) + split.prop(self, "torus_p", text="Revolutions") + split.prop(self, "flip_p", toggle=True, text="", + icon="ARROW_LEFTRIGHT") + + split = box.split(percentage=0.85, align=True) + split.prop(self, "torus_q", text="Spins") + split.prop(self, "flip_q", toggle=True, text="", + icon="ARROW_LEFTRIGHT") links = gcd(self.torus_p, self.torus_q) info = "Multiple Links" + if links > 1: info += " ( " + str(links) + " )" box.prop(self, 'multiple_links', text=info) if self.options_plus: box = box.box() - box.prop(self, 'torus_u') - box.prop(self, 'torus_v') - box.prop(self, 'torus_rP') - box.prop(self, 'torus_sP') + col = box.column(align=True) + col.prop(self, "torus_u") + col.prop(self, "torus_v") + + col = box.column(align=True) + col.prop(self, "torus_rP") + col.prop(self, "torus_sP") -# TORUS DIMENSIONS options + # TORUS DIMENSIONS options col = layout.column(align=True) col.label(text="Torus Dimensions:") box = layout.box() col = box.column(align=True) col.row().prop(self, "mode", expand=True) - if self.mode == 'MAJOR_MINOR': + if self.mode == "MAJOR_MINOR": col = box.column(align=True) col.prop(self, "torus_R") - - col = box.column(align=True) col.prop(self, "torus_r") else: # EXTERIOR-INTERIOR col = box.column(align=True) col.prop(self, "torus_eR") - - col = box.column(align=True) col.prop(self, "torus_iR") if self.options_plus: box = box.box() - box.prop(self, 'torus_s') - box.prop(self, 'torus_h') + col = box.column(align=True) + col.prop(self, "torus_s") + col.prop(self, "torus_h") -# CURVE options + # CURVE options col = layout.column(align=True) col.label(text="Curve Options:") box = layout.box() col = box.column() col.label(text="Output Curve Type:") - col.row().prop(self, 'outputType', expand=True) + col.row().prop(self, "outputType", expand=True) depends = box.column() - depends.prop(self, 'torus_res') -# deactivate the "curve resolution" if "adaptive resolution" is enabled + depends.prop(self, "torus_res") + # deactivate the "curve resolution" if "adaptive resolution" is enabled depends.enabled = not self.adaptive_resolution - box.prop(self, 'adaptive_resolution') - box.prop(self, 'segment_res') + box.prop(self, "adaptive_resolution") + box.prop(self, "segment_res") -# SURFACE options + # SURFACE options col = layout.column() col.label(text="Geometry Options:") box = layout.box() - box.prop(self, 'geo_surface') + box.prop(self, "geo_surface") if self.geo_surface: - box.prop(self, 'geo_bDepth') - box.prop(self, 'geo_bRes') - box.prop(self, 'geo_extrude') - box.prop(self, 'geo_offset') + col = box.column(align=True) + col.prop(self, "geo_bDepth") + col.prop(self, "geo_bRes") -# COLOR options + col = box.column(align=True) + col.prop(self, "geo_extrude") + col.prop(self, "geo_offset") + + # COLOR options col = layout.column() col.label(text="Color Options:") box = layout.box() - box.prop(self, 'use_colors') + box.prop(self, "use_colors") if self.use_colors and self.options_plus: box = box.box() - box.prop(self, 'colorSet') - box.prop(self, 'random_colors') - box.prop(self, 'saturation') + box.prop(self, "colorSet") + box.prop(self, "random_colors") + box.prop(self, "saturation") -# TRANSFORM options + # TRANSFORM options col = layout.column() col.label(text="Transform Options:") box = col.box() - box.prop(self, 'location') - box.prop(self, 'absolute_location') - box.prop(self, 'rotation') + box.prop(self, "location") + box.prop(self, "absolute_location") + box.prop(self, "rotation") -##### POLL ##### @classmethod def poll(cls, context): if context.mode != "OBJECT": return False return context.scene is not None -##### EXECUTE ##### def execute(self, context): if self.mode == 'EXT_INT': # adjust the equivalent radii pair : (R,r) <=> (eR,iR) - self.torus_R = (self.torus_eR + self.torus_iR)*0.5 - self.torus_r = (self.torus_eR - self.torus_iR)*0.5 + self.torus_R = (self.torus_eR + self.torus_iR) * 0.5 + self.torus_r = (self.torus_eR - self.torus_iR) * 0.5 if self.adaptive_resolution: # adjust curve resolution automatically based on (p,q,R,r) values @@ -681,30 +691,35 @@ class torus_knot_plus(Operator, AddObjectHelper): R = self.torus_R r = self.torus_r links = gcd(p, q) -# get an approximate length of the whole TK curve - maxTKLen = 2*pi*sqrt(p*p*(R+r)*(R+r) + q*q*r*r) # upper bound approximation - minTKLen = 2*pi*sqrt(p*p*(R-r)*(R-r) + q*q*r*r) # lower bound approximation - avgTKLen = (minTKLen + maxTKLen)/2 # average approximation + + # get an approximate length of the whole TK curve + # upper bound approximation + maxTKLen = 2 * pi * sqrt(p * p * (R + r) * (R + r) + q * q * r * r) + # lower bound approximation + minTKLen = 2 * pi * sqrt(p * p * (R - r) * (R - r) + q * q * r * r) + avgTKLen = (minTKLen + maxTKLen) / 2 # average approximation + if DEBUG: print("Approximate average TK length = %.2f" % avgTKLen) - self.torus_res = max(3, avgTKLen/links * 8) # x N factor = control points per unit length -# update align matrix + # x N factor = control points per unit length + self.torus_res = max(3, avgTKLen / links * 8) + + # update align matrix self.align_matrix = align_matrix(self, context) -# turn off undo + # turn off undo undo = bpy.context.user_preferences.edit.use_global_undo bpy.context.user_preferences.edit.use_global_undo = False -# create the curve + # create the curve create_torus_knot(self, context) -# restore pre operator undo state + # restore pre operator undo state bpy.context.user_preferences.edit.use_global_undo = undo return {'FINISHED'} -##### INVOKE ##### def invoke(self, context, event): self.execute(context) |