# -*- coding: utf-8 -*- __title__ = "Approximate extension" __author__ = "Christophe Grellier (Chris_G)" __license__ = "LGPL 2.1" __doc__ = "Approximate extension for other FeaturePython objects." import os import FreeCAD import FreeCADGui import Part from freecad.Curves import _utils from FreeCAD import Base debug = _utils.debug #debug = _utils.doNothing # ******************************************************** # ************** Approximate extension ******************* # ******************************************************** """ The following class is an extension to a FeaturePython object. Here is how to use it. In the file where you define your FeaturePython object : - import approximate_extension - in the onChanged method, call the extension onChanged : def onChanged(self, fp, prop): ... if hasattr(fp,"ExtensionProxy"): fp.ExtensionProxy.onChanged(fp, prop) - in the execute method, call the extension's approximate on the output compound : if hasattr(obj,"ExtensionProxy"): obj.Shape = obj.ExtensionProxy.approximate(obj, my_output_compound) else: obj.Shape = my_output_compound - at the creation of the FeaturePython object: fpo = FreeCAD.ActiveDocument.addObject("Part::FeaturePython","my_feature_python") my_proxy(fpo) approximate_extension.ApproximateExtension(fpo) fpo.Active = False """ class ApproximateExtension: def __init__(self, obj): ''' Add the properties ''' debug("\nApproximate extension Init\n") obj.addProperty("App::PropertyInteger", "Samples", "ShapeApproximation", "Number of samples").Samples = 100 obj.addProperty("App::PropertyBool", "Active", "ShapeApproximation", "Use approximation") obj.addProperty("App::PropertyInteger", "DegreeMin", "ShapeApproximation", "Minimum degree of the curve").DegreeMin = 3 obj.addProperty("App::PropertyInteger", "DegreeMax", "ShapeApproximation", "Maximum degree of the curve").DegreeMax = 5 obj.addProperty("App::PropertyFloat", "ApproxTolerance","ShapeApproximation", "Approximation tolerance") obj.addProperty("App::PropertyEnumeration", "Continuity", "ShapeApproximation", "Desired continuity of the curve").Continuity=["C0","C1","G1","C2","G2","C3","CN"] obj.addProperty("App::PropertyEnumeration", "Parametrization","ShapeApproximation", "Parametrization type").Parametrization=["ChordLength","Centripetal","Uniform"] obj.addProperty("App::PropertyPythonObject", "ExtensionProxy", "ShapeApproximation", "Proxy object of the approximation extension").ExtensionProxy = self obj.Parametrization = "ChordLength" obj.Continuity = 'C3' self.setTolerance(obj) obj.Active = False def setTolerance(self, obj): try: l = obj.Shape.BoundBox.DiagonalLength obj.ApproxTolerance = l / 10000.0 except: obj.ApproxTolerance = 0.001 def approximate(self, obj, input_shape): pts = False input_edges = None if isinstance(input_shape,(list, tuple)): #debug(isinstance(input_shape[0],Base.Vector)) if isinstance(input_shape[0],Base.Vector): pts = input_shape elif isinstance(input_shape[0],Part.Edge): input_edges = input_shape else: input_edges = input_shape.Edges if not obj.Active: return Part.Compound(input_shape) edges = list() if input_edges: for e in input_edges: pts = e.discretize(obj.Samples) bs = Part.BSplineCurve() bs.approximate(Points = pts, DegMin = obj.DegreeMin, DegMax = obj.DegreeMax, Tolerance = obj.ApproxTolerance, Continuity = obj.Continuity, ParamType = obj.Parametrization) edges.append(bs.toShape()) se = Part.sortEdges(edges) wires = [] for el in se: if len(el) > 1: wires.append(Part.Wire(el)) else: wires.append(el[0]) if len(wires) > 1: return Part.Compound(wires) else: return wires[0] elif pts: bs = Part.BSplineCurve() bs.approximate(Points = pts, DegMin = obj.DegreeMin, DegMax = obj.DegreeMax, Tolerance = obj.ApproxTolerance, Continuity = obj.Continuity, ParamType = obj.Parametrization) return bs.toShape() def onChanged(self, fp, prop): if prop == "Active": debug("Approximate : Active changed\n") props = ["Samples","DegreeMin","DegreeMax","ApproxTolerance","Continuity","Parametrization"] mode = 2 if fp.Active == True: mode = 0 for p in props: fp.setEditorMode(p, mode) if prop == "DegreeMin": if fp.DegreeMin < 1: fp.DegreeMin = 1 elif fp.DegreeMin > fp.DegreeMax: fp.DegreeMin = fp.DegreeMax debug("Approximate : DegreeMin changed to "+str(fp.DegreeMin)) if prop == "DegreeMax": if fp.DegreeMax < fp.DegreeMin: fp.DegreeMax = fp.DegreeMin elif fp.DegreeMax > 14: fp.DegreeMax = 14 debug("Approximate : DegreeMax changed to "+str(fp.DegreeMax)) if prop == "ApproxTolerance": if fp.ApproxTolerance < 1e-6: fp.ApproxTolerance = 1e-6 elif fp.ApproxTolerance > 1000.0: fp.ApproxTolerance = 1000.0 debug("Approximate : ApproxTolerance changed to "+str(fp.ApproxTolerance)) # ******************************************************** # **** Part.BSplineCurve.approximate() documentation ***** # ******************************************************** #Replaces this B-Spline curve by approximating a set of points. #The function accepts keywords as arguments. #approximate2(Points = list_of_points) #Optional arguments : #DegMin = integer (3) : Minimum degree of the curve. #DegMax = integer (8) : Maximum degree of the curve. #Tolerance = float (1e-3) : approximating tolerance. #Continuity = string ('C2') : Desired continuity of the curve. #Possible values : 'C0','G1','C1','G2','C2','C3','CN' #LengthWeight = float, CurvatureWeight = float, TorsionWeight = float #If one of these arguments is not null, the functions approximates the #points using variational smoothing algorithm, which tries to minimize #additional criterium: #LengthWeight*CurveLength + CurvatureWeight*Curvature + TorsionWeight*Torsion #Continuity must be C0, C1 or C2, else defaults to C2. #Parameters = list of floats : knot sequence of the approximated points. #This argument is only used if the weights above are all null. #ParamType = string ('Uniform','Centripetal' or 'ChordLength') #Parameterization type. Only used if weights and Parameters above aren't specified. #Note : Continuity of the spline defaults to C2. However, it may not be applied if #it conflicts with other parameters ( especially DegMax ). parametrization