# -*- coding: utf-8 -*- __title__ = "Compression Spring" __author__ = "Christophe Grellier (Chris_G)" __license__ = "LGPL 2.1" __doc__ = """Parametric Compression Spring""" import sys if sys.version_info.major >= 3: from importlib import reload import os import FreeCAD import FreeCADGui import Part from Part import Geom2d from math import pi Vector = FreeCAD.Base.Vector Vector2d = FreeCAD.Base.Vector2d from freecad.Curves import _utils from freecad.Curves import ICONPATH TOOL_ICON = os.path.join( ICONPATH, 'spring.svg') #debug = _utils.debug #debug = _utils.doNothing props = """ App::PropertyBool App::PropertyBoolList App::PropertyFloat App::PropertyFloatList App::PropertyFloatConstraint App::PropertyQuantity App::PropertyQuantityConstraint App::PropertyAngle App::PropertyDistance App::PropertyLength App::PropertySpeed App::PropertyAcceleration App::PropertyForce App::PropertyPressure App::PropertyInteger App::PropertyIntegerConstraint App::PropertyPercent App::PropertyEnumeration App::PropertyIntegerList App::PropertyIntegerSet App::PropertyMap App::PropertyString App::PropertyUUID App::PropertyFont App::PropertyStringList App::PropertyLink App::PropertyLinkSub App::PropertyLinkList App::PropertyLinkSubList App::PropertyMatrix App::PropertyVector App::PropertyVectorList App::PropertyPlacement App::PropertyPlacementLink App::PropertyColor App::PropertyColorList App::PropertyMaterial App::PropertyPath App::PropertyFile App::PropertyFileIncluded App::PropertyPythonObject Part::PropertyPartShape Part::PropertyGeometryList Part::PropertyShapeHistory Part::PropertyFilletEdges Sketcher::PropertyConstraintList """ class CompSpring(object): def __init__(self, length=10, turns=8, wireDiam=0.5, diameter=4.0, flatness=100): #self.epsilon = 1e-2 self.length = length self.turns = turns self.wire_diam = wireDiam self.diameter = diameter self.flatness = flatness def compute_path_cp(self): free_turns = self.turns-2 skew = Part.LineSegment(Vector(2*pi,self.wire_diam,0),Vector((self.turns-1)*2*pi,self.length-self.wire_diam,0)) tan = skew.tangent(skew.FirstParameter)[0] tan.normalize() tan.multiply(self.wire_diam/2.) p1 = Vector(-tan.y,tan.x,0) ls = Part.Line(skew.StartPoint+p1,skew.EndPoint-p1) h1 = Part.Line(Vector(0,self.wire_diam/2.,0),Vector(1,self.wire_diam/2.,0)) h2 = Part.Line(Vector(0,self.length-self.wire_diam/2.,0),Vector(1,self.length-self.wire_diam/2.,0)) pts = [Vector2d(0,self.wire_diam/2.)] i1 = h1.intersect(ls)[0] i2 = h2.intersect(ls)[0] pts.append(Vector2d(i1.X,i1.Y)) pts.append(Vector2d(i2.X,i2.Y)) pts.append(Vector2d(self.turns*2*pi,self.length-self.wire_diam/2.)) return pts def path2d(self): poles = self.compute_path_cp() bs = Geom2d.BSplineCurve2d() bs.buildFromPoles(poles) bs.setWeight(2,self.flatness) bs.setWeight(3,self.flatness) return bs def path3d(self): cyl = Part.makeCylinder((self.diameter-self.wire_diam)/2., self.length-self.wire_diam, Vector(), Vector(0,0,1)).Face1 return self.path2d().toShape(cyl.Surface) def min_length(self): return (self.turns+1)*self.wire_diam def shape(self): path = Part.Wire(self.path3d()) c = Part.Circle(path.Edges[0].valueAt(path.Edges[0].FirstParameter), path.Edges[0].tangentAt(path.Edges[0].FirstParameter), self.wire_diam/2.0) pro = Part.Wire([c.toShape()]) ps = Part.BRepOffsetAPI.MakePipeShell(path) ps.setFrenetMode(True) #ps.setForceApproxC1(True) ps.setTolerance(1e-2, 1e-2, 0.1) ps.setMaxDegree(5) ps.setMaxSegments(999) ps.add(pro) if ps.isReady(): ps.build() ps.makeSolid() return ps.shape() return None #def offset_points(self,pts,start,step,power=1): #npts = list() #for i in range(len(pts)): #v = float(i)/(len(pts)-1) #if power < 0: #fac = pow(v,1./abs(power)) #else: #fac = pow(v,power) #npts.append(pts[i] + Vector(0,0,start + fac*step)) #return npts #def point_lists(self): #helix_radius = (self.diameter - self.wire_diam) / 2.0 #if self.length < self.min_length(): #print("Spring too short") #return #free_space = self.length - self.min_length() #if self.turns <= 2: #print("Spring must have more than 2 turns") #return #step = free_space / (self.turns - 2) #circle = Part.Circle(Vector(),Vector(0,0,1),helix_radius) #pts = circle.discretize(self.samples) #points = list() #start = self.wire_diam / 2.0 #pts1 = self.offset_points(pts,start,self.wire_diam+self.epsilon,2) #points.append(pts1) #start += self.wire_diam+self.epsilon #for i in range(self.turns-2): #pts1 = self.offset_points(pts,start,self.wire_diam+step) #points.append(pts1) #start += self.wire_diam+step #pts1 = self.offset_points(pts,start,self.wire_diam+self.epsilon,-2) #points.append(pts1) #return points #def curves(self): #curves = list() #pl = self.point_lists() #for pts in pl: #bs = Part.BSplineCurve() #bs.interpolate(pts) #curves.append(bs) #return curves #def edges(self): #edges = list() #for c in self.curves(): #edges.append(c.toShape()) #return edges #def wire(self, single=True): #if single: #return Part.Wire(self.path3d())) #self.single_edge()) #else: #return Part.Wire(self.edges()) #def single_curve(self): #pl = self.point_lists() #pts = pl[0][:-1] #for arr in pl[1:-1]: #pts += arr[1:-1] #pts += pl[-1][1:] #bs = Part.BSplineCurve() #bs.interpolate(pts) #return bs #def single_edge(self): #return self.single_curve().toShape() class CompSpringFP: """Creates a Parametric Compression Spring""" def __init__(self, obj): """Add the properties""" obj.addProperty("App::PropertyFloat", "Length", "CompSpring", "Spring Length").Length = 10.0 obj.addProperty("App::PropertyInteger", "Turns", "CompSpring", "Number of turns").Turns = 5 obj.addProperty("App::PropertyFloat", "WireDiameter", "CompSpring", "Diameter of the spring wire").WireDiameter = 0.5 obj.addProperty("App::PropertyFloat", "Diameter", "CompSpring", "Diameter of the spring").Diameter = 4.0 obj.addProperty("App::PropertyInteger", "Flatness", "Setting", "Flatness of spring extremities from 0 to 4").Flatness = 0 #obj.addProperty("App::PropertyBool", "Smooth", "Setting", "make spring with a single tube surface").Smooth = False obj.addProperty("App::PropertyBool", "WireOutput", "Setting", "Output a wire shape").WireOutput=True obj.Proxy = self def spring(self, obj): try: f = pow(10, obj.Flatness-1) return CompSpring(obj.Length, obj.Turns, obj.WireDiameter, obj.Diameter, f) except AttributeError: return None def execute(self, obj): cs = self.spring(obj) if not cs: return if obj.WireOutput: obj.Shape = cs.path3d() else: obj.Shape = cs.shape() return cs def onChanged(self, obj, prop): if prop in ("Length","Turns","WireDiameter"): cs = self.spring(obj) if cs: if obj.Length < cs.min_length(): obj.Length = cs.min_length() if prop == "Flatness": if obj.Flatness < 0: obj.Flatness = 0 elif obj.Flatness > 4: obj.Flatness = 4 class CompSpringVP: def __init__(self,vobj): vobj.Proxy = self def getIcon(self): return TOOL_ICON def attach(self, vobj): self.Object = vobj.Object def __getstate__(self): return {"name": self.Object.Name} def __setstate__(self,state): self.Object = FreeCAD.ActiveDocument.getObject(state["name"]) return None class CompSpringCommand: """Creates a Parametric Compression Spring""" def makeFeature(self): fp = FreeCAD.ActiveDocument.addObject("Part::FeaturePython","CompSpring") CompSpringFP(fp) CompSpringVP(fp.ViewObject) FreeCAD.ActiveDocument.recompute() def Activated(self): self.makeFeature() def IsActive(self): if FreeCAD.ActiveDocument: return True else: return False def GetResources(self): return {'Pixmap' : TOOL_ICON, 'MenuText': __title__, 'ToolTip': __doc__} FreeCADGui.addCommand('comp_spring', CompSpringCommand())