diff --git a/src/Mod/Draft/Draft.py b/src/Mod/Draft/Draft.py index f432f3fdf..0fcaa8d0b 100644 --- a/src/Mod/Draft/Draft.py +++ b/src/Mod/Draft/Draft.py @@ -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 ####################################### diff --git a/src/Mod/Draft/DraftGeomUtils.py b/src/Mod/Draft/DraftGeomUtils.py index 59d2bcfb4..fa05fc2e3 100755 --- a/src/Mod/Draft/DraftGeomUtils.py +++ b/src/Mod/Draft/DraftGeomUtils.py @@ -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: diff --git a/src/Mod/Draft/DraftTools.py b/src/Mod/Draft/DraftTools.py index 49fd905a9..8cdebc605 100644 --- a/src/Mod/Draft/DraftTools.py +++ b/src/Mod/Draft/DraftTools.py @@ -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" diff --git a/src/Mod/Draft/DraftTrackers.py b/src/Mod/Draft/DraftTrackers.py index 055469a65..51a696ebe 100644 --- a/src/Mod/Draft/DraftTrackers.py +++ b/src/Mod/Draft/DraftTrackers.py @@ -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()