Added RFE #588 - Curved corners to Draft objects

Draft Rectangle, Wire and Polygon now have a "Fillet Radius"
property, which can be used to round their corners. Code
donated by Jacques-Antoine Gaudin
This commit is contained in:
Yorik van Havre 2012-02-07 20:58:44 -02:00
parent fe7c57b8c5
commit c79e5d50e5
2 changed files with 302 additions and 3 deletions

View File

@ -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

View File

@ -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 *********************************************************