From 5858c59db11fae85dc6ca622d8a202767f837a38 Mon Sep 17 00:00:00 2001 From: lijenstina Date: Sun, 2 Apr 2017 22:28:43 +0200 Subject: Curve Simplify: Cleanup, remove dead code Bumped version to 1.0.2 Pep8 cleanup imports as tuples Removed two empty Menu classes: CurveMenu and GRAPH_OT_simplifyf and unused UI code Small UI and tooltip fixes Consistent Props declarations TO DO: see if the different Algo UI options can be re-enabled --- curve_simplify.py | 482 +++++++++++++++++++++++++----------------------------- 1 file changed, 220 insertions(+), 262 deletions(-) (limited to 'curve_simplify.py') diff --git a/curve_simplify.py b/curve_simplify.py index 6309c78e..d1e8e17a 100644 --- a/curve_simplify.py +++ b/curve_simplify.py @@ -19,12 +19,12 @@ bl_info = { "name": "Simplify Curves", "author": "testscreenings", - "version": (1, 0, 1), + "version": (1, 0, 2), "blender": (2, 75, 0), "location": "Search > Simplify Curves", "description": "Simplifies 3D Curve objects and animation F-Curves", "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/Curve_Simplify", "category": "Add Curve", } @@ -33,78 +33,84 @@ bl_info = { This script simplifies Curve objects and animation F-Curves. """ -#################################################### import bpy -from bpy.props import * -import mathutils -import math +from bpy.props import ( + BoolProperty, + EnumProperty, + FloatProperty, + IntProperty, + ) +from mathutils import Vector +from math import ( + sin, + pow, + ) +from bpy.types import Operator -from bpy.types import Menu +# Check for curve - ## Check for curve +# ### simplipoly algorithm ### -############################## -#### simplipoly algorithm #### -############################## # get SplineVertIndices to keep def simplypoly(splineVerts, options): # main vars - newVerts = [] # list of vertindices to keep - points = splineVerts # list of 3dVectors - pointCurva = [] # table with curvatures - curvatures = [] # averaged curvatures per vert + newVerts = [] # list of vertindices to keep + points = splineVerts # list of 3dVectors + pointCurva = [] # table with curvatures + curvatures = [] # averaged curvatures per vert for p in points: pointCurva.append([]) - order = options[3] # order of sliding beziercurves - k_thresh = options[2] # curvature threshold - dis_error = options[6] # additional distance error + order = options[3] # order of sliding beziercurves + k_thresh = options[2] # curvature threshold + dis_error = options[6] # additional distance error # get curvatures per vert - for i, point in enumerate(points[:-(order-1)]): - BVerts = points[i:i+order] - for b, BVert in enumerate(BVerts[1:-1]): - deriv1 = getDerivative(BVerts, 1/(order-1), order-1) - deriv2 = getDerivative(BVerts, 1/(order-1), order-2) + for i, point in enumerate(points[: -(order - 1)]): + BVerts = points[i: i + order] + for b, BVert in enumerate(BVerts[1: -1]): + deriv1 = getDerivative(BVerts, 1 / (order - 1), order - 1) + deriv2 = getDerivative(BVerts, 1 / (order - 1), order - 2) curva = getCurvature(deriv1, deriv2) - pointCurva[i+b+1].append(curva) + pointCurva[i + b + 1].append(curva) # average the curvatures for i in range(len(points)): - avgCurva = sum(pointCurva[i]) / (order-1) + avgCurva = sum(pointCurva[i]) / (order - 1) curvatures.append(avgCurva) # get distancevalues per vert - same as Ramer-Douglas-Peucker # but for every vert - distances = [0.0] #first vert is always kept - for i, point in enumerate(points[1:-1]): - dist = altitude(points[i], points[i+2], points[i+1]) + distances = [0.0] # first vert is always kept + for i, point in enumerate(points[1: -1]): + dist = altitude(points[i], points[i + 2], points[i + 1]) distances.append(dist) - distances.append(0.0) # last vert is always kept + distances.append(0.0) # last vert is always kept - # generate list of vertindices to keep + # generate list of vert indices to keep # tested against averaged curvatures and distances of neighbour verts - newVerts.append(0) # first vert is always kept + newVerts.append(0) # first vert is always kept for i, curv in enumerate(curvatures): - if (curv >= k_thresh*0.01 - or distances[i] >= dis_error*0.1): + if (curv >= k_thresh * 0.01 or distances[i] >= dis_error * 0.1): newVerts.append(i) - newVerts.append(len(curvatures)-1) # last vert is always kept + newVerts.append(len(curvatures) - 1) # last vert is always kept return newVerts + # get binomial coefficient def binom(n, m): - b = [0] * (n+1) + b = [0] * (n + 1) b[0] = 1 - for i in range(1, n+1): + for i in range(1, n + 1): b[i] = 1 - j = i-1 + j = i - 1 while j > 0: - b[j] += b[j-1] - j-= 1 + b[j] += b[j - 1] + j -= 1 return b[m] + # get nth derivative of order(len(verts)) bezier curve def getDerivative(verts, t, nth): order = len(verts) - 1 - nth @@ -115,34 +121,35 @@ def getDerivative(verts, t, nth): if QVerts: verts = QVerts derivVerts = [] - for i in range(len(verts)-1): - derivVerts.append(verts[i+1] - verts[i]) + for i in range(len(verts) - 1): + derivVerts.append(verts[i + 1] - verts[i]) QVerts = derivVerts else: QVerts = verts if len(verts[0]) == 3: - point = mathutils.Vector((0, 0, 0)) + point = Vector((0, 0, 0)) if len(verts[0]) == 2: - point = mathutils.Vector((0, 0)) + point = Vector((0, 0)) for i, vert in enumerate(QVerts): - point += binom(order, i) * math.pow(t, i) * math.pow(1-t, order-i) * vert + point += binom(order, i) * pow(t, i) * pow(1 - t, order - i) * vert deriv = point return deriv + # get curvature from first, second derivative def getCurvature(deriv1, deriv2): - if deriv1.length == 0: # in case of points in straight line + if deriv1.length == 0: # in case of points in straight line curvature = 0 return curvature - curvature = (deriv1.cross(deriv2)).length / math.pow(deriv1.length, 3) + curvature = (deriv1.cross(deriv2)).length / pow(deriv1.length, 3) return curvature -######################################### -#### Ramer-Douglas-Peucker algorithm #### -######################################### + +# ### Ramer-Douglas-Peucker algorithm ### + # get altitude of vert def altitude(point1, point2, pointn): edge1 = point2 - point1 @@ -154,61 +161,58 @@ def altitude(point1, point2, pointn): altitude = edge2.length return altitude alpha = edge1.angle(edge2) - altitude = math.sin(alpha) * edge2.length + altitude = sin(alpha) * edge2.length return altitude + # iterate through verts def iterate(points, newVerts, error): new = [] - for newIndex in range(len(newVerts)-1): + for newIndex in range(len(newVerts) - 1): bigVert = 0 alti_store = 0 - for i, point in enumerate(points[newVerts[newIndex]+1:newVerts[newIndex+1]]): - alti = altitude(points[newVerts[newIndex]], points[newVerts[newIndex+1]], point) + for i, point in enumerate(points[newVerts[newIndex] + 1: newVerts[newIndex + 1]]): + alti = altitude(points[newVerts[newIndex]], points[newVerts[newIndex + 1]], point) if alti > alti_store: alti_store = alti if alti_store >= error: - bigVert = i+1+newVerts[newIndex] + bigVert = i + 1 + newVerts[newIndex] if bigVert: new.append(bigVert) if new == []: return False return new -#### get SplineVertIndices to keep + +# get SplineVertIndices to keep def simplify_RDP(splineVerts, options): - #main vars + # main vars error = options[4] # set first and last vert - newVerts = [0, len(splineVerts)-1] + newVerts = [0, len(splineVerts) - 1] # iterate through the points new = 1 - while new != False: + while new is not False: new = iterate(splineVerts, newVerts, error) if new: newVerts += new newVerts.sort() return newVerts -########################## -#### CURVE GENERATION #### -########################## -# set bezierhandles to auto -def setBezierHandles(newCurve): - #bpy.ops.object.mode_set(mode='EDIT', toggle=True) - #bpy.ops.curve.select_all(action='SELECT') - #bpy.ops.curve.handle_type_set(type='AUTOMATIC') - #bpy.ops.object.mode_set(mode='OBJECT', toggle=True) +# ### CURVE GENERATION ### +# set bezierhandles to auto +def setBezierHandles(newCurve): # Faster: for spline in newCurve.data.splines: for p in spline.bezier_points: p.handle_left_type = 'AUTO' p.handle_right_type = 'AUTO' + # get array of new coords for new spline from vertindices def vertsToPoints(newVerts, splineVerts, splineType): # main vars @@ -224,18 +228,15 @@ def vertsToPoints(newVerts, splineVerts, splineType): for v in newVerts: newPoints += (splineVerts[v].to_tuple()) if splineType == 'NURBS': - newPoints.append(1) #for nurbs w=1 - else: #for poly w=0 + newPoints.append(1) # for nurbs w = 1 + else: # for poly w = 0 newPoints.append(0) return newPoints -######################### -#### MAIN OPERATIONS #### -######################### + +# ### MAIN OPERATIONS ### def main(context, obj, options): - #print("\n_______START_______") - # main vars mode = options[0] output = options[1] degreeOut = options[5] @@ -245,24 +246,24 @@ def main(context, obj, options): splines = obj.data.splines.values() # create curvedatablock - curve = bpy.data.curves.new("Simple_"+obj.name, type = 'CURVE') + curve = bpy.data.curves.new("Simple_" + obj.name, type='CURVE') # go through splines for spline_i, spline in enumerate(splines): # test if spline is a long enough if len(spline.points) >= 7 or keepShort: - #check what type of spline to create + # check what type of spline to create if output == 'INPUT': splineType = spline.type else: splineType = output # get vec3 list to simplify - if spline.type == 'BEZIER': # get bezierverts + if spline.type == 'BEZIER': # get bezierverts splineVerts = [splineVert.co.copy() for splineVert in spline.bezier_points.values()] - else: # verts from all other types of curves + else: # verts from all other types of curves splineVerts = [splineVert.co.to_3d() for splineVert in spline.points.values()] @@ -277,14 +278,14 @@ def main(context, obj, options): newPoints = vertsToPoints(newVerts, splineVerts, splineType) # create new spline - newSpline = curve.splines.new(type = splineType) + newSpline = curve.splines.new(type=splineType) # put newPoints into spline according to type if splineType == 'BEZIER': - newSpline.bezier_points.add(int(len(newPoints)*0.33)) + newSpline.bezier_points.add(int(len(newPoints) * 0.33)) newSpline.bezier_points.foreach_set('co', newPoints) else: - newSpline.points.add(int(len(newPoints)*0.25 - 1)) + newSpline.points.add(int(len(newPoints) * 0.25 - 1)) newSpline.points.foreach_set('co', newPoints) # set degree of outputNurbsCurve @@ -295,7 +296,7 @@ def main(context, obj, options): newSpline.use_endpoint_u = spline.use_endpoint_u # create ne object and put into scene - newCurve = bpy.data.objects.new("Simple_"+obj.name, curve) + newCurve = bpy.data.objects.new("Simple_" + obj.name, curve) scene.objects.link(newCurve) newCurve.select = True scene.objects.active = newCurve @@ -304,11 +305,10 @@ def main(context, obj, options): # set bezierhandles to auto setBezierHandles(newCurve) - #print("________END________\n") return -################## -## get preoperator fcurves + +# get preoperator fcurves def getFcurveData(obj): fcurves = [] for fc in obj.animation_data.action.fcurves: @@ -318,6 +318,7 @@ def getFcurveData(obj): fcurves.append(fcVerts) return fcurves + def selectedfcurves(obj): fcurves_sel = [] for i, fc in enumerate(obj.animation_data.action.fcurves): @@ -325,20 +326,19 @@ def selectedfcurves(obj): fcurves_sel.append(fc) return fcurves_sel -########################################################### -## fCurves Main + +# fCurves Main def fcurves_simplify(context, obj, options, fcurves): # main vars mode = options[0] - #get indices of selected fcurves + # get indices of selected fcurves fcurve_sel = selectedfcurves(obj) # go through fcurves for fcurve_i, fcurve in enumerate(fcurves): # test if fcurve is long enough if len(fcurve) >= 7: - # simplify spline according to mode if mode == 'DISTANCE': newVerts = simplify_RDP(fcurve, options) @@ -349,113 +349,91 @@ def fcurves_simplify(context, obj, options, fcurves): # convert indices into vectors3D newPoints = [] - #this is different from the main() function for normal curves, different api... + # this is different from the main() function for normal curves, different api... for v in newVerts: newPoints.append(fcurve[v]) - #remove all points from curve first - for i in range(len(fcurve)-1,0,-1): + # remove all points from curve first + for i in range(len(fcurve) - 1, 0, -1): fcurve_sel[fcurve_i].keyframe_points.remove(fcurve_sel[fcurve_i].keyframe_points[i]) # put newPoints into fcurve for v in newPoints: - fcurve_sel[fcurve_i].keyframe_points.insert(frame=v[0],value=v[1]) - #fcurve.points.foreach_set('co', newPoints) + fcurve_sel[fcurve_i].keyframe_points.insert(frame=v[0], value=v[1]) return -### MENU ### -class GRAPH_OT_simplifyf(bpy.types.Menu): - bl_space_type = "GRAPH_EDITOR" - bl_label = "Simplify F Curves" - - def draw(self, context): - layout = self.layout +# ### MENU append ### def menu_func(self, context): - self.layout.operator(GRAPH_OT_simplify.bl_idname) - -class CurveMenu(Menu): - bl_space_type = "3D_VIEW" - bl_label = "Simplify Curves" + self.layout.operator("graph.simplify") - def draw(self, context): - layout = self.layout def menu(self, context): self.layout.operator("curve.simplify", text="Curve Simplify", icon="CURVE_DATA") -################################################# -#### ANIMATION CURVES OPERATOR ################## -################################################# -class GRAPH_OT_simplify(bpy.types.Operator): - """""" + +# ### ANIMATION CURVES OPERATOR ### + +class GRAPH_OT_simplify(Operator): bl_idname = "graph.simplify" - bl_label = "Simplifiy F-Curves" - bl_description = "Simplify selected Curves" + bl_label = "Simplify F-Curves" + bl_description = ("Simplify selected Curves\n" + "Does not operate on short Splines (less than 6 points)") bl_options = {'REGISTER', 'UNDO'} - ## Properties + # Properties opModes = [ ('DISTANCE', 'Distance', 'Distance-based simplification (Poly)'), ('CURVATURE', 'Curvature', 'Curvature-based simplification (RDP)')] - mode = EnumProperty(name="Mode", - description="Choose algorithm to use", - items=opModes) - k_thresh = FloatProperty(name="k", - min=0, soft_min=0, - default=0, precision=3, - description="Threshold") - pointsNr = IntProperty(name="n", - min=5, soft_min=5, - max=16, soft_max=9, - default=5, - description="Degree of curve to get averaged curvatures") - error = FloatProperty(name="Error", - description="Maximum error to allow - distance", - min=0.0, soft_min=0.0, - default=0, precision=3) - degreeOut = IntProperty(name="Degree", - min=3, soft_min=3, - max=7, soft_max=7, - default=5, - description="Degree of new curve") - dis_error = FloatProperty(name="Distance error", - description="Maximum error in Blender Units to allow - distance", - min=0, soft_min=0, - default=0.0, precision=3) + mode = EnumProperty( + name="Mode", + description="Choose algorithm to use", + items=opModes + ) + k_thresh = FloatProperty( + name="k", + min=0, soft_min=0, + default=0, precision=3, + description="Threshold" + ) + pointsNr = IntProperty( + name="n", + min=5, soft_min=5, + max=16, soft_max=9, + default=5, + description="Degree of curve to get averaged curvatures" + ) + error = FloatProperty( + name="Error", + description="Maximum allowed distance error", + min=0.0, soft_min=0.0, + default=0, precision=3 + ) + degreeOut = IntProperty( + name="Degree", + min=3, soft_min=3, + max=7, soft_max=7, + default=5, + description="Degree of new curve" + ) + dis_error = FloatProperty( + name="Distance error", + description="Maximum allowed distance error in Blender Units", + min=0, soft_min=0, + default=0.0, precision=3 + ) fcurves = [] - ''' Remove curvature mode as long as it isn't significantly improved - def draw(self, context): layout = self.layout col = layout.column() - col.label('Mode:') - col.prop(self, 'mode', expand=True) - if self.mode == 'DISTANCE': - box = layout.box() - box.label(self.mode, icon='ARROW_LEFTRIGHT') - box.prop(self, 'error', expand=True) - if self.mode == 'CURVATURE': - box = layout.box() - box.label('Degree', icon='SMOOTHCURVE') - box.prop(self, 'pointsNr', expand=True) - box.label('Threshold', icon='PARTICLE_PATH') - box.prop(self, 'k_thresh', expand=True) - box.label('Distance', icon='ARROW_LEFTRIGHT') - box.prop(self, 'dis_error', expand=True) - col = layout.column() - ''' - def draw(self, context): - layout = self.layout - col = layout.column() - col.label(text = "Simplify F-Curves") - col.prop(self, 'error', expand=True) + col.label(text="Distance Error:") + col.prop(self, "error", expand=True) - ## Check for animdata @classmethod def poll(cls, context): + # Check for animdata obj = context.active_object fcurves = False if obj: @@ -466,18 +444,16 @@ class GRAPH_OT_simplify(bpy.types.Operator): fcurves = act.fcurves return (obj and fcurves) - ## execute def execute(self, context): - #print("------START------") - options = [ - self.mode, #0 - self.mode, #1 - self.k_thresh, #2 - self.pointsNr, #3 - self.error, #4 - self.degreeOut, #6 - self.dis_error] #7 + self.mode, # 0 + self.mode, # 1 + self.k_thresh, # 2 + self.pointsNr, # 3 + self.error, # 4 + self.degreeOut, # 6 + self.dis_error # 7 + ] obj = context.active_object @@ -486,116 +462,99 @@ class GRAPH_OT_simplify(bpy.types.Operator): fcurves_simplify(context, obj, options, self.fcurves) - #print("-------END-------") return {'FINISHED'} -########################### -##### Curves OPERATOR ##### -########################### -class CURVE_OT_simplify(bpy.types.Operator): - """""" + +# ### Curves OPERATOR ### +class CURVE_OT_simplify(Operator): bl_idname = "curve.simplify" - bl_label = "Simplifiy Curves" + bl_label = "Simplify Curves" bl_description = "Simplify Curves" bl_options = {'REGISTER', 'UNDO'} - ## Properties + # Properties opModes = [ ('DISTANCE', 'Distance', 'Distance-based simplification (Poly)'), ('CURVATURE', 'Curvature', 'Curvature-based simplification (RDP)')] - mode = EnumProperty(name="Mode", - description="Choose algorithm to use", - items=opModes) + mode = EnumProperty( + name="Mode", + description="Choose algorithm to use", + items=opModes + ) SplineTypes = [ ('INPUT', 'Input', 'Same type as input spline'), ('NURBS', 'Nurbs', 'NURBS'), ('BEZIER', 'Bezier', 'BEZIER'), ('POLY', 'Poly', 'POLY')] - output = EnumProperty(name="Output splines", - description="Type of splines to output", - items=SplineTypes) - k_thresh = FloatProperty(name="k", - min=0, soft_min=0, - default=0, precision=3, - description="Threshold") + output = EnumProperty( + name="Output splines", + description="Type of splines to output", + items=SplineTypes + ) + k_thresh = FloatProperty( + name="k", + min=0, soft_min=0, + default=0, precision=3, + description="Threshold" + ) pointsNr = IntProperty(name="n", - min=5, soft_min=5, - max=9, soft_max=9, - default=5, - description="Degree of curve to get averaged curvatures") - error = FloatProperty(name="Error in Blender Units", - description="Maximum error in Blender Units to allow - distance", - min=0, soft_min=0, - default=0.0, precision=3) + min=5, soft_min=5, + max=9, soft_max=9, + default=5, + description="Degree of curve to get averaged curvatures" + ) + error = FloatProperty( + name="Error", + description="Maximum allowed distance error in Blender Units", + min=0, soft_min=0, + default=0.0, precision=3 + ) degreeOut = IntProperty(name="Degree", - min=3, soft_min=3, - max=7, soft_max=7, - default=5, - description="Degree of new curve") - dis_error = FloatProperty(name="Distance error", - description="Maximum error in Blender Units to allow - distance", - min=0, soft_min=0, - default=0.0) - keepShort = BoolProperty(name="Keep short splines", - description="Keep short splines (less then 7 points)", - default=True) - - ''' Remove curvature mode as long as it isn't significantly improved + min=3, soft_min=3, + max=7, soft_max=7, + default=5, + description="Degree of new curve" + ) + dis_error = FloatProperty( + name="Distance error", + description="Maximum allowed distance error in Blender Units", + min=0, soft_min=0, + default=0.0 + ) + keepShort = BoolProperty( + name="Keep short splines", + description="Keep short splines (less than 7 points)", + default=True + ) def draw(self, context): layout = self.layout col = layout.column() - col.label('Mode:') - col.prop(self, 'mode', expand=True) - if self.mode == 'DISTANCE': - box = layout.box() - box.label(self.mode, icon='ARROW_LEFTRIGHT') - box.prop(self, 'error', expand=True) - if self.mode == 'CURVATURE': - box = layout.box() - box.label('Degree', icon='SMOOTHCURVE') - box.prop(self, 'pointsNr', expand=True) - box.label('Threshold', icon='PARTICLE_PATH') - box.prop(self, 'k_thresh', expand=True) - box.label('Distance', icon='ARROW_LEFTRIGHT') - box.prop(self, 'dis_error', expand=True) - col = layout.column() - col.separator() - col.prop(self, 'output', text='Output', icon='OUTLINER_OB_CURVE') - if self.output == 'NURBS': - col.prop(self, 'degreeOut', expand=True) - col.prop(self, 'keepShort', expand=True) - ''' - - def draw(self, context): - layout = self.layout - col = layout.column() - col.prop(self, 'error', expand=True) - col.prop(self, 'output', text='Output', icon='OUTLINER_OB_CURVE') - if self.output == 'NURBS': - col.prop(self, 'degreeOut', expand=True) - col.prop(self, 'keepShort', expand=True) + col.label("Distance Error:") + col.prop(self, "error", expand=True) + col.prop(self, "output", text="Output", icon="OUTLINER_OB_CURVE") + if self.output == "NURBS": + col.prop(self, "degreeOut", expand=True) + col.separator() + col.prop(self, "keepShort", expand=True) @classmethod def poll(cls, context): obj = context.active_object return (obj and obj.type == 'CURVE') - ## execute def execute(self, context): - #print("------START------") - options = [ - self.mode, #0 - self.output, #1 - self.k_thresh, #2 - self.pointsNr, #3 - self.error, #4 - self.degreeOut, #5 - self.dis_error, #6 - self.keepShort] #7 - + self.mode, # 0 + self.output, # 1 + self.k_thresh, # 2 + self.pointsNr, # 3 + self.error, # 4 + self.degreeOut, # 5 + self.dis_error, # 6 + self.keepShort # 7 + ] bpy.context.user_preferences.edit.use_global_undo = False @@ -606,12 +565,11 @@ class CURVE_OT_simplify(bpy.types.Operator): bpy.context.user_preferences.edit.use_global_undo = True - #print("-------END-------") return {'FINISHED'} -################################################# -#### REGISTER ################################### -################################################# + +# Register + def register(): bpy.utils.register_module(__name__) @@ -619,14 +577,14 @@ def register(): bpy.types.DOPESHEET_MT_channel.append(menu_func) bpy.types.INFO_MT_curve_add.append(menu) -def unregister(): +def unregister(): bpy.types.GRAPH_MT_channel.remove(menu_func) bpy.types.DOPESHEET_MT_channel.remove(menu_func) bpy.types.INFO_MT_curve_add.remove(menu) bpy.utils.unregister_module(__name__) + if __name__ == "__main__": register() - -- cgit v1.2.3