Draft Bezier Curves: Add functionality to mockup

- Split poles to multiple bezier segments
     - set degree of bezier curve
     - Close Draft bezier curves
     - set Degree at creation
     - implement addPoint for piecewise Bezier curves

Draft BezCurve bugfixes
    - fix Close button for BexCurve Edit
    - fix tracker behaviour in for BezCurve.undoLast

Draft Bezier Curve: use circle trackers for control points

Draft: prepare functions to make bezier knots tangent or symmetric
This commit is contained in:
Sebastian Hoogen 2014-01-19 13:30:10 +01:00 committed by Yorik van Havre
parent 2d43d61b92
commit 69b09448d9
4 changed files with 184 additions and 100 deletions

View File

@ -831,7 +831,7 @@ def makeBSpline(pointslist,closed=False,placement=None,face=True,support=None):
FreeCAD.ActiveDocument.recompute()
return obj
#######################################
def makeBezCurve(pointslist,placement=None,support=None):
def makeBezCurve(pointslist,closed=False,placement=None,support=None,Degree=None):
'''makeBezCurve(pointslist,[closed],[placement]): Creates a Bezier Curve object
from the given list of vectors. Instead of a pointslist, you can also pass a Part Wire.'''
if not isinstance(pointslist,list):
@ -845,7 +845,13 @@ def makeBezCurve(pointslist,placement=None,support=None):
obj = FreeCAD.ActiveDocument.addObject("Part::Part2DObjectPython",fname)
_BezCurve(obj)
obj.Points = pointslist
# obj.Closed = closed
if Degree:
obj.Degree = Degree
else:
import Part
obj.Degree = min((len(pointslist)-(1 * (not closed))),\
Part.BezierCurve().MaxDegree)
obj.Closed = closed
obj.Support = support
if placement: obj.Placement = placement
if gui:
@ -3963,12 +3969,10 @@ class _BezCurve(_DraftObject):
_DraftObject.__init__(self,obj,"BezCurve")
obj.addProperty("App::PropertyVectorList","Points","Draft",
"The points of the Bezier curve")
# obj.addProperty("App::PropertyBool","Closed","Draft",
# "If the Bezier curve is closed or not")
obj.addProperty("App::PropertyInteger","Degree","Draft",
"The degree of the Bezier function")
obj.addProperty("App::PropertyBool","Closed","Draft",
"If the Bezier curve is closed or not(??)")
"If the Bezier curve should be closed or not")
obj.Closed = False
obj.Degree = 3
@ -3976,28 +3980,51 @@ class _BezCurve(_DraftObject):
self.createGeometry(fp)
def onChanged(self, fp, prop):
if prop in ["Points","Degree"]:
if prop in ["Points","Degree", "Closed"]:
self.createGeometry(fp)
def createGeometry(self,fp):
import Part
plm = fp.Placement
if fp.Points:
# if fp.Points[0] == fp.Points[-1]:
# if not fp.Closed: fp.Closed = True
# fp.Points.pop()
# if fp.Closed and (len(fp.Points) > 2):
c = Part.BezierCurve()
c.setPoles(fp.Points)
e = Part.Edge(c)
w = Part.Wire(e)
startpoint=fp.Points[0]
poles=fp.Points[1:]
if fp.Closed and (len(fp.Points) > 2):
poles.append(fp.Points[0])
segpoleslst=[poles[x:x+fp.Degree] for x in \
xrange(0, len(poles), fp.Degree)]
edges = []
for segpoles in segpoleslst:
# if len(segpoles) == fp.Degree # would skip additional poles
c = Part.BezierCurve() #last segment may have lower degree
c.increase(len(segpoles))
c.setPoles([startpoint]+segpoles)
edges.append(Part.Edge(c))
startpoint = segpoles[-1]
w = Part.Wire(edges)
fp.Shape = w
# else:
# spline = Part.BezCurveCurve()
# spline.interpolate(fp.Points, False)
# fp.Shape = spline.toShape()
fp.Placement = plm
@staticmethod
def symmetricpoles(knot, p1, p2):
"""make two poles symmetric respective to the knot"""
p1h=FreeCAD.Vector(p1)
p2h=FreeCAD.Vector(p2)
p1h.multiply(0.5)
p2h.multiply(0.5)
return ( knot+p1-p2 , knot+p2-p1)
@staticmethod
def tangentpoles(knot, p1, p2):
"""make two poles have the same tangent at knot"""
p12n=p2.sub(p1)
p12n.normalize()
p1k=knot-p1
p1k_= FreeCAD.Vector(p12n)
p1k_.multiply(p1k*p12n)
pk_k=knot-p1-p1k_
return (p1+pk_k,p2+pk_k)
# for compatibility with older versions ???????
_ViewProviderBezCurve = _ViewProviderWire
#######################################

