diff --git a/src/Mod/Draft/CMakeLists.txt b/src/Mod/Draft/CMakeLists.txt index 96c7a0de3b49..b0320636142e 100644 --- a/src/Mod/Draft/CMakeLists.txt +++ b/src/Mod/Draft/CMakeLists.txt @@ -64,14 +64,20 @@ SET(Draft_functions SET(Draft_make_functions draftmake/__init__.py + draftmake/make_bezcurve.py + draftmake/make_bspline.py draftmake/make_circle.py + draftmake/make_line.py draftmake/make_polygon.py draftmake/make_rectangle.py + draftmake/make_wire.py ) SET(Draft_objects draftobjects/__init__.py draftobjects/base.py + draftobjects/bezcurve.py + draftobjects/bspline.py draftobjects/circulararray.py draftobjects/circle.py draftobjects/orthoarray.py @@ -83,6 +89,7 @@ SET(Draft_objects draftobjects/polygon.py draftobjects/rectangle.py draftobjects/text.py + draftobjects/wire.py draftobjects/README.md ) @@ -97,6 +104,7 @@ SET(Draft_view_providers draftviewproviders/view_dimension.py draftviewproviders/view_rectangle.py draftviewproviders/view_text.py + draftviewproviders/view_wire.py draftviewproviders/README.md ) diff --git a/src/Mod/Draft/Draft.py b/src/Mod/Draft/Draft.py index e3aeab23a572..75c38c877ff7 100644 --- a/src/Mod/Draft/Draft.py +++ b/src/Mod/Draft/Draft.py @@ -206,7 +206,27 @@ from draftmake.make_polygon import make_polygon, makePolygon from draftobjects.polygon import Polygon, _Polygon +# wire and line +from draftmake.make_line import make_line, makeLine +from draftmake.make_wire import make_wire, makeWire +from draftobjects.wire import Wire, _Wire +if FreeCAD.GuiUp: + from draftviewproviders.view_wire import ViewProviderWire + from draftviewproviders.view_wire import _ViewProviderWire +# bspline +from draftmake.make_bspline import make_bspline, makeBSpline +from draftobjects.bspline import BSpline, _BSpline +if FreeCAD.GuiUp: + # for compatibility with older versions + _ViewProviderBSpline = ViewProviderWire + +# bezcurve +from draftmake.make_bezcurve import make_bezcurve, makeBezCurve +from draftobjects.bezcurve import BezCurve, _BezCurve +if FreeCAD.GuiUp: + # for compatibility with older versions + _ViewProviderBezCurve = ViewProviderWire #--------------------------------------------------------------------------- # Draft annotation objects @@ -322,105 +342,6 @@ def makeWire(pointslist,closed=False,placement=None,face=None,support=None,bs2wi return obj -def makeLine(p1,p2=None): - """makeLine(p1,p2): Creates a line between p1 and p2. - makeLine(LineSegment): Creates a line from a Part.LineSegment - makeLine(Shape): Creates a line from first vertex to last vertex of the given shape""" - if not p2: - if hasattr(p1,"StartPoint") and hasattr(p1,"EndPoint"): - p2 = p1.EndPoint - p1 = p1.StartPoint - elif hasattr(p1,"Vertexes"): - p2 = p1.Vertexes[-1].Point - p1 = p1.Vertexes[0].Point - else: - FreeCAD.Console.PrintError("Unable to create a line from the given data\n") - return - obj = makeWire([p1,p2]) - return obj - -def makeBSpline(pointslist,closed=False,placement=None,face=None,support=None): - """makeBSpline(pointslist,[closed],[placement]): Creates a B-Spline object - from the given list of vectors. If closed is True or first - and last points are identical, the wire is closed. If face is - true (and wire is closed), the wire will appear filled. Instead of - a pointslist, you can also pass a Part Wire.""" - if not FreeCAD.ActiveDocument: - FreeCAD.Console.PrintError("No active document. Aborting\n") - return - if not isinstance(pointslist,list): - nlist = [] - for v in pointslist.Vertexes: - nlist.append(v.Point) - pointslist = nlist - if len(pointslist) < 2: - FreeCAD.Console.PrintError(translate("draft","Draft.makeBSpline: not enough points")+"\n") - return - if (pointslist[0] == pointslist[-1]): - if len(pointslist) > 2: - closed = True - pointslist.pop() - FreeCAD.Console.PrintWarning(translate("draft","Draft.makeBSpline: Equal endpoints forced Closed")+"\n") - else: # len == 2 and first == last GIGO - FreeCAD.Console.PrintError(translate("draft","Draft.makeBSpline: Invalid pointslist")+"\n") - return - # should have sensible parms from here on - if placement: typecheck([(placement,FreeCAD.Placement)], "makeBSpline") - if len(pointslist) == 2: fname = "Line" - else: fname = "BSpline" - obj = FreeCAD.ActiveDocument.addObject("Part::Part2DObjectPython",fname) - _BSpline(obj) - obj.Closed = closed - obj.Points = pointslist - obj.Support = support - if face != None: - obj.MakeFace = face - if placement: obj.Placement = placement - if gui: - _ViewProviderWire(obj.ViewObject) - formatObject(obj) - select(obj) - - return obj - -def makeBezCurve(pointslist,closed=False,placement=None,face=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 FreeCAD.ActiveDocument: - FreeCAD.Console.PrintError("No active document. Aborting\n") - return - if not isinstance(pointslist,list): - nlist = [] - for v in pointslist.Vertexes: - nlist.append(v.Point) - pointslist = nlist - if placement: typecheck([(placement,FreeCAD.Placement)], "makeBezCurve") - if len(pointslist) == 2: fname = "Line" - else: fname = "BezCurve" - obj = FreeCAD.ActiveDocument.addObject("Part::Part2DObjectPython",fname) - _BezCurve(obj) - obj.Points = pointslist - 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 face != None: - obj.MakeFace = face - obj.Proxy.resetcontinuity(obj) - if placement: obj.Placement = placement - if gui: - _ViewProviderWire(obj.ViewObject) -# if not face: obj.ViewObject.DisplayMode = "Wireframe" -# obj.ViewObject.DisplayMode = "Wireframe" - formatObject(obj) - select(obj) - - return obj - def makeCopy(obj,force=None,reparent=False): """makeCopy(object): returns an exact copy of an object""" if not FreeCAD.ActiveDocument: @@ -3102,270 +3023,6 @@ def execute(self, obj): obj.Placement = plm obj.positionBySupport() -class _Wire(_DraftObject): - """The Wire object""" - - def __init__(self, obj): - _DraftObject.__init__(self,obj,"Wire") - obj.addProperty("App::PropertyVectorList","Points","Draft",QT_TRANSLATE_NOOP("App::Property","The vertices of the wire")) - obj.addProperty("App::PropertyBool","Closed","Draft",QT_TRANSLATE_NOOP("App::Property","If the wire is closed or not")) - obj.addProperty("App::PropertyLink","Base","Draft",QT_TRANSLATE_NOOP("App::Property","The base object is the wire, it's formed from 2 objects")) - obj.addProperty("App::PropertyLink","Tool","Draft",QT_TRANSLATE_NOOP("App::Property","The tool object is the wire, it's formed from 2 objects")) - obj.addProperty("App::PropertyVectorDistance","Start","Draft",QT_TRANSLATE_NOOP("App::Property","The start point of this line")) - obj.addProperty("App::PropertyVectorDistance","End","Draft",QT_TRANSLATE_NOOP("App::Property","The end point of this line")) - obj.addProperty("App::PropertyLength","Length","Draft",QT_TRANSLATE_NOOP("App::Property","The length of this line")) - obj.addProperty("App::PropertyLength","FilletRadius","Draft",QT_TRANSLATE_NOOP("App::Property","Radius to use to fillet the corners")) - obj.addProperty("App::PropertyLength","ChamferSize","Draft",QT_TRANSLATE_NOOP("App::Property","Size of the chamfer to give to the corners")) - obj.addProperty("App::PropertyBool","MakeFace","Draft",QT_TRANSLATE_NOOP("App::Property","Create a face if this object is closed")) - obj.addProperty("App::PropertyInteger","Subdivisions","Draft",QT_TRANSLATE_NOOP("App::Property","The number of subdivisions of each edge")) - obj.addProperty("App::PropertyArea","Area","Draft",QT_TRANSLATE_NOOP("App::Property","The area of this object")) - obj.MakeFace = getParam("fillmode",True) - obj.Closed = False - - def execute(self, obj): - import Part, DraftGeomUtils - plm = obj.Placement - if obj.Base and (not obj.Tool): - if obj.Base.isDerivedFrom("Sketcher::SketchObject"): - shape = obj.Base.Shape.copy() - if obj.Base.Shape.isClosed(): - if hasattr(obj,"MakeFace"): - if obj.MakeFace: - shape = Part.Face(shape) - else: - shape = Part.Face(shape) - obj.Shape = shape - elif obj.Base and obj.Tool: - if hasattr(obj.Base,'Shape') and hasattr(obj.Tool,'Shape'): - if (not obj.Base.Shape.isNull()) and (not obj.Tool.Shape.isNull()): - sh1 = obj.Base.Shape.copy() - sh2 = obj.Tool.Shape.copy() - shape = sh1.fuse(sh2) - if DraftGeomUtils.isCoplanar(shape.Faces): - shape = DraftGeomUtils.concatenate(shape) - obj.Shape = shape - p = [] - for v in shape.Vertexes: p.append(v.Point) - if obj.Points != p: obj.Points = p - elif obj.Points: - if obj.Points[0] == obj.Points[-1]: - if not obj.Closed: obj.Closed = True - obj.Points.pop() - if obj.Closed and (len(obj.Points) > 2): - pts = obj.Points - if hasattr(obj,"Subdivisions"): - if obj.Subdivisions > 0: - npts = [] - for i in range(len(pts)): - p1 = pts[i] - npts.append(pts[i]) - if i == len(pts)-1: - p2 = pts[0] - else: - p2 = pts[i+1] - v = p2.sub(p1) - v = DraftVecUtils.scaleTo(v,v.Length/(obj.Subdivisions+1)) - for j in range(obj.Subdivisions): - npts.append(p1.add(FreeCAD.Vector(v).multiply(j+1))) - pts = npts - shape = Part.makePolygon(pts+[pts[0]]) - if "ChamferSize" in obj.PropertiesList: - if obj.ChamferSize.Value != 0: - w = DraftGeomUtils.filletWire(shape,obj.ChamferSize.Value,chamfer=True) - if w: - shape = w - if "FilletRadius" in obj.PropertiesList: - if obj.FilletRadius.Value != 0: - w = DraftGeomUtils.filletWire(shape,obj.FilletRadius.Value) - if w: - shape = w - try: - if hasattr(obj,"MakeFace"): - if obj.MakeFace: - shape = Part.Face(shape) - else: - shape = Part.Face(shape) - except Part.OCCError: - pass - else: - edges = [] - pts = obj.Points[1:] - lp = obj.Points[0] - for p in pts: - if not DraftVecUtils.equals(lp,p): - if hasattr(obj,"Subdivisions"): - if obj.Subdivisions > 0: - npts = [] - v = p.sub(lp) - v = DraftVecUtils.scaleTo(v,v.Length/(obj.Subdivisions+1)) - edges.append(Part.LineSegment(lp,lp.add(v)).toShape()) - lv = lp.add(v) - for j in range(obj.Subdivisions): - edges.append(Part.LineSegment(lv,lv.add(v)).toShape()) - lv = lv.add(v) - else: - edges.append(Part.LineSegment(lp,p).toShape()) - else: - edges.append(Part.LineSegment(lp,p).toShape()) - lp = p - try: - shape = Part.Wire(edges) - except Part.OCCError: - print("Error wiring edges") - shape = None - if "ChamferSize" in obj.PropertiesList: - if obj.ChamferSize.Value != 0: - w = DraftGeomUtils.filletWire(shape,obj.ChamferSize.Value,chamfer=True) - if w: - shape = w - if "FilletRadius" in obj.PropertiesList: - if obj.FilletRadius.Value != 0: - w = DraftGeomUtils.filletWire(shape,obj.FilletRadius.Value) - if w: - shape = w - if shape: - obj.Shape = shape - if hasattr(obj,"Area") and hasattr(shape,"Area"): - obj.Area = shape.Area - if hasattr(obj,"Length"): - obj.Length = shape.Length - obj.Placement = plm - obj.positionBySupport() - self.onChanged(obj,"Placement") - - def onChanged(self, obj, prop): - if prop == "Start": - pts = obj.Points - invpl = FreeCAD.Placement(obj.Placement).inverse() - realfpstart = invpl.multVec(obj.Start) - if pts: - if pts[0] != realfpstart: - pts[0] = realfpstart - obj.Points = pts - elif prop == "End": - pts = obj.Points - invpl = FreeCAD.Placement(obj.Placement).inverse() - realfpend = invpl.multVec(obj.End) - if len(pts) > 1: - if pts[-1] != realfpend: - pts[-1] = realfpend - obj.Points = pts - elif prop == "Length": - if obj.Shape and not obj.Shape.isNull(): - if obj.Length.Value != obj.Shape.Length: - if len(obj.Points) == 2: - v = obj.Points[-1].sub(obj.Points[0]) - v = DraftVecUtils.scaleTo(v,obj.Length.Value) - obj.Points = [obj.Points[0],obj.Points[0].add(v)] - - elif prop == "Placement": - pl = FreeCAD.Placement(obj.Placement) - if len(obj.Points) >= 2: - displayfpstart = pl.multVec(obj.Points[0]) - displayfpend = pl.multVec(obj.Points[-1]) - if obj.Start != displayfpstart: - obj.Start = displayfpstart - if obj.End != displayfpend: - obj.End = displayfpend - - -class _ViewProviderWire(_ViewProviderDraft): - """A View Provider for the Wire object""" - def __init__(self, obj): - _ViewProviderDraft.__init__(self,obj) - obj.addProperty("App::PropertyBool","EndArrow","Draft",QT_TRANSLATE_NOOP("App::Property","Displays a Dimension symbol at the end of the wire")) - obj.addProperty("App::PropertyLength","ArrowSize","Draft",QT_TRANSLATE_NOOP("App::Property","Arrow size")) - obj.addProperty("App::PropertyEnumeration","ArrowType","Draft",QT_TRANSLATE_NOOP("App::Property","Arrow type")) - obj.ArrowSize = getParam("arrowsize",0.1) - obj.ArrowType = arrowtypes - obj.ArrowType = arrowtypes[getParam("dimsymbol",0)] - - def attach(self, obj): - from pivy import coin - self.Object = obj.Object - col = coin.SoBaseColor() - col.rgb.setValue(obj.LineColor[0],obj.LineColor[1],obj.LineColor[2]) - self.coords = coin.SoTransform() - self.pt = coin.SoSeparator() - self.pt.addChild(col) - self.pt.addChild(self.coords) - self.symbol = dimSymbol() - self.pt.addChild(self.symbol) - _ViewProviderDraft.attach(self,obj) - self.onChanged(obj,"EndArrow") - - def updateData(self, obj, prop): - from pivy import coin - if prop == "Points": - if obj.Points: - p = obj.Points[-1] - if hasattr(self,"coords"): - self.coords.translation.setValue((p.x,p.y,p.z)) - if len(obj.Points) >= 2: - v1 = obj.Points[-2].sub(obj.Points[-1]) - if not DraftVecUtils.isNull(v1): - v1.normalize() - _rot = coin.SbRotation() - _rot.setValue(coin.SbVec3f(1, 0, 0), coin.SbVec3f(v1[0], v1[1], v1[2])) - self.coords.rotation.setValue(_rot) - return - - def onChanged(self, vobj, prop): - from pivy import coin - if prop in ["EndArrow","ArrowSize","ArrowType","Visibility"]: - rn = vobj.RootNode - if hasattr(self,"pt") and hasattr(vobj,"EndArrow"): - if vobj.EndArrow and vobj.Visibility: - self.pt.removeChild(self.symbol) - s = arrowtypes.index(vobj.ArrowType) - self.symbol = dimSymbol(s) - self.pt.addChild(self.symbol) - self.updateData(vobj.Object,"Points") - if hasattr(vobj,"ArrowSize"): - s = vobj.ArrowSize - else: - s = getParam("arrowsize",0.1) - self.coords.scaleFactor.setValue((s,s,s)) - rn.addChild(self.pt) - else: - if self.symbol: - if self.pt.findChild(self.symbol) != -1: - self.pt.removeChild(self.symbol) - if rn.findChild(self.pt) != -1: - rn.removeChild(self.pt) - if prop in ["LineColor"]: - if hasattr(self, "pt"): - self.pt[0].rgb.setValue(vobj.LineColor[0],vobj.LineColor[1],vobj.LineColor[2]) - _ViewProviderDraft.onChanged(self,vobj,prop) - return - - def claimChildren(self): - if hasattr(self.Object,"Base"): - return [self.Object.Base,self.Object.Tool] - return [] - - def setupContextMenu(self,vobj,menu): - from PySide import QtCore,QtGui - action1 = QtGui.QAction(QtGui.QIcon(":/icons/Draft_Edit.svg"),"Flatten this wire",menu) - QtCore.QObject.connect(action1,QtCore.SIGNAL("triggered()"),self.flatten) - menu.addAction(action1) - - def flatten(self): - if hasattr(self,"Object"): - if len(self.Object.Shape.Wires) == 1: - import DraftGeomUtils - fw = DraftGeomUtils.flattenWire(self.Object.Shape.Wires[0]) - points = [v.Point for v in fw.Vertexes] - if len(points) == len(self.Object.Points): - if points != self.Object.Points: - FreeCAD.ActiveDocument.openTransaction("Flatten wire") - FreeCADGui.doCommand("FreeCAD.ActiveDocument."+self.Object.Name+".Points="+str(points).replace("Vector","FreeCAD.Vector").replace(" ","")) - FreeCAD.ActiveDocument.commitTransaction() - - else: - from DraftTools import translate - FreeCAD.Console.PrintMessage(translate("Draft","This Wire is already flat")+"\n") - class _DrawingView(_DraftObject): """The Draft DrawingView object""" @@ -3429,215 +3086,6 @@ def getDXF(self,obj): "returns a DXF fragment" return getDXF(obj) -class _BSpline(_DraftObject): - """The BSpline object""" - - def __init__(self, obj): - _DraftObject.__init__(self,obj,"BSpline") - obj.addProperty("App::PropertyVectorList","Points","Draft", QT_TRANSLATE_NOOP("App::Property","The points of the B-spline")) - obj.addProperty("App::PropertyBool","Closed","Draft",QT_TRANSLATE_NOOP("App::Property","If the B-spline is closed or not")) - obj.addProperty("App::PropertyBool","MakeFace","Draft",QT_TRANSLATE_NOOP("App::Property","Create a face if this spline is closed")) - obj.addProperty("App::PropertyArea","Area","Draft",QT_TRANSLATE_NOOP("App::Property","The area of this object")) - obj.MakeFace = getParam("fillmode",True) - obj.Closed = False - obj.Points = [] - self.assureProperties(obj) - - def assureProperties(self, obj): # for Compatibility with older versions - if not hasattr(obj, "Parameterization"): - obj.addProperty("App::PropertyFloat","Parameterization","Draft",QT_TRANSLATE_NOOP("App::Property","Parameterization factor")) - obj.Parameterization = 1.0 - self.knotSeq = [] - - def parameterization (self, pts, a, closed): - # Computes a knot Sequence for a set of points - # fac (0-1) : parameterization factor - # fac=0 -> Uniform / fac=0.5 -> Centripetal / fac=1.0 -> Chord-Length - if closed: # we need to add the first point as the end point - pts.append(pts[0]) - params = [0] - for i in range(1,len(pts)): - p = pts[i].sub(pts[i-1]) - pl = pow(p.Length,a) - params.append(params[-1] + pl) - return params - - def onChanged(self, fp, prop): - if prop == "Parameterization": - if fp.Parameterization < 0.: - fp.Parameterization = 0. - if fp.Parameterization > 1.0: - fp.Parameterization = 1.0 - - def execute(self, obj): - import Part - self.assureProperties(obj) - if obj.Points: - self.knotSeq = self.parameterization(obj.Points, obj.Parameterization, obj.Closed) - plm = obj.Placement - if obj.Closed and (len(obj.Points) > 2): - if obj.Points[0] == obj.Points[-1]: # should not occur, but OCC will crash - FreeCAD.Console.PrintError(translate('draft', "_BSpline.createGeometry: Closed with same first/last Point. Geometry not updated.")+"\n") - return - spline = Part.BSplineCurve() - spline.interpolate(obj.Points, PeriodicFlag = True, Parameters = self.knotSeq) - # DNC: bug fix: convert to face if closed - shape = Part.Wire(spline.toShape()) - # Creating a face from a closed spline cannot be expected to always work - # Usually, if the spline is not flat the call of Part.Face() fails - try: - if hasattr(obj,"MakeFace"): - if obj.MakeFace: - shape = Part.Face(shape) - else: - shape = Part.Face(shape) - except Part.OCCError: - pass - obj.Shape = shape - if hasattr(obj,"Area") and hasattr(shape,"Area"): - obj.Area = shape.Area - else: - spline = Part.BSplineCurve() - spline.interpolate(obj.Points, PeriodicFlag = False, Parameters = self.knotSeq) - shape = spline.toShape() - obj.Shape = shape - if hasattr(obj,"Area") and hasattr(shape,"Area"): - obj.Area = shape.Area - obj.Placement = plm - obj.positionBySupport() - -# for compatibility with older versions -_ViewProviderBSpline = _ViewProviderWire - -class _BezCurve(_DraftObject): - """The BezCurve object""" - - def __init__(self, obj): - _DraftObject.__init__(self,obj,"BezCurve") - obj.addProperty("App::PropertyVectorList","Points","Draft",QT_TRANSLATE_NOOP("App::Property","The points of the Bezier curve")) - obj.addProperty("App::PropertyInteger","Degree","Draft",QT_TRANSLATE_NOOP("App::Property","The degree of the Bezier function")) - obj.addProperty("App::PropertyIntegerList","Continuity","Draft",QT_TRANSLATE_NOOP("App::Property","Continuity")) - obj.addProperty("App::PropertyBool","Closed","Draft",QT_TRANSLATE_NOOP("App::Property","If the Bezier curve should be closed or not")) - obj.addProperty("App::PropertyBool","MakeFace","Draft",QT_TRANSLATE_NOOP("App::Property","Create a face if this curve is closed")) - obj.addProperty("App::PropertyLength","Length","Draft",QT_TRANSLATE_NOOP("App::Property","The length of this object")) - obj.addProperty("App::PropertyArea","Area","Draft",QT_TRANSLATE_NOOP("App::Property","The area of this object")) - obj.MakeFace = getParam("fillmode",True) - obj.Closed = False - obj.Degree = 3 - obj.Continuity = [] - #obj.setEditorMode("Degree",2)#hide - obj.setEditorMode("Continuity",1)#ro - - def execute(self, fp): - self.createGeometry(fp) - fp.positionBySupport() - - def _segpoleslst(self,fp): - """split the points into segments""" - if not fp.Closed and len(fp.Points) >= 2: #allow lower degree segment - poles=fp.Points[1:] - elif fp.Closed and len(fp.Points) >= fp.Degree: #drawable - #poles=fp.Points[1:(fp.Degree*(len(fp.Points)//fp.Degree))]+fp.Points[0:1] - poles=fp.Points[1:]+fp.Points[0:1] - else: - poles=[] - return [poles[x:x+fp.Degree] for x in \ - range(0, len(poles), (fp.Degree or 1))] - - def resetcontinuity(self,fp): - fp.Continuity = [0]*(len(self._segpoleslst(fp))-1+1*fp.Closed) - #nump= len(fp.Points)-1+fp.Closed*1 - #numsegments = (nump // fp.Degree) + 1 * (nump % fp.Degree > 0) -1 - #fp.Continuity = [0]*numsegments - - def onChanged(self, fp, prop): - if prop == 'Closed': # if remove the last entry when curve gets opened - oldlen = len(fp.Continuity) - newlen = (len(self._segpoleslst(fp))-1+1*fp.Closed) - if oldlen > newlen: - fp.Continuity = fp.Continuity[:newlen] - if oldlen < newlen: - fp.Continuity = fp.Continuity + [0]*(newlen-oldlen) - if hasattr(fp,'Closed') and fp.Closed and prop in ['Points','Degree','Closed'] and\ - len(fp.Points) % fp.Degree: # the curve editing tools can't handle extra points - fp.Points=fp.Points[:(fp.Degree*(len(fp.Points)//fp.Degree))] #for closed curves - if prop in ["Degree"] and fp.Degree >= 1: #reset Continuity - self.resetcontinuity(fp) - if prop in ["Points","Degree","Continuity","Closed"]: - self.createGeometry(fp) - - def createGeometry(self,fp): - import Part - plm = fp.Placement - if fp.Points: - startpoint=fp.Points[0] - edges = [] - for segpoles in self._segpoleslst(fp): -# 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) - if fp.Closed and w.isClosed(): - try: - if hasattr(fp,"MakeFace"): - if fp.MakeFace: - w = Part.Face(w) - else: - w = Part.Face(w) - except Part.OCCError: - pass - fp.Shape = w - if hasattr(fp,"Area") and hasattr(w,"Area"): - fp.Area = w.Area - if hasattr(fp,"Length") and hasattr(w,"Length"): - fp.Length = w.Length - fp.Placement = plm - - @classmethod - def symmetricpoles(cls,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+p1h-p2h , knot+p2h-p1h) - - @classmethod - def tangentpoles(cls,knot, p1, p2,allowsameside=False): - """make two poles have the same tangent at knot""" - p12n=p2.sub(p1) - p12n.normalize() - p1k=knot-p1 - p2k=knot-p2 - p1k_= FreeCAD.Vector(p12n) - kon12=(p1k*p12n) - if allowsameside or not (kon12 < 0 or p2k*p12n > 0):# instead of moving - p1k_.multiply(kon12) - pk_k=knot-p1-p1k_ - return (p1+pk_k,p2+pk_k) - else: - return cls.symmetricpoles(knot, p1, p2) - - @staticmethod - def modifysymmetricpole(knot,p1): - """calculate the coordinates of the opposite pole - of a symmetric knot""" - return knot+knot-p1 - - @staticmethod - def modifytangentpole(knot,p1,oldp2): - """calculate the coordinates of the opposite pole - of a tangent knot""" - pn=knot-p1 - pn.normalize() - pn.multiply((knot-oldp2).Length) - return pn+knot - -# for compatibility with older versions ??????? -_ViewProviderBezCurve = _ViewProviderWire class _Block(_DraftObject): """The Block object""" diff --git a/src/Mod/Draft/draftmake/make_bezcurve.py b/src/Mod/Draft/draftmake/make_bezcurve.py new file mode 100644 index 000000000000..9707227ea5e8 --- /dev/null +++ b/src/Mod/Draft/draftmake/make_bezcurve.py @@ -0,0 +1,107 @@ +# *************************************************************************** +# * Copyright (c) 2009, 2010 Yorik van Havre * +# * Copyright (c) 2009, 2010 Ken Cline * +# * Copyright (c) 2020 FreeCAD Developers * +# * * +# * This program is free software; you can redistribute it and/or modify * +# * it under the terms of the GNU Lesser General Public License (LGPL) * +# * as published by the Free Software Foundation; either version 2 of * +# * the License, or (at your option) any later version. * +# * for detail see the LICENCE text file. * +# * * +# * This program is distributed in the hope that it will be useful, * +# * but WITHOUT ANY WARRANTY; without even the implied warranty of * +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +# * GNU Library General Public License for more details. * +# * * +# * You should have received a copy of the GNU Library General Public * +# * License along with this program; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** +"""This module provides the code for Draft make_bezcurve function. +""" +## @package make_bezcurve +# \ingroup DRAFT +# \brief This module provides the code for Draft make_bezcurve function. + +import FreeCAD as App + +from draftutils.gui_utils import format_object +from draftutils.gui_utils import select + +from draftutils.utils import type_check +from draftutils.translate import translate + +from draftobjects.bezcurve import BezCurve +if App.GuiUp: + from draftviewproviders.view_bezcurve import ViewProviderBezCurve + + +def make_bezcurve(pointslist, closed=False, placement=None, face=None, support=None, degree=None): + """make_bezcurve(pointslist, [closed], [placement]) + + Creates a Bezier Curve object from the given list of vectors. + + Parameters + ---------- + pointlist : [Base.Vector] + List of points to create the polyline. + Instead of a pointslist, you can also pass a Part Wire. + TODO: Change the name so! + + closed : bool + If closed is True or first and last points are identical, + the created BSpline will be closed. + + placement : Base.Placement + If a placement is given, it is used. + + face : Bool + If face is False, the rectangle is shown as a wireframe, + otherwise as a face. + + support : + TODO: Describe + + degree : int + Degree of the BezCurve + """ + if not App.ActiveDocument: + App.Console.PrintError("No active document. Aborting\n") + return + if not isinstance(pointslist,list): + nlist = [] + for v in pointslist.Vertexes: + nlist.append(v.Point) + pointslist = nlist + if placement: type_check([(placement,App.Placement)], "make_bezcurve") + if len(pointslist) == 2: fname = "Line" + else: fname = "BezCurve" + obj = App.ActiveDocument.addObject("Part::Part2DObjectPython",fname) + BezCurve(obj) + obj.Points = pointslist + 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 face != None: + obj.MakeFace = face + obj.Proxy.resetcontinuity(obj) + if placement: obj.Placement = placement + if App.GuiUp: + ViewProviderBezCurve(obj.ViewObject) +# if not face: obj.ViewObject.DisplayMode = "Wireframe" +# obj.ViewObject.DisplayMode = "Wireframe" + format_object(obj) + select(obj) + + return obj + + +makeBezCurve = make_bezcurve \ No newline at end of file diff --git a/src/Mod/Draft/draftmake/make_bspline.py b/src/Mod/Draft/draftmake/make_bspline.py new file mode 100644 index 000000000000..415a17e5cd40 --- /dev/null +++ b/src/Mod/Draft/draftmake/make_bspline.py @@ -0,0 +1,111 @@ +# *************************************************************************** +# * Copyright (c) 2009, 2010 Yorik van Havre * +# * Copyright (c) 2009, 2010 Ken Cline * +# * Copyright (c) 2020 FreeCAD Developers * +# * * +# * This program is free software; you can redistribute it and/or modify * +# * it under the terms of the GNU Lesser General Public License (LGPL) * +# * as published by the Free Software Foundation; either version 2 of * +# * the License, or (at your option) any later version. * +# * for detail see the LICENCE text file. * +# * * +# * This program is distributed in the hope that it will be useful, * +# * but WITHOUT ANY WARRANTY; without even the implied warranty of * +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +# * GNU Library General Public License for more details. * +# * * +# * You should have received a copy of the GNU Library General Public * +# * License along with this program; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** +"""This module provides the code for Draft make_bspline function. +""" +## @package make_bspline +# \ingroup DRAFT +# \brief This module provides the code for Draft make_bspline function. + +import FreeCAD as App + +from draftutils.gui_utils import format_object +from draftutils.gui_utils import select + +from draftutils.utils import type_check +from draftutils.translate import translate + +from draftobjects.bspline import BSpline +if App.GuiUp: + from draftviewproviders.view_bspline import ViewProviderBSpline + + +def make_bspline(pointslist, closed=False, placement=None, face=None, support=None): + """make_bspline(pointslist, [closed], [placement]) + + Creates a B-Spline object from the given list of vectors. + + Parameters + ---------- + pointlist : [Base.Vector] + List of points to create the polyline. + Instead of a pointslist, you can also pass a Part Wire. + TODO: Change the name so! + + closed : bool + If closed is True or first and last points are identical, + the created BSpline will be closed. + + placement : Base.Placement + If a placement is given, it is used. + + face : Bool + If face is False, the rectangle is shown as a wireframe, + otherwise as a face. + + support : + TODO: Describe + """ + if not App.ActiveDocument: + App.Console.PrintError("No active document. Aborting\n") + return + if not isinstance(pointslist,list): + nlist = [] + for v in pointslist.Vertexes: + nlist.append(v.Point) + pointslist = nlist + if len(pointslist) < 2: + _err = "Draft.makeBSpline: not enough points" + App.Console.PrintError(translate("draft", _err)+"\n") + return + if (pointslist[0] == pointslist[-1]): + if len(pointslist) > 2: + closed = True + pointslist.pop() + _err = "Draft.makeBSpline: Equal endpoints forced Closed" + App.Console.PrintWarning(translate("Draft", _err) + _err + "\n") + else: + # len == 2 and first == last GIGO + _err = "Draft.makeBSpline: Invalid pointslist" + App.Console.PrintError(translate("Draft", _err)+"\n") + return + # should have sensible parms from here on + if placement: type_check([(placement,App.Placement)], "make_bspline") + if len(pointslist) == 2: fname = "Line" + else: fname = "BSpline" + obj = App.ActiveDocument.addObject("Part::Part2DObjectPython",fname) + BSpline(obj) + obj.Closed = closed + obj.Points = pointslist + obj.Support = support + if face != None: + obj.MakeFace = face + if placement: obj.Placement = placement + if App.GuiUp: + ViewProviderBSpline(obj.ViewObject) + format_object(obj) + select(obj) + + return obj + + +makeBSpline = make_bspline \ No newline at end of file diff --git a/src/Mod/Draft/draftmake/make_line.py b/src/Mod/Draft/draftmake/make_line.py new file mode 100644 index 000000000000..e7f84fee27f1 --- /dev/null +++ b/src/Mod/Draft/draftmake/make_line.py @@ -0,0 +1,73 @@ +# *************************************************************************** +# * Copyright (c) 2009, 2010 Yorik van Havre * +# * Copyright (c) 2009, 2010 Ken Cline * +# * Copyright (c) 2020 FreeCAD Developers * +# * * +# * This program is free software; you can redistribute it and/or modify * +# * it under the terms of the GNU Lesser General Public License (LGPL) * +# * as published by the Free Software Foundation; either version 2 of * +# * the License, or (at your option) any later version. * +# * for detail see the LICENCE text file. * +# * * +# * This program is distributed in the hope that it will be useful, * +# * but WITHOUT ANY WARRANTY; without even the implied warranty of * +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +# * GNU Library General Public License for more details. * +# * * +# * You should have received a copy of the GNU Library General Public * +# * License along with this program; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** +"""This module provides the code for Draft make_line function. +""" +## @package make_line +# \ingroup DRAFT +# \brief This module provides the code for Draft make_line function. + +import FreeCAD as App + +from draftutils.gui_utils import format_object +from draftutils.gui_utils import select + +from draftmake.make_wire import make_wire + + +def make_line(first_param, last_param=None): + """makeLine(first_param, p2) + + Creates a line from 2 points or from a given object. + + Parameters + ---------- + first_param : + Base.Vector -> First point of the line (if p2 == None) + Part.LineSegment -> Line is created from the given Linesegment + Shape -> Line is created from the give Shape + + last_param : Base.Vector + Second point of the line, if not set the function evaluates + the first_param to look for a Part.LineSegment or a Shape + """ + if last_param: + p1 = first_param + p2 = last_param + else: + if hasattr(first_param, "StartPoint") and hasattr(first_param, "EndPoint"): + p2 = first_param.EndPoint + p1 = first_param.StartPoint + elif hasattr(p1,"Vertexes"): + p2 = first_param.Vertexes[-1].Point + p1 = first_param.Vertexes[0].Point + else: + _err = "Unable to create a line from the given parameters" + App.Console.PrintError(_err + "\n") + return + + obj = make_wire([p1,p2]) + + return obj + + +makeLine = make_line \ No newline at end of file diff --git a/src/Mod/Draft/draftmake/make_wire.py b/src/Mod/Draft/draftmake/make_wire.py new file mode 100644 index 000000000000..f84ad5e095ac --- /dev/null +++ b/src/Mod/Draft/draftmake/make_wire.py @@ -0,0 +1,123 @@ +# *************************************************************************** +# * Copyright (c) 2009, 2010 Yorik van Havre * +# * Copyright (c) 2009, 2010 Ken Cline * +# * * +# * This program is free software; you can redistribute it and/or modify * +# * it under the terms of the GNU Lesser General Public License (LGPL) * +# * as published by the Free Software Foundation; either version 2 of * +# * the License, or (at your option) any later version. * +# * for detail see the LICENCE text file. * +# * * +# * This program is distributed in the hope that it will be useful, * +# * but WITHOUT ANY WARRANTY; without even the implied warranty of * +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +# * GNU Library General Public License for more details. * +# * * +# * You should have received a copy of the GNU Library General Public * +# * License along with this program; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** +"""This module provides the code for Draft make_wire function. +""" +## @package make_wire +# \ingroup DRAFT +# \brief This module provides the code for Draft make_wire function. + +import FreeCAD as App + +import DraftGeomUtils + +from draftutils.gui_utils import format_object +from draftutils.gui_utils import select + +from draftutils.utils import type_check + +from draftobjects.wire import Wire +if App.GuiUp: + from draftviewproviders.view_wire import ViewProviderWire + + +def make_wire(pointslist, closed=False, placement=None, face=None, support=None, bs2wire=False): + """makeWire(pointslist,[closed],[placement]) + + Creates a Wire object from the given list of vectors. If face is + true (and wire is closed), the wire will appear filled. Instead of + a pointslist, you can also pass a Part Wire. + + Parameters + ---------- + pointlist : [Base.Vector] + List of points to create the polyline + + closed : bool + If closed is True or first and last points are identical, + the created polyline will be closed. + + placement : Base.Placement + If a placement is given, it is used. + + face : Bool + If face is False, the rectangle is shown as a wireframe, + otherwise as a face. + + support : + TODO: Describe + + bs2wire : bool + TODO: Describe + """ + if not App.ActiveDocument: + App.Console.PrintError("No active document. Aborting\n") + return + + import Part + + if not isinstance(pointslist, list): + e = pointslist.Wires[0].Edges + pointslist = Part.Wire(Part.__sortEdges__(e)) + nlist = [] + for v in pointslist.Vertexes: + nlist.append(v.Point) + if DraftGeomUtils.isReallyClosed(pointslist): + closed = True + pointslist = nlist + + if len(pointslist) == 0: + print("Invalid input points: ", pointslist) + #print(pointslist) + #print(closed) + + if placement: + type_check([(placement, App.Placement)], "make_wire") + ipl = placement.inverse() + if not bs2wire: + pointslist = [ipl.multVec(p) for p in pointslist] + + if len(pointslist) == 2: + fname = "Line" + else: + fname = "Wire" + + obj = App.ActiveDocument.addObject("Part::Part2DObjectPython", fname) + Wire(obj) + obj.Points = pointslist + obj.Closed = closed + obj.Support = support + + if face != None: + obj.MakeFace = face + + if placement: + obj.Placement = placement + + if App.GuiUp: + ViewProviderWire(obj.ViewObject) + format_object(obj) + select(obj) + + return obj + + +makeWire = make_wire \ No newline at end of file diff --git a/src/Mod/Draft/draftobjects/bezcurve.py b/src/Mod/Draft/draftobjects/bezcurve.py new file mode 100644 index 000000000000..b091aa6f07e7 --- /dev/null +++ b/src/Mod/Draft/draftobjects/bezcurve.py @@ -0,0 +1,196 @@ +# *************************************************************************** +# * Copyright (c) 2009, 2010 Yorik van Havre * +# * Copyright (c) 2009, 2010 Ken Cline * +# * Copyright (c) 2020 FreeCAD Developers * +# * * +# * This program is free software; you can redistribute it and/or modify * +# * it under the terms of the GNU Lesser General Public License (LGPL) * +# * as published by the Free Software Foundation; either version 2 of * +# * the License, or (at your option) any later version. * +# * for detail see the LICENCE text file. * +# * * +# * This program is distributed in the hope that it will be useful, * +# * but WITHOUT ANY WARRANTY; without even the implied warranty of * +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +# * GNU Library General Public License for more details. * +# * * +# * You should have received a copy of the GNU Library General Public * +# * License along with this program; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** +"""This module provides the object code for Draft BezCurve. +""" +## @package bezcurve +# \ingroup DRAFT +# \brief This module provides the object code for Draft BezCurve. + +from PySide.QtCore import QT_TRANSLATE_NOOP + +import FreeCAD as App + +from draftutils.utils import get_param + +from draftobjects.base import DraftObject + + +class BezCurve(DraftObject): + """The BezCurve object""" + + def __init__(self, obj): + super(BezCurve, self).__init__(obj, "BezCurve") + + _tip = "The points of the Bezier curve" + obj.addProperty("App::PropertyVectorList", "Points", + "Draft",QT_TRANSLATE_NOOP("App::Property", _tip)) + + _tip = "The degree of the Bezier function" + obj.addProperty("App::PropertyInteger", "Degree", + "Draft",QT_TRANSLATE_NOOP("App::Property", _tip)) + + _tip = "Continuity" + obj.addProperty("App::PropertyIntegerList", "Continuity", + "Draft",QT_TRANSLATE_NOOP("App::Property", _tip)) + + _tip = "If the Bezier curve should be closed or not" + obj.addProperty("App::PropertyBool", "Closed", + "Draft",QT_TRANSLATE_NOOP("App::Property", _tip)) + + _tip = "Create a face if this curve is closed" + obj.addProperty("App::PropertyBool", "MakeFace", + "Draft",QT_TRANSLATE_NOOP("App::Property", _tip)) + + _tip = "The length of this object" + obj.addProperty("App::PropertyLength", "Length", + "Draft",QT_TRANSLATE_NOOP("App::Property", _tip)) + + _tip = "The area of this object" + obj.addProperty("App::PropertyArea", "Area", + "Draft",QT_TRANSLATE_NOOP("App::Property", _tip)) + + obj.MakeFace = get_param("fillmode", True) + obj.Closed = False + obj.Degree = 3 + obj.Continuity = [] + #obj.setEditorMode("Degree",2) + obj.setEditorMode("Continuity", 1) + + def execute(self, fp): + self.createGeometry(fp) + fp.positionBySupport() + + def _segpoleslst(self,fp): + """Split the points into segments.""" + if not fp.Closed and len(fp.Points) >= 2: #allow lower degree segment + poles=fp.Points[1:] + elif fp.Closed and len(fp.Points) >= fp.Degree: #drawable + #poles=fp.Points[1:(fp.Degree*(len(fp.Points)//fp.Degree))]+fp.Points[0:1] + poles=fp.Points[1:]+fp.Points[0:1] + else: + poles=[] + return [poles[x:x+fp.Degree] for x in \ + range(0, len(poles), (fp.Degree or 1))] + + def resetcontinuity(self,fp): + fp.Continuity = [0]*(len(self._segpoleslst(fp))-1+1*fp.Closed) + #nump= len(fp.Points)-1+fp.Closed*1 + #numsegments = (nump // fp.Degree) + 1 * (nump % fp.Degree > 0) -1 + #fp.Continuity = [0]*numsegments + + def onChanged(self, fp, prop): + if prop == 'Closed': + # if remove the last entry when curve gets opened + oldlen = len(fp.Continuity) + newlen = (len(self._segpoleslst(fp))-1+1*fp.Closed) + if oldlen > newlen: + fp.Continuity = fp.Continuity[:newlen] + if oldlen < newlen: + fp.Continuity = fp.Continuity + [0]*(newlen-oldlen) + + if (hasattr(fp,'Closed') and + fp.Closed and + prop in ['Points','Degree','Closed'] and + len(fp.Points) % fp.Degree): + # the curve editing tools can't handle extra points + fp.Points=fp.Points[:(fp.Degree*(len(fp.Points)//fp.Degree))] + #for closed curves + + if prop in ["Degree"] and fp.Degree >= 1: + self.resetcontinuity(fp) + + if prop in ["Points","Degree","Continuity","Closed"]: + self.createGeometry(fp) + + def createGeometry(self,fp): + import Part + plm = fp.Placement + if fp.Points: + startpoint=fp.Points[0] + edges = [] + for segpoles in self._segpoleslst(fp): +# 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) + if fp.Closed and w.isClosed(): + try: + if hasattr(fp,"MakeFace"): + if fp.MakeFace: + w = Part.Face(w) + else: + w = Part.Face(w) + except Part.OCCError: + pass + fp.Shape = w + if hasattr(fp,"Area") and hasattr(w,"Area"): + fp.Area = w.Area + if hasattr(fp,"Length") and hasattr(w,"Length"): + fp.Length = w.Length + fp.Placement = plm + + @classmethod + def symmetricpoles(cls,knot, p1, p2): + """Make two poles symmetric respective to the knot.""" + p1h = App.Vector(p1) + p2h = App.Vector(p2) + p1h.multiply(0.5) + p2h.multiply(0.5) + return ( knot+p1h-p2h , knot+p2h-p1h ) + + @classmethod + def tangentpoles(cls,knot, p1, p2,allowsameside=False): + """Make two poles have the same tangent at knot.""" + p12n = p2.sub(p1) + p12n.normalize() + p1k = knot-p1 + p2k = knot-p2 + p1k_= App.Vector(p12n) + kon12=(p1k * p12n) + if allowsameside or not (kon12 < 0 or p2k * p12n > 0):# instead of moving + p1k_.multiply(kon12) + pk_k = knot - p1 - p1k_ + return (p1 + pk_k, p2 + pk_k) + else: + return cls.symmetricpoles(knot, p1, p2) + + @staticmethod + def modifysymmetricpole(knot,p1): + """calculate the coordinates of the opposite pole + of a symmetric knot""" + return knot + knot - p1 + + @staticmethod + def modifytangentpole(knot,p1,oldp2): + """calculate the coordinates of the opposite pole + of a tangent knot""" + pn = knot - p1 + pn.normalize() + pn.multiply((knot - oldp2).Length) + return pn + knot + + +_BezCurve = BezCurve \ No newline at end of file diff --git a/src/Mod/Draft/draftobjects/bspline.py b/src/Mod/Draft/draftobjects/bspline.py new file mode 100644 index 000000000000..b2874dce2f24 --- /dev/null +++ b/src/Mod/Draft/draftobjects/bspline.py @@ -0,0 +1,136 @@ +# *************************************************************************** +# * Copyright (c) 2009, 2010 Yorik van Havre * +# * Copyright (c) 2009, 2010 Ken Cline * +# * Copyright (c) 2020 FreeCAD Developers * +# * * +# * This program is free software; you can redistribute it and/or modify * +# * it under the terms of the GNU Lesser General Public License (LGPL) * +# * as published by the Free Software Foundation; either version 2 of * +# * the License, or (at your option) any later version. * +# * for detail see the LICENCE text file. * +# * * +# * This program is distributed in the hope that it will be useful, * +# * but WITHOUT ANY WARRANTY; without even the implied warranty of * +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +# * GNU Library General Public License for more details. * +# * * +# * You should have received a copy of the GNU Library General Public * +# * License along with this program; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** +"""This module provides the object code for Draft BSpline. +""" +## @package bspline +# \ingroup DRAFT +# \brief This module provides the object code for Draft BSpline. + +from PySide.QtCore import QT_TRANSLATE_NOOP + +import FreeCAD as App + +from draftutils.utils import get_param + +from draftobjects.base import DraftObject + + +class BSpline(DraftObject): + """The BSpline object""" + + def __init__(self, obj): + super(BSpline, self).__init__(obj, "BSpline") + + _tip = "The points of the B-spline" + obj.addProperty("App::PropertyVectorList","Points", + "Draft", QT_TRANSLATE_NOOP("App::Property", _tip)) + + _tip = "If the B-spline is closed or not" + obj.addProperty("App::PropertyBool","Closed", + "Draft",QT_TRANSLATE_NOOP("App::Property", _tip)) + + _tip = "Create a face if this spline is closed" + obj.addProperty("App::PropertyBool","MakeFace", + "Draft",QT_TRANSLATE_NOOP("App::Property", _tip)) + + _tip = "The area of this object" + obj.addProperty("App::PropertyArea","Area", + "Draft",QT_TRANSLATE_NOOP("App::Property", _tip)) + + obj.MakeFace = get_param("fillmode",True) + obj.Closed = False + obj.Points = [] + self.assureProperties(obj) + + def assureProperties(self, obj): # for Compatibility with older versions + if not hasattr(obj, "Parameterization"): + obj.addProperty("App::PropertyFloat","Parameterization","Draft",QT_TRANSLATE_NOOP("App::Property","Parameterization factor")) + obj.Parameterization = 1.0 + self.knotSeq = [] + + def parameterization (self, pts, a, closed): + """Computes a knot Sequence for a set of points. + fac (0-1) : parameterization factor + fac = 0 -> Uniform / fac=0.5 -> Centripetal / fac=1.0 -> Chord-Length + """ + if closed: # we need to add the first point as the end point + pts.append(pts[0]) + params = [0] + for i in range(1,len(pts)): + p = pts[i].sub(pts[i-1]) + pl = pow(p.Length,a) + params.append(params[-1] + pl) + return params + + def onChanged(self, fp, prop): + if prop == "Parameterization": + if fp.Parameterization < 0.: + fp.Parameterization = 0. + if fp.Parameterization > 1.0: + fp.Parameterization = 1.0 + + def execute(self, obj): + import Part + + self.assureProperties(obj) + + if not obj.Points: + obj.positionBySupport() + + self.knotSeq = self.parameterization(obj.Points, obj.Parameterization, obj.Closed) + plm = obj.Placement + if obj.Closed and (len(obj.Points) > 2): + if obj.Points[0] == obj.Points[-1]: # should not occur, but OCC will crash + _err = "_BSpline.createGeometry: \ + Closed with same first/last Point. Geometry not updated." + App.Console.PrintError(QT_TRANSLATE_NOOP('Draft', _err)+"\n") + return + spline = Part.BSplineCurve() + spline.interpolate(obj.Points, PeriodicFlag = True, Parameters = self.knotSeq) + # DNC: bug fix: convert to face if closed + shape = Part.Wire(spline.toShape()) + # Creating a face from a closed spline cannot be expected to always work + # Usually, if the spline is not flat the call of Part.Face() fails + try: + if hasattr(obj,"MakeFace"): + if obj.MakeFace: + shape = Part.Face(shape) + else: + shape = Part.Face(shape) + except Part.OCCError: + pass + obj.Shape = shape + if hasattr(obj,"Area") and hasattr(shape,"Area"): + obj.Area = shape.Area + else: + spline = Part.BSplineCurve() + spline.interpolate(obj.Points, PeriodicFlag = False, Parameters = self.knotSeq) + shape = spline.toShape() + obj.Shape = shape + if hasattr(obj,"Area") and hasattr(shape,"Area"): + obj.Area = shape.Area + obj.Placement = plm + obj.positionBySupport() + + +_BSpline = BSpline \ No newline at end of file diff --git a/src/Mod/Draft/draftobjects/wire.py b/src/Mod/Draft/draftobjects/wire.py new file mode 100644 index 000000000000..466a10348c28 --- /dev/null +++ b/src/Mod/Draft/draftobjects/wire.py @@ -0,0 +1,251 @@ +# *************************************************************************** +# * Copyright (c) 2009, 2010 Yorik van Havre * +# * Copyright (c) 2009, 2010 Ken Cline * +# * Copyright (c) 2020 FreeCAD Developers * +# * * +# * This program is free software; you can redistribute it and/or modify * +# * it under the terms of the GNU Lesser General Public License (LGPL) * +# * as published by the Free Software Foundation; either version 2 of * +# * the License, or (at your option) any later version. * +# * for detail see the LICENCE text file. * +# * * +# * This program is distributed in the hope that it will be useful, * +# * but WITHOUT ANY WARRANTY; without even the implied warranty of * +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +# * GNU Library General Public License for more details. * +# * * +# * You should have received a copy of the GNU Library General Public * +# * License along with this program; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** +"""This module provides the object code for Draft Wire. +""" +## @package wire +# \ingroup DRAFT +# \brief This module provides the object code for Draft Wire. + +import math + +import FreeCAD as App + +import DraftGeomUtils +import DraftVecUtils + +from PySide.QtCore import QT_TRANSLATE_NOOP + +from draftutils.utils import get_param + +from draftobjects.base import DraftObject + + + +class Wire(DraftObject): + """The Wire object""" + + def __init__(self, obj): + super(Wire, self).__init__(obj, "Wire") + + _tip = "The vertices of the wire" + obj.addProperty("App::PropertyVectorList","Points", + "Draft",QT_TRANSLATE_NOOP("App::Property", _tip)) + + _tip = "If the wire is closed or not" + obj.addProperty("App::PropertyBool","Closed", + "Draft",QT_TRANSLATE_NOOP("App::Property", _tip)) + + _tip = "The base object is the wire, it's formed from 2 objects" + obj.addProperty("App::PropertyLink","Base", + "Draft",QT_TRANSLATE_NOOP("App::Property", _tip)) + + _tip = "The tool object is the wire, it's formed from 2 objects" + obj.addProperty("App::PropertyLink","Tool", + "Draft",QT_TRANSLATE_NOOP("App::Property", _tip)) + + _tip = "The start point of this line" + obj.addProperty("App::PropertyVectorDistance","Start", + "Draft",QT_TRANSLATE_NOOP("App::Property", _tip)) + + _tip = "The end point of this line" + obj.addProperty("App::PropertyVectorDistance","End", + "Draft",QT_TRANSLATE_NOOP("App::Property", _tip)) + + _tip = "The length of this line" + obj.addProperty("App::PropertyLength","Length", + "Draft",QT_TRANSLATE_NOOP("App::Property", _tip)) + + _tip = "Radius to use to fillet the corners" + obj.addProperty("App::PropertyLength","FilletRadius", + "Draft",QT_TRANSLATE_NOOP("App::Property", _tip)) + + _tip = "Size of the chamfer to give to the corners" + obj.addProperty("App::PropertyLength","ChamferSize", + "Draft",QT_TRANSLATE_NOOP("App::Property", _tip)) + + _tip = "Create a face if this object is closed" + obj.addProperty("App::PropertyBool","MakeFace", + "Draft",QT_TRANSLATE_NOOP("App::Property", _tip)) + + _tip = "The number of subdivisions of each edge" + obj.addProperty("App::PropertyInteger","Subdivisions", + "Draft",QT_TRANSLATE_NOOP("App::Property", _tip)) + + _tip = "The area of this object" + obj.addProperty("App::PropertyArea","Area", + "Draft",QT_TRANSLATE_NOOP("App::Property", _tip)) + + obj.MakeFace = get_param("fillmode",True) + obj.Closed = False + + def execute(self, obj): + import Part + plm = obj.Placement + if obj.Base and (not obj.Tool): + if obj.Base.isDerivedFrom("Sketcher::SketchObject"): + shape = obj.Base.Shape.copy() + if obj.Base.Shape.isClosed(): + if hasattr(obj,"MakeFace"): + if obj.MakeFace: + shape = Part.Face(shape) + else: + shape = Part.Face(shape) + obj.Shape = shape + elif obj.Base and obj.Tool: + if hasattr(obj.Base,'Shape') and hasattr(obj.Tool,'Shape'): + if (not obj.Base.Shape.isNull()) and (not obj.Tool.Shape.isNull()): + sh1 = obj.Base.Shape.copy() + sh2 = obj.Tool.Shape.copy() + shape = sh1.fuse(sh2) + if DraftGeomUtils.isCoplanar(shape.Faces): + shape = DraftGeomUtils.concatenate(shape) + obj.Shape = shape + p = [] + for v in shape.Vertexes: p.append(v.Point) + if obj.Points != p: obj.Points = p + elif obj.Points: + if obj.Points[0] == obj.Points[-1]: + if not obj.Closed: obj.Closed = True + obj.Points.pop() + if obj.Closed and (len(obj.Points) > 2): + pts = obj.Points + if hasattr(obj,"Subdivisions"): + if obj.Subdivisions > 0: + npts = [] + for i in range(len(pts)): + p1 = pts[i] + npts.append(pts[i]) + if i == len(pts)-1: + p2 = pts[0] + else: + p2 = pts[i+1] + v = p2.sub(p1) + v = DraftVecUtils.scaleTo(v,v.Length/(obj.Subdivisions+1)) + for j in range(obj.Subdivisions): + npts.append(p1.add(App.Vector(v).multiply(j+1))) + pts = npts + shape = Part.makePolygon(pts+[pts[0]]) + if "ChamferSize" in obj.PropertiesList: + if obj.ChamferSize.Value != 0: + w = DraftGeomUtils.filletWire(shape,obj.ChamferSize.Value,chamfer=True) + if w: + shape = w + if "FilletRadius" in obj.PropertiesList: + if obj.FilletRadius.Value != 0: + w = DraftGeomUtils.filletWire(shape,obj.FilletRadius.Value) + if w: + shape = w + try: + if hasattr(obj,"MakeFace"): + if obj.MakeFace: + shape = Part.Face(shape) + else: + shape = Part.Face(shape) + except Part.OCCError: + pass + else: + edges = [] + pts = obj.Points[1:] + lp = obj.Points[0] + for p in pts: + if not DraftVecUtils.equals(lp,p): + if hasattr(obj,"Subdivisions"): + if obj.Subdivisions > 0: + npts = [] + v = p.sub(lp) + v = DraftVecUtils.scaleTo(v,v.Length/(obj.Subdivisions+1)) + edges.append(Part.LineSegment(lp,lp.add(v)).toShape()) + lv = lp.add(v) + for j in range(obj.Subdivisions): + edges.append(Part.LineSegment(lv,lv.add(v)).toShape()) + lv = lv.add(v) + else: + edges.append(Part.LineSegment(lp,p).toShape()) + else: + edges.append(Part.LineSegment(lp,p).toShape()) + lp = p + try: + shape = Part.Wire(edges) + except Part.OCCError: + print("Error wiring edges") + shape = None + if "ChamferSize" in obj.PropertiesList: + if obj.ChamferSize.Value != 0: + w = DraftGeomUtils.filletWire(shape,obj.ChamferSize.Value,chamfer=True) + if w: + shape = w + if "FilletRadius" in obj.PropertiesList: + if obj.FilletRadius.Value != 0: + w = DraftGeomUtils.filletWire(shape,obj.FilletRadius.Value) + if w: + shape = w + if shape: + obj.Shape = shape + if hasattr(obj,"Area") and hasattr(shape,"Area"): + obj.Area = shape.Area + if hasattr(obj,"Length"): + obj.Length = shape.Length + + obj.Placement = plm + obj.positionBySupport() + self.onChanged(obj,"Placement") + + def onChanged(self, obj, prop): + if prop == "Start": + pts = obj.Points + invpl = App.Placement(obj.Placement).inverse() + realfpstart = invpl.multVec(obj.Start) + if pts: + if pts[0] != realfpstart: + pts[0] = realfpstart + obj.Points = pts + + elif prop == "End": + pts = obj.Points + invpl = App.Placement(obj.Placement).inverse() + realfpend = invpl.multVec(obj.End) + if len(pts) > 1: + if pts[-1] != realfpend: + pts[-1] = realfpend + obj.Points = pts + + elif prop == "Length": + if obj.Shape and not obj.Shape.isNull(): + if obj.Length.Value != obj.Shape.Length: + if len(obj.Points) == 2: + v = obj.Points[-1].sub(obj.Points[0]) + v = DraftVecUtils.scaleTo(v,obj.Length.Value) + obj.Points = [obj.Points[0],obj.Points[0].add(v)] + + elif prop == "Placement": + pl = App.Placement(obj.Placement) + if len(obj.Points) >= 2: + displayfpstart = pl.multVec(obj.Points[0]) + displayfpend = pl.multVec(obj.Points[-1]) + if obj.Start != displayfpstart: + obj.Start = displayfpstart + if obj.End != displayfpend: + obj.End = displayfpend + + +_Wire = Wire \ No newline at end of file diff --git a/src/Mod/Draft/draftviewproviders/view_wire.py b/src/Mod/Draft/draftviewproviders/view_wire.py new file mode 100644 index 000000000000..5fc84342a9fb --- /dev/null +++ b/src/Mod/Draft/draftviewproviders/view_wire.py @@ -0,0 +1,160 @@ +# *************************************************************************** +# * Copyright (c) 2009, 2010 Yorik van Havre * +# * Copyright (c) 2009, 2010 Ken Cline * +# * Copyright (c) 2019 Eliud Cabrera Castillo * +# * * +# * This program is free software; you can redistribute it and/or modify * +# * it under the terms of the GNU Lesser General Public License (LGPL) * +# * as published by the Free Software Foundation; either version 2 of * +# * the License, or (at your option) any later version. * +# * for detail see the LICENCE text file. * +# * * +# * This program is distributed in the hope that it will be useful, * +# * but WITHOUT ANY WARRANTY; without even the implied warranty of * +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +# * GNU Library General Public License for more details. * +# * * +# * You should have received a copy of the GNU Library General Public * +# * License along with this program; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** +"""This module provides the view provider code for Draft wire related objects. +""" +## @package view_base +# \ingroup DRAFT +# \brief This module provides the view provider code for Draft objects like\ +# Line, Polyline, BSpline, BezCurve. + + +from pivy import coin +from PySide import QtCore +from PySide import QtGui + +from PySide.QtCore import QT_TRANSLATE_NOOP + +import FreeCAD as App +import FreeCADGui as Gui + +import draftutils.utils as utils +import draftutils.gui_utils as gui_utils +import DraftVecUtils +import DraftGeomUtils + +from draftviewproviders.view_base import ViewProviderDraft + + +class ViewProviderWire(ViewProviderDraft): + """A base View Provider for the Wire object""" + def __init__(self, vobj): + super(ViewProviderWire, self).__init__(vobj) + + _tip = "Displays a Dimension symbol at the end of the wire" + vobj.addProperty("App::PropertyBool", "EndArrow", + "Draft", QT_TRANSLATE_NOOP("App::Property", _tip)) + + _tip = "Arrow size" + vobj.addProperty("App::PropertyLength", "ArrowSize", + "Draft", QT_TRANSLATE_NOOP("App::Property", _tip)) + + _tip = "Arrow type" + vobj.addProperty("App::PropertyEnumeration", "ArrowType", + "Draft", QT_TRANSLATE_NOOP("App::Property", _tip)) + + vobj.ArrowSize = utils.get_param("arrowsize",0.1) + vobj.ArrowType = utils.ARROW_TYPES + vobj.ArrowType = utils.ARROW_TYPES[utils.get_param("dimsymbol",0)] + + def attach(self, vobj): + self.Object = vobj.Object + col = coin.SoBaseColor() + col.rgb.setValue(vobj.LineColor[0],vobj.LineColor[1],vobj.LineColor[2]) + self.coords = coin.SoTransform() + self.pt = coin.SoSeparator() + self.pt.addChild(col) + self.pt.addChild(self.coords) + self.symbol = gui_utils.dim_symbol() + self.pt.addChild(self.symbol) + super(ViewProviderWire, self).attach(vobj) + self.onChanged(vobj,"EndArrow") + + def updateData(self, obj, prop): + if prop == "Points": + if obj.Points: + p = obj.Points[-1] + if hasattr(self,"coords"): + self.coords.translation.setValue((p.x,p.y,p.z)) + if len(obj.Points) >= 2: + v1 = obj.Points[-2].sub(obj.Points[-1]) + if not DraftVecUtils.isNull(v1): + v1.normalize() + _rot = coin.SbRotation() + _rot.setValue(coin.SbVec3f(1, 0, 0), coin.SbVec3f(v1[0], v1[1], v1[2])) + self.coords.rotation.setValue(_rot) + return + + def onChanged(self, vobj, prop): + if prop in ["EndArrow","ArrowSize","ArrowType","Visibility"]: + rn = vobj.RootNode + if hasattr(self,"pt") and hasattr(vobj,"EndArrow"): + if vobj.EndArrow and vobj.Visibility: + self.pt.removeChild(self.symbol) + s = utils.ARROW_TYPES.index(vobj.ArrowType) + self.symbol = gui_utils.dim_symbol(s) + self.pt.addChild(self.symbol) + self.updateData(vobj.Object,"Points") + if hasattr(vobj,"ArrowSize"): + s = vobj.ArrowSize + else: + s = utils.get_param("arrowsize",0.1) + self.coords.scaleFactor.setValue((s,s,s)) + rn.addChild(self.pt) + else: + if self.symbol: + if self.pt.findChild(self.symbol) != -1: + self.pt.removeChild(self.symbol) + if rn.findChild(self.pt) != -1: + rn.removeChild(self.pt) + + if prop in ["LineColor"]: + if hasattr(self, "pt"): + self.pt[0].rgb.setValue(vobj.LineColor[0],vobj.LineColor[1],vobj.LineColor[2]) + + super(ViewProviderWire, self).onChanged(vobj, prop) + return + + def claimChildren(self): + if hasattr(self.Object,"Base"): + return [self.Object.Base,self.Object.Tool] + return [] + + def setupContextMenu(self,vobj,menu): + action1 = QtGui.QAction(QtGui.QIcon(":/icons/Draft_Edit.svg"), + "Flatten this wire", + menu) + QtCore.QObject.connect(action1, + QtCore.SIGNAL("triggered()"), + self.flatten) + menu.addAction(action1) + + def flatten(self): + if hasattr(self,"Object"): + if len(self.Object.Shape.Wires) == 1: + fw = DraftGeomUtils.flattenWire(self.Object.Shape.Wires[0]) + points = [v.Point for v in fw.Vertexes] + if len(points) == len(self.Object.Points): + if points != self.Object.Points: + App.ActiveDocument.openTransaction("Flatten wire") + Gui.doCommand("FreeCAD.ActiveDocument." + + self.Object.Name + + ".Points=" + + str(points).replace("Vector", "FreeCAD.Vector").replace(" " , "")) + App.ActiveDocument.commitTransaction() + + else: + _msg = "This Wire is already flat" + App.Console.PrintMessage(QT_TRANSLATE_NOOP("Draft", _msg) + "\n") + + +_ViewProviderWire = ViewProviderWire \ No newline at end of file