diff --git a/src/Mod/Draft/Draft.py b/src/Mod/Draft/Draft.py index ae198a4f4..3e733d387 100644 --- a/src/Mod/Draft/Draft.py +++ b/src/Mod/Draft/Draft.py @@ -2028,6 +2028,7 @@ class _Rectangle: def __init__(self, obj): obj.addProperty("App::PropertyDistance","Length","Base","Length of the rectangle") obj.addProperty("App::PropertyDistance","Height","Base","Height of the rectange") + obj.addProperty("App::PropertyDistance","FilletRadius","Base","Radius to use to fillet the corners") obj.Proxy = self obj.Length=1 obj.Height=1 @@ -2042,12 +2043,18 @@ class _Rectangle: def createGeometry(self,fp): import Part + from draftlibs import fcgeo plm = fp.Placement p1 = Vector(0,0,0) p2 = Vector(p1.x+fp.Length,p1.y,p1.z) p3 = Vector(p1.x+fp.Length,p1.y+fp.Height,p1.z) p4 = Vector(p1.x,p1.y+fp.Height,p1.z) shape = Part.makePolygon([p1,p2,p3,p4,p1]) + if "FilletRadius" in fp.PropertiesList: + if fp.FilletRadius != 0: + w = fcgeo.filletWire(shape,fp.FilletRadius) + if w: + shape = w shape = Part.Face(shape) fp.Shape = shape fp.Placement = plm @@ -2079,9 +2086,9 @@ class _Circle: "The Circle object" def __init__(self, obj): - obj.addProperty("App::PropertyAngle","FirstAngle","Arc", + obj.addProperty("App::PropertyAngle","FirstAngle","Base", "Start angle of the arc") - obj.addProperty("App::PropertyAngle","LastAngle","Arc", + obj.addProperty("App::PropertyAngle","LastAngle","Base", "End angle of the arc (for a full circle, give it same value as First Angle)") obj.addProperty("App::PropertyDistance","Radius","Base", "Radius of the circle") @@ -2122,6 +2129,7 @@ class _Wire: "The start point of this line") obj.addProperty("App::PropertyVector","End","Base", "The end point of this line") + obj.addProperty("App::PropertyDistance","FilletRadius","Base","Radius to use to fillet the corners") obj.Proxy = self obj.Closed = False self.Type = "Wire" @@ -2130,7 +2138,7 @@ class _Wire: self.createGeometry(fp) def onChanged(self, fp, prop): - if prop in ["Points","Closed","Base","Tool"]: + if prop in ["Points","Closed","Base","Tool","FilletRadius"]: self.createGeometry(fp) if prop == "Points": if fp.Start != fp.Points[0]: @@ -2183,6 +2191,11 @@ class _Wire: fp.Points.pop() if fp.Closed and (len(fp.Points) > 2): shape = Part.makePolygon(fp.Points+[fp.Points[0]]) + if "FilletRadius" in fp.PropertiesList: + if fp.FilletRadius != 0: + w = fcgeo.filletWire(shape,fp.FilletRadius) + if w: + shape = w shape = Part.Face(shape) else: edges = [] @@ -2192,6 +2205,11 @@ class _Wire: edges.append(Part.Line(lp,p).toShape()) lp = p shape = Part.Wire(edges) + if "FilletRadius" in fp.PropertiesList: + if fp.FilletRadius != 0: + w = fcgeo.filletWire(shape,fp.FilletRadius) + if w: + shape = w fp.Shape = shape fp.Placement = plm @@ -2240,6 +2258,7 @@ class _Polygon: obj.addProperty("App::PropertyInteger","FacesNumber","Base","Number of faces") obj.addProperty("App::PropertyDistance","Radius","Base","Radius of the control circle") obj.addProperty("App::PropertyEnumeration","DrawMode","Base","How the polygon must be drawn from the control circle") + obj.addProperty("App::PropertyDistance","FilletRadius","Base","Radius to use to fillet the corners") obj.DrawMode = ['inscribed','circumscribed'] obj.FacesNumber = 3 obj.Radius = 1 @@ -2255,6 +2274,7 @@ class _Polygon: def createGeometry(self,fp): import Part + from draftlibs import fcgeo plm = fp.Placement angle = (math.pi*2)/fp.FacesNumber if fp.DrawMode == 'inscribed': @@ -2267,6 +2287,11 @@ class _Polygon: pts.append(Vector(delta*math.cos(ang),delta*math.sin(ang),0)) pts.append(pts[0]) shape = Part.makePolygon(pts) + if "FilletRadius" in fp.PropertiesList: + if fp.FilletRadius != 0: + w = fcgeo.filletWire(shape,fp.FilletRadius) + if w: + shape = w shape = Part.Face(shape) fp.Shape = shape fp.Placement = plm diff --git a/src/Mod/Draft/draftlibs/fcgeo.py b/src/Mod/Draft/draftlibs/fcgeo.py index a2844af07..06bc691f2 100755 --- a/src/Mod/Draft/draftlibs/fcgeo.py +++ b/src/Mod/Draft/draftlibs/fcgeo.py @@ -1195,6 +1195,280 @@ def arcFromSpline(edge): return Part.makeCircle(radius,center) except: print "couldn't make a circle out of this edge" + +# Fillet code graciously donated by Jacques-Antoine Gaudin + +def fillet(lEdges,r): + ''' Take a list of two Edges & a float as argument, + Returns a list of sorted edges describing a round corner''' + + def getCurveType(edge,existingCurveType = None): + '''Builds or completes a dictionnary containing edges with keys "Arc" and "Line"''' + if not existingCurveType : + existingCurveType = { 'Line' : [], 'Arc' : [] } + if issubclass(type(edge.Curve),Part.Line) : + existingCurveType['Line'] += [edge] + elif issubclass(type(edge.Curve),Part.Circle) : + existingCurveType['Arc'] += [edge] + else : + raise Exception("Edge's curve must be either Line or Arc") + return existingCurveType + + rndEdges = lEdges[0:2] + rndEdges = sortEdges(rndEdges) + + if len(rndEdges) < 2 : + return rndEdges + + if r <= 0 : + print "fcgeo.fillet : Error : radius is negative." + return rndEdges + + curveType = getCurveType(rndEdges[0]) + curveType = getCurveType(rndEdges[1],curveType) + + lVertexes = rndEdges[0].Vertexes + [rndEdges[1].Vertexes[-1]] + + if len(curveType['Line']) == 2: + + # Deals with 2-line-edges lists -------------------------------------- + + U1 = lVertexes[0].Point.sub(lVertexes[1].Point) ; U1.normalize() + U2 = lVertexes[2].Point.sub(lVertexes[1].Point) ; U2.normalize() + alpha = U1.getAngle(U2) + + if round(alpha,precision) == 0 or round(alpha - math.pi,precision) == 0: # Edges have same direction + print "fcgeo.fillet : Warning : edges have same direction. Did nothing" + return rndEdges + + dToCenter = r / math.sin(alpha/2.) + dToTangent = (dToCenter**2-r**2)**(0.5) + dirVect = Vector(U1) ; dirVect.scale(dToTangent,dToTangent,dToTangent) + arcPt1 = lVertexes[1].Point.add(dirVect) + + dirVect = U2.add(U1) ; dirVect.normalize() + dirVect.scale(dToCenter-r,dToCenter-r,dToCenter-r) + arcPt2 = lVertexes[1].Point.add(dirVect) + + dirVect = Vector(U2) ; dirVect.scale(dToTangent,dToTangent,dToTangent) + arcPt3 = lVertexes[1].Point.add(dirVect) + + if (dToTangent>lEdges[0].Length) or (dToTangent>lEdges[1].Length) : + print "fcgeo.fillet : Error : radius value ", r," is too high" + return rndEdges + + rndEdges[1] = Part.Edge(Part.Arc(arcPt1,arcPt2,arcPt3)) + rndEdges[0] = Part.Edge(Part.Line(lVertexes[0].Point,arcPt1)) + rndEdges += [Part.Edge(Part.Line(arcPt3,lVertexes[2].Point))] + + return rndEdges + + elif len(curveType['Arc']) == 1 : + + # Deals with lists containing an arc and a line ---------------------------------- + + if lEdges[0] in curveType['Arc'] : + lineEnd = lVertexes[2] ; arcEnd = lVertexes[0] ; arcFirst = True + else : + lineEnd = lVertexes[0] ; arcEnd = lVertexes[2] ; arcFirst = False + arcCenter = curveType['Arc'][0].Curve.Center + arcRadius = curveType['Arc'][0].Curve.Radius + arcAxis = curveType['Arc'][0].Curve.Axis + arcLength = curveType['Arc'][0].Length + + U1 = lineEnd.Point.sub(lVertexes[1].Point) ; U1.normalize() + toCenter = arcCenter.sub(lVertexes[1].Point) + if arcFirst : # make sure the tangent points towards the arc + T = arcAxis.cross(toCenter) + else : + T = toCenter.cross(arcAxis) + + projCenter = toCenter.dot(U1) + if round(abs(projCenter),precision) > 0 : + normToLine = U1.cross(T).cross(U1) + else : + normToLine = Vector(toCenter) + normToLine.normalize() + + dCenterToLine = toCenter.dot(normToLine) - r + + if round(projCenter,precision) > 0 : + newRadius = arcRadius - r + elif round(projCenter,precision) < 0 or (round(projCenter,precision) == 0 and U1.dot(T) > 0): + newRadius = arcRadius + r + else : + print "fcgeo.fillet : Warning : edges are already tangent. Did nothing" + return rndEdges + + toNewCent = newRadius**2-dCenterToLine**2 + if toNewCent > 0 : + toNewCent = abs(abs(projCenter) - toNewCent**(0.5)) + else : + print "fcgeo.fillet : Error : radius value ", r," is too high" + return rndEdges + + U1.scale(toNewCent,toNewCent,toNewCent) + normToLine.scale(r,r,r) + newCent = lVertexes[1].Point.add(U1).add(normToLine) + + arcPt1= lVertexes[1].Point.add(U1) + arcPt2= lVertexes[1].Point.sub(newCent); arcPt2.normalize() + arcPt2.scale(r,r,r) ; arcPt2 = arcPt2.add(newCent) + if newRadius == arcRadius - r : + arcPt3= newCent.sub(arcCenter) + else : + arcPt3= arcCenter.sub(newCent) + arcPt3.normalize() + arcPt3.scale(r,r,r) ; arcPt3 = arcPt3.add(newCent) + arcPt = [arcPt1,arcPt2,arcPt3] + + + # Warning : In the following I used a trick for calling the right element + # in arcPt or V : arcFirst is a boolean so - not arcFirst is -0 or -1 + # list[-1] is the last element of a list and list[0] the first + # this way I don't have to proceed tests to know the position of the arc + + myTrick = not arcFirst + + V = [arcPt3] + V += [arcEnd.Point] + + toCenter.scale(-1,-1,-1) + + delLength = arcRadius * V[0].sub(arcCenter).getAngle(toCenter) + if delLength > arcLength or toNewCent > curveType['Line'][0].Length: + print "fcgeo.fillet : Error : radius value ", r," is too high" + return rndEdges + + arcAsEdge = arcFrom2Pts(V[-arcFirst],V[-myTrick],arcCenter,arcAxis) + + V = [lineEnd.Point,arcPt1] + lineAsEdge = Part.Edge(Part.Line(V[-arcFirst],V[myTrick])) + + rndEdges[not arcFirst] = arcAsEdge + rndEdges[arcFirst] = lineAsEdge + rndEdges[1:1] = [Part.Edge(Part.Arc(arcPt[- arcFirst],arcPt[1],arcPt[- myTrick]))] + + return rndEdges + + elif len(curveType['Arc']) == 2 : + + # Deals with lists of 2 arc-edges -------------------------------------------- + + arcCenter, arcRadius, arcAxis, arcLength, toCenter, T, newRadius = [], [], [], [], [], [], [] + for i in range(2) : + arcCenter += [curveType['Arc'][i].Curve.Center] + arcRadius += [curveType['Arc'][i].Curve.Radius] + arcAxis += [curveType['Arc'][i].Curve.Axis] + arcLength += [curveType['Arc'][i].Length] + toCenter += [arcCenter[i].sub(lVertexes[1].Point)] + T += [arcAxis[0].cross(toCenter[0])] + T += [toCenter[1].cross(arcAxis[1])] + CentToCent = toCenter[1].sub(toCenter[0]) + dCentToCent = CentToCent.Length + + sameDirection = (arcAxis[0].dot(arcAxis[1]) > 0) + TcrossT = T[0].cross(T[1]) + if sameDirection : + if round(TcrossT.dot(arcAxis[0]),precision) > 0 : + newRadius += [arcRadius[0]+r] + newRadius += [arcRadius[1]+r] + elif round(TcrossT.dot(arcAxis[0]),precision) < 0 : + newRadius += [arcRadius[0]-r] + newRadius += [arcRadius[1]-r] + elif T[0].dot(T[1]) > 0 : + newRadius += [arcRadius[0]+r] + newRadius += [arcRadius[1]+r] + else : + print "fcgeo.fillet : Warning : edges are already tangent. Did nothing" + return rndEdges + elif not sameDirection : + if round(TcrossT.dot(arcAxis[0]),precision) > 0 : + newRadius += [arcRadius[0]+r] + newRadius += [arcRadius[1]-r] + elif round(TcrossT.dot(arcAxis[0]),precision) < 0 : + newRadius += [arcRadius[0]-r] + newRadius += [arcRadius[1]+r] + elif T[0].dot(T[1]) > 0 : + if arcRadius[0] > arcRadius[1] : + newRadius += [arcRadius[0]-r] + newRadius += [arcRadius[1]+r] + elif arcRadius[1] > arcRadius[0] : + newRadius += [arcRadius[0]+r] + newRadius += [arcRadius[1]-r] + else : + print "fcgeo.fillet : Warning : arcs are coincident. Did nothing" + return rndEdges + else : + print "fcgeo.fillet : Warning : edges are already tangent. Did nothing" + return rndEdges + + if newRadius[0]+newRadius[1] < dCentToCent or \ + newRadius[0]-newRadius[1] > dCentToCent or \ + newRadius[1]-newRadius[0] > dCentToCent : + print "fcgeo.fillet : Error : radius value ", r," is too high" + return rndEdges + + x = (dCentToCent**2+newRadius[0]**2-newRadius[1]**2)/(2*dCentToCent) + y = (newRadius[0]**2-x**2)**(0.5) + + CentToCent.normalize() ; toCenter[0].normalize() ; toCenter[1].normalize() + if abs(toCenter[0].dot(toCenter[1])) != 1 : + normVect = CentToCent.cross(CentToCent.cross(toCenter[0])) + else : + normVect = T[0] + normVect.normalize() + CentToCent.scale(x,x,x) ; normVect.scale(y,y,y) + newCent = arcCenter[0].add(CentToCent.add(normVect)) + CentToNewCent = [newCent.sub(arcCenter[0]),newCent.sub(arcCenter[1])] + for i in range(2) : + CentToNewCent[i].normalize() + if newRadius[i] == arcRadius[i]+r : + CentToNewCent[i].scale(-r,-r,-r) + else : + CentToNewCent[i].scale(r,r,r) + toThirdPt = lVertexes[1].Point.sub(newCent) ; toThirdPt.normalize() + toThirdPt.scale(r,r,r) + arcPt1 = newCent.add(CentToNewCent[0]) + arcPt2 = newCent.add(toThirdPt) + arcPt3 = newCent.add(CentToNewCent[1]) + arcPt = [arcPt1,arcPt2,arcPt3] + + arcAsEdge = [] + for i in range(2) : + toCenter[i].scale(-1,-1,-1) + delLength = arcRadius[i] * arcPt[-i].sub(arcCenter[i]).getAngle(toCenter[i]) + if delLength > arcLength[i] : + print "fcgeo.fillet : Error : radius value ", r," is too high" + return rndEdges + V = [arcPt[-i],lVertexes[-i].Point] + arcAsEdge += [arcFrom2Pts(V[i-1],V[-i],arcCenter[i],arcAxis[i])] + + rndEdges[0] = arcAsEdge[0] + rndEdges[1] = arcAsEdge[1] + rndEdges[1:1] = [Part.Edge(Part.Arc(arcPt[0],arcPt[1],arcPt[2]))] + + return rndEdges + +def filletWire(aWire,r,makeClosed=True): + ''' Fillets each angle of a wire with r as radius value''' + + edges = aWire.Edges + edges = sortEdges(edges) + filEdges = [edges[0]] + for i in range(len(edges)-1): + result = fillet([filEdges[-1],edges[i+1]],r) + if len(result)>2: + filEdges[-1:] = result[0:3] + else : + filEdges[-1:] = result[0:2] + if isReallyClosed(aWire) and makeClosed : + result = fillet([filEdges[-1],filEdges[0]],r) + if len(result)>2: + filEdges[-1:] = result[0:2] + filEdges[0] = result[2] + return Part.Wire(filEdges) + # circle functions *********************************************************