#*************************************************************************** #* * #* Copyright (c) 2009, 2010 * #* Yorik van Havre , 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 * #* * #*************************************************************************** from __future__ import division __title__="FreeCAD Draft Workbench" __author__ = "Yorik van Havre, Werner Mayer, Martin Burbaum, Ken Cline, Dmitry Chigrin, Daniel Falck" __url__ = "http://free-cad.sourceforge.net" ''' General description: The Draft module is a FreeCAD module for drawing/editing 2D entities. The aim is to give FreeCAD basic 2D-CAD capabilities (similar to Autocad and other similar software). This modules is made to be run inside FreeCAD and needs the PyQt4 and pivy modules available. User manual: http://sourceforge.net/apps/mediawiki/free-cad/index.php?title=2d_Drafting_Module How it works / how to extend: This module is written entirely in python. If you know a bit of python language, you are welcome to modify this module or to help us to improve it. Suggestions are also welcome on the FreeCAD discussion forum. If you want to have a look at the code, here is a general explanation. The Draft module is divided in several files: - Draft.py: Hosts the functions that are useful for scripting outside of the Draft module, it is the "Draft API" - DraftGui.py: Creates and manages the special Draft toolbar - DraftTools.py: Contains the user tools of the Draft module (the commands from the Draft menu), and a couple of helpers such as the "Trackers" (temporary geometry used while drawing) - DraftVecUtils.py: a vector math library, contains functions that are not implemented in the standard FreeCAD vector - DraftGeomUtils.py: a library of misc functions to manipulate shapes. The Draft.py contains everything to create geometry in the scene. You should start there if you intend to modify something. Then, the DraftTools are where the FreeCAD commands are defined, while in DraftGui.py you have the ui part, ie. the draft command bar. Both DraftTools and DraftGui are loaded at module init by InitGui.py, which is called directly by FreeCAD. The tools all have an Activated() function, which is called by FreeCAD when the corresponding FreeCAD command is invoked. Most tools then create the trackers they will need during operation, then place a callback mechanism, which will detect user input and do the necessary cad operations. They also send commands to the command bar, which will display the appropriate controls. While the scene event callback watches mouse events, the keyboard is being watched by the command bar. ''' # import FreeCAD modules import FreeCAD, math, sys, os, DraftVecUtils, Draft_rc from FreeCAD import Vector from pivy import coin if FreeCAD.GuiUp: import FreeCADGui, WorkingPlane gui = True else: print "FreeCAD Gui not present. Draft module will have some features disabled." gui = False #--------------------------------------------------------------------------- # General functions #--------------------------------------------------------------------------- def typecheck (args_and_types, name="?"): "typecheck([arg1,type),(arg2,type),...]): checks arguments types" for v,t in args_and_types: if not isinstance (v,t): w = "typecheck[" + str(name) + "]: " w += str(v) + " is not " + str(t) + "\n" FreeCAD.Console.PrintWarning(w) raise TypeError("Draft." + str(name)) def getParamType(param): if param in ["dimsymbol","dimPrecision","dimorientation","precision","defaultWP", "snapRange","gridEvery","linewidth","UiMode","modconstrain","modsnap", "modalt"]: return "int" elif param in ["constructiongroupname","textfont","patternFile","template","maxSnapEdges", "snapModes"]: return "string" elif param in ["textheight","tolerance","gridSpacing"]: return "float" elif param in ["selectBaseObjects","alwaysSnap","grid","fillmode","saveonexit","maxSnap", "SvgLinesBlack","dxfStdSize","showSnapBar","hideSnapBar","alwaysShowGrid", "renderPolylineWidth","showPlaneTracker"]: return "bool" elif param in ["color","constructioncolor","snapcolor"]: return "unsigned" else: return None def getParam(param): "getParam(parameterName): returns a Draft parameter value from the current config" p = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft") t = getParamType(param) if t == "int": return p.GetInt(param) elif t == "string": return p.GetString(param) elif t == "float": return p.GetFloat(param) elif t == "bool": return p.GetBool(param) elif t == "unsigned": return p.GetUnsigned(param) else: return None def setParam(param,value): "setParam(parameterName,value): sets a Draft parameter with the given value" p = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft") t = getParamType(param) if t == "int": p.SetInt(param,value) elif t == "string": p.SetString(param,value) elif t == "float": p.SetFloat(param,value) elif t == "bool": p.SetBool(param,value) elif t == "unsigned": p.SetUnsigned(param,value) def precision(): "precision(): returns the precision value from Draft user settings" return getParam("precision") def tolerance(): "tolerance(): returns the tolerance value from Draft user settings" return getParam("tolerance") def getRealName(name): "getRealName(string): strips the trailing numbers from a string name" for i in range(1,len(name)): if not name[-i] in '1234567890': return name[:len(name)-(i-1)] return name def getType(obj): "getType(object): returns the Draft type of the given object" import Part if isinstance(obj,Part.Shape): return "Shape" if "Proxy" in obj.PropertiesList: if hasattr(obj.Proxy,"Type"): return obj.Proxy.Type if obj.isDerivedFrom("Sketcher::SketchObject"): return "Sketch" if obj.isDerivedFrom("Part::Feature"): return "Part" if (obj.Type == "App::Annotation"): return "Annotation" if obj.isDerivedFrom("Mesh::Feature"): return "Mesh" if obj.isDerivedFrom("Points::Feature"): return "Points" if (obj.Type == "App::DocumentObjectGroup"): return "Group" return "Unknown" def getObjectsOfType(objectslist,typ): """getObjectsOfType(objectslist,typ): returns a list of objects of type "typ" found in the given object list""" objs = [] for o in objectslist: if getType(o) == typ: objs.append(o) return objs def get3DView(): "get3DView(): returns the current view if it is 3D, or the first 3D view found, or None" v = FreeCADGui.ActiveDocument.ActiveView if str(type(v)) == "": return v v = FreeCADGui.ActiveDocument.mdiViewsOfType("Gui::View3DInventor") if v: return v[0] return None def isClone(obj,objtype): """isClone(obj,objtype): returns True if the given object is a clone of an object of the given type""" if getType(obj) == "Clone": if len(obj.Objects) == 1: if getType(obj.Objects[0]) == objtype: return True return False def getGroupNames(): "returns a list of existing groups in the document" glist = [] doc = FreeCAD.ActiveDocument for obj in doc.Objects: if obj.Type == "App::DocumentObjectGroup": glist.append(obj.Name) return glist def ungroup(obj): "removes the current object from any group it belongs to" for g in getGroupNames(): grp = FreeCAD.ActiveDocument.getObject(g) if grp.hasObject(obj): grp.removeObject(obj) def dimSymbol(): "returns the current dim symbol from the preferences as a pivy SoMarkerSet" s = getParam("dimsymbol") marker = coin.SoMarkerSet() if s == 0: marker.markerIndex = coin.SoMarkerSet.CIRCLE_FILLED_5_5 elif s == 1: marker.markerIndex = coin.SoMarkerSet.CIRCLE_FILLED_7_7 elif s == 2: marker.markerIndex = coin.SoMarkerSet.CIRCLE_FILLED_9_9 elif s == 3: marker.markerIndex = coin.SoMarkerSet.CIRCLE_LINE_5_5 elif s == 4: marker.markerIndex = coin.SoMarkerSet.CIRCLE_LINE_7_7 elif s == 5: marker.markerIndex = coin.SoMarkerSet.CIRCLE_LINE_9_9 elif s == 6: marker.markerIndex = coin.SoMarkerSet.SLASH_5_5 elif s == 7: marker.markerIndex = coin.SoMarkerSet.SLASH_7_7 elif s == 8: marker.markerIndex = coin.SoMarkerSet.SLASH_9_9 elif s == 9: marker.markerIndex = coin.SoMarkerSet.BACKSLASH_5_5 elif s == 10: marker.markerIndex = coin.SoMarkerSet.BACKSLASH_7_7 elif s == 11: marker.markerIndex = coin.SoMarkerSet.BACKSLASH_9_9 return marker def shapify(obj): '''shapify(object): transforms a parametric shape object into non-parametric and returns the new object''' if not (obj.isDerivedFrom("Part::Feature")): return None if not "Shape" in obj.PropertiesList: return None shape = obj.Shape if len(shape.Faces) == 1: name = "Face" elif len(shape.Solids) > 0: name = "Solid" elif len(shape.Faces) > 1: name = "Shell" elif len(shape.Wires) == 1: name = "Wire" elif len(shape.Edges) == 1: if isinstance(shape.Edges[0].Curve,Part.Line): name = "Line" else: name = "Circle" else: name = getRealName(obj.Name) FreeCAD.ActiveDocument.removeObject(obj.Name) newobj = FreeCAD.ActiveDocument.addObject("Part::Feature",name) newobj.Shape = shape FreeCAD.ActiveDocument.recompute() return newobj def getGroupContents(objectslist,walls=False): '''getGroupContents(objectlist): if any object of the given list is a group, its content is appened to the list, which is returned''' newlist = [] for obj in objectslist: if obj.isDerivedFrom("App::DocumentObjectGroup"): newlist.extend(getGroupContents(obj.Group)) else: newlist.append(obj) if walls: if getType(obj) == "Wall": for o in obj.OutList: if (getType(o) == "Window") or isClone(o,"Window"): newlist.append(o) return newlist def printShape(shape): """prints detailed information of a shape""" print "solids: ", len(shape.Solids) print "faces: ", len(shape.Faces) print "wires: ", len(shape.Wires) print "edges: ", len(shape.Edges) print "verts: ", len(shape.Vertexes) if shape.Faces: for f in range(len(shape.Faces)): print "face ",f,":" for v in shape.Faces[f].Vertexes: print " ",v.Point elif shape.Wires: for w in range(len(shape.Wires)): print "wire ",w,":" for v in shape.Wires[w].Vertexes: print " ",v.Point else: for v in shape.Vertexes: print " ",v.Point def formatObject(target,origin=None): ''' formatObject(targetObject,[originObject]): This function applies to the given target object the current properties set on the toolbar (line color and line width), or copies the properties of another object if given as origin. It also places the object in construction group if needed. ''' obrep = target.ViewObject ui = None if gui: if hasattr(FreeCADGui,"draftToolBar"): ui = FreeCADGui.draftToolBar if ui: doc = FreeCAD.ActiveDocument if ui.isConstructionMode(): col = fcol = ui.getDefaultColor("constr") gname = getParam("constructiongroupname") if not gname: gname = "Construction" grp = doc.getObject(gname) if not grp: grp = doc.addObject("App::DocumentObjectGroup",gname) grp.addObject(target) obrep.Transparency = 80 else: col = ui.getDefaultColor("ui") fcol = ui.getDefaultColor("face") col = (float(col[0]),float(col[1]),float(col[2]),0.0) fcol = (float(fcol[0]),float(fcol[1]),float(fcol[2]),0.0) lw = ui.linewidth fs = ui.fontsize if not origin: if "FontSize" in obrep.PropertiesList: obrep.FontSize = fs if "TextColor" in obrep.PropertiesList: obrep.TextColor = col if "LineWidth" in obrep.PropertiesList: obrep.LineWidth = lw if "PointColor" in obrep.PropertiesList: obrep.PointColor = col if "LineColor" in obrep.PropertiesList: obrep.LineColor = col if "ShapeColor" in obrep.PropertiesList: obrep.ShapeColor = fcol else: matchrep = origin.ViewObject for p in matchrep.PropertiesList: if not p in ["DisplayMode","BoundingBox","Proxy","RootNode","Visibility"]: if p in obrep.PropertiesList: val = getattr(matchrep,p) setattr(obrep,p,val) if matchrep.DisplayMode in obrep.listDisplayModes(): obrep.DisplayMode = matchrep.DisplayMode def getSelection(): "getSelection(): returns the current FreeCAD selection" if gui: return FreeCADGui.Selection.getSelection() return None def select(objs=None): "select(object): deselects everything and selects only the passed object or list" if gui: FreeCADGui.Selection.clearSelection() if objs: if not isinstance(objs,list): objs = [objs] for obj in objs: FreeCADGui.Selection.addSelection(obj) def loadTexture(filename): "loadTexture(filename): returns a SoSFImage from a file" if gui: from pivy import coin from PyQt4 import QtGui try: p = QtGui.QImage(filename) size = coin.SbVec2s(p.width(), p.height()) buffersize = p.numBytes() numcomponents = int (buffersize / ( size[0] * size[1] )) img = coin.SoSFImage() width = size[0] height = size[1] bytes = "" for y in range(height): #line = width*numcomponents*(height-(y)); for x in range(width): rgb = p.pixel(x,y) if numcomponents == 1: bytes = bytes + chr(QtGui.qGray( rgb )) elif numcomponents == 2: bytes = bytes + chr(QtGui.qGray( rgb )) bytes = bytes + chr(QtGui.qAlpha( rgb )) elif numcomponents == 3: bytes = bytes + chr(QtGui.qRed( rgb )) bytes = bytes + chr(QtGui.qGreen( rgb )) bytes = bytes + chr(QtGui.qBlue( rgb )) elif numcomponents == 4: bytes = bytes + chr(QtGui.qRed( rgb )) bytes = bytes + chr(QtGui.qGreen( rgb )) bytes = bytes + chr(QtGui.qBlue( rgb )) bytes = bytes + chr(QtGui.qAlpha( rgb )) #line += numcomponents img.setValue(size, numcomponents, bytes) except: return None else: return img return None def makeCircle(radius, placement=None, face=True, startangle=None, endangle=None, support=None): '''makeCircle(radius,[placement,face,startangle,endangle]) or makeCircle(edge,[face]): Creates a circle object with given radius. If placement is given, it is used. If face is False, the circle is shown as a wireframe, otherwise as a face. If startangle AND endangle are given (in degrees), they are used and the object appears as an arc. If an edge is passed, its Curve must be a Part.Circle''' import Part if placement: typecheck([(placement,FreeCAD.Placement)], "makeCircle") obj = FreeCAD.ActiveDocument.addObject("Part::Part2DObjectPython","Circle") _Circle(obj) if isinstance(radius,Part.Edge): edge = radius if isinstance(edge.Curve,Part.Circle): obj.Radius = edge.Curve.Radius placement = FreeCAD.Placement(edge.Placement) delta = edge.Curve.Center.sub(placement.Base) placement.move(delta) if len(edge.Vertexes) > 1: ref = placement.multVec(FreeCAD.Vector(1,0,0)) v1 = (edge.Vertexes[0].Point).sub(edge.Curve.Center) v2 = (edge.Vertexes[-1].Point).sub(edge.Curve.Center) a1 = -math.degrees(DraftVecUtils.angle(v1,ref)) a2 = -math.degrees(DraftVecUtils.angle(v2,ref)) obj.FirstAngle = a1 obj.LastAngle = a2 else: obj.Radius = radius if (startangle != None) and (endangle != None): if startangle == -0: startangle = 0 obj.FirstAngle = startangle obj.LastAngle = endangle obj.Support = support if placement: obj.Placement = placement if gui: _ViewProviderDraft(obj.ViewObject) if not face: obj.ViewObject.DisplayMode = "Wireframe" formatObject(obj) select(obj) FreeCAD.ActiveDocument.recompute() return obj def makeRectangle(length, height, placement=None, face=True, support=None): '''makeRectangle(length,width,[placement],[face]): Creates a Rectangle object with length in X direction and height in Y direction. If a placement is given, it is used. If face is False, the rectangle is shown as a wireframe, otherwise as a face.''' if placement: typecheck([(placement,FreeCAD.Placement)], "makeRectangle") obj = FreeCAD.ActiveDocument.addObject("Part::Part2DObjectPython","Rectangle") _Rectangle(obj) obj.Length = length obj.Height = height obj.Support = support if placement: obj.Placement = placement if gui: _ViewProviderRectangle(obj.ViewObject) if not face: obj.ViewObject.DisplayMode = "Wireframe" formatObject(obj) select(obj) FreeCAD.ActiveDocument.recompute() return obj def makeDimension(p1,p2,p3=None,p4=None): '''makeDimension(p1,p2,[p3]) or makeDimension(object,i1,i2,p3) or makeDimension(objlist,indices,p3): Creates a Dimension object with the dimension line passign through p3.The current line width and color will be used. There are multiple ways to create a dimension, depending on the arguments you pass to it: - (p1,p2,p3): creates a standard dimension from p1 to p2 - (object,i1,i2,p3): creates a linked dimension to the given object, measuring the distance between its vertices indexed i1 and i2 - (object,i1,mode,p3): creates a linked dimension to the given object, i1 is the index of the (curved) edge to measure, and mode is either "radius" or "diameter". ''' obj = FreeCAD.ActiveDocument.addObject("App::FeaturePython","Dimension") _Dimension(obj) if gui: _ViewProviderDimension(obj.ViewObject) if isinstance(p1,Vector) and isinstance(p2,Vector): obj.Start = p1 obj.End = p2 if not p3: p3 = p2.sub(p1) p3.multiply(0.5) p3 = p1.add(p3) elif isinstance(p2,int) and isinstance(p3,int): obj.Base = p1 obj.LinkedVertices = idx = [p2,p3] p3 = p4 if not p3: v1 = obj.Base.Shape.Vertexes[idx[0]].Point v2 = obj.Base.Shape.Vertexes[idx[1]].Point p3 = v2.sub(v1) p3.multiply(0.5) p3 = v1.add(p3) elif isinstance(p3,str): obj.Base = p1 if p3 == "radius": obj.LinkedVertices = [p2,1,1] obj.ViewObject.Override = "rdim" elif p3 == "diameter": obj.LinkedVertices = [p2,2,1] obj.ViewObject.Override = "ddim" p3 = p4 if not p3: p3 = obj.Base.Shape.Edges[0].Curve.Center.add(Vector(1,0,0)) obj.Dimline = p3 if gui: formatObject(obj) select(obj) FreeCAD.ActiveDocument.recompute() return obj def makeAngularDimension(center,angles,p3): '''makeAngularDimension(center,[angle1,angle2],p3): creates an angular Dimension from the given center, with the given list of angles, passing through p3. ''' obj = FreeCAD.ActiveDocument.addObject("App::FeaturePython","Dimension") _AngularDimension(obj) obj.Center = center for a in range(len(angles)): if angles[a] > 2*math.pi: angles[a] = angles[a]-(2*math.pi) obj.FirstAngle = math.degrees(angles[1]) obj.LastAngle = math.degrees(angles[0]) obj.Dimline = p3 if gui: _ViewProviderAngularDimension(obj.ViewObject) formatObject(obj) select(obj) FreeCAD.ActiveDocument.recompute() return obj def makeWire(pointslist,closed=False,placement=None,face=True,support=None): '''makeWire(pointslist,[closed],[placement]): Creates a Wire 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.''' import DraftGeomUtils, Part if not isinstance(pointslist,list): e = pointslist.Wires[0].Edges pointslist = Part.Wire(DraftGeomUtils.sortEdges(e)) nlist = [] for v in pointslist.Vertexes: nlist.append(v.Point) if DraftGeomUtils.isReallyClosed(pointslist): closed = True pointslist = nlist print pointslist print closed if placement: typecheck([(placement,FreeCAD.Placement)], "makeWire") if len(pointslist) == 2: fname = "Line" else: fname = "DWire" obj = FreeCAD.ActiveDocument.addObject("Part::Part2DObjectPython",fname) _Wire(obj) obj.Points = pointslist obj.Closed = closed obj.Support = support if placement: obj.Placement = placement if gui: _ViewProviderWire(obj.ViewObject) if not face: obj.ViewObject.DisplayMode = "Wireframe" formatObject(obj) select(obj) FreeCAD.ActiveDocument.recompute() return obj def makePolygon(nfaces,radius=1,inscribed=True,placement=None,face=True,support=None): '''makePolgon(nfaces,[radius],[inscribed],[placement],[face]): Creates a polygon object with the given number of faces and the radius. if inscribed is False, the polygon is circumscribed around a circle with the given radius, otherwise it is inscribed. If face is True, the resulting shape is displayed as a face, otherwise as a wireframe. ''' if nfaces < 3: return None obj = FreeCAD.ActiveDocument.addObject("Part::Part2DObjectPython","Polygon") _Polygon(obj) obj.FacesNumber = nfaces obj.Radius = radius if inscribed: obj.DrawMode = "inscribed" else: obj.DrawMode = "circumscribed" obj.Support = support if placement: obj.Placement = placement if gui: _ViewProviderDraft(obj.ViewObject) if not face: obj.ViewObject.DisplayMode = "Wireframe" formatObject(obj) select(obj) FreeCAD.ActiveDocument.recompute() return obj def makeLine(p1,p2): '''makeLine(p1,p2): Creates a line between p1 and p2.''' obj = makeWire([p1,p2]) return obj def makeBSpline(pointslist,closed=False,placement=None,face=True,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 isinstance(pointslist,list): nlist = [] for v in pointslist.Vertexes: nlist.append(v.Point) pointslist = nlist 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.Points = pointslist obj.Closed = closed obj.Support = support if placement: obj.Placement = placement if gui: _ViewProviderBSpline(obj.ViewObject) if not face: obj.ViewObject.DisplayMode = "Wireframe" formatObject(obj) select(obj) FreeCAD.ActiveDocument.recompute() return obj def makeText(stringslist,point=Vector(0,0,0),screen=False): '''makeText(strings,[point],[screen]): Creates a Text object at the given point, containing the strings given in the strings list, one string by line (strings can also be one single string). The current color and text height and font specified in preferences are used. If screen is True, the text always faces the view direction.''' typecheck([(point,Vector)], "makeText") if not isinstance(stringslist,list): stringslist = [stringslist] textbuffer = [] for l in stringslist: textbuffer.append(unicode(l).encode('utf-8')) obj=FreeCAD.ActiveDocument.addObject("App::Annotation","Text") obj.LabelText=textbuffer obj.Position=point if not screen: obj.ViewObject.DisplayMode="World" h = getParam("textheight") if screen: h = h*10 obj.ViewObject.FontSize = h obj.ViewObject.FontName = getParam("textfont") obj.ViewObject.LineSpacing = 0.6 formatObject(obj) select(obj) return obj def makeCopy(obj,force=None,reparent=False): '''makeCopy(object): returns an exact copy of an object''' if (getType(obj) == "Rectangle") or (force == "Rectangle"): newobj = FreeCAD.ActiveDocument.addObject(obj.Type,getRealName(obj.Name)) _Rectangle(newobj) if gui: _ViewProviderRectangle(newobj.ViewObject) elif (getType(obj) == "Dimension") or (force == "Dimension"): newobj = FreeCAD.ActiveDocument.addObject(obj.Type,getRealName(obj.Name)) _Dimension(newobj) if gui: _ViewProviderDimension(newobj.ViewObject) elif (getType(obj) == "Wire") or (force == "Wire"): newobj = FreeCAD.ActiveDocument.addObject(obj.Type,getRealName(obj.Name)) _Wire(newobj) if gui: _ViewProviderWire(newobj.ViewObject) elif (getType(obj) == "Circle") or (force == "Circle"): newobj = FreeCAD.ActiveDocument.addObject(obj.Type,getRealName(obj.Name)) _Circle(newobj) if gui: _ViewProviderDraft(newobj.ViewObject) elif (getType(obj) == "Polygon") or (force == "Polygon"): newobj = FreeCAD.ActiveDocument.addObject(obj.Type,getRealName(obj.Name)) _Polygon(newobj) if gui: _ViewProviderPolygon(newobj.ViewObject) elif (getType(obj) == "BSpline") or (force == "BSpline"): newobj = FreeCAD.ActiveDocument.addObject(obj.Type,getRealName(obj.Name)) _BSpline(newobj) if gui: _ViewProviderBSpline(newobj.ViewObject) elif (getType(obj) == "Block") or (force == "BSpline"): newobj = FreeCAD.ActiveDocument.addObject(obj.Type,getRealName(obj.Name)) _Block(newobj) if gui: _ViewProviderDraftPart(newobj.ViewObject) elif (getType(obj) == "Structure") or (force == "Structure"): import ArchStructure newobj = FreeCAD.ActiveDocument.addObject(obj.Type,getRealName(obj.Name)) ArchStructure._Structure(newobj) if gui: ArchStructure._ViewProviderStructure(newobj.ViewObject) elif (getType(obj) == "Wall") or (force == "Wall"): import ArchWall newobj = FreeCAD.ActiveDocument.addObject(obj.Type,getRealName(obj.Name)) ArchWall._Wall(newobj) if gui: ArchWall._ViewProviderWall(newobj.ViewObject) elif (getType(obj) == "Window") or (force == "Window"): import ArchWindow newobj = FreeCAD.ActiveDocument.addObject(obj.Type,getRealName(obj.Name)) ArchWindow._Window(newobj) if gui: Archwindow._ViewProviderWindow(newobj.ViewObject) elif (getType(obj) == "Cell") or (force == "Cell"): import ArchCell newobj = FreeCAD.ActiveDocument.addObject(obj.Type,getRealName(obj.Name)) ArchCell._Cell(newobj) if gui: ArchCell._ViewProviderCell(newobj.ViewObject) elif (getType(obj) == "Sketch") or (force == "Sketch"): newobj = FreeCAD.ActiveDocument.addObject("Sketcher::SketchObject",getRealName(obj.Name)) for geo in obj.Geometries: newobj.addGeometry(geo) for con in obj.constraints: newobj.addConstraint(con) elif obj.isDerivedFrom("Part::Feature"): newobj = FreeCAD.ActiveDocument.addObject("Part::Feature",getRealName(obj.Name)) newobj.Shape = obj.Shape else: print "Error: Object type cannot be copied" return None for p in obj.PropertiesList: if not p in ["Proxy"]: if p in newobj.PropertiesList: setattr(newobj,p,obj.getPropertyByName(p)) if reparent: parents = obj.InList if parents: for par in parents: if par.Type == "App::DocumentObjectGroup": par.addObject(newobj) else: for prop in par.PropertiesList: if getattr(par,prop) == obj: setattr(par,prop,newobj) formatObject(newobj,obj) return newobj def makeBlock(objectslist): '''makeBlock(objectslist): Creates a Draft Block from the given objects''' obj = FreeCAD.ActiveDocument.addObject("Part::Part2DObjectPython","Block") _Block(obj) obj.Components = objectslist if gui: _ViewProviderDraftPart(obj.ViewObject) for o in objectslist: o.ViewObject.Visibility = False select(obj) return obj def makeArray(baseobject,arg1,arg2,arg3,arg4=None): '''makeArray(object,xvector,yvector,xnum,ynum) for rectangular array, or makeArray(object,center,totalangle,totalnum) for polar array: Creates an array of the given object with, in case of rectangular array, xnum of iterations in the x direction at xvector distance between iterations, and same for y direction with yvector and ynum. In case of polar array, center is a vector, totalangle is the angle to cover (in degrees) and totalnum is the number of objects, including the original. The result is a parametric Draft Array.''' obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython","Array") _Array(obj) obj.Base = baseobject if arg4: obj.ArrayType = "ortho" obj.IntervalX = arg1 obj.IntervalY = arg2 obj.NumberX = arg3 obj.NumberY = arg4 else: obj.ArrayType = "polar" obj.Center = arg1 obj.Angle = arg2 obj.NumberPolar = arg3 if gui: _ViewProviderDraftPart(obj.ViewObject) baseobject.ViewObject.hide() select(obj) return obj def extrude(obj,vector): '''makeExtrusion(object,vector): extrudes the given object in the direction given by the vector. The original object gets hidden.''' newobj = FreeCAD.ActiveDocument.addObject("Part::Extrusion","Extrusion") newobj.Base = obj newobj.Dir = vector obj.ViewObject.Visibility = False formatObject(newobj,obj) FreeCAD.ActiveDocument.recompute() return newobj def fuse(object1,object2): '''fuse(oject1,object2): returns an object made from the union of the 2 given objects. If the objects are coplanar, a special Draft Wire is used, otherwise we use a standard Part fuse.''' import DraftGeomUtils, Part # testing if we have holes: holes = False fshape = object1.Shape.fuse(object2.Shape) fshape = fshape.removeSplitter() for f in fshape.Faces: if len(f.Wires) > 1: holes = True if DraftGeomUtils.isCoplanar(object1.Shape.fuse(object2.Shape).Faces) and not holes: obj = FreeCAD.ActiveDocument.addObject("Part::Part2DObjectPython","Fusion") _Wire(obj) if gui: _ViewProviderWire(obj.ViewObject) obj.Base = object1 obj.Tool = object2 elif holes: # temporary hack, since Part::Fuse objects don't remove splitters obj = FreeCAD.ActiveDocument.addObject("Part::Feature","Fusion") obj.Shape = fshape else: obj = FreeCAD.ActiveDocument.addObject("Part::Fuse","Fusion") obj.Base = object1 obj.Tool = object2 if gui: object1.ViewObject.Visibility = False object2.ViewObject.Visibility = False formatObject(obj,object1) FreeCAD.ActiveDocument.recompute() return obj def cut(object1,object2): '''cut(oject1,object2): returns a cut object made from the difference of the 2 given objects.''' obj = FreeCAD.ActiveDocument.addObject("Part::Cut","Cut") obj.Base = object1 obj.Tool = object2 object1.ViewObject.Visibility = False object2.ViewObject.Visibility = False formatObject(obj,object1) FreeCAD.ActiveDocument.recompute() return obj def move(objectslist,vector,copy=False): '''move(objects,vector,[copy]): Moves the objects contained in objects (that can be an object or a list of objects) in the direction and distance indicated by the given vector. If copy is True, the actual objects are not moved, but copies are created instead.he objects (or their copies) are returned.''' typecheck([(vector,Vector), (copy,bool)], "move") if not isinstance(objectslist,list): objectslist = [objectslist] newobjlist = [] for obj in objectslist: if (obj.isDerivedFrom("Part::Feature")): if copy: newobj = makeCopy(obj) else: newobj = obj pla = newobj.Placement pla.move(vector) elif getType(obj) == "Annotation": if copy: newobj = FreeCAD.ActiveDocument.addObject("App::Annotation",getRealName(obj.Name)) newobj.LabelText = obj.LabelText else: newobj = obj newobj.Position = obj.Position.add(vector) elif getType(obj) == "Dimension": if copy: newobj = FreeCAD.ActiveDocument.addObject("App::FeaturePython",getRealName(obj.Name)) _Dimension(newobj) if gui: _ViewProviderDimension(newobj.ViewObject) else: newobj = obj newobj.Start = obj.Start.add(vector) newobj.End = obj.End.add(vector) newobj.Dimline = obj.Dimline.add(vector) else: if copy: print "Mesh copy not supported at the moment" # TODO newobj = obj if "Placement" in obj.PropertiesList: pla = obj.Placement pla.move(vector) newobjlist.append(newobj) if copy and getParam("selectBaseObjects"): select(objectslist) else: select(newobjlist) if len(newobjlist) == 1: return newobjlist[0] return newobjlist def array(objectslist,arg1,arg2,arg3,arg4=None): '''array(objectslist,xvector,yvector,xnum,ynum) for rectangular array, or array(objectslist,center,totalangle,totalnum) for polar array: Creates an array of the objects contained in list (that can be an object or a list of objects) with, in case of rectangular array, xnum of iterations in the x direction at xvector distance between iterations, and same for y direction with yvector and ynum. In case of polar array, center is a vector, totalangle is the angle to cover (in degrees) and totalnum is the number of objects, including the original.''' def rectArray(objectslist,xvector,yvector,xnum,ynum): typecheck([(xvector,Vector), (yvector,Vector), (xnum,int), (ynum,int)], "rectArray") if not isinstance(objectslist,list): objectslist = [objectslist] for xcount in range(xnum): currentxvector=DraftVecUtils.scale(xvector,xcount) if not xcount==0: move(objectslist,currentxvector,True) for ycount in range(ynum): currentxvector=FreeCAD.Base.Vector(currentxvector) currentyvector=currentxvector.add(DraftVecUtils.scale(yvector,ycount)) if not ycount==0: move(objectslist,currentyvector,True) def polarArray(objectslist,center,angle,num): typecheck([(center,Vector), (num,int)], "polarArray") if not isinstance(objectslist,list): objectslist = [objectslist] fraction = angle/num for i in range(num): currangle = fraction + (i*fraction) rotate(objectslist,currangle,center,copy=True) if arg4: rectArray(objectslist,arg1,arg2,arg3,arg4) else: polarArray(objectslist,arg1,arg2,arg3) def rotate(objectslist,angle,center=Vector(0,0,0),axis=Vector(0,0,1),copy=False): '''rotate(objects,angle,[center,axis,copy]): Rotates the objects contained in objects (that can be a list of objects or an object) of the given angle (in degrees) around the center, using axis as a rotation axis. If axis is omitted, the rotation will be around the vertical Z axis. If copy is True, the actual objects are not moved, but copies are created instead. The objects (or their copies) are returned.''' import Part typecheck([(copy,bool)], "rotate") if not isinstance(objectslist,list): objectslist = [objectslist] newobjlist = [] for obj in objectslist: if copy: newobj = makeCopy(obj) else: newobj = obj if (obj.isDerivedFrom("Part::Feature")): shape = obj.Shape.copy() shape.rotate(DraftVecUtils.tup(center), DraftVecUtils.tup(axis), angle) newobj.Shape = shape elif (obj.isDerivedFrom("App::Annotation")): if axis.normalize() == Vector(1,0,0): newobj.ViewObject.RotationAxis = "X" newobj.ViewObject.Rotation = angle elif axis.normalize() == Vector(0,1,0): newobj.ViewObject.RotationAxis = "Y" newobj.ViewObject.Rotation = angle elif axis.normalize() == Vector(0,-1,0): newobj.ViewObject.RotationAxis = "Y" newobj.ViewObject.Rotation = -angle elif axis.normalize() == Vector(0,0,1): newobj.ViewObject.RotationAxis = "Z" newobj.ViewObject.Rotation = angle elif axis.normalize() == Vector(0,0,-1): newobj.ViewObject.RotationAxis = "Z" newobj.ViewObject.Rotation = -angle elif hasattr(obj,"Placement"): shape = Part.Shape() shape.Placement = obj.Placement shape.rotate(DraftVecUtils.tup(center), DraftVecUtils.tup(axis), angle) newobj.Placement = shape.Placement if copy: formatObject(newobj,obj) newobjlist.append(newobj) if copy and getParam("selectBaseObjects"): select(objectslist) else: select(newobjlist) if len(newobjlist) == 1: return newobjlist[0] return newobjlist def scale(objectslist,delta=Vector(1,1,1),center=Vector(0,0,0),copy=False,legacy=False): '''scale(objects,vector,[center,copy,legacy]): Scales the objects contained in objects (that can be a list of objects or an object) of the given scale factors defined by the given vector (in X, Y and Z directions) around given center. If legacy is True, direct (old) mode is used, otherwise a parametric copy is made. If copy is True, the actual objects are not moved, but copies are created instead. The objects (or their copies) are returned.''' if not isinstance(objectslist,list): objectslist = [objectslist] if legacy: newobjlist = [] for obj in objectslist: if copy: newobj = makeCopy(obj) else: newobj = obj sh = obj.Shape.copy() m = FreeCAD.Matrix() m.scale(delta) sh = sh.transformGeometry(m) corr = Vector(center.x,center.y,center.z) corr.scale(delta.x,delta.y,delta.z) corr = DraftVecUtils.neg(corr.sub(center)) sh.translate(corr) if getType(obj) == "Rectangle": p = [] for v in sh.Vertexes: p.append(v.Point) pl = obj.Placement.copy() pl.Base = p[0] diag = p[2].sub(p[0]) bb = p[1].sub(p[0]) bh = p[3].sub(p[0]) nb = DraftVecUtils.project(diag,bb) nh = DraftVecUtils.project(diag,bh) if obj.Length < 0: l = -nb.Length else: l = nb.Length if obj.Height < 0: h = -nh.Length else: h = nh.Length newobj.Length = l newobj.Height = h tr = p[0].sub(obj.Shape.Vertexes[0].Point) newobj.Placement = pl elif getType(obj) == "Wire": p = [] for v in sh.Vertexes: p.append(v.Point) newobj.Points = p elif (obj.isDerivedFrom("Part::Feature")): newobj.Shape = sh elif (obj.Type == "App::Annotation"): factor = delta.x * delta.y * delta.z * obj.ViewObject.FontSize obj.ViewObject.Fontsize = factor if copy: formatObject(newobj,obj) newobjlist.append(newobj) if copy and getParam("selectBaseObjects"): select(objectslist) else: select(newobjlist) if len(newobjlist) == 1: return newobjlist[0] return newobjlist else: obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython","Scale") _Clone(obj) obj.Objects = objectslist obj.Scale = delta corr = Vector(center.x,center.y,center.z) corr.scale(delta.x,delta.y,delta.z) corr = DraftVecUtils.neg(corr.sub(center)) p = obj.Placement p.move(corr) obj.Placement = p if not copy: for o in objectslist: o.ViewObject.hide() if gui: _ViewProviderDraftPart(obj.ViewObject) formatObject(obj,objectslist[-1]) select(obj) return obj def offset(obj,delta,copy=False,bind=False,sym=False,occ=False): '''offset(object,Vector,[copymode],[bind]): offsets the given wire by applying the given Vector to its first vertex. If copymode is True, another object is created, otherwise the same object gets offsetted. If bind is True, and provided the wire is open, the original and the offsetted wires will be bound by their endpoints, forming a face if sym is True, bind must be true too, and the offset is made on both sides, the total width being the given delta length.''' import Part, DraftGeomUtils def getRect(p,obj): "returns length,heigh,placement" pl = obj.Placement.copy() pl.Base = p[0] diag = p[2].sub(p[0]) bb = p[1].sub(p[0]) bh = p[3].sub(p[0]) nb = DraftVecUtils.project(diag,bb) nh = DraftVecUtils.project(diag,bh) if obj.Length < 0: l = -nb.Length else: l = nb.Length if obj.Height < 0: h = -nh.Length else: h = nh.Length return l,h,pl def getRadius(obj,delta): "returns a new radius for a regular polygon" an = math.pi/obj.FacesNumber nr = DraftVecUtils.rotate(delta,-an) nr.multiply(1/math.cos(an)) nr = obj.Shape.Vertexes[0].Point.add(nr) nr = nr.sub(obj.Placement.Base) nr = nr.Length if obj.DrawMode == "inscribed": return nr else: return nr * math.cos(math.pi/obj.FacesNumber) if getType(obj) == "Circle": pass elif getType(obj) == "BSpline": pass else: if sym: d1 = delta.multiply(0.5) d2 = DraftVecUtils.neg(d1) n1 = DraftGeomUtils.offsetWire(obj.Shape,d1) n2 = DraftGeomUtils.offsetWire(obj.Shape,d2) else: newwire = DraftGeomUtils.offsetWire(obj.Shape,delta) p = DraftGeomUtils.getVerts(newwire) if occ: newobj = FreeCAD.ActiveDocument.addObject("Part::Feature","Offset") newobj.Shape = DraftGeomUtils.offsetWire(obj.Shape,delta,occ=True) formatObject(newobj,obj) elif bind: if not DraftGeomUtils.isReallyClosed(obj.Shape): if sym: s1 = n1 s2 = n2 else: s1 = obj.Shape s2 = newwire w1 = s1.Edges w2 = s2.Edges w3 = Part.Line(s1.Vertexes[0].Point,s2.Vertexes[0].Point).toShape() w4 = Part.Line(s1.Vertexes[-1].Point,s2.Vertexes[-1].Point).toShape() newobj = Part.Face(Part.Wire(w1+[w3]+w2+[w4])) else: newobj = Part.Face(obj.Shape.Wires[0]) elif copy: if sym: return None if getType(obj) == "Wire": newobj = makeWire(p) newobj.Closed = obj.Closed elif getType(obj) == "Rectangle": length,height,plac = getRect(p,obj) newobj = makeRectangle(length,height,plac) elif getType(obj) == "Circle": pl = obj.Placement newobj = makeCircle(delta) newobj.FirstAngle = obj.FirstAngle newobj.LastAngle = obj.LastAngle newobj.Placement = pl elif getType(obj) == "Polygon": pl = obj.Placement newobj = makePolygon(obj.FacesNumber) newobj.Radius = getRadius(obj,delta) newobj.DrawMode = obj.DrawMode newobj.Placement = pl elif getType(obj) == "Part": newobj = makeWire(p) newobj.Closed = obj.Shape.isClosed() elif getType(obj) == "BSpline": newobj = makeBSpline(delta) newobj.Closed = obj.Closed formatObject(newobj,obj) else: if sym: return None if getType(obj) == "Wire": if obj.Base or obj.Tool: FreeCAD.Console.PrintWarning("Warning: object history removed\n") obj.Base = None obj.Tool = None obj.Points = p elif getType(obj) == "BSpline": print delta obj.Points = delta print "done" elif getType(obj) == "Rectangle": length,height,plac = getRect(p,obj) obj.Placement = plac obj.Length = length obj.Height = height elif getType(obj) == "Circle": obj.Radius = delta elif getType(obj) == "Polygon": obj.Radius = getRadius(obj,delta) elif getType(obj) == 'Part': print "unsupported object" # TODO newobj = obj if copy and getParam("selectBaseObjects"): select(newobj) else: select(obj) return newobj def draftify(objectslist,makeblock=False): '''draftify(objectslist,[makeblock]): turns each object of the given list (objectslist can also be a single object) into a Draft parametric wire. If makeblock is True, multiple objects will be grouped in a block''' import DraftGeomUtils, Part if not isinstance(objectslist,list): objectslist = [objectslist] newobjlist = [] for obj in objectslist: if obj.isDerivedFrom('Part::Feature'): for w in obj.Shape.Wires: if DraftGeomUtils.hasCurves(w): if (len(w.Edges) == 1) and isinstance(w.Edges[0].Curve,Part.Circle): nobj = makeCircle(w.Edges[0]) else: nobj = FreeCAD.ActiveDocument.addObject("Part::Feature",obj.Name) nobj.Shape = w else: nobj = makeWire(w) if obj.Shape.Faces: nobj.ViewObject.DisplayMode = "Flat Lines" else: nobj.ViewObject.DisplayMode = "Wireframe" newobjlist.append(nobj) formatObject(nobj,obj) FreeCAD.ActiveDocument.removeObject(obj.Name) FreeCAD.ActiveDocument.recompute() if makeblock: return makeBlock(newobjlist) else: if len(newobjlist) == 1: return newobjlist[0] return newobjlist def getSVG(obj,scale=1,linewidth=0.35,fontsize=12,fillstyle="shape color",direction=None): '''getSVG(object,[scale], [linewidth],[fontsize],[fillstyle],[direction]): returns a string containing a SVG representation of the given object, with the given linewidth and fontsize (used if the given object contains any text). You can also supply an arbitrary projection vector. the scale parameter allows to scale linewidths down, so they are resolution-independant.''' import Part, DraftGeomUtils svg = "" linewidth = linewidth/scale fontsize = (fontsize/scale)/2 plane = None if direction: if isinstance(direction,FreeCAD.Vector): if direction != Vector(0,0,0): plane = WorkingPlane.plane() plane.alignToPointAndAxis(Vector(0,0,0),DraftVecUtils.neg(direction),0) elif isinstance(direction,WorkingPlane.plane): plane = direction def getLineStyle(obj): "returns a linestyle pattern for a given object" if obj.ViewObject: if hasattr(obj.ViewObject,"DrawStyle"): ds = obj.ViewObject.DrawStyle if ds == "Dashed": return "0.09,0.05" elif ds == "Dashdot": return "0.09,0.05,0.02,0.05" elif ds == "Dotted": return "0.02,0.02" return "none" def getrgb(color): "getRGB(color): returns a rgb value #000000 from a freecad color" r = str(hex(int(color[0]*255)))[2:].zfill(2) g = str(hex(int(color[1]*255)))[2:].zfill(2) b = str(hex(int(color[2]*255)))[2:].zfill(2) col = "#"+r+g+b if col == "#ffffff": print getParam('SvgLinesBlack') if getParam('SvgLinesBlack'): col = "#000000" return col def getProj(vec): if not plane: return vec nx = DraftVecUtils.project(vec,plane.u) lx = nx.Length if abs(nx.getAngle(plane.u)) > 0.1: lx = -lx ny = DraftVecUtils.project(vec,plane.v) ly = ny.Length if abs(ny.getAngle(plane.v)) > 0.1: ly = -ly return Vector(lx,ly,0) def getPattern(pat): if pat in FreeCAD.svgpatterns: return FreeCAD.svgpatterns[pat] return '' def getPath(edges): svg =' 1 flag_sweep = e.Curve.Axis * drawing_plane_normal >= 0 v = getProj(e.Vertexes[-1].Point) svg += 'A ' + str(r) + ' ' + str(r) + ' ' svg += '0 ' + str(int(flag_large_arc)) + ' ' + str(int(flag_sweep)) + ' ' svg += str(v.x) + ' ' + str(v.y) + ' ' if fill != 'none': svg += 'Z' svg += '" ' svg += 'stroke="' + stroke + '" ' svg += 'stroke-width="' + str(linewidth) + ' px" ' svg += 'style="stroke-width:'+ str(linewidth) svg += ';stroke-miterlimit:4' svg += ';stroke-dasharray:' + lstyle svg += ';fill:' + fill + '"' svg += '/>\n' return svg def getCircle(edge): cen = getProj(edge.Curve.Center) rad = edge.Curve.Radius svg = '\n' svg += '\n' elif getType(obj) == "Axis": "returns the SVG representation of an Arch Axis system" color = getrgb(obj.ViewObject.LineColor) lorig = getLineStyle(obj) name = obj.Name stroke = getrgb(obj.ViewObject.LineColor) fill = 'none' invpl = obj.Placement.inverse() n = 0 for e in obj.Shape.Edges: lstyle = lorig svg += getPath([e]) p1 = invpl.multVec(e.Vertexes[0].Point) p2 = invpl.multVec(e.Vertexes[1].Point) dv = p2.sub(p1) dv.normalize() rad = obj.ViewObject.BubbleSize center = p2.add(dv.scale(rad,rad,rad)) lstyle = "none" svg += getCircle(Part.makeCircle(rad,center)) svg += '\n' svg += '\n' n += 1 elif obj.isDerivedFrom('Part::Feature'): if obj.Shape.isNull(): return '' color = getrgb(obj.ViewObject.LineColor) # setting fill if obj.Shape.Faces and (obj.ViewObject.DisplayMode != "Wireframe"): if fillstyle == "shape color": fill = getrgb(obj.ViewObject.ShapeColor) else: fill = 'url(#'+fillstyle+')' svg += getPattern(fillstyle) else: fill = 'none' lstyle = getLineStyle(obj) name = obj.Name if obj.ViewObject.DisplayMode == "Shaded": stroke = "none" else: stroke = getrgb(obj.ViewObject.LineColor) if len(obj.Shape.Vertexes) > 1: wiredEdges = [] if obj.Shape.Faces: for f in obj.Shape.Faces: svg += getPath(f.Edges) wiredEdges.extend(f.Edges) else: for w in obj.Shape.Wires: svg += getPath(w.Edges) wiredEdges.extend(w.Edges) if len(wiredEdges) != len(obj.Shape.Edges): for e in obj.Shape.Edges: if (DraftGeomUtils.findEdge(e,wiredEdges) == None): svg += getPath([e]) else: svg = getCircle(obj.Shape.Edges[0]) return svg def makeDrawingView(obj,page,lwmod=None,tmod=None): ''' makeDrawingView(object,page,[lwmod,tmod]) - adds a View of the given object to the given page. lwmod modifies lineweights (in percent), tmod modifies text heights (in percent). The Hint scale, X and Y of the page are used. ''' viewobj = FreeCAD.ActiveDocument.addObject("Drawing::FeatureViewPython","View"+obj.Name) _DrawingView(viewobj) page.addObject(viewobj) viewobj.Scale = page.ViewObject.HintScale viewobj.X = page.ViewObject.HintOffsetX viewobj.Y = page.ViewObject.HintOffsetY viewobj.Source = obj if lwmod: viewobj.LineweightModifier = lwmod if tmod: viewobj.TextModifier = tmod return viewobj def makeShape2DView(baseobj,projectionVector=None): ''' makeShape2DView(object,[projectionVector]) - adds a 2D shape to the document, which is a 2D projection of the given object. A specific projection vector can also be given. ''' obj = FreeCAD.ActiveDocument.addObject("Part::Part2DObjectPython","Shape2DView") _Shape2DView(obj) if gui: _ViewProviderDraft(obj.ViewObject) obj.Base = baseobj if projectionVector: obj.Projection = projectionVector select(obj) return obj def makeSketch(objectslist,autoconstraints=False,addTo=None,name="Sketch"): '''makeSketch(objectslist,[autoconstraints],[addTo],[name]): makes a Sketch objectslist with the given Draft objects. If autoconstraints is True, constraints will be automatically added to wire nodes, rectangles and circles. If addTo is an existing sketch, geometry will be added to it instead of creating a new one.''' import Part, DraftGeomUtils from Sketcher import Constraint StartPoint = 1 EndPoint = 2 MiddlePoint = 3 if not isinstance(objectslist,list): objectslist = [objectslist] if addTo: nobj = addTo else: nobj = FreeCAD.ActiveDocument.addObject("Sketcher::SketchObject",name) nobj.ViewObject.Autoconstraints = False for obj in objectslist: ok = False tp = getType(obj) if tp == "BSpline": print "makeSketch: BSplines not supported" elif tp == "Circle": if obj.FirstAngle == obj.LastAngle: nobj.addGeometry(obj.Shape.Edges[0].Curve) else: nobj.addGeometry(Part.ArcOfCircle(obj.Shape.Edges[0].Curve,math.radians(obj.FirstAngle),math.radians(obj.LastAngle))) # TODO add Radius constraits ok = True elif tp == "Rectangle": if obj.FilletRadius == 0: for edge in obj.Shape.Edges: nobj.addGeometry(edge.Curve) if autoconstraints: last = nobj.GeometryCount - 1 segs = [last-3,last-2,last-1,last] if obj.Placement.Rotation.Q == (0,0,0,1): nobj.addConstraint(Constraint("Coincident",last-3,EndPoint,last-2,StartPoint)) nobj.addConstraint(Constraint("Coincident",last-2,EndPoint,last-1,StartPoint)) nobj.addConstraint(Constraint("Coincident",last-1,EndPoint,last,StartPoint)) nobj.addConstraint(Constraint("Coincident",last,EndPoint,last-3,StartPoint)) nobj.addConstraint(Constraint("Horizontal",last-3)) nobj.addConstraint(Constraint("Vertical",last-2)) nobj.addConstraint(Constraint("Horizontal",last-1)) nobj.addConstraint(Constraint("Vertical",last)) ok = True elif tp in ["Wire","Polygon"]: closed = False if tp == "Polygon": closed = True elif hasattr(obj,"Closed"): closed = obj.Closed for edge in obj.Shape.Edges: nobj.addGeometry(edge.Curve) if autoconstraints: last = nobj.GeometryCount segs = range(last-len(obj.Shape.Edges),last-1) for seg in segs: nobj.addConstraint(Constraint("Coincident",seg,EndPoint,seg+1,StartPoint)) if DraftGeomUtils.isAligned(nobj.Geometry[seg],"x"): nobj.addConstraint(Constraint("Vertical",seg)) elif DraftGeomUtils.isAligned(nobj.Geometry[seg],"y"): nobj.addConstraint(Constraint("Horizontal",seg)) if closed: nobj.addConstraint(Constraint("Coincident",last-1,EndPoint,segs[0],StartPoint)) ok = True if (not ok) and obj.isDerivedFrom("Part::Feature"): if not DraftGeomUtils.isPlanar(obj.Shape): print "Error: The given object is not planar and cannot be converted into a sketch." return None if not addTo: nobj.Placement.Rotation = DraftGeomUtils.calculatePlacement(obj.Shape).Rotation edges = [] for e in obj.Shape.Edges: g = (DraftGeomUtils.geom(e,nobj.Placement)) if g: nobj.addGeometry(g) ok = True if ok: FreeCAD.ActiveDocument.removeObject(obj.Name) FreeCAD.ActiveDocument.recompute() return nobj def makePoint(X=0, Y=0, Z=0,color=None,name = "Point", point_size= 5): ''' make a point (at coordinates x,y,z ,color(r,g,b),point_size) example usage: p1 = makePoint() p1.ViewObject.Visibility= False # make it invisible p1.ViewObject.Visibility= True # make it visible p1 = makePoint(-1,0,0) #make a point at -1,0,0 p1 = makePoint(1,0,0,(1,0,0)) # color = red p1.X = 1 #move it in x p1.ViewObject.PointColor =(0.0,0.0,1.0) #change the color-make sure values are floats ''' obj=FreeCAD.ActiveDocument.addObject("Part::FeaturePython",name) _Point(obj,X,Y,Z) obj.X = X obj.Y = Y obj.Z = Z if gui: _ViewProviderPoint(obj.ViewObject) if not color: color = FreeCADGui.draftToolBar.getDefaultColor('ui') obj.ViewObject.PointColor = (float(color[0]), float(color[1]), float(color[2])) obj.ViewObject.PointSize = point_size obj.ViewObject.Visibility = True FreeCAD.ActiveDocument.recompute() return obj def clone(obj,delta=None): '''clone(obj,[delta]): makes a clone of the given object(s). The clone is an exact, linked copy of the given object. If the original object changes, the final object changes too. Optionally, you can give a delta Vector to move the clone from the original position.''' if not isinstance(obj,list): obj = [obj] cl = FreeCAD.ActiveDocument.addObject("Part::FeaturePython","Clone") cl.Label = "Clone of " + obj[0].Label _Clone(cl) if gui: _ViewProviderClone(cl.ViewObject) formatObject(cl,obj[0]) cl.Objects = obj if delta: cl.Placement.move(delta) return cl def heal(objlist=None,delete=True,reparent=True): '''heal([objlist],[delete],[reparent]) - recreates Draft objects that are damaged, for example if created from an earlier version. If delete is True, the damaged objects are deleted (default). If ran without arguments, all the objects in the document will be healed if they are damaged. If reparent is True (default), new objects go at the very same place in the tree than their original.''' if not objlist: objlist = FreeCAD.ActiveDocument.Objects print "Healing whole document..." if not isinstance(objlist,list): objlist = [objlist] dellist = [] got = False for obj in objlist: dtype = getType(obj) ftype = obj.Type if ftype in ["Part::FeaturePython","App::FeaturePython"]: if obj.ViewObject.Proxy == 1 and dtype in ["Unknown","Part"]: got = True dellist.append(obj.Name) props = obj.PropertiesList if ("Dimline" in props) and ("Start" in props): print "Healing " + obj.Name + " of type Dimension" nobj = makeCopy(obj,force="Dimension",reparent=reparent) elif ("Height" in props) and ("Length" in props): print "Healing " + obj.Name + " of type Rectangle" nobj = makeCopy(obj,force="Rectangle",reparent=reparent) elif ("Points" in props) and ("Closed" in props): print "Healing " + obj.Name + " of type Wire" nobj = makeCopy(obj,force="Wire",reparent=reparent) elif ("Radius" in props) and ("FirstAngle" in props): print "Healing " + obj.Name + " of type Circle" nobj = makeCopy(obj,force="Circle",reparent=reparent) else: dellist.pop() print "Object " + obj.Name + " is not healable" if not got: print "No object seems to need healing" else: print "Healed ",len(dellist)," objects" if dellist and delete: for n in dellist: FreeCAD.ActiveDocument.removeObject(n) #--------------------------------------------------------------------------- # Python Features definitions #--------------------------------------------------------------------------- class _ViewProviderDraft: "A generic View Provider for Draft objects" def __init__(self, obj): obj.Proxy = self self.Object = obj.Object def attach(self, obj): self.Object = obj.Object return def updateData(self, fp, prop): return def getDisplayModes(self,obj): modes=[] return modes def setDisplayMode(self,mode): return mode def onChanged(self, vp, prop): return def __getstate__(self): return None def __setstate__(self,state): return None def setEdit(self,vp,mode): FreeCADGui.runCommand("Draft_Edit") return True def unsetEdit(self,vp,mode): if FreeCAD.activeDraftCommand: FreeCAD.activeDraftCommand.finish() return def getIcon(self): return(":/icons/Draft_Draft.svg") def claimChildren(self): objs = [] if hasattr(self.Object,"Base"): objs.append(self.Object.Base) if hasattr(self.Object,"Objects"): objs.extend(self.Object.Objects) if hasattr(self.Object,"Components"): objs.extend(self.Object.Components) return objs class _Dimension: "The Dimension object" def __init__(self, obj): obj.addProperty("App::PropertyVector","Start","Base", "Startpoint of dimension") obj.addProperty("App::PropertyVector","End","Base", "Endpoint of dimension") obj.addProperty("App::PropertyVector","Dimline","Base", "Point through which the dimension line passes") obj.addProperty("App::PropertyLink","Base","Base", "The base object this dimension is linked to") obj.addProperty("App::PropertyIntegerList","LinkedVertices","Base", "The indices of the vertices from the base object to measure") obj.Start = FreeCAD.Vector(0,0,0) obj.End = FreeCAD.Vector(1,0,0) obj.Dimline = FreeCAD.Vector(0,1,0) obj.Proxy = self self.Type = "Dimension" def onChanged(self, obj, prop): pass def execute(self, obj): if obj.ViewObject: obj.ViewObject.update() class _ViewProviderDimension: "A View Provider for the Dimension object" def __init__(self, obj): obj.addProperty("App::PropertyLength","FontSize","Base","Font size") obj.addProperty("App::PropertyString","FontName","Base","Font name") obj.addProperty("App::PropertyLength","LineWidth","Base","Line width") obj.addProperty("App::PropertyColor","LineColor","Base","Line color") obj.addProperty("App::PropertyLength","ExtLines","Base","Ext lines") obj.addProperty("App::PropertyVector","TextPosition","Base","The position of the text. Leave (0,0,0) for automatic position") obj.addProperty("App::PropertyString","Override","Base","Text override. Use 'dim' to insert the dimension length") obj.Proxy = self obj.FontSize=getParam("textheight") obj.FontName=getParam("textfont") obj.ExtLines=0.3 obj.Override = '' def calcGeom(self,obj): import Part, DraftGeomUtils p1 = obj.Start p4 = obj.End base = Part.Line(p1,p4).toShape() proj = DraftGeomUtils.findDistance(obj.Dimline,base) if not proj: p2 = p1 p3 = p4 else: p2 = p1.add(DraftVecUtils.neg(proj)) p3 = p4.add(DraftVecUtils.neg(proj)) dmax = obj.ViewObject.ExtLines if dmax and (proj.Length > dmax): p1 = p2.add(DraftVecUtils.scaleTo(proj,dmax)) p4 = p3.add(DraftVecUtils.scaleTo(proj,dmax)) midpoint = p2.add(DraftVecUtils.scale(p3.sub(p2),0.5)) if not proj: ed = DraftGeomUtils.vec(base) proj = ed.cross(Vector(0,0,1)) if not proj: norm = Vector(0,0,1) else: norm = DraftVecUtils.neg(p3.sub(p2).cross(proj)) if not DraftVecUtils.isNull(norm): norm.normalize() va = get3DView().getViewDirection() if va.getAngle(norm) < math.pi/2: norm = DraftVecUtils.neg(norm) u = p3.sub(p2) u.normalize() c = get3DView().getCameraNode() r = c.orientation.getValue() ru = Vector(r.multVec(coin.SbVec3f(1,0,0)).getValue()) if ru.getAngle(u) > math.pi/2: u = DraftVecUtils.neg(u) v = norm.cross(u) offset = DraftVecUtils.scaleTo(v,obj.ViewObject.FontSize*.2) if obj.ViewObject: if hasattr(obj.ViewObject,"DisplayMode"): if obj.ViewObject.DisplayMode == "3D": offset = DraftVecUtils.neg(offset) if hasattr(obj.ViewObject,"TextPosition"): if obj.ViewObject.TextPosition == Vector(0,0,0): tbase = midpoint.add(offset) else: tbase = obj.ViewObject.TextPosition else: tbase = midpoint.add(offset) else: tbase = midpoint rot = FreeCAD.Placement(DraftVecUtils.getPlaneRotation(u,v,norm)).Rotation.Q return p1,p2,p3,p4,tbase,norm,rot def attach(self, obj): self.Object = obj.Object p1,p2,p3,p4,tbase,norm,rot = self.calcGeom(obj.Object) self.color = coin.SoBaseColor() self.color.rgb.setValue(obj.LineColor[0], obj.LineColor[1], obj.LineColor[2]) self.font = coin.SoFont() self.font3d = coin.SoFont() self.text = coin.SoAsciiText() self.text3d = coin.SoText2() self.text.justification = self.text3d.justification = coin.SoAsciiText.CENTER self.text.string = self.text3d.string = '' self.textpos = coin.SoTransform() self.textpos.translation.setValue([tbase.x,tbase.y,tbase.z]) tm = DraftVecUtils.getPlaneRotation(p3.sub(p2),norm) rm = coin.SbRotation() self.textpos.rotation = rm label = coin.SoSeparator() label.addChild(self.textpos) label.addChild(self.color) label.addChild(self.font) label.addChild(self.text) label3d = coin.SoSeparator() label3d.addChild(self.textpos) label3d.addChild(self.color) label3d.addChild(self.font3d) label3d.addChild(self.text3d) self.coord1 = coin.SoCoordinate3() self.coord1.point.setValue((p2.x,p2.y,p2.z)) self.coord2 = coin.SoCoordinate3() self.coord2.point.setValue((p3.x,p3.y,p3.z)) marks = coin.SoAnnotation() marks.addChild(self.color) marks.addChild(self.coord1) marks.addChild(dimSymbol()) marks.addChild(self.coord2) marks.addChild(dimSymbol()) self.drawstyle = coin.SoDrawStyle() self.drawstyle.lineWidth = 1 self.line = coin.SoLineSet() self.coords = coin.SoCoordinate3() selnode=coin.SoType.fromName("SoFCSelection").createInstance() selnode.documentName.setValue(FreeCAD.ActiveDocument.Name) selnode.objectName.setValue(obj.Object.Name) selnode.subElementName.setValue("Line") selnode.addChild(self.line) self.node = coin.SoGroup() self.node.addChild(self.color) self.node.addChild(self.drawstyle) self.node.addChild(self.coords) self.node.addChild(selnode) self.node.addChild(marks) self.node.addChild(label) self.node3d = coin.SoGroup() self.node3d.addChild(self.color) self.node3d.addChild(self.drawstyle) self.node3d.addChild(self.coords) self.node3d.addChild(selnode) self.node3d.addChild(marks) self.node3d.addChild(label3d) obj.addDisplayMode(self.node,"2D") obj.addDisplayMode(self.node3d,"3D") self.onChanged(obj,"FontSize") self.onChanged(obj,"FontName") def updateData(self, obj, prop): try: dm = obj.ViewObject.DisplayMode except: dm = "2D" text = None if obj.Base and obj.LinkedVertices: if "Shape" in obj.Base.PropertiesList: if len(obj.LinkedVertices) == 3: # arc linked dimension e = obj.Base.Shape.Edges[obj.LinkedVertices[0]] c = e.Curve.Center bray = DraftVecUtils.scaleTo(obj.Dimline.sub(c),e.Curve.Radius) if obj.LinkedVertices[1] == 1: v1 = c else: v1 = c.add(DraftVecUtils.neg(bray)) v2 = c.add(bray) else: # linear linked dimension v1 = obj.Base.Shape.Vertexes[obj.LinkedVertices[0]].Point v2 = obj.Base.Shape.Vertexes[obj.LinkedVertices[1]].Point if v1 != obj.Start: obj.Start = v1 if v2 != obj.End: obj.End = v2 p1,p2,p3,p4,tbase,norm,rot = self.calcGeom(obj) if 'Override' in obj.ViewObject.PropertiesList: text = str(obj.ViewObject.Override) dtext = getParam("dimPrecision") dtext = "%."+str(dtext)+"f" dtext = (dtext % p3.sub(p2).Length) if text: text = text.replace("dim",dtext) else: text = dtext if hasattr(self,"text"): self.text.string = self.text3d.string = text self.textpos.rotation = coin.SbRotation(rot[0],rot[1],rot[2],rot[3]) self.textpos.translation.setValue([tbase.x,tbase.y,tbase.z]) if dm == "2D": self.coords.point.setValues([[p1.x,p1.y,p1.z], [p2.x,p2.y,p2.z], [p3.x,p3.y,p3.z], [p4.x,p4.y,p4.z]]) self.line.numVertices.setValue(4) else: ts = (len(text)*obj.ViewObject.FontSize)/4 rm = ((p3.sub(p2)).Length/2)-ts p2a = p2.add(DraftVecUtils.scaleTo(p3.sub(p2),rm)) p2b = p3.add(DraftVecUtils.scaleTo(p2.sub(p3),rm)) self.coords.point.setValues([[p1.x,p1.y,p1.z], [p2.x,p2.y,p2.z], [p2a.x,p2a.y,p2a.z], [p2b.x,p2b.y,p2b.z], [p3.x,p3.y,p3.z], [p4.x,p4.y,p4.z]]) self.line.numVertices.setValues([3,3]) self.coord1.point.setValue((p2.x,p2.y,p2.z)) self.coord2.point.setValue((p3.x,p3.y,p3.z)) def onChanged(self, vp, prop): self.Object = vp.Object if prop == "FontSize": self.font.size = vp.FontSize self.font3d.size = vp.FontSize*100 elif prop == "FontName": self.font.name = self.font3d.name = str(vp.FontName) elif prop == "LineColor": c = vp.LineColor self.color.rgb.setValue(c[0],c[1],c[2]) elif prop == "LineWidth": self.drawstyle.lineWidth = vp.LineWidth else: self.drawstyle.lineWidth = vp.LineWidth self.updateData(vp.Object, None) def getDisplayModes(self,obj): return ["2D","3D"] def getDefaultDisplayMode(self): if hasattr(self,"defaultmode"): return self.defaultmode else: return "2D" def setDisplayMode(self,mode): return mode def getIcon(self): if self.Object.Base: return """ /* XPM */ static char * dim_xpm[] = { "16 16 6 1", " c None", ". c #000000", "+ c #FFFF00", "@ c #FFFFFF", "$ c #141010", "# c #615BD2", " $$$$$$$$", " $##$$#$$", " . $##$$##$", " .. $##$$##$", " .+. $######$", " .++. $##$$##$", " .+++. .$##$$##$", ".++++. .$######$", " .+++. .$$$$$$$$" " .++. .++. ", " .+. .+. ", " .. .. ", " . . ", " ", " ", " "}; """ else: return """ /* XPM */ static char * dim_xpm[] = { "16 16 4 1", " c None", ". c #000000", "+ c #FFFF00", "@ c #FFFFFF", " ", " ", " . . ", " .. .. ", " .+. .+. ", " .++. .++. ", " .+++. .. .+++. ", ".++++. .. .++++.", " .+++. .. .+++. ", " .++. .++. ", " .+. .+. ", " .. .. ", " . . ", " ", " ", " "}; """ def __getstate__(self): return self.Object.ViewObject.DisplayMode def __setstate__(self,state): if state: self.defaultmode = state self.setDisplayMode(state) class _AngularDimension: "The AngularDimension object" def __init__(self, obj): obj.addProperty("App::PropertyAngle","FirstAngle","Base", "Start angle of the dimension") obj.addProperty("App::PropertyAngle","LastAngle","Base", "End angle of the dimension") obj.addProperty("App::PropertyVector","Dimline","Base", "Point through which the dimension line passes") obj.addProperty("App::PropertyVector","Center","Base", "The center point of this dimension") obj.FirstAngle = 0 obj.LastAngle = 90 obj.Dimline = FreeCAD.Vector(0,1,0) obj.Center = FreeCAD.Vector(0,0,0) obj.Proxy = self self.Type = "AngularDimension" def onChanged(self, fp, prop): pass def execute(self, fp): if fp.ViewObject: fp.ViewObject.update() class _ViewProviderAngularDimension: "A View Provider for the Angular Dimension object" def __init__(self, obj): obj.addProperty("App::PropertyLength","FontSize","Base","Font size") obj.addProperty("App::PropertyString","FontName","Base","Font name") obj.addProperty("App::PropertyLength","LineWidth","Base","Line width") obj.addProperty("App::PropertyColor","LineColor","Base","Line color") obj.addProperty("App::PropertyVector","TextPosition","Base","The position of the text. Leave (0,0,0) for automatic position") obj.addProperty("App::PropertyString","Override","Base","Text override. Use 'dim' to insert the dimension length") obj.Proxy = self obj.FontSize=getParam("textheight") obj.FontName=getParam("textfont") obj.Override = '' def attach(self, vobj): self.Object = vobj.Object self.arc = None c,tbase,trot,p2,p3 = self.calcGeom(vobj.Object) self.color = coin.SoBaseColor() self.color.rgb.setValue(vobj.LineColor[0], vobj.LineColor[1], vobj.LineColor[2]) self.font = coin.SoFont() self.font3d = coin.SoFont() self.text = coin.SoAsciiText() self.text3d = coin.SoText2() self.text.justification = self.text3d.justification = coin.SoAsciiText.CENTER self.text.string = self.text3d.string = '' self.textpos = coin.SoTransform() self.textpos.translation.setValue([tbase.x,tbase.y,tbase.z]) self.textpos.rotation = coin.SbRotation() label = coin.SoSeparator() label.addChild(self.textpos) label.addChild(self.color) label.addChild(self.font) label.addChild(self.text) label3d = coin.SoSeparator() label3d.addChild(self.textpos) label3d.addChild(self.color) label3d.addChild(self.font3d) label3d.addChild(self.text3d) self.coord1 = coin.SoCoordinate3() self.coord1.point.setValue((p2.x,p2.y,p2.z)) self.coord2 = coin.SoCoordinate3() self.coord2.point.setValue((p3.x,p3.y,p3.z)) marks = coin.SoAnnotation() marks.addChild(self.color) marks.addChild(self.coord1) marks.addChild(dimSymbol()) marks.addChild(self.coord2) marks.addChild(dimSymbol()) self.drawstyle = coin.SoDrawStyle() self.drawstyle.lineWidth = 1 self.coords = coin.SoCoordinate3() self.selnode=coin.SoType.fromName("SoFCSelection").createInstance() self.selnode.documentName.setValue(FreeCAD.ActiveDocument.Name) self.selnode.objectName.setValue(vobj.Object.Name) self.selnode.subElementName.setValue("Arc") self.node = coin.SoGroup() self.node.addChild(self.color) self.node.addChild(self.drawstyle) self.node.addChild(self.coords) self.node.addChild(self.selnode) self.node.addChild(marks) self.node.addChild(label) self.node3d = coin.SoGroup() self.node3d.addChild(self.color) self.node3d.addChild(self.drawstyle) self.node3d.addChild(self.coords) self.node3d.addChild(self.selnode) self.node3d.addChild(marks) self.node3d.addChild(label3d) vobj.addDisplayMode(self.node,"2D") vobj.addDisplayMode(self.node3d,"3D") self.onChanged(vobj,"FontSize") self.onChanged(vobj,"FontName") def calcGeom(self,obj): import Part, DraftGeomUtils rad = (obj.Dimline.sub(obj.Center)).Length cir = Part.makeCircle(rad,obj.Center,Vector(0,0,1),obj.FirstAngle,obj.LastAngle) cp = DraftGeomUtils.findMidpoint(cir.Edges[0]) rv = cp.sub(obj.Center) rv = DraftVecUtils.scaleTo(rv,rv.Length + obj.ViewObject.FontSize*.2) tbase = obj.Center.add(rv) trot = DraftVecUtils.angle(rv)-math.pi/2 if (trot > math.pi/2) or (trot < -math.pi/2): trot = trot + math.pi s = getParam("dimorientation") if s == 0: if round(trot,precision()) == round(-math.pi/2,precision()): trot = math.pi/2 return cir, tbase, trot, cir.Vertexes[0].Point, cir.Vertexes[-1].Point def updateData(self, obj, prop): text = None ivob = None c,tbase,trot,p2,p3 = self.calcGeom(obj) buf=c.writeInventor(2,0.01) ivin = coin.SoInput() ivin.setBuffer(buf) ivob = coin.SoDB.readAll(ivin) arc = ivob.getChildren()[1] # In case reading from buffer failed if ivob and ivob.getNumChildren() > 1: arc = ivob.getChild(1).getChild(0) arc.removeChild(arc.getChild(0)) arc.removeChild(arc.getChild(0)) if self.arc: self.selnode.removeChild(self.arc) self.arc = arc self.selnode.addChild(self.arc) if 'Override' in obj.ViewObject.PropertiesList: text = str(obj.ViewObject.Override) dtext = getParam("dimPrecision") dtext = "%."+str(dtext)+"f" if obj.LastAngle > obj.FirstAngle: dtext = (dtext % (obj.LastAngle-obj.FirstAngle))+'\xb0' else: dtext = (dtext % ((360-obj.FirstAngle)+obj.LastAngle))+'\xb0' if text: text = text.replace("dim",dtext) else: text = dtext self.text.string = self.text3d.string = text self.textpos.translation.setValue([tbase.x,tbase.y,tbase.z]) m = FreeCAD.Matrix() m.rotateZ(trot) tm = FreeCAD.Placement(m).Rotation.Q self.textpos.rotation = coin.SbRotation(tm[0],tm[1],tm[2],tm[3]) self.coord1.point.setValue((p2.x,p2.y,p2.z)) self.coord2.point.setValue((p3.x,p3.y,p3.z)) def onChanged(self, vobj, prop): if prop == "FontSize": self.font.size = vobj.FontSize self.font3d.size = vobj.FontSize*100 elif prop == "FontName": self.font.name = self.font3d.name = str(vobj.FontName) elif prop == "LineColor": c = vobj.LineColor self.color.rgb.setValue(c[0],c[1],c[2]) elif prop == "LineWidth": self.drawstyle.lineWidth = vobj.LineWidth elif prop == "DisplayMode": pass else: self.updateData(vobj.Object, None) def getDisplayModes(self,obj): modes=[] modes.extend(["2D","3D"]) return modes def getDefaultDisplayMode(self): return "2D" def getIcon(self): return """ /* XPM */ static char * dim_xpm[] = { "16 16 4 1", " c None", ". c #000000", "+ c #FFFF00", "@ c #FFFFFF", " ", " ", " . . ", " .. .. ", " .+. .+. ", " .++. .++. ", " .+++. .. .+++. ", ".++++. .. .++++.", " .+++. .. .+++. ", " .++. .++. ", " .+. .+. ", " .. .. ", " . . ", " ", " ", " "}; """ class _Rectangle: "The Rectangle object" def __init__(self, obj): obj.addProperty("App::PropertyDistance","Length","Base","Length of the rectangle") obj.addProperty("App::PropertyDistance","Height","Base","Height of the rectange") obj.addProperty("App::PropertyDistance","FilletRadius","Base","Radius to use to fillet the corners") obj.Proxy = self obj.Length=1 obj.Height=1 self.Type = "Rectangle" def execute(self, fp): self.createGeometry(fp) def onChanged(self, fp, prop): if prop in ["Length","Height"]: self.createGeometry(fp) def createGeometry(self,fp): import Part, DraftGeomUtils plm = fp.Placement p1 = Vector(0,0,0) p2 = Vector(p1.x+fp.Length,p1.y,p1.z) p3 = Vector(p1.x+fp.Length,p1.y+fp.Height,p1.z) p4 = Vector(p1.x,p1.y+fp.Height,p1.z) shape = Part.makePolygon([p1,p2,p3,p4,p1]) if "FilletRadius" in fp.PropertiesList: if fp.FilletRadius != 0: w = DraftGeomUtils.filletWire(shape,fp.FilletRadius) if w: shape = w shape = Part.Face(shape) fp.Shape = shape fp.Placement = plm class _ViewProviderRectangle(_ViewProviderDraft): "A View Provider for the Rectangle object" def __init__(self, vobj): _ViewProviderDraft.__init__(self,vobj) vobj.addProperty("App::PropertyFile","TextureImage", "Base","Uses an image as a texture map") def attach(self,vobj): self.texture = None self.Object = vobj.Object def onChanged(self, vp, prop): if prop == "TextureImage": r = vp.RootNode if os.path.exists(vp.TextureImage): im = loadTexture(vp.TextureImage) if im: self.texture = coin.SoTexture2() self.texture.image = im r.insertChild(self.texture,1) else: if self.texture: r.removeChild(self.texture) self.texture = None return class _Circle: "The Circle object" def __init__(self, obj): obj.addProperty("App::PropertyAngle","FirstAngle","Base", "Start angle of the arc") obj.addProperty("App::PropertyAngle","LastAngle","Base", "End angle of the arc (for a full circle, give it same value as First Angle)") obj.addProperty("App::PropertyDistance","Radius","Base", "Radius of the circle") obj.Proxy = self self.Type = "Circle" def execute(self, fp): self.createGeometry(fp) def onChanged(self, fp, prop): if prop in ["Radius","FirstAngle","LastAngle"]: self.createGeometry(fp) def createGeometry(self,fp): import Part plm = fp.Placement shape = Part.makeCircle(fp.Radius,Vector(0,0,0), Vector(0,0,1),fp.FirstAngle,fp.LastAngle) if fp.FirstAngle == fp.LastAngle: shape = Part.Wire(shape) shape = Part.Face(shape) fp.Shape = shape fp.Placement = plm class _Wire: "The Wire object" def __init__(self, obj): obj.addProperty("App::PropertyVectorList","Points","Base", "The vertices of the wire") obj.addProperty("App::PropertyBool","Closed","Base", "If the wire is closed or not") obj.addProperty("App::PropertyLink","Base","Base", "The base object is the wire is formed from 2 objects") obj.addProperty("App::PropertyLink","Tool","Base", "The tool object is the wire is formed from 2 objects") obj.addProperty("App::PropertyVector","Start","Base", "The start point of this line") obj.addProperty("App::PropertyVector","End","Base", "The end point of this line") obj.addProperty("App::PropertyDistance","FilletRadius","Base","Radius to use to fillet the corners") obj.Proxy = self obj.Closed = False self.Type = "Wire" def execute(self, fp): self.createGeometry(fp) def onChanged(self, fp, prop): if prop in ["Points","Closed","Base","Tool","FilletRadius"]: self.createGeometry(fp) if prop == "Points": if fp.Start != fp.Points[0]: fp.Start = fp.Points[0] if fp.End != fp.Points[-1]: fp.End = fp.Points[-1] if len(fp.Points) > 2: fp.setEditorMode('Start',2) fp.setEditorMode('End',2) elif prop == "Start": pts = fp.Points if pts: if pts[0] != fp.Start: pts[0] = fp.Start fp.Points = pts elif prop == "End": pts = fp.Points if len(pts) > 1: if pts[-1] != fp.End: pts[-1] = fp.End fp.Points = pts def createGeometry(self,fp): import Part, DraftGeomUtils plm = fp.Placement if fp.Base and (not fp.Tool): if fp.Base.isDerivedFrom("Sketcher::SketchObject"): shape = fp.Base.Shape.copy() if fp.Base.Shape.isClosed(): shape = Part.Face(shape) fp.Shape = shape p = [] for v in shape.Vertexes: p.append(v.Point) if fp.Points != p: fp.Points = p elif fp.Base and fp.Tool: if fp.Base.isDerivedFrom("Part::Feature") and fp.Tool.isDerivedFrom("Part::Feature"): if (not fp.Base.Shape.isNull()) and (not fp.Tool.Shape.isNull()): sh1 = fp.Base.Shape.copy() sh2 = fp.Tool.Shape.copy() shape = sh1.fuse(sh2) if DraftGeomUtils.isCoplanar(shape.Faces): shape = DraftGeomUtils.concatenate(shape) fp.Shape = shape p = [] for v in shape.Vertexes: p.append(v.Point) if fp.Points != p: fp.Points = p elif 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): shape = Part.makePolygon(fp.Points+[fp.Points[0]]) if "FilletRadius" in fp.PropertiesList: if fp.FilletRadius != 0: w = DraftGeomUtils.filletWire(shape,fp.FilletRadius) if w: shape = w shape = Part.Face(shape) else: edges = [] pts = fp.Points[1:] lp = fp.Points[0] for p in pts: edges.append(Part.Line(lp,p).toShape()) lp = p shape = Part.Wire(edges) if "FilletRadius" in fp.PropertiesList: if fp.FilletRadius != 0: w = DraftGeomUtils.filletWire(shape,fp.FilletRadius) if w: shape = w fp.Shape = shape fp.Placement = plm class _ViewProviderWire(_ViewProviderDraft): "A View Provider for the Wire object" def __init__(self, obj): _ViewProviderDraft.__init__(self,obj) obj.addProperty("App::PropertyBool","EndArrow","Base", "Displays a dim symbol at the end of the wire") def attach(self, obj): self.Object = obj.Object col = coin.SoBaseColor() col.rgb.setValue(obj.LineColor[0], obj.LineColor[1], obj.LineColor[2]) self.coords = coin.SoCoordinate3() self.pt = coin.SoAnnotation() self.pt.addChild(col) self.pt.addChild(self.coords) self.pt.addChild(dimSymbol()) def updateData(self, obj, prop): if prop == "Points": if obj.Points: p = obj.Points[-1] self.coords.point.setValue((p.x,p.y,p.z)) return def onChanged(self, vp, prop): if prop == "EndArrow": rn = vp.RootNode if vp.EndArrow: rn.addChild(self.pt) else: rn.removeChild(self.pt) return def claimChildren(self): return [self.Object.Base,self.Object.Tool] class _Polygon: "The Polygon object" def __init__(self, obj): obj.addProperty("App::PropertyInteger","FacesNumber","Base","Number of faces") obj.addProperty("App::PropertyDistance","Radius","Base","Radius of the control circle") obj.addProperty("App::PropertyEnumeration","DrawMode","Base","How the polygon must be drawn from the control circle") obj.addProperty("App::PropertyDistance","FilletRadius","Base","Radius to use to fillet the corners") obj.DrawMode = ['inscribed','circumscribed'] obj.FacesNumber = 3 obj.Radius = 1 obj.Proxy = self self.Type = "Polygon" def execute(self, fp): self.createGeometry(fp) def onChanged(self, fp, prop): if prop in ["FacesNumber","Radius","DrawMode"]: self.createGeometry(fp) def createGeometry(self,fp): import Part, DraftGeomUtils plm = fp.Placement angle = (math.pi*2)/fp.FacesNumber if fp.DrawMode == 'inscribed': delta = fp.Radius else: delta = fp.Radius/math.cos(angle/2) pts = [Vector(delta,0,0)] for i in range(fp.FacesNumber-1): ang = (i+1)*angle pts.append(Vector(delta*math.cos(ang),delta*math.sin(ang),0)) pts.append(pts[0]) shape = Part.makePolygon(pts) if "FilletRadius" in fp.PropertiesList: if fp.FilletRadius != 0: w = DraftGeomUtils.filletWire(shape,fp.FilletRadius) if w: shape = w shape = Part.Face(shape) fp.Shape = shape fp.Placement = plm class _DrawingView: def __init__(self, obj): obj.addProperty("App::PropertyVector","Direction","Shape View","Projection direction") obj.addProperty("App::PropertyFloat","LineWidth","Drawing View","The width of the lines inside this object") obj.addProperty("App::PropertyFloat","FontSize","Drawing View","The size of the texts inside this object") obj.addProperty("App::PropertyLink","Source","Base","The linked object") obj.addProperty("App::PropertyEnumeration","FillStyle","Drawing View","Shape Fill Style") fills = ['shape color'] for f in FreeCAD.svgpatterns.keys(): fills.append(f) obj.FillStyle = fills obj.Proxy = self obj.LineWidth = 0.35 obj.FontSize = 12 self.Type = "DrawingView" def execute(self, obj): if obj.Source: obj.ViewResult = self.updateSVG(obj) def onChanged(self, obj, prop): if prop in ["X","Y","Scale","LineWidth","FontSize","FillStyle","Direction"]: obj.ViewResult = self.updateSVG(obj) def updateSVG(self, obj): "encapsulates a svg fragment into a transformation node" svg = getSVG(obj.Source,obj.Scale,obj.LineWidth,obj.FontSize,obj.FillStyle,obj.Direction) result = '' result += ' 2): spline = Part.BSplineCurve() spline.interpolate(fp.Points, True) # DNC: bug fix: convert to face if closed shape = Part.Wire(spline.toShape()) shape = Part.Face(shape) fp.Shape = shape else: spline = Part.BSplineCurve() spline.interpolate(fp.Points, False) fp.Shape = spline.toShape() fp.Placement = plm class _ViewProviderBSpline(_ViewProviderDraft): "A View Provider for the BSPline object" def __init__(self, obj): _ViewProviderDraft.__init__(self,obj) obj.addProperty("App::PropertyBool","EndArrow", "Base","Displays a dim symbol at the end of the wire") col = coin.SoBaseColor() col.rgb.setValue(obj.LineColor[0], obj.LineColor[1], obj.LineColor[2]) self.coords = coin.SoCoordinate3() self.pt = coin.SoAnnotation() self.pt.addChild(col) self.pt.addChild(self.coords) self.pt.addChild(dimSymbol()) def updateData(self, obj, prop): if prop == "Points": if obj.Points: p = obj.Points[-1] if hasattr(self,"coords"): self.coords.point.setValue((p.x,p.y,p.z)) return def onChanged(self, vp, prop): if prop == "EndArrow": rn = vp.RootNode if vp.EndArrow: rn.addChild(self.pt) else: rn.removeChild(self.pt) return class _Block: "The Block object" def __init__(self, obj): obj.addProperty("App::PropertyLinkList","Components","Base", "The components of this block") obj.Proxy = self self.Type = "Block" def execute(self, fp): self.createGeometry(fp) def onChanged(self, fp, prop): if prop in ["Components"]: self.createGeometry(fp) def createGeometry(self,fp): import Part plm = fp.Placement shps = [] for c in fp.Components: shps.append(c.Shape) if shps: shape = Part.makeCompound(shps) fp.Shape = shape fp.Placement = plm class _Shape2DView: "The Shape2DView object" def __init__(self,obj): obj.addProperty("App::PropertyLink","Base","Base", "The base object this 2D view must represent") obj.addProperty("App::PropertyVector","Projection","Base", "The projection vector of this object") obj.Projection = Vector(0,0,-1) obj.Proxy = self self.Type = "2DShapeView" def execute(self,obj): self.createGeometry(obj) def onChanged(self,obj,prop): if prop in ["Projection","Base"]: self.createGeometry(obj) def createGeometry(self,obj): import Drawing, DraftGeomUtils pl = obj.Placement if obj.Base: if obj.Base.isDerivedFrom("Part::Feature"): if not DraftVecUtils.isNull(obj.Projection): [visibleG0,visibleG1,hiddenG0,hiddenG1] = Drawing.project(obj.Base.Shape,obj.Projection) if visibleG0: obj.Shape = visibleG0 if not DraftGeomUtils.isNull(pl): obj.Placement = pl class _Array: "The Draft Array object" def __init__(self,obj): obj.addProperty("App::PropertyLink","Base","Base", "The base object that must be duplicated") obj.addProperty("App::PropertyEnumeration","ArrayType","Base", "The type of array to create") obj.addProperty("App::PropertyVector","Axis","Base", "The axis direction") obj.addProperty("App::PropertyInteger","NumberX","Base", "Number of copies in X direction") obj.addProperty("App::PropertyInteger","NumberY","Base", "Number of copies in Y direction") obj.addProperty("App::PropertyInteger","NumberZ","Base", "Number of copies in Z direction") obj.addProperty("App::PropertyInteger","NumberPolar","Base", "Number of copies") obj.addProperty("App::PropertyVector","IntervalX","Base", "Distance and orientation of intervals in X direction") obj.addProperty("App::PropertyVector","IntervalY","Base", "Distance and orientation of intervals in Y direction") obj.addProperty("App::PropertyVector","IntervalZ","Base", "Distance and orientation of intervals in Z direction") obj.addProperty("App::PropertyVector","Center","Base", "Center point") obj.addProperty("App::PropertyAngle","Angle","Base", "Angle to cover with copies") obj.Proxy = self self.Type = "Array" obj.ArrayType = ['ortho','polar'] obj.NumberX = 1 obj.NumberY = 1 obj.NumberZ = 1 obj.NumberPolar = 1 obj.IntervalX = Vector(1,0,0) obj.IntervalY = Vector(0,1,0) obj.IntervalZ = Vector(0,0,1) obj.Angle = 360 obj.Axis = Vector(0,0,1) def execute(self,obj): self.createGeometry(obj) def onChanged(self,obj,prop): if prop == "ArrayType": if obj.ViewObject: if obj.ArrayType == "ortho": obj.ViewObject.setEditorMode('Axis',2) obj.ViewObject.setEditorMode('NumberPolar',2) obj.ViewObject.setEditorMode('Center',2) obj.ViewObject.setEditorMode('Angle',2) obj.ViewObject.setEditorMode('NumberX',0) obj.ViewObject.setEditorMode('NumberY',0) obj.ViewObject.setEditorMode('NumberZ',0) obj.ViewObject.setEditorMode('IntervalX',0) obj.ViewObject.setEditorMode('IntervalY',0) obj.ViewObject.setEditorMode('IntervalZ',0) else: obj.ViewObject.setEditorMode('Axis',0) obj.ViewObject.setEditorMode('NumberPolar',0) obj.ViewObject.setEditorMode('Center',0) obj.ViewObject.setEditorMode('Angle',0) obj.ViewObject.setEditorMode('NumberX',2) obj.ViewObject.setEditorMode('NumberY',2) obj.ViewObject.setEditorMode('NumberY',2) obj.ViewObject.setEditorMode('IntervalX',2) obj.ViewObject.setEditorMode('IntervalY',2) obj.ViewObject.setEditorMode('IntervalZ',2) if prop in ["ArrayType","NumberX","NumberY","NumberZ","NumberPolar", "IntervalX","IntervalY","IntervalZ","Angle","Center","Axis"]: self.createGeometry(obj) def createGeometry(self,obj): import DraftGeomUtils if obj.Base: pl = obj.Placement if obj.ArrayType == "ortho": sh = self.rectArray(obj.Base.Shape,obj.IntervalX,obj.IntervalY, obj.IntervalZ,obj.NumberX,obj.NumberY,obj.NumberZ) else: sh = self.polarArray(obj.Base.Shape,obj.Center,obj.Angle,obj.NumberPolar,obj.Axis) obj.Shape = sh if not DraftGeomUtils.isNull(pl): obj.Placement = pl def rectArray(self,shape,xvector,yvector,zvector,xnum,ynum,znum): import Part base = [shape.copy()] for xcount in range(xnum): currentxvector=DraftVecUtils.scale(xvector,xcount) if not xcount==0: nshape = shape.copy() nshape.translate(currentxvector) base.append(nshape) for ycount in range(ynum): currentyvector=FreeCAD.Vector(currentxvector) currentyvector=currentyvector.add(DraftVecUtils.scale(yvector,ycount)) if not ycount==0: nshape = shape.copy() nshape.translate(currentyvector) base.append(nshape) for zcount in range(znum): currentzvector=FreeCAD.Vector(currentyvector) currentzvector=currentzvector.add(DraftVecUtils.scale(zvector,zcount)) if not zcount==0: nshape = shape.copy() nshape.translate(currentzvector) base.append(nshape) return Part.makeCompound(base) def polarArray(self,shape,center,angle,num,axis): import Part fraction = angle/num base = [shape.copy()] for i in range(num): currangle = fraction + (i*fraction) nshape = shape.copy() nshape.rotate(DraftVecUtils.tup(center), DraftVecUtils.tup(axis), currangle) base.append(nshape) return Part.makeCompound(base) class _Point: def __init__(self, obj,x,y,z): obj.addProperty("App::PropertyFloat","X","Point","Location").X = x obj.addProperty("App::PropertyFloat","Y","Point","Location").Y = y obj.addProperty("App::PropertyFloat","Z","Point","Location").Z = z mode = 2 obj.setEditorMode('Placement',mode) obj.Proxy = self self.Type = "Point" def execute(self, fp): self.createGeometry(fp) def createGeometry(self,fp): import Part shape = Part.Vertex(Vector(fp.X,fp.Y,fp.Z)) fp.Shape = shape class _ViewProviderPoint: def __init__(self, obj): obj.Proxy = self def onChanged(self, vp, prop): mode = 2 vp.setEditorMode('LineColor',mode) vp.setEditorMode('LineWidth',mode) vp.setEditorMode('BoundingBox',mode) vp.setEditorMode('ControlPoints',mode) vp.setEditorMode('Deviation',mode) vp.setEditorMode('DiffuseColor',mode) vp.setEditorMode('DisplayMode',mode) vp.setEditorMode('Lighting',mode) vp.setEditorMode('LineMaterial',mode) vp.setEditorMode('ShapeColor',mode) vp.setEditorMode('ShapeMaterial',mode) vp.setEditorMode('Transparency',mode) def getIcon(self): return ":/icons/Draft_Dot.svg" class _Clone: "The Clone object" def __init__(self,obj): obj.addProperty("App::PropertyLinkList","Objects","Base", "The objects included in this scale object") obj.addProperty("App::PropertyVector","Scale","Base", "The scale vector of this object") obj.Scale = Vector(1,1,1) obj.Proxy = self self.Type = "Clone" def execute(self,obj): self.createGeometry(obj) def onChanged(self,obj,prop): if prop in ["Scale","Objects"]: self.createGeometry(obj) def createGeometry(self,obj): import Part, DraftGeomUtils pl = obj.Placement shapes = [] for o in obj.Objects: if o.isDerivedFrom("Part::Feature"): sh = o.Shape.copy() m = FreeCAD.Matrix() if hasattr(obj,"Scale") and not sh.isNull(): m.scale(obj.Scale) sh = sh.transformGeometry(m) if not sh.isNull(): shapes.append(sh) if shapes: if len(shapes) == 1: obj.Shape = shapes[0] else: obj.Shape = Part.makeCompound(shapes) if not DraftGeomUtils.isNull(pl): obj.Placement = pl class _ViewProviderDraftPart(_ViewProviderDraft): "a view provider that displays a Part icon instead of a Draft icon" def __init__(self,vobj): _ViewProviderDraft.__init__(self,vobj) def getIcon(self): return ":/icons/Tree_Part.svg" def claimChildren(self): return [] class _ViewProviderClone(_ViewProviderDraft): "a view provider that displays a Part icon instead of a Draft icon" def __init__(self,vobj): _ViewProviderDraft.__init__(self,vobj) def getIcon(self): return ":/icons/Draft_Clone.svg" def claimChildren(self): return [] if not hasattr(FreeCADGui,"Snapper"): import DraftSnap