View File

@ -193,6 +193,8 @@ def geomType(edge):
return "Circle"
elif isinstance(edge.Curve,Part.BSplineCurve):
return "BSplineCurve"
elif isinstance(edge.Curve,Part.BezierCurve):
return "BezierCurve"
elif isinstance(edge.Curve,Part.Ellipse):
return "Ellipse"
else:
@ -610,7 +612,8 @@ def sortEdges(lEdges, aVertex=None):
elif geomType(result[3]) == "Circle":
mp = findMidpoint(result[3])
return [Part.Arc(aVertex.Point,mp,result[3].Vertexes[0].Point).toShape()]
elif geomType(result[3]) == "BSplineCurve":
elif geomType(result[3]) == "BSplineCurve" or\
geomType(result[3]) == "BezierCurve":
if isLine(result[3].Curve):
return [Part.Line(aVertex.Point,result[3].Vertexes[0].Point).toShape()]
else:
@ -648,7 +651,8 @@ def sortEdges(lEdges, aVertex=None):
mp = findMidpoint(result[3])
newedge = Part.Arc(aVertex.Point,mp,result[3].Vertexes[0].Point).toShape()
olEdges += [newedge] + next
elif geomType(result[3]) == "BSplineCurve":
elif geomType(result[3]) == "BSplineCurve" or \
geomType(result[3]) == "BezierCurve":
if isLine(result[3].Curve):
newedge = Part.Line(aVertex.Point,result[3].Vertexes[0].Point).toShape()
olEdges += [newedge] + next
@ -1099,7 +1103,8 @@ def findDistance(point,edge,strict=False):
return None
else:
return dist
elif geomType(edge) == "BSplineCurve":
elif geomType(edge) == "BSplineCurve" or \
geomType(edge) == "BezierCurve":
try:
pr = edge.Curve.parameter(point)
np = edge.Curve.value(pr)
@ -1214,7 +1219,8 @@ def getTangent(edge,frompoint=None):
'''
if geomType(edge) == "Line":
return vec(edge)
elif geomType(edge) == "BSplineCurve":
elif geomType(edge) == "BSplineCurve" or \
geomType(edge) == "BezierCurve":
if not frompoint:
return None
cp = edge.Curve.parameter(frompoint)
@ -1817,7 +1823,8 @@ def cleanProjection(shape,tessellate=False):
newedges.append(a)
else:
newedges.append(e.Curve.toShape())
elif geomType(e) == "BSplineCurve":
elif geomType(e) == "BSplineCurve" or \
geomType(e) == "BezierCurve":
if tessellate:
newedges.append(Part.Wire(curvetowire(e,e.Curve.NbPoles)))
else:

View File

@ -665,7 +665,7 @@ class BSpline(Line):
if self.ui:
if self.ui.continueMode:
self.Activated()
#######################################
class BezCurve(Line):
"a FreeCAD command for creating a Bezier Curve"
@ -718,20 +718,10 @@ class BezCurve(Line):
def undolast(self):
"undoes last line segment"
### this won't work exactly the same way for Bcurve????
if (len(self.node) > 1):
self.node.pop()
self.bezcurvetrack.update(self.node)
### self.obj.Shape.Edge[0].Curve.removePole(???)
# c = Part.BezierCurve()
# c.setPoles(self.Points)
# e = Part.Edge(c)
# w = Part.Wire(e)
## spline = Part.bSplineCurve()
## spline.interpolate(self.node, False)
## self.obj.Shape = spline.toShape()
# self.obj.Shape = w
msg(translate("draft", "BezCurve sb undoing last segment\n"))
self.obj.Shape = self.updateShape(self.node)
msg(translate("draft", "Last point has been removed\n"))
def drawUpdate(self,point):
@ -742,13 +732,18 @@ class BezCurve(Line):
self.planetrack.set(self.node[0])
msg(translate("draft", "Pick next point:\n"))
else:
c = Part.BezierCurve()
c.setPoles(self.node)
e = Part.Edge(c)
w = Part.Wire(e)
self.obj.Shape = w
self.obj.Shape = self.updateShape(self.node)
msg(translate("draft", "Pick next point, or (F)inish or (C)lose:\n"))
def updateShape(self, pts):
'''creates shape for display during creation process.'''
# not quite right. draws 1 big bez. sb segmented
c = Part.BezierCurve()
c.setPoles(pts)
e = Part.Edge(c)
w = Part.Wire(e)
return(w)
def finish(self,closed=False,cont=False):
"terminates the operation and closes the poly if asked"
if self.ui:
@ -764,14 +759,13 @@ class BezCurve(Line):
self.commit(translate("draft","Create BezCurve"),
['import Draft',
'points='+pts,
'Draft.makeBezCurve(points,support='+sup+')'])
'Draft.makeBezCurve(points,closed='+str(closed)+',support='+sup+')'])
except:
print "Draft: error delaying commit"
Creator.finish(self)
if self.ui:
if self.ui.continueMode:
self.Activated()
#######################################
class FinishLine:
"a FreeCAD command to finish any running Line drawing operation"
@ -3210,10 +3204,19 @@ class Edit(Modifier):
if "Placement" in self.obj.PropertiesList:
self.pl = self.obj.Placement
self.invpl = self.pl.inverse()
if Draft.getType(self.obj) in ["Wire","BSpline","BezCurve"]:
if Draft.getType(self.obj) in ["Wire","BSpline"]:
for p in self.obj.Points:
if self.pl: p = self.pl.multVec(p)
self.editpoints.append(p)
elif Draft.getType(self.obj) == "BezCurve":
self.resetTrackersBezier()
self.call = self.view.addEventCallback("SoEvent",self.action)
self.running = True
plane.save()
if "Shape" in self.obj.PropertiesList:
plane.alignToFace(self.obj.Shape)
if self.planetrack:
self.planetrack.set(self.editpoints[0])
elif Draft.getType(self.obj) == "Circle":
self.editpoints.append(self.obj.Placement.Base)
if self.obj.FirstAngle == self.obj.LastAngle:
@ -3237,21 +3240,22 @@ class Edit(Modifier):
self.editpoints.append(self.obj.End)
self.editpoints.append(self.obj.Dimline)
self.editpoints.append(Vector(p[0],p[1],p[2]))
self.trackers = []
if self.editpoints:
for ep in range(len(self.editpoints)):
self.trackers.append(editTracker(self.editpoints[ep],self.obj.Name,
ep,self.obj.ViewObject.LineColor))
self.call = self.view.addEventCallback("SoEvent",self.action)
self.running = True
plane.save()
if "Shape" in self.obj.PropertiesList:
plane.alignToFace(self.obj.Shape)
if self.planetrack:
self.planetrack.set(self.editpoints[0])
else:
msg(translate("draft", "This object type is not editable\n"),'warning')
self.finish()
if Draft.getType(self.obj) != "BezCurve":
self.trackers = []
if self.editpoints:
for ep in range(len(self.editpoints)):
self.trackers.append(editTracker(self.editpoints[ep],self.obj.Name,
ep,self.obj.ViewObject.LineColor))
self.call = self.view.addEventCallback("SoEvent",self.action)
self.running = True
plane.save()
if "Shape" in self.obj.PropertiesList:
plane.alignToFace(self.obj.Shape)
if self.planetrack:
self.planetrack.set(self.editpoints[0])
else:
msg(translate("draft", "This object type is not editable\n"),'warning')
self.finish()
else:
self.finish()
@ -3302,7 +3306,7 @@ class Edit(Modifier):
if self.ui.addButton.isChecked():
if self.point:
self.pos = arg["Position"]
self.addPoint(self.point)
self.addPoint(self.point,info)
elif self.ui.delButton.isChecked():
if 'EditNode' in info["Component"]:
self.delPoint(int(info["Component"][8:]))
@ -3405,41 +3409,65 @@ class Edit(Modifier):
self.ui.editUi()
self.node = []
def addPoint(self,point):
def addPoint(self,point,info=None):
if not (Draft.getType(self.obj) in ["Wire","BSpline","BezCurve"]): return
pts = self.obj.Points
if ( Draft.getType(self.obj) == "Wire" ):
if (self.obj.Closed == True):
# DNC: work around.... seems there is a
# bug in approximate method for closed wires...
edges = self.obj.Shape.Wires[0].Edges
e1 = edges[-1] # last edge
v1 = e1.Vertexes[0].Point
v2 = e1.Vertexes[1].Point
v2.multiply(0.9999)
edges[-1] = Part.makeLine(v1,v2)
edges.reverse()
wire = Part.Wire(edges)
curve = wire.approximate(0.0001,0.0001,100,25)
else:
# DNC: this version is much more reliable near sharp edges!
curve = self.obj.Shape.Wires[0].approximate(0.0001,0.0001,100,25)
elif ( Draft.getType(self.obj) in ["BSpline","BezCurve"]):
if (self.obj.Closed == True):
curve = self.obj.Shape.Edges[0].Curve
else:
curve = self.obj.Shape.Curve
uNewPoint = curve.parameter(point)
uPoints = []
for p in self.obj.Points:
uPoints.append(curve.parameter(p))
for i in range(len(uPoints)-1):
if ( uNewPoint > uPoints[i] ) and ( uNewPoint < uPoints[i+1] ):
pts.insert(i+1, self.invpl.multVec(point))
break
# DNC: fix: add points to last segment if curve is closed
if ( self.obj.Closed ) and ( uNewPoint > uPoints[-1] ) :
pts.append(self.invpl.multVec(point))
if Draft.getType(self.obj) == "BezCurve":
if not info['Component'].startswith('Edge'):
return # clicked control point
edgeindex = int(info['Component'].lstrip('Edge'))-1
wire=self.obj.Shape.Wires[0]
bz=wire.Edges[edgeindex].Curve
param=bz.parameter(point)
seg1=wire.Edges[edgeindex].copy().Curve
seg2=wire.Edges[edgeindex].copy().Curve
seg1.segment(seg1.FirstParameter,param)
seg2.segment(param,seg2.LastParameter)
if edgeindex == len(wire.Edges):
#we hit the last segment, we need to fix the degree
degree=wire.Edges[0].Curve.Degree
seg1.increase(degree)
seg2.increase(degree)
edges=wire.Edges[0:edgeindex]+[Part.Edge(seg1),Part.Edge(seg2)]\
+ wire.Edges[edgeindex+1:]
pts = edges[0].Curve.getPoles()[0:1]
for edge in edges:
pts.extend(edge.Curve.getPoles()[1:])
if self.obj.Closed:
pts.pop()
else:
if ( Draft.getType(self.obj) == "Wire" ):
if (self.obj.Closed == True):
# DNC: work around.... seems there is a
# bug in approximate method for closed wires...
edges = self.obj.Shape.Wires[0].Edges
e1 = edges[-1] # last edge
v1 = e1.Vertexes[0].Point
v2 = e1.Vertexes[1].Point
v2.multiply(0.9999)
edges[-1] = Part.makeLine(v1,v2)
edges.reverse()
wire = Part.Wire(edges)
curve = wire.approximate(0.0001,0.0001,100,25)
else:
# DNC: this version is much more reliable near sharp edges!
curve = self.obj.Shape.Wires[0].approximate(0.0001,0.0001,100,25)
elif ( Draft.getType(self.obj) in ["BSpline"]):
if (self.obj.Closed == True):
curve = self.obj.Shape.Edges[0].Curve
else:
curve = self.obj.Shape.Curve
uNewPoint = curve.parameter(point)
uPoints = []
for p in self.obj.Points:
uPoints.append(curve.parameter(p))
for i in range(len(uPoints)-1):
if ( uNewPoint > uPoints[i] ) and ( uNewPoint < uPoints[i+1] ):
pts.insert(i+1, self.invpl.multVec(point))
break
# DNC: fix: add points to last segment if curve is closed
if ( self.obj.Closed ) and ( uNewPoint > uPoints[-1] ) :
pts.append(self.invpl.multVec(point))
self.doc.openTransaction("Edit "+self.obj.Name)
self.obj.Points = pts
self.doc.commitTransaction()
@ -3457,16 +3485,37 @@ class Edit(Modifier):
self.doc.commitTransaction()
self.resetTrackers()
def resetTrackersBezier(self):
knotmarker = coin.SoMarkerSet.SQUARE_FILLED_9_9
polemarker = coin.SoMarkerSet.CIRCLE_FILLED_9_9
self.trackers=[]
pointswithmarkers=[(self.obj.Shape.Edges[0].Curve.\
getPole(1),knotmarker)]
for edgeindex, edge in enumerate(self.obj.Shape.Edges):
poles=edge.Curve.getPoles()
pointswithmarkers.extend([(point,polemarker) for \
point in poles[1:-1]])
if not self.obj.Closed or len(self.obj.Shape.Edges) > edgeindex +1:
pointswithmarkers.append((poles[-1],knotmarker))
for index,pwm in enumerate(pointswithmarkers):
p,marker=pwm
if self.pl: p = self.pl.multVec(p)
self.trackers.append(editTracker(p,self.obj.Name,\
index,self.obj.ViewObject.LineColor,\
marker=marker))
def resetTrackers(self):
for t in self.trackers:
t.finalize()
self.trackers = []
for ep in range(len(self.obj.Points)):
objPoints = self.obj.Points[ep]
if self.pl: objPoints = self.pl.multVec(objPoints)
self.trackers.append(editTracker(objPoints,self.obj.Name,ep,self.obj.ViewObject.LineColor))
if Draft.getType(self.obj) == "BezCurve":
self.resetTrackersBezier()
else:
for ep in range(len(self.obj.Points)):
objPoints = self.obj.Points[ep]
if self.pl: objPoints = self.pl.multVec(objPoints)
self.trackers.append(editTracker(objPoints,self.obj.Name,ep,self.obj.ViewObject.LineColor))
class AddToGroup():
"The AddToGroup FreeCAD command definition"

View File

@ -605,14 +605,15 @@ class ghostTracker(Tracker):
class editTracker(Tracker):
"A node edit tracker"
def __init__(self,pos=Vector(0,0,0),name="None",idx=0,objcol=None):
def __init__(self,pos=Vector(0,0,0),name="None",idx=0,objcol=None,\
marker=coin.SoMarkerSet.SQUARE_FILLED_9_9):
color = coin.SoBaseColor()
if objcol:
color.rgb = objcol[:3]
else:
color.rgb = FreeCADGui.draftToolBar.getDefaultColor("snap")
self.marker = coin.SoMarkerSet() # this is the marker symbol
self.marker.markerIndex = coin.SoMarkerSet.SQUARE_FILLED_9_9
self.marker.markerIndex = marker
self.coords = coin.SoCoordinate3() # this is the coordinate
self.coords.point.setValue((pos.x,pos.y,pos.z))
selnode = coin.SoType.fromName("SoFCSelection").createInstance()