diff --git a/src/Mod/Draft/DraftTools.py b/src/Mod/Draft/DraftTools.py index 92371daa8c..4efc8feea8 100644 --- a/src/Mod/Draft/DraftTools.py +++ b/src/Mod/Draft/DraftTools.py @@ -996,6 +996,170 @@ class BezCurve(Line): if self.ui.continueMode: self.Activated() +class CubicBezCurve(Line): + "a FreeCAD command for creating a 3rd degree Bezier Curve" + + def __init__(self): + Line.__init__(self,wiremode=True) + self.degree = 3 + + def GetResources(self): + return {'Pixmap' : 'Draft_BezCurve', + #'Accel' : "B, Z", + 'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_CubicBezCurve", "CubicBezCurve"), + 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Draft_CubicBezCurve", "Creates a Cubic Bezier curve \nClick and drag to define control points. CTRL to snap, SHIFT to constrain")} + + def Activated(self): + Line.Activated(self,name=translate("draft","CubicBezCurve")) + if self.doc: + self.bezcurvetrack = bezcurveTracker() + + def action(self,arg): + "scene event handler" + if arg["Type"] == "SoKeyboardEvent": + if arg["Key"] == "ESCAPE": + self.finish() + elif arg["Type"] == "SoLocation2Event": #mouse movement detection + self.point,ctrlPoint,info = getPoint(self,arg,noTracker=True) + if (len(self.node)-1) % self.degree == 0 and len(self.node) > 2 : + prevctrl = 2 * self.node[-1] - self.point + self.bezcurvetrack.update(self.node[0:-2] + [prevctrl] + [self.node[-1]] +[self.point],degree=self.degree) #existing points + this pointer position + else: + self.bezcurvetrack.update(self.node + [self.point],degree=self.degree) #existing points + this pointer position + redraw3DView() + elif arg["Type"] == "SoMouseButtonEvent": + if (arg["State"] == "DOWN") and (arg["Button"] == "BUTTON1"): #left click + if (arg["Position"] == self.pos): #double click? + if len(self.node) > 2: + self.node = self.node[0:-2] + else: + self.node = [] + return + else: + if (not self.node) and (not self.support): #first point + getSupport(arg) + self.point,ctrlPoint,info = getPoint(self,arg,noTracker=True) + if self.point: + self.ui.redraw() + self.pos = arg["Position"] + self.node.append(self.point) #add point to "clicked list" + # sb add a control point, if mod(len(cpoints),2) == 0) then create 2 handle points? + self.drawUpdate(self.point) #??? + if (not self.isWire and len(self.node) == 2): + self.finish(False,cont=True) + if (len(self.node) > 2): #does this make sense for a BCurve? + self.node.append(self.point) #add point to "clicked list" + self.drawUpdate(self.point) + # DNC: allows to close the curve + # by placing ends close to each other + # with tol = Draft tolerance + # old code has been to insensitive + if ((self.point-self.node[0]).Length < Draft.tolerance()) and len(self.node) >= 4: + #self.undolast() + self.node=self.node[0:-2] + self.node.append(2 * self.node[0] - self.node[1]) #close the curve with a smooth simmetric knot + self.finish(True,cont=True) + msg(translate("draft", "Bezier curve has been closed")+"\n") + if (arg["State"] == "UP") and (arg["Button"] == "BUTTON1"): #left click + if (arg["Position"] == self.pos): #double click? + self.node = self.node[0:-2] + return + else: + if (not self.node) and (not self.support): #first point + return + if self.point: + self.ui.redraw() + self.pos = arg["Position"] + self.node.append(self.point) #add point to "clicked list" + # sb add a control point, if mod(len(cpoints),2) == 0) then create 2 handle points? + self.drawUpdate(self.point) #??? + if (not self.isWire and len(self.node) == 2): + self.finish(False,cont=True) + if (len(self.node) > 2): #does this make sense for a BCurve? + self.node[-3] = 2 * self.node[-2] - self.node[-1] + self.drawUpdate(self.point) + # DNC: allows to close the curve + # by placing ends close to each other + # with tol = Draft tolerance + # old code has been to insensitive + + def undolast(self): + "undoes last line segment" + if (len(self.node) > 1): + self.node.pop() + self.bezcurvetrack.update(self.node,degree=self.degree) + self.obj.Shape = self.updateShape(self.node) + msg(translate("draft", "Last point has been removed")+"\n") + + + def drawUpdate(self,point): + if (len(self.node) == 1): + self.bezcurvetrack.on() + if self.planetrack: + self.planetrack.set(self.node[0]) + msg(translate("draft", "Click and drag to define next knot:\n")) + elif (len(self.node)-1) % self.degree == 1 and len(self.node) > 2 : #is a knot + self.obj.Shape = self.updateShape(self.node[:-1]) + msg(translate("draft", "Click and drag to define next knot: ESC to Finish or close (o):")+"\n") + + def updateShape(self, pts): + '''creates shape for display during creation process.''' +# not quite right. draws 1 big bez. sb segmented + edges = [] + + if len(pts) >= 2: #allow lower degree segment + poles=pts[1:] + else: + poles=[] + + if self.degree: + segpoleslst = [poles[x:x+self.degree] for x in range(0, len(poles), (self.degree or 1))] + else: + segpoleslst = [pts] + + startpoint=pts[0] + + for segpoles in segpoleslst: + 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) + return(w) + + def finish(self,closed=False,cont=False): + "terminates the operation and closes the poly if asked" + if self.ui: + if hasattr(self,"bezcurvetrack"): + self.bezcurvetrack.finalize() + if not Draft.getParam("UiMode",1): + FreeCADGui.Control.closeDialog() + if self.obj: + # remove temporary object, if any + old = self.obj.Name + todo.delay(self.doc.removeObject,old) + if closed == False : + cleannd=(len(self.node)-1) % self.degree + if cleannd == 0 : self.node = self.node[0:-3] + if cleannd > 0 : self.node = self.node[0:-cleannd] + if (len(self.node) > 1): + try: + # building command string + rot,sup,pts,fil = self.getStrings() + FreeCADGui.addModule("Draft") + self.commit(translate("draft","Create BezCurve"), + ['points = '+pts, + 'bez = Draft.makeBezCurve(points,closed='+str(closed)+',support='+sup+',Degree='+str(self.degree)+')', + 'Draft.autogroup(bez)']) + 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" @@ -6169,7 +6333,20 @@ FreeCADGui.addCommand('Draft_Rectangle',Rectangle()) FreeCADGui.addCommand('Draft_Dimension',Dimension()) FreeCADGui.addCommand('Draft_Polygon',Polygon()) FreeCADGui.addCommand('Draft_BSpline',BSpline()) + +class CommandBezierGroup: + def GetCommands(self): + return tuple(['Draft_BezCurve','Draft_CubicBezCurve']) + def GetResources(self): + return { 'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_BezierTools",'Bezier tools'), + 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Draft_BezierTools",'Bezier tools') + } + def IsActive(self): + return not FreeCAD.ActiveDocument is None FreeCADGui.addCommand('Draft_BezCurve',BezCurve()) +FreeCADGui.addCommand('Draft_CubicBezCurve',CubicBezCurve()) +FreeCADGui.addCommand('Draft_BezierTools', CommandBezierGroup()) + FreeCADGui.addCommand('Draft_Point',Point()) FreeCADGui.addCommand('Draft_Ellipse',Ellipse()) FreeCADGui.addCommand('Draft_ShapeString',ShapeString()) diff --git a/src/Mod/Draft/InitGui.py b/src/Mod/Draft/InitGui.py index c638fe3c3b..26f1668780 100644 --- a/src/Mod/Draft/InitGui.py +++ b/src/Mod/Draft/InitGui.py @@ -71,7 +71,7 @@ class DraftWorkbench (Workbench): self.cmdList = ["Draft_Line","Draft_Wire","Draft_Circle","Draft_Arc","Draft_Arc_3Points","Draft_Ellipse", "Draft_Polygon","Draft_Rectangle", "Draft_Text", "Draft_Dimension", "Draft_BSpline","Draft_Point", - "Draft_ShapeString","Draft_Facebinder","Draft_BezCurve","Draft_Label"] + "Draft_ShapeString","Draft_Facebinder","Draft_BezierTools","Draft_Label"] self.modList = ["Draft_Move","Draft_Rotate","Draft_Offset", "Draft_Trimex", "Draft_Join", "Draft_Split", "Draft_Upgrade", "Draft_Downgrade", "Draft_Scale", "Draft_Edit","Draft_WireToBSpline","Draft_AddPoint",