The DXF exporter now features an option in Draft preferences to project the selected objects along the current view direction on export.
3664 lines
145 KiB
Python
3664 lines
145 KiB
Python
#***************************************************************************
|
|
#* *
|
|
#* Copyright (c) 2009, 2010 *
|
|
#* Yorik van Havre <yorik@uncreated.net>, Ken Cline <cline@frii.com> *
|
|
#* *
|
|
#* 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
|
|
|
|
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","UsePartPrimitives"]:
|
|
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)) == "<type 'View3DInventorPy'>":
|
|
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")
|
|
from pivy import coin
|
|
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:
|
|
import DraftGeomUtils
|
|
if DraftGeomUtils.geomType(shape.Edges[0]) == "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 removeHidden(objectslist):
|
|
"""removeHidden(objectslist): removes hidden objects from the list"""
|
|
newlist = objectslist[:]
|
|
for o in objectslist:
|
|
if o.ViewObject:
|
|
if not o.ViewObject.isVisible():
|
|
newlist.remove(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
|
|
if hasattr(matchrep,"DiffuseColor") and hasattr(obrep,"DiffuseColor"):
|
|
obrep.DiffuseColor = matchrep.DiffuseColor
|
|
|
|
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, DraftGeomUtils
|
|
if placement: typecheck([(placement,FreeCAD.Placement)], "makeCircle")
|
|
obj = FreeCAD.ActiveDocument.addObject("Part::Part2DObjectPython","Circle")
|
|
_Circle(obj)
|
|
if isinstance(radius,Part.Edge):
|
|
edge = radius
|
|
if DraftGeomUtils.geomType(edge) == "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:
|
|
_ViewProviderDraft(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 makeEllipse(majradius,minradius,placement=None):
|
|
'''makeEllipse(majradius,minradius,[placement]): makes
|
|
an ellipse with the given major and minor radius, and optionally
|
|
a placement.'''
|
|
import Part
|
|
e = Part.Ellipse(FreeCAD.Vector(0,0,0),majradius,minradius)
|
|
newobj = FreeCAD.ActiveDocument.addObject("Part::Feature","Ellipse")
|
|
newobj.Shape = e.toShape()
|
|
if placement: newobj.Placement = placement
|
|
FreeCAD.ActiveDocument.recompute()
|
|
return newobj
|
|
|
|
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:
|
|
_ViewProviderClone(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,delete=True):
|
|
'''draftify(objectslist,[makeblock],[delete]): 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.
|
|
If delete = False, old objects are not deleted'''
|
|
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 (DraftGeomUtils.geomType(w.Edges[0]) == "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)
|
|
if delete:
|
|
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
|
|
pointratio = 4 # the number of times the dots are smaller than the font size
|
|
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 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):
|
|
import DraftGeomUtils
|
|
svg ='<path id="' + name + '" '
|
|
edges = DraftGeomUtils.sortEdges(edges)
|
|
v = getProj(edges[0].Vertexes[0].Point)
|
|
svg += 'd="M '+ str(v.x) +' '+ str(v.y) + ' '
|
|
for e in edges:
|
|
if (DraftGeomUtils.geomType(e) == "Line") or (DraftGeomUtils.geomType(e) == "BSplineCurve"):
|
|
v = getProj(e.Vertexes[-1].Point)
|
|
svg += 'L '+ str(v.x) +' '+ str(v.y) + ' '
|
|
elif DraftGeomUtils.geomType(e) == "Circle":
|
|
if len(e.Vertexes) == 1:
|
|
# complete circle
|
|
svg = getCircle(e)
|
|
return svg
|
|
r = e.Curve.Radius
|
|
drawing_plane_normal = FreeCAD.DraftWorkingPlane.axis
|
|
if plane: drawing_plane_normal = plane.axis
|
|
flag_large_arc = (((e.ParameterRange[1] - e.ParameterRange[0]) / math.pi) % 2) > 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 = '<circle cx="' + str(cen.x)
|
|
svg += '" cy="' + str(cen.y)
|
|
svg += '" r="' + str(rad)+'" '
|
|
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
|
|
|
|
if getType(obj) == "Dimension":
|
|
p1,p2,p3,p4,tbase,norm,rot = obj.ViewObject.Proxy.calcGeom(obj)
|
|
dimText = getParam("dimPrecision")
|
|
dimText = "%."+str(dimText)+"f"
|
|
p1 = getProj(p1)
|
|
p2 = getProj(p2)
|
|
p3 = getProj(p3)
|
|
p4 = getProj(p4)
|
|
tbase = getProj(tbase)
|
|
svg = '<g id="'+obj.Name+'"><path '
|
|
if obj.ViewObject.DisplayMode == "2D":
|
|
m = FreeCAD.Placement()
|
|
m.Rotation.Q = rot
|
|
m = m.toMatrix()
|
|
if plane:
|
|
vtext = m.multiply(plane.u)
|
|
else:
|
|
vtext = m.multiply(Vector(1,0,0))
|
|
angle = -DraftVecUtils.angle(vtext)
|
|
svg += 'd="M '+str(p1.x)+' '+str(p1.y)+' '
|
|
svg += 'L '+str(p2.x)+' '+str(p2.y)+' '
|
|
svg += 'L '+str(p3.x)+' '+str(p3.y)+' '
|
|
svg += 'L '+str(p4.x)+' '+str(p4.y)+'" '
|
|
else:
|
|
ts = (len(dimText)*obj.ViewObject.FontSize)/4
|
|
rm = ((p3.sub(p2)).Length/2)-ts
|
|
p2a = getProj(p2.add(DraftVecUtils.scaleTo(p3.sub(p2),rm)))
|
|
p2b = getProj(p3.add(DraftVecUtils.scaleTo(p2.sub(p3),rm)))
|
|
angle = 0
|
|
svg += 'd="M '+str(p1.x)+' '+str(p1.y)+' '
|
|
svg += 'L '+str(p2.x)+' '+str(p2.y)+' '
|
|
svg += 'L '+str(p2a.x)+' '+str(p2a.y)+' '
|
|
svg += 'M '+str(p2b.x)+' '+str(p2b.y)+' '
|
|
svg += 'L '+str(p3.x)+' '+str(p3.y)+' '
|
|
svg += 'L '+str(p4.x)+' '+str(p4.y)+'" '
|
|
svg += 'fill="none" stroke="'
|
|
svg += getrgb(obj.ViewObject.LineColor) + '" '
|
|
svg += 'stroke-width="' + str(linewidth) + ' px" '
|
|
svg += 'style="stroke-width:'+ str(linewidth)
|
|
svg += ';stroke-miterlimit:4;stroke-dasharray:none" '
|
|
svg += 'freecad:basepoint1="'+str(p1.x)+' '+str(p1.y)+'" '
|
|
svg += 'freecad:basepoint2="'+str(p4.x)+' '+str(p4.y)+'" '
|
|
svg += 'freecad:dimpoint="'+str(p2.x)+' '+str(p2.y)+'"'
|
|
svg += '/>\n'
|
|
svg += '<circle cx="'+str(p2.x)+'" cy="'+str(p2.y)
|
|
svg += '" r="'+str(fontsize/pointratio)+'" '
|
|
svg += 'fill="'+ getrgb(obj.ViewObject.LineColor) +'" stroke="none" '
|
|
svg += 'style="stroke-miterlimit:4;stroke-dasharray:none" '
|
|
svg += 'freecad:skip="1"'
|
|
svg += '/>\n'
|
|
svg += '<circle cx="'+str(p3.x)+'" cy="'+str(p3.y)
|
|
svg += '" r="'+str(fontsize/pointratio)+'" '
|
|
svg += 'fill="#000000" stroke="none" '
|
|
svg += 'style="stroke-miterlimit:4;stroke-dasharray:none" '
|
|
svg += 'freecad:skip="1"'
|
|
svg += '/>\n'
|
|
svg += '<text id="' + obj.Name + '" fill="'
|
|
svg += getrgb(obj.ViewObject.LineColor) +'" font-size="'
|
|
svg += str(fontsize)+'" '
|
|
svg += 'style="text-anchor:middle;text-align:center;'
|
|
svg += 'font-family:'+obj.ViewObject.FontName+'" '
|
|
svg += 'transform="rotate('+str(math.degrees(angle))
|
|
svg += ','+ str(tbase.x) + ',' + str(tbase.y) + ') '
|
|
svg += 'translate(' + str(tbase.x) + ',' + str(tbase.y) + ') '
|
|
#svg += 'scale('+str(tmod/2000)+',-'+str(tmod/2000)+') '
|
|
svg += 'scale(1,-1) '
|
|
svg += '" freecad:skip="1"'
|
|
svg += '>\n'
|
|
svg += dimText % p3.sub(p2).Length
|
|
svg += '</text>\n</g>\n'
|
|
|
|
elif getType(obj) == "Annotation":
|
|
"returns an svg representation of a document annotation"
|
|
p = getProj(obj.Position)
|
|
svg = '<text id="' + obj.Name + '" fill="'
|
|
svg += getrgb(obj.ViewObject.TextColor)
|
|
svg += '" font-size="'
|
|
svg += str(fontsize)+'" '
|
|
svg += 'style="text-anchor:middle;text-align:center;'
|
|
svg += 'font-family:'+obj.ViewObject.FontName+'" '
|
|
svg += 'transform="'
|
|
if obj.ViewObject.RotationAxis == 'Z':
|
|
if obj.ViewObject.Rotation != 0:
|
|
svg += 'rotate('+str(obj.ViewObject.Rotation)
|
|
svg += ','+ str(p.x) + ',' + str(p.y) + ') '
|
|
svg += 'translate(' + str(p.x) + ',' + str(p.y) + ') '
|
|
#svg +='scale('+str(tmod/2000)+','+str(-tmod/2000)+')'
|
|
svg += 'scale(1,-1) '
|
|
svg += '">\n'
|
|
for l in obj.LabelText:
|
|
svg += '<tspan>'+l+'</tspan>\n'
|
|
svg += '</text>\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 += '<text fill="' + color + '" '
|
|
svg += 'font-size="' + str(rad) + '" '
|
|
svg += 'style="text-anchor:middle;'
|
|
svg += 'text-align:center;'
|
|
svg += 'font-family: Arial,sans;" '
|
|
svg += 'transform="translate(' + str(center.x+rad/4) + ',' + str(center.y-rad/3) + ') '
|
|
svg += 'scale(1,-1)"> '
|
|
svg += '<tspan>' + obj.ViewObject.Proxy.getNumber(n) + '</tspan>\n'
|
|
svg += '</text>\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 getrgb(color,testbw=True):
|
|
"""getRGB(color,[testbw]): returns a rgb value #000000 from a freecad color
|
|
if testwb = True (default), pure white will be converted into pure black"""
|
|
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 testbw:
|
|
if col == "#ffffff":
|
|
#print getParam('SvgLinesBlack')
|
|
if getParam('SvgLinesBlack'):
|
|
col = "#000000"
|
|
return col
|
|
|
|
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.
|
|
'''
|
|
if getType(obj) == "SectionPlane":
|
|
import ArchSectionPlane
|
|
viewobj = FreeCAD.ActiveDocument.addObject("Drawing::FeatureViewPython","View")
|
|
page.addObject(viewobj)
|
|
ArchSectionPlane._ArchDrawingView(viewobj)
|
|
viewobj.Source = obj
|
|
viewobj.Label = "View of "+obj.Name
|
|
else:
|
|
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,facenumbers=[]):
|
|
'''
|
|
makeShape2DView(object,[projectionVector,facenumbers]) - adds a 2D shape to the document, which is a
|
|
2D projection of the given object. A specific projection vector can also be given. You can also
|
|
specify a list of face numbers to be considered in individual faces mode.
|
|
'''
|
|
obj = FreeCAD.ActiveDocument.addObject("Part::Part2DObjectPython","Shape2DView")
|
|
_Shape2DView(obj)
|
|
if gui:
|
|
_ViewProviderDraftAlt(obj.ViewObject)
|
|
obj.Base = baseobj
|
|
if projectionVector:
|
|
obj.Projection = projectionVector
|
|
if facenumbers:
|
|
obj.FaceNumbers = facenumbers
|
|
select(obj)
|
|
return obj
|
|
|
|
def makeSketch(objectslist,autoconstraints=False,addTo=None,delete=False,name="Sketch"):
|
|
'''makeSketch(objectslist,[autoconstraints],[addTo],[delete],[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. If delete is True, the original object will be deleted'''
|
|
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":
|
|
g = (DraftGeomUtils.geom(obj.Shape.Edges[0],nobj.Placement))
|
|
nobj.addGeometry(g)
|
|
# 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"]:
|
|
if obj.FilletRadius == 0:
|
|
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
|
|
for e in obj.Shape.Edges:
|
|
if DraftGeomUtils.geomType(e) == "BSplineCurve":
|
|
print "Error: One of the selected object contains BSplines, unable to convert"
|
|
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
|
|
formatObject(nobj,obj)
|
|
if ok and delete:
|
|
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)
|
|
cl.Objects = obj
|
|
if delta:
|
|
cl.Placement.move(delta)
|
|
formatObject(cl,obj[0])
|
|
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","Part::Part2DObjectPython"]:
|
|
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):
|
|
if "BSpline" in obj.Name:
|
|
print "Healing " + obj.Name + " of type BSpline"
|
|
nobj = makeCopy(obj,force="BSpline",reparent=reparent)
|
|
else:
|
|
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)
|
|
elif ("DrawMode" in props) and ("FacesNumber" in props):
|
|
print "Healing " + obj.Name + " of type Polygon"
|
|
nobj = makeCopy(obj,force="Polygon",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)
|
|
|
|
def upgrade(objects,delete=False,force=None):
|
|
"""upgrade(objects,delete=False,force=None): Upgrades the given object(s) (can be
|
|
an object or a list of objects). If delete is True, old objects are deleted.
|
|
The force attribute can be used to
|
|
force a certain way of upgrading. It can be: makeCompound, closeGroupWires,
|
|
makeSolid, closeWire, turnToParts, makeFusion, makeShell, makeFaces, draftify,
|
|
joinFaces, makeSketchFace, makeWires
|
|
Returns a dictionnary containing two lists, a list of new objects and a list
|
|
of objects to be deleted"""
|
|
|
|
import Part, DraftGeomUtils
|
|
from DraftTools import msg,translate
|
|
|
|
if not isinstance(objects,list):
|
|
objects = [objects]
|
|
|
|
global deleteList, newList
|
|
deleteList = []
|
|
addList = []
|
|
|
|
# definitions of actions to perform
|
|
|
|
def makeCompound(objectslist):
|
|
"""returns a compound object made from the given objects"""
|
|
newobj = makeBlock(objectslist)
|
|
addList.append(newobj)
|
|
return newobj
|
|
|
|
def closeGroupWires(groupslist):
|
|
"""closes every open wire in the given groups"""
|
|
result = False
|
|
for grp in groupslist:
|
|
for obj in grp.Group:
|
|
newobj = closeWire(obj)
|
|
# add new objects to their respective groups
|
|
if newobj:
|
|
result = True
|
|
grp.addObject(newobj)
|
|
return result
|
|
|
|
def makeSolid(obj):
|
|
"""turns an object into a solid, if possible"""
|
|
if obj.Shape.Solids:
|
|
return None
|
|
sol = None
|
|
try:
|
|
sol = Part.makeSolid(obj.Shape)
|
|
except:
|
|
return None
|
|
else:
|
|
if sol:
|
|
if sol.isClosed():
|
|
newobj = FreeCAD.ActiveDocument.addObject("Part::Feature","Solid")
|
|
newobj.Shape = sol
|
|
addList.append(newobj)
|
|
deleteList.append(obj)
|
|
return newob
|
|
|
|
def closeWire(obj):
|
|
"""closes a wire object, if possible"""
|
|
if obj.Shape.Faces:
|
|
return None
|
|
if len(obj.Shape.Wires) != 1:
|
|
return None
|
|
if len(obj.Shape.Edges) == 1:
|
|
return None
|
|
if getType(obj) == "Wire":
|
|
obj.Closed = True
|
|
return True
|
|
else:
|
|
w = obj.Shape.Wires[0]
|
|
if not w.isClosed():
|
|
edges = w.Edges
|
|
p0 = w.Vertexes[0].Point
|
|
p1 = w.Vertexes[-1].Point
|
|
if p0 == p1:
|
|
# sometimes an open wire can have its start and end points identical (OCC bug)
|
|
# in that case, although it is not closed, face works...
|
|
f = Part.Face(w)
|
|
newobj = FreeCAD.ActiveDocument.addObject("Part::Feature","Face")
|
|
newobj.Shape = f
|
|
else:
|
|
edges.append(Part.Line(p1,p0).toShape())
|
|
w = Part.Wire(DraftGeomUtils.sortEdges(edges))
|
|
newobj = FreeCAD.ActiveDocument.addObject("Part::Feature","Wire")
|
|
newobj.Shape = w
|
|
addList.append(newobj)
|
|
deleteList.append(obj)
|
|
return newobj
|
|
else:
|
|
return None
|
|
|
|
def turnToParts(meshes):
|
|
"""turn given meshes to parts"""
|
|
result = False
|
|
import Arch
|
|
for mesh in meshes:
|
|
sh = Arch.getShapeFromMesh(mesh.Mesh)
|
|
if sh:
|
|
newobj = FreeCAD.ActiveDocument.addObject("Part::Feature","Shell")
|
|
newobj.Shape = sh
|
|
addList.append(newobj)
|
|
deleteList.append(mesh)
|
|
result = True
|
|
return result
|
|
|
|
def makeFusion(obj1,obj2):
|
|
"""makes a Draft or Part fusion between 2 given objects"""
|
|
newobj = fuse(obj1,obj2)
|
|
if newobj:
|
|
addList.append(newobj)
|
|
return newobj
|
|
return None
|
|
|
|
def makeShell(objectslist):
|
|
"""makes a shell with the given objects"""
|
|
faces = []
|
|
for obj in objectslist:
|
|
faces.append(obj.Shape.Faces)
|
|
sh = Part.makeShell(faces)
|
|
if sh:
|
|
if sh.Faces:
|
|
newob = FreeCAD.ActiveDocument.addObject("Part::Feature","Shell")
|
|
newob.Shape = sh
|
|
addList.append(newobj)
|
|
deleteList.extend(objectslist)
|
|
return newobj
|
|
return None
|
|
|
|
def joinFaces(objectslist):
|
|
"""makes one big face from selected objects, if possible"""
|
|
faces = []
|
|
for obj in objectslist:
|
|
faces.append(obj.Shape.Faces)
|
|
u = faces.pop(0)
|
|
for f in faces:
|
|
u = u.fuse(f)
|
|
if DraftGeomUtils.isCoplanar(faces):
|
|
u = DraftGeomUtils.concatenate(u)
|
|
if not DraftGeomUtils.hasCurves(u):
|
|
# several coplanar and non-curved faces: they can becoem a Draft wire
|
|
newobj = makeWire(u.Wires[0],closed=True,face=True)
|
|
else:
|
|
# if not possible, we do a non-parametric union
|
|
newobj = FreeCAD.ActiveDocument.addObject("Part::Feature","Union")
|
|
newobj.Shape = u
|
|
addList.append(newobj)
|
|
deleteList.extend(objectslist)
|
|
return newobj
|
|
return None
|
|
|
|
def makeSketchFace(obj):
|
|
"""Makes a Draft face out of a sketch"""
|
|
newobj = makeWire(obj.Shape,closed=True)
|
|
if newobj:
|
|
newobj.Base = obj
|
|
obj.ViewObject.Visibility = False
|
|
addList.append(newobj)
|
|
return newobj
|
|
return None
|
|
|
|
def makeFaces(objectslist):
|
|
"""make a face from every closed wire in the list"""
|
|
result = False
|
|
for o in objectslist:
|
|
for w in o.Shape.Wires:
|
|
if w.isClosed() and DraftGeomUtils.isPlanar(w):
|
|
f = Part.Face(w)
|
|
if f:
|
|
newobj = FreeCAD.ActiveDocument.addObject("Part::Feature","Face")
|
|
newobj.Shape = f
|
|
addList.append(newobj)
|
|
result = True
|
|
if not o in deleteList:
|
|
deleteList.append(o)
|
|
return result
|
|
|
|
def makeWires(objectslist):
|
|
"""joins edges in the given objects list into wires"""
|
|
edges = []
|
|
for o in objectslist:
|
|
for e in o.Shape.Edges:
|
|
edges.append(e)
|
|
try:
|
|
nedges = DraftGeomUtils.sortEdges(edges[:])
|
|
# for e in nedges: print "debug: ",e.Curve,e.Vertexes[0].Point,e.Vertexes[-1].Point
|
|
w = Part.Wire(nedges)
|
|
except:
|
|
return None
|
|
else:
|
|
if len(w.Edges) == len(edges):
|
|
newobj = FreeCAD.ActiveDocument.addObject("Part::Feature","Wire")
|
|
newobj.Shape = w
|
|
addList.append(newobj)
|
|
deleteList.extend(objectslist)
|
|
return True
|
|
return None
|
|
|
|
# analyzing what we have in our selection
|
|
|
|
edges = []
|
|
wires = []
|
|
openwires = []
|
|
faces = []
|
|
groups = []
|
|
parts = []
|
|
curves = []
|
|
facewires = []
|
|
loneedges = []
|
|
meshes = []
|
|
for ob in objects:
|
|
if ob.Type == "App::DocumentObjectGroup":
|
|
groups.append(ob)
|
|
elif ob.isDerivedFrom("Part::Feature"):
|
|
parts.append(ob)
|
|
faces.extend(ob.Shape.Faces)
|
|
wires.extend(ob.Shape.Wires)
|
|
edges.extend(ob.Shape.Edges)
|
|
for f in ob.Shape.Faces:
|
|
facewires.extend(f.Wires)
|
|
wirededges = []
|
|
for w in ob.Shape.Wires:
|
|
if len(w.Edges) > 1:
|
|
for e in w.Edges:
|
|
wirededges.append(e.hashCode())
|
|
if not w.isClosed():
|
|
openwires.append(w)
|
|
for e in ob.Shape.Edges:
|
|
if DraftGeomUtils.geomType(e) != "Line":
|
|
curves.append(e)
|
|
if not e.hashCode() in wirededges:
|
|
loneedges.append(e)
|
|
elif ob.isDerivedFrom("Mesh::Feature"):
|
|
meshes.append(ob)
|
|
objects = parts
|
|
|
|
#print "objects:",objects," edges:",edges," wires:",wires," openwires:",openwires," faces:",faces
|
|
#print "groups:",groups," curves:",curves," facewires:",facewires, "loneedges:", loneedges
|
|
|
|
if force:
|
|
if force in ["makeCompound","closeGroupWires","makeSolid","closeWire","turnToParts","makeFusion",
|
|
"makeShell","makeFaces","draftify","joinFaces","makeSketchFace","makeWires"]:
|
|
result = eval(force)(objects)
|
|
else:
|
|
msg(translate("Upgrade: Unknow force method:")+" "+force)
|
|
result = None
|
|
|
|
else:
|
|
|
|
# applying transformations automatically
|
|
|
|
result = None
|
|
|
|
# if we have a group: turn each closed wire inside into a face
|
|
if groups:
|
|
result = closeGroupWires(groups)
|
|
if result: msg(translate("draft", "Found groups: closing each open object inside\n"))
|
|
|
|
# if we have meshes, we try to turn them into shapes
|
|
elif meshes:
|
|
result = turnToParts(meshes)
|
|
if result: msg(translate("draft", "Found mesh(es): turning into Part shapes\n"))
|
|
|
|
# we have only faces here, no lone edges
|
|
elif faces and (len(wires) + len(openwires) == len(facewires)):
|
|
|
|
# we have one shell: we try to make a solid
|
|
if (len(objects) == 1) and (len(faces) > 3):
|
|
result = makeSolid(objects[0])
|
|
if result: msg(translate("draft", "Found 1 solidificable object: solidifying it\n"))
|
|
|
|
# we have exactly 2 objects: we fuse them
|
|
elif (len(objects) == 2) and (not curves):
|
|
result = makeFusion(objects[0],objects[1])
|
|
if result: msg(translate("draft", "Found 2 objects: fusing them\n"))
|
|
|
|
# we have many separate faces: we try to make a shell
|
|
elif (len(objects) > 2) and (len(faces) > 1) and (not loneedges):
|
|
result = makeShell(objects)
|
|
if result: msg(translate("draft", "Found several objects: making a shell\n"))
|
|
|
|
# we have faces: we try to join them if they are coplanar
|
|
elif len(faces) > 1:
|
|
result = joinFaces(objects)
|
|
if result: msg(translate("draft", "Found several coplanar objects or faces: making one face\n"))
|
|
|
|
# only one object: if not parametric, we "draftify" it
|
|
elif len(objects) == 1 and (not objects[0].isDerivedFrom("Part::Part2DObjectPython")):
|
|
result = draftify(objects[0])
|
|
if result: msg(translate("draft", "Found 1 non-parametric objects: draftifying it\n"))
|
|
|
|
# we have only closed wires, no faces
|
|
elif wires and (not faces) and (not openwires):
|
|
|
|
# we have a sketch: Extract a face
|
|
if (len(objects) == 1) and objects[0].isDerivedFrom("Sketcher::SketchObject") and (not curves):
|
|
result = makeSketchFace(objects[0])
|
|
if result: msg(translate("draft", "Found 1 closed sketch object: making a face from it\n"))
|
|
|
|
# only closed wires
|
|
else:
|
|
result = makeFaces(objects)
|
|
if result: msg(translate("draft", "Found closed wires: making faces\n"))
|
|
|
|
# special case, we have only one open wire. We close it, unless it has only 1 edge!"
|
|
elif (len(openwires) == 1) and (not faces) and (not loneedges):
|
|
result = closeWire(objects[0])
|
|
if result: msg(translate("draft", "Found 1 open wire: closing it\n"))
|
|
|
|
# only open wires and edges: we try to join their edges
|
|
elif openwires and (not wires) and (not faces):
|
|
result = makeWires(objects)
|
|
if result: msg(translate("draft", "Found several open wires: joining them\n"))
|
|
|
|
# only loneedges: we try to join them
|
|
elif loneedges and (not facewires):
|
|
result = makeWires(objects)
|
|
if result: msg(translate("draft", "Found several edges: wiring them\n"))
|
|
|
|
# all other cases, if more than 1 object, make a compound
|
|
elif (len(objects) > 1):
|
|
result = makeCompound(objects)
|
|
if result: msg(translate("draft", "Found several non-treatable objects: making compound\n"))
|
|
|
|
# no result has been obtained
|
|
if not result:
|
|
msg(translate("draft", "Unable to upgrade these objects\n"))
|
|
|
|
if delete:
|
|
names = []
|
|
for o in deleteList:
|
|
names.append(o.Name)
|
|
deleteList = []
|
|
for n in names:
|
|
FreeCAD.ActiveDocument.removeObject(n)
|
|
|
|
return [addList,deleteList]
|
|
|
|
def downgrade(objects,delete=False,force=None):
|
|
"""downgrade(objects,delete=False,force=None): Downgrades the given object(s) (can be
|
|
an object or a list of objects). If delete is True, old objects are deleted.
|
|
The force attribute can be used to
|
|
force a certain way of downgrading. It can be: explode, shapify, subtr,
|
|
splitFaces, cut2, getWire, splitWires.
|
|
Returns a dictionnary containing two lists, a list of new objects and a list
|
|
of objects to be deleted"""
|
|
|
|
import Part, DraftGeomUtils
|
|
from DraftTools import msg,translate
|
|
|
|
if not isinstance(objects,list):
|
|
objects = [objects]
|
|
|
|
global deleteList, newList
|
|
deleteList = []
|
|
addList = []
|
|
|
|
# actions definitions
|
|
|
|
def explode(obj):
|
|
"""explodes a Draft block"""
|
|
pl = obj.Placement
|
|
newobj = []
|
|
for o in obj.Components:
|
|
o.ViewObject.Visibility = True
|
|
o.Placement = o.Placement.multiply(pl)
|
|
if newobj:
|
|
deleteList(obj)
|
|
return newobj
|
|
return None
|
|
|
|
def cut2(objects):
|
|
"""cuts first object from the last one"""
|
|
newobj = cut(objects[0],objects[1])
|
|
if newobj:
|
|
addList.append(newobj)
|
|
return newobj
|
|
return None
|
|
|
|
def splitFaces(objects):
|
|
"""split faces contained in objects into new objects"""
|
|
result = False
|
|
for o in objects:
|
|
if o.Shape.Faces:
|
|
for f in o.Shape.Faces:
|
|
newobj = FreeCAD.ActiveDocument.addObject("Part::Feature","Face")
|
|
newobj.Shape = f
|
|
addList.append(newobj)
|
|
result = True
|
|
deleteList.append(o)
|
|
return result
|
|
|
|
def subtr(objects):
|
|
"""subtracts objects from the first one"""
|
|
faces = []
|
|
for o in objects:
|
|
if o.Shape.Faces:
|
|
faces.append(o.Shape.Faces)
|
|
deleteList.append(o)
|
|
u = faces.pop(0)
|
|
for f in faces:
|
|
u = u.cut(f)
|
|
if not u.isNull():
|
|
newobj = FreeCAD.ActiveDocument.addObject("Part::Feature","Subtraction")
|
|
newobj.Shape = u
|
|
addList.append(newobj)
|
|
return newobj
|
|
return None
|
|
|
|
def getWire(obj):
|
|
"""gets the wire from a face object"""
|
|
result = False
|
|
for w in obj.Shape.Faces[0].Wires:
|
|
newobj = FreeCAD.ActiveDocument.addObject("Part::Feature","Wire")
|
|
newobj.Shape = w
|
|
addList.append(newobj)
|
|
result = True
|
|
deleteList.append(obj)
|
|
return result
|
|
|
|
def splitWires(objects):
|
|
"""splits the wires contained in objects into edges"""
|
|
result = False
|
|
for o in objects:
|
|
if o.Shape.Edges:
|
|
for e in o.Shape.Edges:
|
|
newobj = FreeCAD.ActiveDocument.addObject("Part::Feature","Edge")
|
|
newobj.Shape = e
|
|
addList.append(newobj)
|
|
deleteList.append(o)
|
|
result = True
|
|
return result
|
|
|
|
# analyzing objects
|
|
|
|
faces = []
|
|
edges = []
|
|
onlyedges = True
|
|
parts = []
|
|
|
|
for o in objects:
|
|
if o.isDerivedFrom("Part::Feature"):
|
|
for f in o.Shape.Faces:
|
|
faces.append(f)
|
|
for e in o.Shape.Edges:
|
|
edges.append(e)
|
|
if o.Shape.ShapeType != "Edge":
|
|
onlyedges = False
|
|
parts.append(o)
|
|
objects = parts
|
|
|
|
if force:
|
|
if force in ["explode","shapify","subtr","splitFaces","cut2","getWire","splitWires"]:
|
|
result = eval(force)(objects)
|
|
else:
|
|
msg(translate("Upgrade: Unknow force method:")+" "+force)
|
|
result = None
|
|
|
|
else:
|
|
|
|
# applying transformation automatically
|
|
|
|
# we have a block, we explode it
|
|
if (len(objects) == 1) and (getType(objects[0]) == "Block"):
|
|
result = explode(objects[0])
|
|
if result: msg(translate("draft", "Found 1 block: exploding it\n"))
|
|
|
|
# special case, we have one parametric object: we "de-parametrize" it
|
|
elif (len(objects) == 1) and (objects[0].isDerivedFrom("Part::Feature")) and ("Base" in objects[0].PropertiesList):
|
|
result = shapify(objects[0])
|
|
if result: msg(translate("draft", "Found 1 parametric object: breaking its dependencies\n"))
|
|
|
|
# we have only 2 objects: cut 2nd from 1st
|
|
elif len(objects) == 2:
|
|
result = cut2(objects)
|
|
if result: msg(translate("draft", "Found 2 objects: subtracting them\n"))
|
|
|
|
elif (len(faces) > 1):
|
|
|
|
# one object with several faces: split it
|
|
if len(objects) == 1:
|
|
result = splitFaces(objects)
|
|
if result: msg(translate("draft", "Found several faces: splitting them\n"))
|
|
|
|
# several objects: remove all the faces from the first one
|
|
else:
|
|
result = subtr(objects)
|
|
if result: msg(translate("draft", "Found several objects: subtracting them from the first one\n"))
|
|
|
|
# only one face: we extract its wires
|
|
elif (len(faces) > 0):
|
|
result = getWire(objects[0])
|
|
if result: msg(translate("draft", "Found 1 face: extracting its wires\n"))
|
|
|
|
# no faces: split wire into single edges
|
|
elif not onlyedges:
|
|
result = splitWires(objects)
|
|
if result: msg(translate("draft", "Found only wires: extracting their edges\n"))
|
|
|
|
# no result has been obtained
|
|
if not result:
|
|
msg(translate("draft", "No more downgrade possible\n"))
|
|
|
|
if delete:
|
|
names = []
|
|
for o in deleteList:
|
|
names.append(o.Name)
|
|
deleteList = []
|
|
for n in names:
|
|
FreeCAD.ActiveDocument.removeObject(n)
|
|
|
|
return [addList,deleteList]
|
|
|
|
|
|
#---------------------------------------------------------------------------
|
|
# Python Features definitions
|
|
#---------------------------------------------------------------------------
|
|
|
|
class _DraftObject:
|
|
"The base class for Draft objects"
|
|
def __init__(self,obj,tp="Unknown"):
|
|
obj.Proxy = self
|
|
self.Type = tp
|
|
|
|
def __getstate__(self):
|
|
return self.Type
|
|
|
|
def __setstate__(self,state):
|
|
if state:
|
|
self.Type = state
|
|
|
|
def execute(self,obj):
|
|
pass
|
|
|
|
def onChanged(self, fp, prop):
|
|
pass
|
|
|
|
class _ViewProviderDraft:
|
|
"The base class for Draft Viewproviders"
|
|
|
|
def __init__(self, obj):
|
|
obj.Proxy = self
|
|
self.Object = obj.Object
|
|
|
|
def __getstate__(self):
|
|
return None
|
|
|
|
def __setstate__(self,state):
|
|
return None
|
|
|
|
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 execute(self,obj):
|
|
return
|
|
|
|
def setEdit(self,vp,mode):
|
|
FreeCADGui.runCommand("Draft_Edit")
|
|
return True
|
|
|
|
def unsetEdit(self,vp,mode):
|
|
if FreeCAD.activeDraftCommand:
|
|
FreeCAD.activeDraftCommand.finish()
|
|
FreeCADGui.Control.closeDialog()
|
|
return False
|
|
|
|
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 _ViewProviderDraftAlt(_ViewProviderDraft):
|
|
"a view provider that doesn't swallow its base object"
|
|
|
|
def __init__(self,vobj):
|
|
_ViewProviderDraft.__init__(self,vobj)
|
|
|
|
def claimChildren(self):
|
|
return []
|
|
|
|
class _ViewProviderDraftPart(_ViewProviderDraftAlt):
|
|
"a view provider that displays a Part icon instead of a Draft icon"
|
|
|
|
def __init__(self,vobj):
|
|
_ViewProviderDraftAlt.__init__(self,vobj)
|
|
|
|
def getIcon(self):
|
|
return ":/icons/Tree_Part.svg"
|
|
|
|
|
|
class _Dimension(_DraftObject):
|
|
"The Draft Dimension object"
|
|
def __init__(self, obj):
|
|
_DraftObject.__init__(self,obj,"Dimension")
|
|
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.addProperty("App::PropertyLength","Distance","Base","The measurement of this dimension")
|
|
obj.Start = FreeCAD.Vector(0,0,0)
|
|
obj.End = FreeCAD.Vector(1,0,0)
|
|
obj.Dimline = FreeCAD.Vector(0,1,0)
|
|
|
|
def onChanged(self, obj, prop):
|
|
obj.setEditorMode('Distance',1)
|
|
|
|
def execute(self, obj):
|
|
if obj.ViewObject:
|
|
obj.ViewObject.update()
|
|
|
|
class _ViewProviderDimension(_ViewProviderDraft):
|
|
"A View Provider for the Draft 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.FontSize=getParam("textheight")
|
|
obj.FontName=getParam("textfont")
|
|
obj.ExtLines=0.3
|
|
obj.Override = ''
|
|
_ViewProviderDraft.__init__(self,obj)
|
|
|
|
def calcGeom(self,obj):
|
|
import Part, DraftGeomUtils
|
|
from pivy import coin
|
|
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):
|
|
from pivy import coin
|
|
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):
|
|
if not prop in ["Start","End","Dimline"]:
|
|
return
|
|
from pivy import coin
|
|
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)
|
|
# print p1,p2,p3,p4,tbase,norm,rot
|
|
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))
|
|
if hasattr(obj,"Distance"):
|
|
l = p3.sub(p2).Length
|
|
if round(obj.Distance,precision()) != round(l,precision()):
|
|
obj.Distance = l
|
|
|
|
def onChanged(self, vp, prop):
|
|
self.Object = vp.Object
|
|
if prop == "FontSize":
|
|
if hasattr(self,"font"):
|
|
self.font.size = vp.FontSize
|
|
if hasattr(self,"font3d"):
|
|
self.font3d.size = vp.FontSize*100
|
|
elif prop == "FontName":
|
|
if hasattr(self,"font") and hasattr(self,"font3d"):
|
|
self.font.name = self.font3d.name = str(vp.FontName)
|
|
elif prop == "LineColor":
|
|
c = vp.LineColor
|
|
if hasattr(self,"color"):
|
|
self.color.rgb.setValue(c[0],c[1],c[2])
|
|
elif prop == "LineWidth":
|
|
if hasattr(self,"drawstyle"):
|
|
self.drawstyle.lineWidth = vp.LineWidth
|
|
else:
|
|
if hasattr(self,"drawstyle"):
|
|
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(_DraftObject):
|
|
"The Draft AngularDimension object"
|
|
def __init__(self, obj):
|
|
_DraftObject.__init__(self,obj,"AngularDimension")
|
|
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)
|
|
|
|
def execute(self, fp):
|
|
if fp.ViewObject:
|
|
fp.ViewObject.update()
|
|
|
|
class _ViewProviderAngularDimension(_ViewProviderDraft):
|
|
"A View Provider for the Draft 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.FontSize=getParam("textheight")
|
|
obj.FontName=getParam("textfont")
|
|
obj.Override = ''
|
|
_ViewProviderDraft.__init__(self,obj)
|
|
|
|
def attach(self, vobj):
|
|
from pivy import coin
|
|
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):
|
|
from pivy import coin
|
|
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(_DraftObject):
|
|
"The Rectangle object"
|
|
|
|
def __init__(self, obj):
|
|
_DraftObject.__init__(self,obj,"Rectangle")
|
|
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.addProperty("App::PropertyDistance","ChamferSize","Base","Size of the chamfer to give to the corners")
|
|
obj.Length=1
|
|
obj.Height=1
|
|
|
|
def execute(self, fp):
|
|
self.createGeometry(fp)
|
|
|
|
def onChanged(self, fp, prop):
|
|
if prop in ["Length","Height"]:
|
|
self.createGeometry(fp)
|
|
|
|
def createGeometry(self,fp):
|
|
if (fp.Length != 0) and (fp.Height != 0):
|
|
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 "ChamferSize" in fp.PropertiesList:
|
|
if fp.ChamferSize != 0:
|
|
w = DraftGeomUtils.filletWire(shape,fp.ChamferSize,chamfer=True)
|
|
if w:
|
|
shape = w
|
|
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):
|
|
from pivy import coin
|
|
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(_DraftObject):
|
|
"The Circle object"
|
|
|
|
def __init__(self, obj):
|
|
_DraftObject.__init__(self,obj,"Circle")
|
|
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")
|
|
|
|
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(_DraftObject):
|
|
"The Wire object"
|
|
|
|
def __init__(self, obj):
|
|
_DraftObject.__init__(self,obj,"Wire")
|
|
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.addProperty("App::PropertyDistance","ChamferSize","Base","Size of the chamfer to give to the corners")
|
|
obj.Closed = False
|
|
|
|
def execute(self, fp):
|
|
self.createGeometry(fp)
|
|
|
|
def updateProps(self,fp):
|
|
"sets the start and end properties"
|
|
pl = FreeCAD.Placement(fp.Placement)
|
|
if len(fp.Points) == 2:
|
|
displayfpstart = pl.multVec(fp.Points[0])
|
|
displayfpend = pl.multVec(fp.Points[-1])
|
|
if fp.Start != displayfpstart:
|
|
fp.Start = displayfpstart
|
|
if fp.End != displayfpend:
|
|
fp.End = displayfpend
|
|
if len(fp.Points) > 2:
|
|
fp.setEditorMode('Start',2)
|
|
fp.setEditorMode('End',2)
|
|
|
|
def onChanged(self, fp, prop):
|
|
if prop in ["Points","Closed","Base","Tool","FilletRadius"]:
|
|
self.createGeometry(fp)
|
|
if prop == "Points":
|
|
self.updateProps(fp)
|
|
elif prop == "Start":
|
|
pts = fp.Points
|
|
invpl = FreeCAD.Placement(fp.Placement).inverse()
|
|
realfpstart = invpl.multVec(fp.Start)
|
|
if pts:
|
|
if pts[0] != realfpstart:
|
|
pts[0] = realfpstart
|
|
fp.Points = pts
|
|
elif prop == "End":
|
|
pts = fp.Points
|
|
invpl = fp.Placement.inverse()
|
|
realfpend = invpl.multVec(fp.End)
|
|
if len(pts) > 1:
|
|
if pts[-1] != realfpend:
|
|
pts[-1] = realfpend
|
|
fp.Points = pts
|
|
elif prop == "Placement":
|
|
self.updateProps(fp)
|
|
|
|
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 "ChamferSize" in fp.PropertiesList:
|
|
if fp.ChamferSize != 0:
|
|
w = DraftGeomUtils.filletWire(shape,fp.ChamferSize,chamfer=True)
|
|
if w:
|
|
shape = w
|
|
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):
|
|
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.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(_DraftObject):
|
|
"The Polygon object"
|
|
|
|
def __init__(self, obj):
|
|
_DraftObject.__init__(self,obj,"Polygon")
|
|
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.addProperty("App::PropertyDistance","ChamferSize","Base","Size of the chamfer to give to the corners")
|
|
obj.DrawMode = ['inscribed','circumscribed']
|
|
obj.FacesNumber = 0
|
|
obj.Radius = 1
|
|
|
|
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):
|
|
if (fp.FacesNumber >= 3) and (fp.Radius > 0):
|
|
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 "ChamferSize" in fp.PropertiesList:
|
|
if fp.ChamferSize != 0:
|
|
w = DraftGeomUtils.filletWire(shape,fp.ChamferSize,chamfer=True)
|
|
if w:
|
|
shape = w
|
|
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(_DraftObject):
|
|
"The Draft DrawingView object"
|
|
def __init__(self, obj):
|
|
_DraftObject.__init__(self,obj,"DrawingView")
|
|
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.LineWidth = 0.35
|
|
obj.FontSize = 12
|
|
|
|
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 += '<g id="' + obj.Name + '"'
|
|
result += ' transform="'
|
|
result += 'rotate('+str(obj.Rotation)+','+str(obj.X)+','+str(obj.Y)+') '
|
|
result += 'translate('+str(obj.X)+','+str(obj.Y)+') '
|
|
result += 'scale('+str(obj.Scale)+','+str(-obj.Scale)+')'
|
|
result += '">'
|
|
result += svg
|
|
result += '</g>'
|
|
return result
|
|
|
|
class _BSpline(_DraftObject):
|
|
"The BSpline object"
|
|
|
|
def __init__(self, obj):
|
|
_DraftObject.__init__(self,obj,"BSpline")
|
|
obj.addProperty("App::PropertyVectorList","Points","Base",
|
|
"The points of the b-spline")
|
|
obj.addProperty("App::PropertyBool","Closed","Base",
|
|
"If the b-spline is closed or not")
|
|
obj.Closed = False
|
|
|
|
def execute(self, fp):
|
|
self.createGeometry(fp)
|
|
|
|
def onChanged(self, fp, prop):
|
|
if prop in ["Points","Closed"]:
|
|
self.createGeometry(fp)
|
|
|
|
def createGeometry(self,fp):
|
|
import Part
|
|
plm = fp.Placement
|
|
if fp.Points:
|
|
if fp.Points[0] == fp.Points[-1]:
|
|
if not fp.Closed: fp.Closed = True
|
|
fp.Points.pop()
|
|
if fp.Closed and (len(fp.Points) > 2):
|
|
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):
|
|
from pivy import coin
|
|
_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(_DraftObject):
|
|
"The Block object"
|
|
|
|
def __init__(self, obj):
|
|
_DraftObject.__init__(self,obj,"Block")
|
|
obj.addProperty("App::PropertyLinkList","Components","Base",
|
|
"The components of this 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(_DraftObject):
|
|
"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.addProperty("App::PropertyEnumeration","ProjectionMode","Base",
|
|
"The way the viewed object must be projected")
|
|
obj.addProperty("App::PropertyIntegerList","FaceNumbers","Base",
|
|
"The indices of the faces to be projected in Individual Faces mode")
|
|
obj.addProperty("App::PropertyBool","HiddenLines","Base",
|
|
"Show hidden lines")
|
|
obj.Projection = Vector(0,0,1)
|
|
obj.ProjectionMode = ["Solid","Individual Faces","Cutlines"]
|
|
obj.HiddenLines = False
|
|
_DraftObject.__init__(self,obj,"Shape2DView")
|
|
|
|
def execute(self,obj):
|
|
self.createGeometry(obj)
|
|
|
|
def onChanged(self,obj,prop):
|
|
if prop in ["Projection","Base","ProjectionMode","FaceNumbers"]:
|
|
self.createGeometry(obj)
|
|
|
|
def getProjected(self,obj,shape,direction):
|
|
"returns projected edges from a shape and a direction"
|
|
import Part,Drawing
|
|
edges = []
|
|
groups = Drawing.projectEx(shape,direction)
|
|
for g in groups[0:5]:
|
|
if g:
|
|
edges.append(g)
|
|
if hasattr(obj,"HiddenLines"):
|
|
if obj.HiddenLines:
|
|
for g in groups[5:]:
|
|
edges.append(g)
|
|
#return Part.makeCompound(edges)
|
|
return DraftGeomUtils.cleanProjection(Part.makeCompound(edges))
|
|
|
|
def createGeometry(self,obj):
|
|
import DraftGeomUtils
|
|
pl = obj.Placement
|
|
if obj.Base:
|
|
if getType(obj.Base) == "SectionPlane":
|
|
if obj.Base.Objects:
|
|
import Arch, Part, Drawing
|
|
objs = getGroupContents(obj.Base.Objects,walls=True)
|
|
objs = removeHidden(objs)
|
|
shapes = []
|
|
for o in objs:
|
|
if o.isDerivedFrom("Part::Feature"):
|
|
shapes.extend(o.Shape.Solids)
|
|
cutp,cutv,iv =Arch.getCutVolume(obj.Base.Shape,shapes)
|
|
cuts = []
|
|
if obj.ProjectionMode == "Solid":
|
|
for sh in shapes:
|
|
if sh.Volume < 0:
|
|
sh.reverse()
|
|
c = sh.cut(cutv)
|
|
cuts.extend(c.Solids)
|
|
comp = Part.makeCompound(cuts)
|
|
opl = FreeCAD.Placement(obj.Base.Placement)
|
|
proj = opl.Rotation.multVec(FreeCAD.Vector(0,0,1))
|
|
obj.Shape = self.getProjected(obj,comp,proj)
|
|
elif obj.ProjectionMode == "Cutlines":
|
|
for sh in shapes:
|
|
if sh.Volume < 0:
|
|
sh.reverse()
|
|
c = sh.section(cutp)
|
|
cuts.append(c)
|
|
comp = Part.makeCompound(cuts)
|
|
opl = FreeCAD.Placement(obj.Base.Placement)
|
|
comp.Placement = opl.inverse()
|
|
if comp:
|
|
obj.Shape = comp
|
|
|
|
elif obj.Base.isDerivedFrom("Part::Feature"):
|
|
if not DraftVecUtils.isNull(obj.Projection):
|
|
if obj.ProjectionMode == "Solid":
|
|
obj.Shape = self.getProjected(obj,obj.Base.Shape,obj.Projection)
|
|
elif obj.ProjectionMode == "Individual Faces":
|
|
import Part
|
|
if obj.FaceNumbers:
|
|
faces = []
|
|
for i in obj.FaceNumbers:
|
|
if len(obj.Base.Shape.Faces) > i:
|
|
faces.append(obj.Base.Shape.Faces[i])
|
|
views = []
|
|
for f in faces:
|
|
views.append(self.getProjected(obj,f,obj.Projection))
|
|
if views:
|
|
obj.Shape = Part.makeCompound(views)
|
|
if not DraftGeomUtils.isNull(pl):
|
|
obj.Placement = pl
|
|
|
|
class _Array(_DraftObject):
|
|
"The Draft Array object"
|
|
|
|
def __init__(self,obj):
|
|
_DraftObject.__init__(self,obj,"Array")
|
|
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.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):
|
|
print "angle ",angle," num ",num
|
|
import Part
|
|
if angle == 360:
|
|
fraction = angle/num
|
|
else:
|
|
if num == 0:
|
|
return shape
|
|
fraction = angle/(num-1)
|
|
base = [shape.copy()]
|
|
for i in range(num-1):
|
|
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(_DraftObject):
|
|
"The Draft Point object"
|
|
def __init__(self, obj,x,y,z):
|
|
_DraftObject.__init__(self,obj,"Point")
|
|
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)
|
|
|
|
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(_ViewProviderDraft):
|
|
"A viewprovider for the Draft Point object"
|
|
def __init__(self, obj):
|
|
_ViewProviderDraft.__init__(self,obj)
|
|
|
|
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(_DraftObject):
|
|
"The Clone object"
|
|
|
|
def __init__(self,obj):
|
|
_DraftObject.__init__(self,obj,"Clone")
|
|
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)
|
|
|
|
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 _ViewProviderClone(_ViewProviderDraftAlt):
|
|
"a view provider that displays a Part icon instead of a Draft icon"
|
|
|
|
def __init__(self,vobj):
|
|
_ViewProviderDraftAlt.__init__(self,vobj)
|
|
|
|
def getIcon(self):
|
|
return ":/icons/Draft_Clone.svg"
|
|
|
|
if gui:
|
|
if not hasattr(FreeCADGui,"Snapper"):
|
|
import DraftSnap
|