5407 lines
224 KiB
Python
5407 lines
224 KiB
Python
# -*- coding: utf8 -*-
|
|
|
|
#***************************************************************************
|
|
#* *
|
|
#* 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://www.freecadweb.org"
|
|
|
|
## \addtogroup DRAFT
|
|
#
|
|
# This module offers a range of tools to create and manipulate basic 2D objects
|
|
#
|
|
# The module allows to create 2D geometric objects such as line, rectangle, circle,
|
|
# etc, modify these objects by moving, scaling or rotating them, and offers a couple of
|
|
# other utilities to manipulate further these objects, such as decompose them (downgrade)
|
|
# into smaller elements.
|
|
#
|
|
# The functionality of the module is divided into GUI tools, usable from the
|
|
# FreeCAD interface, and corresponding python functions, that can perform the same
|
|
# operation programmatically.
|
|
#
|
|
|
|
'''The Draft module offers a range of tools to create and manipulate basic 2D objects'''
|
|
|
|
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
|
|
|
|
arrowtypes = ["Dot","Circle","Arrow"]
|
|
|
|
#---------------------------------------------------------------------------
|
|
# General functions
|
|
#---------------------------------------------------------------------------
|
|
|
|
def stringencodecoin(ustr):
|
|
"""stringencodecoin(str): Encodes a unicode object to be used as a string in coin"""
|
|
try:
|
|
from pivy import coin
|
|
coin4 = coin.COIN_MAJOR_VERSION >= 4
|
|
except (ImportError, AttributeError):
|
|
coin4 = False
|
|
if coin4:
|
|
return ustr.encode('utf-8')
|
|
else:
|
|
return ustr.encode('latin1')
|
|
|
|
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",
|
|
"maxSnapEdges","modalt","HatchPatternResolution","snapStyle",
|
|
"dimstyle","gridSize"]:
|
|
return "int"
|
|
elif param in ["constructiongroupname","textfont","patternFile","template",
|
|
"snapModes","FontFile"]:
|
|
return "string"
|
|
elif param in ["textheight","tolerance","gridSpacing","arrowsize","extlines","dimspacing"]:
|
|
return "float"
|
|
elif param in ["selectBaseObjects","alwaysSnap","grid","fillmode","saveonexit","maxSnap",
|
|
"SvgLinesBlack","dxfStdSize","showSnapBar","hideSnapBar","alwaysShowGrid",
|
|
"renderPolylineWidth","showPlaneTracker","UsePartPrimitives","DiscretizeEllipses",
|
|
"showUnit"]:
|
|
return "bool"
|
|
elif param in ["color","constructioncolor","snapcolor"]:
|
|
return "unsigned"
|
|
else:
|
|
return None
|
|
|
|
def getParam(param,default=None):
|
|
"getParam(parameterName): returns a Draft parameter value from the current config"
|
|
p = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft")
|
|
t = getParamType(param)
|
|
#print("getting param ",param, " of type ",t, " default: ",str(default))
|
|
if t == "int":
|
|
if default == None:
|
|
default = 0
|
|
return p.GetInt(param,default)
|
|
elif t == "string":
|
|
if default == None:
|
|
default = ""
|
|
return p.GetString(param,default)
|
|
elif t == "float":
|
|
if default == None:
|
|
default = 0
|
|
return p.GetFloat(param,default)
|
|
elif t == "bool":
|
|
if default == None:
|
|
default = False
|
|
return p.GetBool(param,default)
|
|
elif t == "unsigned":
|
|
if default == None:
|
|
default = 0
|
|
return p.GetUnsigned(param,default)
|
|
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",6)
|
|
|
|
def tolerance():
|
|
"tolerance(): returns the tolerance value from Draft user settings"
|
|
return getParam("tolerance",0.05)
|
|
|
|
def epsilon():
|
|
''' epsilon(): returns a small number based on Draft.tolerance() for use in
|
|
floating point comparisons. Use with caution. '''
|
|
return (1.0/(10.0**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 not obj:
|
|
return None
|
|
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.TypeId == "Part::Line"):
|
|
return "Part::Line"
|
|
if obj.isDerivedFrom("Part::Feature"):
|
|
return "Part"
|
|
if (obj.TypeId == "App::Annotation"):
|
|
return "Annotation"
|
|
if obj.isDerivedFrom("Mesh::Feature"):
|
|
return "Mesh"
|
|
if obj.isDerivedFrom("Points::Feature"):
|
|
return "Points"
|
|
if (obj.TypeId == "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"
|
|
if FreeCAD.GuiUp:
|
|
import FreeCADGui
|
|
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,recursive=False):
|
|
"""isClone(obj,objtype,[recursive]): returns True if the given object is
|
|
a clone of an object of the given type. If recursive is True, also check if
|
|
the clone is a clone of clone (of clone...) of the given type."""
|
|
if getType(obj) == "Clone":
|
|
if len(obj.Objects) == 1:
|
|
if getType(obj.Objects[0]) == objtype:
|
|
return True
|
|
elif recursive and (getType(obj.Objects[0]) == "Clone"):
|
|
return isClone(obj.Objects[0],objtype,recursive)
|
|
elif hasattr(obj,"CloneOf"):
|
|
if obj.CloneOf:
|
|
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.TypeId == "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(symbol=None,invert=False):
|
|
"returns the current dim symbol from the preferences as a pivy SoMarkerSet"
|
|
if symbol == None:
|
|
symbol = getParam("dimsymbol",0)
|
|
from pivy import coin
|
|
if symbol == 1:
|
|
marker = coin.SoMarkerSet()
|
|
marker.markerIndex = coin.SoMarkerSet.CIRCLE_LINE_9_9
|
|
return marker
|
|
elif symbol == 2:
|
|
marker = coin.SoSeparator()
|
|
t = coin.SoTransform()
|
|
t.translation.setValue((0,-2,0))
|
|
t.center.setValue((0,2,0))
|
|
if invert:
|
|
t.rotation.setValue(coin.SbVec3f((0,0,1)),-math.pi/2)
|
|
else:
|
|
t.rotation.setValue(coin.SbVec3f((0,0,1)),math.pi/2)
|
|
c = coin.SoCone()
|
|
c.height.setValue(4)
|
|
marker.addChild(t)
|
|
marker.addChild(c)
|
|
return marker
|
|
elif symbol == 3:
|
|
print("Draft.dimsymbol: Not implemented")
|
|
return coin.SoSphere()
|
|
|
|
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) == 1:
|
|
name = "Solid"
|
|
elif len(shape.Solids) > 1:
|
|
name = "Compound"
|
|
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,addgroups=False):
|
|
'''getGroupContents(objectlist,[walls,addgroups]): if any object of the given list
|
|
is a group, its content is appened to the list, which is returned. If walls is True,
|
|
walls are also scanned for included windows. If addgroups is true, the group itself
|
|
is also included in the list.'''
|
|
newlist = []
|
|
if not isinstance(objectslist,list):
|
|
objectslist = [objectslist]
|
|
for obj in objectslist:
|
|
if obj.isDerivedFrom("App::DocumentObjectGroup") or ((getType(obj) == "Space") and hasattr(obj,"Group")):
|
|
if obj.isDerivedFrom("Drawing::FeaturePage"):
|
|
# skip if the group is a page
|
|
newlist.append(obj)
|
|
else:
|
|
if addgroups:
|
|
newlist.append(obj)
|
|
newlist.extend(getGroupContents(obj.Group,walls,addgroups))
|
|
else:
|
|
#print("adding ",obj.Name)
|
|
newlist.append(obj)
|
|
if walls:
|
|
if getType(obj) in ["Wall","Structure"]:
|
|
for o in obj.OutList:
|
|
if (getType(o) == "Window") or isClone(o,"Window"):
|
|
newlist.append(o)
|
|
# cleaning possible duplicates
|
|
cleanlist = []
|
|
for obj in newlist:
|
|
if not obj in cleanlist:
|
|
cleanlist.append(obj)
|
|
return cleanlist
|
|
|
|
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 compareObjects(obj1,obj2):
|
|
"Prints the differences between 2 objects"
|
|
|
|
if obj1.TypeId != obj2.TypeId:
|
|
print(obj1.Name + " and " + obj2.Name + " are of different types")
|
|
elif getType(obj1) != getType(obj2):
|
|
print(obj1.Name + " and " + obj2.Name + " are of different types")
|
|
else:
|
|
for p in obj1.PropertiesList:
|
|
if p in obj2.PropertiesList:
|
|
if p in ["Shape","Label"]:
|
|
pass
|
|
elif p == "Placement":
|
|
delta = str((obj1.Placement.Base.sub(obj2.Placement.Base)).Length)
|
|
print("Objects have different placements. Distance between the 2: " + delta + " units")
|
|
else:
|
|
if getattr(obj1,p) != getattr(obj2,p):
|
|
print("Property " + p + " has a different value")
|
|
else:
|
|
print("Property " + p + " doesn't exist in one of the objects")
|
|
|
|
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","Construction")
|
|
grp = doc.getObject(gname)
|
|
if not grp:
|
|
grp = doc.addObject("App::DocumentObjectGroup",gname)
|
|
grp.addObject(target)
|
|
if hasattr(obrep,"Transparency"):
|
|
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:
|
|
if not obrep.getEditorMode(p):
|
|
if hasattr(getattr(matchrep,p),"Value"):
|
|
val = getattr(matchrep,p).Value
|
|
else:
|
|
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"):
|
|
if matchrep.DiffuseColor:
|
|
FreeCAD.ActiveDocument.recompute()
|
|
obrep.DiffuseColor = matchrep.DiffuseColor
|
|
|
|
def getSelection():
|
|
"getSelection(): returns the current FreeCAD selection"
|
|
if gui:
|
|
return FreeCADGui.Selection.getSelection()
|
|
return None
|
|
|
|
def getSelectionEx():
|
|
"getSelectionEx(): returns the current FreeCAD selection (with subobjects)"
|
|
if gui:
|
|
return FreeCADGui.Selection.getSelectionEx()
|
|
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 loadSvgPatterns():
|
|
"loads the default Draft SVG patterns and custom patters if available"
|
|
import importSVG
|
|
from PySide import QtCore
|
|
FreeCAD.svgpatterns = {}
|
|
# getting default patterns
|
|
patfiles = QtCore.QDir(":/patterns").entryList()
|
|
for fn in patfiles:
|
|
fn = ":/patterns/"+str(fn)
|
|
f = QtCore.QFile(fn)
|
|
f.open(QtCore.QIODevice.ReadOnly)
|
|
p = importSVG.getContents(str(f.readAll()),'pattern',True)
|
|
if p:
|
|
for k in p:
|
|
p[k] = [p[k],fn]
|
|
FreeCAD.svgpatterns.update(p)
|
|
# looking for user patterns
|
|
altpat = getParam("patternFile","")
|
|
if os.path.isdir(altpat):
|
|
for f in os.listdir(altpat):
|
|
if f[-4:].upper() == ".SVG":
|
|
p = importSVG.getContents(altpat+os.sep+f,'pattern')
|
|
if p:
|
|
for k in p:
|
|
p[k] = [p[k],altpat+os.sep+f]
|
|
FreeCAD.svgpatterns.update(p)
|
|
|
|
def svgpatterns():
|
|
"""svgpatterns(): returns a dictionnary with installed SVG patterns"""
|
|
if hasattr(FreeCAD,"svgpatterns"):
|
|
return FreeCAD.svgpatterns
|
|
else:
|
|
loadSvgPatterns()
|
|
if hasattr(FreeCAD,"svgpatterns"):
|
|
return FreeCAD.svgpatterns
|
|
return {}
|
|
|
|
def loadTexture(filename,size=None):
|
|
"""loadTexture(filename,[size]): returns a SoSFImage from a file. If size
|
|
is defined (an int or a tuple), and provided the input image is a png file,
|
|
it will be scaled to match the given size."""
|
|
if gui:
|
|
from pivy import coin
|
|
from PySide import QtGui,QtSvg
|
|
try:
|
|
p = QtGui.QImage(filename)
|
|
# buggy - TODO: allow to use resolutions
|
|
#if size and (".svg" in filename.lower()):
|
|
# # this is a pattern, not a texture
|
|
# if isinstance(size,int):
|
|
# size = (size,size)
|
|
# svgr = QtSvg.QSvgRenderer(filename)
|
|
# p = QtGui.QImage(size[0],size[1],QtGui.QImage.Format_ARGB32)
|
|
# pa = QtGui.QPainter()
|
|
# pa.begin(p)
|
|
# svgr.render(pa)
|
|
# pa.end()
|
|
#else:
|
|
# p = QtGui.QImage(filename)
|
|
size = coin.SbVec2s(p.width(), p.height())
|
|
buffersize = p.numBytes()
|
|
numcomponents = int (float(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:
|
|
print("Draft: unable to load texture")
|
|
return None
|
|
else:
|
|
return img
|
|
return None
|
|
|
|
def getMovableChildren(objectslist,recursive=True):
|
|
'''getMovableChildren(objectslist,[recursive]): extends the given list of objects
|
|
with all child objects that have a "MoveWithHost" property set to True. If
|
|
recursive is True, all descendents are considered, otherwise only direct children.'''
|
|
added = []
|
|
for obj in objectslist:
|
|
if not (getType(obj) in ["Clone","SectionPlane"]):
|
|
# objects that should never move their children
|
|
children = obj.OutList
|
|
if hasattr(obj,"Proxy"):
|
|
if obj.Proxy:
|
|
if hasattr(obj.Proxy,"getSiblings") and not(getType(obj) in ["Window"]):
|
|
children.extend(obj.Proxy.getSiblings(obj))
|
|
for child in children:
|
|
if hasattr(child,"MoveWithHost"):
|
|
if child.MoveWithHost:
|
|
if hasattr(obj,"CloneOf"):
|
|
if obj.CloneOf:
|
|
if obj.CloneOf.Name != child.Name:
|
|
added.append(child)
|
|
else:
|
|
added.append(child)
|
|
else:
|
|
added.append(child)
|
|
if recursive:
|
|
added.extend(getMovableChildren(children))
|
|
return added
|
|
|
|
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")
|
|
if startangle != endangle:
|
|
n = "Arc"
|
|
else:
|
|
n = "Circle"
|
|
obj = FreeCAD.ActiveDocument.addObject("Part::Part2DObjectPython",n)
|
|
_Circle(obj)
|
|
#obj.MakeFace = face
|
|
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)
|
|
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
|
|
#obj.MakeFace = face
|
|
if placement: obj.Placement = placement
|
|
if gui:
|
|
_ViewProviderRectangle(obj.ViewObject)
|
|
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):
|
|
l = []
|
|
l.append((p1,"Vertex"+str(p2+1)))
|
|
l.append((p1,"Vertex"+str(p3+1)))
|
|
obj.LinkedGeometry = l
|
|
obj.Support = p1
|
|
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):
|
|
l = []
|
|
l.append((p1,"Edge"+str(p2+1)))
|
|
if p3 == "radius":
|
|
l.append((p1,"Center"))
|
|
obj.ViewObject.Override = "R $dim"
|
|
elif p3 == "diameter":
|
|
l.append((p1,"Diameter"))
|
|
obj.ViewObject.Override = "Ø $dim"
|
|
obj.LinkedGeometry = l
|
|
obj.Support = p1
|
|
p3 = p4
|
|
if not p3:
|
|
p3 = p1.Shape.Edges[p2].Curve.Center.add(Vector(1,0,0))
|
|
obj.Dimline = p3
|
|
if hasattr(FreeCAD,"DraftWorkingPlane"):
|
|
normal = FreeCAD.DraftWorkingPlane.axis
|
|
else:
|
|
normal = FreeCAD.Vector(0,0,1)
|
|
if gui:
|
|
# invert the normal if we are viewing it from the back
|
|
vnorm = get3DView().getViewDirection()
|
|
if vnorm.getAngle(normal) < math.pi/2:
|
|
normal = normal.negative()
|
|
obj.Normal = normal
|
|
if gui:
|
|
formatObject(obj)
|
|
select(obj)
|
|
FreeCAD.ActiveDocument.recompute()
|
|
return obj
|
|
|
|
def makeAngularDimension(center,angles,p3,normal=None):
|
|
'''makeAngularDimension(center,angle1,angle2,p3,[normal]): 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 not normal:
|
|
if hasattr(FreeCAD,"DraftWorkingPlane"):
|
|
normal = FreeCAD.DraftWorkingPlane.axis
|
|
else:
|
|
normal = Vector(0,0,1)
|
|
if gui:
|
|
# invert the normal if we are viewing it from the back
|
|
vnorm = get3DView().getViewDirection()
|
|
if vnorm.getAngle(normal) < math.pi/2:
|
|
normal = normal.negative()
|
|
obj.Normal = normal
|
|
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(Part.__sortEdges__(e))
|
|
nlist = []
|
|
for v in pointslist.Vertexes:
|
|
nlist.append(v.Point)
|
|
if DraftGeomUtils.isReallyClosed(pointslist):
|
|
closed = True
|
|
pointslist = nlist
|
|
if len(pointslist) == 0:
|
|
print("Invalid input points: ",pointslist)
|
|
#print(pointslist)
|
|
#print(closed)
|
|
if placement: 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
|
|
#obj.MakeFace = face
|
|
if placement: obj.Placement = placement
|
|
if gui:
|
|
_ViewProviderWire(obj.ViewObject)
|
|
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
|
|
#obj.MakeFace = face
|
|
if inscribed:
|
|
obj.DrawMode = "inscribed"
|
|
else:
|
|
obj.DrawMode = "circumscribed"
|
|
obj.Support = support
|
|
if placement: obj.Placement = placement
|
|
if gui:
|
|
_ViewProviderDraft(obj.ViewObject)
|
|
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.'''
|
|
from DraftTools import msg,translate
|
|
if not isinstance(pointslist,list):
|
|
nlist = []
|
|
for v in pointslist.Vertexes:
|
|
nlist.append(v.Point)
|
|
pointslist = nlist
|
|
if len(pointslist) < 2:
|
|
msg(translate("draft","Draft.makeBSpline: not enough points\n"), 'error')
|
|
return
|
|
if (pointslist[0] == pointslist[-1]):
|
|
if len(pointslist) > 2:
|
|
closed = True
|
|
pointslist.pop()
|
|
msg(translate("draft","Draft.makeBSpline: Equal endpoints forced Closed\n"), 'warning')
|
|
else: # len == 2 and first == last GIGO
|
|
msg(translate("draft","Draft.makeBSpline: Invalid pointslist\n"), 'error')
|
|
return
|
|
# should have sensible parms from here on
|
|
if placement: typecheck([(placement,FreeCAD.Placement)], "makeBSpline")
|
|
if len(pointslist) == 2: fname = "Line"
|
|
else: fname = "BSpline"
|
|
obj = FreeCAD.ActiveDocument.addObject("Part::Part2DObjectPython",fname)
|
|
_BSpline(obj)
|
|
obj.Closed = closed
|
|
obj.Points = pointslist
|
|
obj.Support = support
|
|
#obj.MakeFace = face
|
|
if placement: obj.Placement = placement
|
|
if gui:
|
|
_ViewProviderWire(obj.ViewObject)
|
|
formatObject(obj)
|
|
select(obj)
|
|
FreeCAD.ActiveDocument.recompute()
|
|
return obj
|
|
|
|
def makeBezCurve(pointslist,closed=False,placement=None,face=True,support=None,Degree=None):
|
|
'''makeBezCurve(pointslist,[closed],[placement]): Creates a Bezier Curve object
|
|
from the given list of vectors. Instead of a pointslist, you can also pass a Part Wire.'''
|
|
if not isinstance(pointslist,list):
|
|
nlist = []
|
|
for v in pointslist.Vertexes:
|
|
nlist.append(v.Point)
|
|
pointslist = nlist
|
|
if placement: typecheck([(placement,FreeCAD.Placement)], "makeBezCurve")
|
|
if len(pointslist) == 2: fname = "Line"
|
|
else: fname = "BezCurve"
|
|
obj = FreeCAD.ActiveDocument.addObject("Part::Part2DObjectPython",fname)
|
|
_BezCurve(obj)
|
|
obj.Points = pointslist
|
|
if Degree:
|
|
obj.Degree = Degree
|
|
else:
|
|
import Part
|
|
obj.Degree = min((len(pointslist)-(1 * (not closed))),\
|
|
Part.BezierCurve().MaxDegree)
|
|
obj.Closed = closed
|
|
obj.Support = support
|
|
#obj.MakeFace = face
|
|
obj.Proxy.resetcontinuity(obj)
|
|
if placement: obj.Placement = placement
|
|
if gui:
|
|
_ViewProviderWire(obj.ViewObject)
|
|
# if not face: obj.ViewObject.DisplayMode = "Wireframe"
|
|
# obj.ViewObject.DisplayMode = "Wireframe"
|
|
formatObject(obj)
|
|
select(obj)
|
|
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]
|
|
obj=FreeCAD.ActiveDocument.addObject("App::Annotation","Text")
|
|
obj.LabelText=stringslist
|
|
obj.Position=point
|
|
if FreeCAD.GuiUp:
|
|
if not screen:
|
|
obj.ViewObject.DisplayMode="World"
|
|
h = getParam("textheight",0.20)
|
|
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.TypeId,getRealName(obj.Name))
|
|
_Rectangle(newobj)
|
|
if gui:
|
|
_ViewProviderRectangle(newobj.ViewObject)
|
|
elif (getType(obj) == "Point") or (force == "Point"):
|
|
newobj = FreeCAD.ActiveDocument.addObject(obj.TypeId,getRealName(obj.Name))
|
|
_Point(newobj)
|
|
if gui:
|
|
_ViewProviderPoint(newobj.ViewObject)
|
|
elif (getType(obj) == "Dimension") or (force == "Dimension"):
|
|
newobj = FreeCAD.ActiveDocument.addObject(obj.TypeId,getRealName(obj.Name))
|
|
_Dimension(newobj)
|
|
if gui:
|
|
_ViewProviderDimension(newobj.ViewObject)
|
|
elif (getType(obj) == "Wire") or (force == "Wire"):
|
|
newobj = FreeCAD.ActiveDocument.addObject(obj.TypeId,getRealName(obj.Name))
|
|
_Wire(newobj)
|
|
if gui:
|
|
_ViewProviderWire(newobj.ViewObject)
|
|
elif (getType(obj) == "Circle") or (force == "Circle"):
|
|
newobj = FreeCAD.ActiveDocument.addObject(obj.TypeId,getRealName(obj.Name))
|
|
_Circle(newobj)
|
|
if gui:
|
|
_ViewProviderDraft(newobj.ViewObject)
|
|
elif (getType(obj) == "Polygon") or (force == "Polygon"):
|
|
newobj = FreeCAD.ActiveDocument.addObject(obj.TypeId,getRealName(obj.Name))
|
|
_Polygon(newobj)
|
|
if gui:
|
|
_ViewProviderDraft(newobj.ViewObject)
|
|
elif (getType(obj) == "BSpline") or (force == "BSpline"):
|
|
newobj = FreeCAD.ActiveDocument.addObject(obj.TypeId,getRealName(obj.Name))
|
|
_BSpline(newobj)
|
|
if gui:
|
|
_ViewProviderWire(newobj.ViewObject)
|
|
elif (getType(obj) == "Block") or (force == "BSpline"):
|
|
newobj = FreeCAD.ActiveDocument.addObject(obj.TypeId,getRealName(obj.Name))
|
|
_Block(newobj)
|
|
if gui:
|
|
_ViewProviderDraftPart(newobj.ViewObject)
|
|
elif (getType(obj) == "DrawingView") or (force == "DrawingView"):
|
|
newobj = FreeCAD.ActiveDocument.addObject(obj.TypeId,getRealName(obj.Name))
|
|
_DrawingView(newobj)
|
|
elif (getType(obj) == "Structure") or (force == "Structure"):
|
|
import ArchStructure
|
|
newobj = FreeCAD.ActiveDocument.addObject(obj.TypeId,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.TypeId,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.TypeId,getRealName(obj.Name))
|
|
ArchWindow._Window(newobj)
|
|
if gui:
|
|
ArchWindow._ViewProviderWindow(newobj.ViewObject)
|
|
elif (getType(obj) == "Panel") or (force == "Panel"):
|
|
import ArchPanel
|
|
newobj = FreeCAD.ActiveDocument.addObject(obj.TypeId,getRealName(obj.Name))
|
|
ArchPanel._Panel(newobj)
|
|
if gui:
|
|
ArchPanel._ViewProviderPanel(newobj.ViewObject)
|
|
elif (getType(obj) == "Sketch") or (force == "Sketch"):
|
|
newobj = FreeCAD.ActiveDocument.addObject("Sketcher::SketchObject",getRealName(obj.Name))
|
|
for geo in obj.Geometry:
|
|
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:
|
|
if not newobj.getEditorMode(p):
|
|
try:
|
|
setattr(newobj,p,obj.getPropertyByName(p))
|
|
except AttributeError:
|
|
try:
|
|
setattr(newobj,p,obj.getPropertyByName(p).Value)
|
|
except AttributeError:
|
|
pass
|
|
if reparent:
|
|
parents = obj.InList
|
|
if parents:
|
|
for par in parents:
|
|
if par.isDerivedFrom("App::DocumentObjectGroup"):
|
|
par.addObject(newobj)
|
|
else:
|
|
for prop in par.PropertiesList:
|
|
if getattr(par,prop) == obj:
|
|
setattr(par,prop,newobj)
|
|
FreeCAD.ActiveDocument.recompute()
|
|
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,name="Array"):
|
|
'''makeArray(object,xvector,yvector,xnum,ynum,[name]) for rectangular array, or
|
|
makeArray(object,center,totalangle,totalnum,[name]) 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",name)
|
|
_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:
|
|
_ViewProviderDraftArray(obj.ViewObject)
|
|
baseobject.ViewObject.hide()
|
|
select(obj)
|
|
return obj
|
|
|
|
def makePathArray(baseobject,pathobject,count,xlate=None,align=False,pathobjsubs=[]):
|
|
'''makePathArray(docobj,path,count,xlate,align,pathobjsubs): distribute
|
|
count copies of a document baseobject along a pathobject or subobjects of a
|
|
pathobject. Optionally translates each copy by FreeCAD.Vector xlate direction
|
|
and distance to adjust for difference in shape centre vs shape reference point.
|
|
Optionally aligns baseobject to tangent/normal/binormal of path.'''
|
|
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython","PathArray")
|
|
_PathArray(obj)
|
|
obj.Base = baseobject
|
|
obj.PathObj = pathobject
|
|
if pathobjsubs:
|
|
sl = []
|
|
for sub in pathobjsubs:
|
|
sl.append((obj.PathObj,sub))
|
|
obj.PathSubs = list(sl)
|
|
if count > 1:
|
|
obj.Count = count
|
|
if xlate:
|
|
obj.Xlate = xlate
|
|
obj.Align = align
|
|
if gui:
|
|
_ViewProviderDraftArray(obj.ViewObject)
|
|
baseobject.ViewObject.hide()
|
|
formatObject(obj,obj.Base)
|
|
select(obj)
|
|
return obj
|
|
|
|
def makeEllipse(majradius,minradius,placement=None,face=True,support=None):
|
|
'''makeEllipse(majradius,minradius,[placement],[face],[support]): makes
|
|
an ellipse with the given major and minor radius, and optionally
|
|
a placement.'''
|
|
obj = FreeCAD.ActiveDocument.addObject("Part::Part2DObjectPython","Ellipse")
|
|
_Ellipse(obj)
|
|
if minradius > majradius:
|
|
majradius,minradius = minradius,majradius
|
|
obj.MajorRadius = majradius
|
|
obj.MinorRadius = minradius
|
|
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 makeVisGroup(group=None,name="VisGroup"):
|
|
'''makeVisGroup([group]): creates a VisGroup object in the given group, or in the
|
|
active document if no group is given'''
|
|
obj = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroupPython",name)
|
|
_VisGroup(obj)
|
|
if FreeCAD.GuiUp:
|
|
_ViewProviderVisGroup(obj.ViewObject)
|
|
formatObject(obj)
|
|
if group:
|
|
group.addObject(obj)
|
|
return obj
|
|
|
|
def extrude(obj,vector,solid=False):
|
|
'''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
|
|
newobj.Solid = solid
|
|
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]
|
|
objectslist.extend(getMovableChildren(objectslist))
|
|
newobjlist = []
|
|
for obj in objectslist:
|
|
if hasattr(obj,"Placement"):
|
|
if obj.getEditorMode("Placement") == ["ReadOnly"]:
|
|
if not copy:
|
|
FreeCAD.Console.PrintError(obj.Name+" cannot be moved because its placement is readonly.")
|
|
continue
|
|
if getType(obj) == "Point":
|
|
v = Vector(obj.X,obj.Y,obj.Z)
|
|
v = v.add(vector)
|
|
if copy:
|
|
newobj = makeCopy(obj)
|
|
else:
|
|
newobj = obj
|
|
newobj.X = v.x
|
|
newobj.Y = v.y
|
|
newobj.Z = v.z
|
|
elif (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
|
|
if gui:
|
|
formatObject(newobj,obj)
|
|
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)
|
|
formatObject(newobj,obj)
|
|
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",False):
|
|
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.
|
|
|
|
This function creates an array of independent objects. Use makeArray() to create a
|
|
parametric array object.'''
|
|
|
|
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=Vector(xvector).multiply(xcount)
|
|
if not xcount==0:
|
|
move(objectslist,currentxvector,True)
|
|
for ycount in range(ynum):
|
|
currentxvector=FreeCAD.Base.Vector(currentxvector)
|
|
currentyvector=currentxvector.add(Vector(yvector).multiply(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 = float(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]
|
|
objectslist.extend(getMovableChildren(objectslist))
|
|
newobjlist = []
|
|
for obj in objectslist:
|
|
if hasattr(obj,"Placement"):
|
|
if obj.getEditorMode("Placement") == ["ReadOnly"]:
|
|
if not copy:
|
|
FreeCAD.Console.PrintError(obj.Name+" cannot be rotated because its placement is readonly.")
|
|
continue
|
|
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",False):
|
|
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 = (corr.sub(center)).negative()
|
|
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)
|
|
#print(p)
|
|
newobj.Points = p
|
|
elif getType(obj) == "BSpline":
|
|
p = []
|
|
for p1 in obj.Points:
|
|
p2 = p1.sub(center)
|
|
p2.scale(delta.x,delta.y,delta.z)
|
|
p.append(p2)
|
|
newobj.Points = p
|
|
elif (obj.isDerivedFrom("Part::Feature")):
|
|
newobj.Shape = sh
|
|
elif (obj.TypeId == "App::Annotation"):
|
|
factor = delta.x * delta.y * delta.z * obj.ViewObject.FontSize.Value
|
|
obj.ViewObject.Fontsize = factor
|
|
if copy: formatObject(newobj,obj)
|
|
newobjlist.append(newobj)
|
|
if copy and getParam("selectBaseObjects",False):
|
|
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 = (corr.sub(center)).negative()
|
|
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
|
|
newwire = None
|
|
delete = None
|
|
|
|
if getType(obj) in ["Sketch","Part"]:
|
|
copy = True
|
|
print("the offset tool is currently unable to offset a non-Draft object directly - Creating a copy")
|
|
|
|
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.Value < 0: l = -nb.Length
|
|
else: l = nb.Length
|
|
if obj.Height.Value < 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 = Vector(delta).multiply(0.5)
|
|
d2 = d1.negative()
|
|
n1 = DraftGeomUtils.offsetWire(obj.Shape,d1)
|
|
n2 = DraftGeomUtils.offsetWire(obj.Shape,d2)
|
|
else:
|
|
if isinstance(delta,float) and (len(obj.Shape.Edges) == 1):
|
|
# circle
|
|
c = obj.Shape.Edges[0].Curve
|
|
nc = Part.Circle(c.Center,c.Axis,delta)
|
|
if len(obj.Shape.Vertexes) > 1:
|
|
nc = Part.ArcOfCircle(nc,obj.Shape.Edges[0].FirstParameter,obj.Shape.Edges[0].LastParameter)
|
|
newwire = Part.Wire(nc.toShape())
|
|
p = []
|
|
else:
|
|
newwire = DraftGeomUtils.offsetWire(obj.Shape,delta)
|
|
if DraftGeomUtils.hasCurves(newwire) and copy:
|
|
p = []
|
|
else:
|
|
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)
|
|
if not copy:
|
|
delete = obj.Name
|
|
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 = FreeCAD.ActiveDocument.addObject("Part::Feature","Offset")
|
|
newobj.Shape = Part.Face(Part.Wire(w1+[w3]+w2+[w4]))
|
|
else:
|
|
newobj = FreeCAD.ActiveDocument.addObject("Part::Feature","Offset")
|
|
newobj.Shape = Part.Face(obj.Shape.Wires[0])
|
|
if not copy:
|
|
delete = obj.Name
|
|
elif copy:
|
|
newobj = None
|
|
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) == "BSpline":
|
|
newobj = makeBSpline(delta)
|
|
newobj.Closed = obj.Closed
|
|
else:
|
|
# try to offset anyway
|
|
try:
|
|
if p:
|
|
newobj = makeWire(p)
|
|
newobj.Closed = obj.Shape.isClosed()
|
|
except Part.OCCError:
|
|
pass
|
|
if not(newobj) and newwire:
|
|
newobj = FreeCAD.ActiveDocument.addObject("Part::Feature","Offset")
|
|
newobj.Shape = newwire
|
|
else:
|
|
print("Unable to create an offset")
|
|
if newobj:
|
|
formatObject(newobj,obj)
|
|
else:
|
|
newobj = None
|
|
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",False):
|
|
select(newobj)
|
|
else:
|
|
select(obj)
|
|
if delete:
|
|
FreeCAD.ActiveDocument.removeObject(delete)
|
|
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)
|
|
newobjlist.append(nobj)
|
|
formatObject(nobj,obj)
|
|
# sketches are always in wireframe mode. In Draft we don't like that!
|
|
nobj.ViewObject.DisplayMode = "Flat Lines"
|
|
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 getDXF(obj,direction=None):
|
|
'''getDXF(object,[direction]): returns a DXF entity from the given
|
|
object. If direction is given, the object is projected in 2D.'''
|
|
plane = None
|
|
result = ""
|
|
if direction:
|
|
if isinstance(direction,FreeCAD.Vector):
|
|
if direction != Vector(0,0,0):
|
|
plane = WorkingPlane.plane()
|
|
plane.alignToPointAndAxis(Vector(0,0,0),direction)
|
|
|
|
def getProj(vec):
|
|
if not plane: return vec
|
|
nx = DraftVecUtils.project(vec,plane.u)
|
|
ny = DraftVecUtils.project(vec,plane.v)
|
|
return Vector(nx.Length,ny.Length,0)
|
|
|
|
if getType(obj) == "Dimension":
|
|
p1 = getProj(obj.Start)
|
|
p2 = getProj(obj.End)
|
|
p3 = getProj(obj.Dimline)
|
|
result += "0\nDIMENSION\n8\n0\n62\n0\n3\nStandard\n70\n1\n"
|
|
result += "10\n"+str(p3.x)+"\n20\n"+str(p3.y)+"\n30\n"+str(p3.z)+"\n"
|
|
result += "13\n"+str(p1.x)+"\n23\n"+str(p1.y)+"\n33\n"+str(p1.z)+"\n"
|
|
result += "14\n"+str(p2.x)+"\n24\n"+str(p2.y)+"\n34\n"+str(p2.z)+"\n"
|
|
|
|
elif getType(obj) == "Annotation":
|
|
p = getProj(obj.Position)
|
|
count = 0
|
|
for t in obj.LabeLtext:
|
|
result += "0\nTEXT\n8\n0\n62\n0\n"
|
|
result += "10\n"+str(p.x)+"\n20\n"+str(p.y+count)+"\n30\n"+str(p.z)+"\n"
|
|
result += "40\n1\n"
|
|
result += "1\n"+str(t)+"\n"
|
|
result += "7\nSTANDARD\n"
|
|
count += 1
|
|
|
|
elif obj.isDerivedFrom("Part::Feature"):
|
|
# TODO do this the Draft way, for ex. using polylines and rectangles
|
|
import Drawing
|
|
if not direction: direction = FreeCAD.Vector(0,0,-1)
|
|
result += Drawing.projectToDXF(obj.Shape,direction)
|
|
|
|
else:
|
|
print("Draft.getDXF: Unsupported object: ",obj.Label)
|
|
|
|
return result
|
|
|
|
|
|
def getSVG(obj,scale=1,linewidth=0.35,fontsize=12,fillstyle="shape color",direction=None,linestyle=None,color=None):
|
|
'''getSVG(object,[scale], [linewidth],[fontsize],[fillstyle],[direction],[linestyle],[color]):
|
|
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 = float(linewidth)/scale
|
|
fontsize = (float(fontsize)/scale)/2
|
|
pointratio = .75 # the number of times the dots are smaller than the arrow size
|
|
plane = None
|
|
if direction:
|
|
if isinstance(direction,FreeCAD.Vector):
|
|
if direction != Vector(0,0,0):
|
|
plane = WorkingPlane.plane()
|
|
plane.alignToPointAndAxis_SVG(Vector(0,0,0),direction.negative().negative(),0)
|
|
elif isinstance(direction,WorkingPlane.plane):
|
|
plane = direction
|
|
stroke = "#000000"
|
|
if color:
|
|
stroke = getrgb(color)
|
|
elif gui:
|
|
if hasattr(obj.ViewObject,"LineColor"):
|
|
stroke = getrgb(obj.ViewObject.LineColor)
|
|
|
|
def getLineStyle():
|
|
"returns a linestyle"
|
|
p = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft")
|
|
if linestyle == "Dashed":
|
|
return p.GetString("svgDashedLine","0.09,0.05")
|
|
elif linestyle == "Dashdot":
|
|
return p.GetString("svgDashdotLine","0.09,0.05,0.02,0.05")
|
|
elif linestyle == "Dotted":
|
|
return p.GetString("svgDottedLine","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 svgpatterns():
|
|
return svgpatterns()[pat][0]
|
|
return ''
|
|
|
|
def getPath(edges=[],wires=[],pathname=None):
|
|
import DraftGeomUtils
|
|
svg = "<path "
|
|
if pathname is None:
|
|
svg += 'id="%s" ' % obj.Name
|
|
elif pathname != "":
|
|
svg += 'id="%s" ' % pathname
|
|
svg += ' d="'
|
|
if not wires:
|
|
egroups = (Part.__sortEdges__(edges),)
|
|
else:
|
|
egroups = []
|
|
for w in wires:
|
|
w1=w.copy()
|
|
w1.fixWire()
|
|
egroups.append(Part.__sortEdges__(w1.Edges))
|
|
for egroupindex, edges in enumerate(egroups):
|
|
vs=() #skipped for the first edge
|
|
for edgeindex,e in enumerate(edges):
|
|
previousvs = vs
|
|
# vertexes of an edge (reversed if needed)
|
|
vs = e.Vertexes
|
|
if previousvs:
|
|
if (vs[0].Point-previousvs[-1].Point).Length > 1e-6:
|
|
vs.reverse()
|
|
if edgeindex == 0:
|
|
v = getProj(vs[0].Point)
|
|
svg += 'M '+ str(v.x) +' '+ str(v.y) + ' '
|
|
else:
|
|
if (vs[0].Point-previousvs[-1].Point).Length > 1e-6:
|
|
raise ValueError('edges not ordered')
|
|
iscircle = DraftGeomUtils.geomType(e) == "Circle"
|
|
isellipse = DraftGeomUtils.geomType(e) == "Ellipse"
|
|
if iscircle or isellipse:
|
|
import math
|
|
c = e.Curve
|
|
if len(e.Vertexes) == 1 and iscircle: #complete curve
|
|
svg = getCircle(e)
|
|
return svg
|
|
elif len(e.Vertexes) == 1 and isellipse:
|
|
#svg = getEllipse(e)
|
|
#return svg
|
|
endpoints = (getProj(c.value((c.LastParameter-\
|
|
c.FirstParameter)/2.0)), \
|
|
getProj(vs[-1].Point))
|
|
else:
|
|
endpoints = (getProj(vs[-1].Point),)
|
|
|
|
# arc
|
|
if iscircle:
|
|
rx = ry = c.Radius
|
|
rot = 0
|
|
else: #ellipse
|
|
rx = c.MajorRadius
|
|
ry = c.MinorRadius
|
|
rot = math.degrees(c.AngleXU * (c.Axis * \
|
|
FreeCAD.Vector(0,0,1)))
|
|
if rot > 90:
|
|
rot -=180
|
|
if rot < -90:
|
|
rot += 180
|
|
#be carefull with the sweep flag
|
|
if hasattr(FreeCAD,"DraftWorkingPlane"):
|
|
drawing_plane_normal = FreeCAD.DraftWorkingPlane.axis
|
|
else:
|
|
drawing_plane_normal = FreeCAD.Vector(0,0,1)
|
|
if plane: drawing_plane_normal = plane.axis
|
|
flag_large_arc = (((e.ParameterRange[1] - \
|
|
e.ParameterRange[0]) / math.pi) % 2) > 1
|
|
flag_sweep = (c.Axis * drawing_plane_normal >= 0) \
|
|
== (e.LastParameter > e.FirstParameter)
|
|
# == (e.Orientation == "Forward")
|
|
for v in endpoints:
|
|
svg += 'A %s %s %s %s %s %s %s ' % \
|
|
(str(rx),str(ry),str(rot),\
|
|
str(int(flag_large_arc)),\
|
|
str(int(flag_sweep)),str(v.x),str(v.y))
|
|
elif DraftGeomUtils.geomType(e) == "Line":
|
|
v = getProj(vs[-1].Point)
|
|
svg += 'L '+ str(v.x) +' '+ str(v.y) + ' '
|
|
else:
|
|
bspline=e.Curve.toBSpline(e.FirstParameter,e.LastParameter)
|
|
if bspline.Degree > 3 or bspline.isRational():
|
|
try:
|
|
bspline=bspline.approximateBSpline(0.05,50, 3,'C0')
|
|
except RuntimeError:
|
|
print("Debug: unable to approximate bspline")
|
|
if bspline.Degree <= 3 and not bspline.isRational():
|
|
for bezierseg in bspline.toBezier():
|
|
if bezierseg.Degree>3: #should not happen
|
|
raise AssertionError
|
|
elif bezierseg.Degree==1:
|
|
svg +='L '
|
|
elif bezierseg.Degree==2:
|
|
svg +='Q '
|
|
elif bezierseg.Degree==3:
|
|
svg +='C '
|
|
for pole in bezierseg.getPoles()[1:]:
|
|
v = getProj(pole)
|
|
svg += str(v.x) +' '+ str(v.y) + ' '
|
|
else:
|
|
print("Debug: one edge (hash ",e.hashCode(),\
|
|
") has been discretized with parameter 0.1")
|
|
for linepoint in bspline.discretize(0.1)[1:]:
|
|
v = getProj(linepoint)
|
|
svg += 'L '+ 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 += ';fill-rule: evenodd "'
|
|
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
|
|
|
|
def getEllipse(edge):
|
|
cen = getProj(edge.Curve.Center)
|
|
mir = edge.Curve.MinorRadius
|
|
mar = edge.Curve.MajorRadius
|
|
svg = '<ellipse cx="' + str(cen.x)
|
|
svg += '" cy="' + str(cen.y)
|
|
svg += '" rx="' + str(mar)
|
|
svg += '" ry="' + str(mir)+'" '
|
|
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 getArrow(arrowtype,point,arrowsize,color,linewidth,angle=0):
|
|
svg = ""
|
|
if obj.ViewObject.ArrowType == "Circle":
|
|
svg += '<circle cx="'+str(point.x)+'" cy="'+str(point.y)
|
|
svg += '" r="'+str(arrowsize)+'" '
|
|
svg += 'fill="none" stroke="'+ color + '" '
|
|
svg += 'style="stroke-width:'+ str(linewidth) + ';stroke-miterlimit:4;stroke-dasharray:none" '
|
|
svg += 'freecad:skip="1"'
|
|
svg += '/>\n'
|
|
elif obj.ViewObject.ArrowType == "Dot":
|
|
svg += '<circle cx="'+str(point.x)+'" cy="'+str(point.y)
|
|
svg += '" r="'+str(arrowsize)+'" '
|
|
svg += 'fill="'+ color +'" stroke="none" '
|
|
svg += 'style="stroke-miterlimit:4;stroke-dasharray:none" '
|
|
svg += 'freecad:skip="1"'
|
|
svg += '/>\n'
|
|
elif obj.ViewObject.ArrowType == "Arrow":
|
|
svg += '<path transform="rotate('+str(math.degrees(angle))
|
|
svg += ','+ str(point.x) + ',' + str(point.y) + ') '
|
|
svg += 'translate(' + str(point.x) + ',' + str(point.y) + ') '
|
|
svg += 'scale('+str(arrowsize)+','+str(arrowsize)+')" freecad:skip="1" '
|
|
svg += 'fill="'+ color +'" stroke="none" '
|
|
svg += 'style="stroke-miterlimit:4;stroke-dasharray:none" '
|
|
svg += 'd="M 0 0 L 4 1 L 4 -1 Z"/>\n'
|
|
else:
|
|
print("getSVG: arrow type not implemented")
|
|
return svg
|
|
|
|
def getText(color,fontsize,fontname,angle,base,text,linespacing=0.5,align="center",flip=True):
|
|
if not isinstance(text,list):
|
|
text = text.split("\n")
|
|
if align.lower() == "center":
|
|
anchor = "middle"
|
|
elif align.lower() == "left":
|
|
anchor = "start"
|
|
else:
|
|
anchor = "end"
|
|
svg = '<text fill="'
|
|
svg += color +'" font-size="'
|
|
svg += str(fontsize) + '" '
|
|
svg += 'style="text-anchor:'+anchor+';text-align:'+align.lower()+';'
|
|
svg += 'font-family:'+ fontname +'" '
|
|
svg += 'transform="rotate('+str(math.degrees(angle))
|
|
svg += ','+ str(base.x) + ',' + str(base.y) + ') '
|
|
if flip:
|
|
svg += 'translate(' + str(base.x) + ',' + str(base.y) + ') '
|
|
else:
|
|
svg += 'translate(' + str(base.x) + ',' + str(-base.y) + ') '
|
|
#svg += 'scale('+str(tmod/2000)+',-'+str(tmod/2000)+') '
|
|
if flip:
|
|
svg += 'scale(1,-1) '
|
|
else:
|
|
svg += 'scale(1,1) '
|
|
svg += '" freecad:skip="1"'
|
|
svg += '>\n'
|
|
if len(text) == 1:
|
|
try:
|
|
svg += text[0]
|
|
except:
|
|
svg += text[0].decode("utf8")
|
|
else:
|
|
for i in range(len(text)):
|
|
if i == 0:
|
|
svg += '<tspan>'
|
|
else:
|
|
svg += '<tspan x="0" dy="'+str(linespacing)+'">'
|
|
try:
|
|
svg += text[i]
|
|
except:
|
|
svg += text[i].decode("utf8")
|
|
svg += '</tspan>\n'
|
|
svg += '</text>\n'
|
|
return svg
|
|
|
|
|
|
if not obj:
|
|
pass
|
|
|
|
elif isinstance(obj,Part.Shape):
|
|
fill = 'url(#'+fillstyle+')'
|
|
lstyle = getLineStyle()
|
|
svg += getPath(obj.Edges,pathname="")
|
|
|
|
|
|
elif getType(obj) == "Dimension":
|
|
if obj.ViewObject.Proxy:
|
|
if hasattr(obj.ViewObject.Proxy,"p1"):
|
|
prx = obj.ViewObject.Proxy
|
|
ts = (len(prx.string)*obj.ViewObject.FontSize.Value)/4.0
|
|
rm = ((prx.p3.sub(prx.p2)).Length/2.0)-ts
|
|
p2a = getProj(prx.p2.add(DraftVecUtils.scaleTo(prx.p3.sub(prx.p2),rm)))
|
|
p2b = getProj(prx.p3.add(DraftVecUtils.scaleTo(prx.p2.sub(prx.p3),rm)))
|
|
p1 = getProj(prx.p1)
|
|
p2 = getProj(prx.p2)
|
|
p3 = getProj(prx.p3)
|
|
p4 = getProj(prx.p4)
|
|
tbase = getProj(prx.tbase)
|
|
r = prx.textpos.rotation.getValue().getValue()
|
|
rv = FreeCAD.Rotation(r[0],r[1],r[2],r[3]).multVec(FreeCAD.Vector(1,0,0))
|
|
angle = -DraftVecUtils.angle(getProj(rv))
|
|
#angle = -DraftVecUtils.angle(p3.sub(p2))
|
|
|
|
# drawing lines
|
|
svg = '<path '
|
|
if obj.ViewObject.DisplayMode == "2D":
|
|
tangle = angle
|
|
if tangle > math.pi/2:
|
|
tangle = tangle-math.pi
|
|
#elif (tangle <= -math.pi/2) or (tangle > math.pi/2):
|
|
# tangle = tangle+math.pi
|
|
#tbase = tbase.add(DraftVecUtils.rotate(Vector(0,2/scale,0),tangle))
|
|
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:
|
|
tangle = 0
|
|
tbase = tbase.add(Vector(0,-2.0/scale,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 += stroke + '" '
|
|
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'
|
|
|
|
# drawing arrows
|
|
if hasattr(obj.ViewObject,"ArrowType"):
|
|
arrowsize = obj.ViewObject.ArrowSize.Value/pointratio
|
|
if hasattr(obj.ViewObject,"FlipArrows"):
|
|
if obj.ViewObject.FlipArrows:
|
|
angle = angle+math.pi
|
|
svg += getArrow(obj.ViewObject.ArrowType,p2,arrowsize,stroke,linewidth,angle)
|
|
svg += getArrow(obj.ViewObject.ArrowType,p3,arrowsize,stroke,linewidth,angle+math.pi)
|
|
|
|
# drawing text
|
|
svg += getText(stroke,fontsize,obj.ViewObject.FontName,tangle,tbase,prx.string)
|
|
|
|
elif getType(obj) == "AngularDimension":
|
|
if obj.ViewObject.Proxy:
|
|
if hasattr(obj.ViewObject.Proxy,"circle"):
|
|
prx = obj.ViewObject.Proxy
|
|
|
|
# drawing arc
|
|
fill= "none"
|
|
lstyle = getLineStyle()
|
|
if obj.ViewObject.DisplayMode == "2D":
|
|
svg += getPath([prx.circle])
|
|
else:
|
|
if hasattr(prx,"circle1"):
|
|
svg += getPath([prx.circle1])
|
|
svg += getPath([prx.circle2])
|
|
else:
|
|
svg += getPath([prx.circle])
|
|
|
|
# drawing arrows
|
|
if hasattr(obj.ViewObject,"ArrowType"):
|
|
p2 = getProj(prx.p2)
|
|
p3 = getProj(prx.p3)
|
|
arrowsize = obj.ViewObject.ArrowSize.Value/pointratio
|
|
arrowlength = 4*obj.ViewObject.ArrowSize.Value
|
|
u1 = getProj((prx.circle.valueAt(prx.circle.FirstParameter+arrowlength)).sub(prx.circle.valueAt(prx.circle.FirstParameter)))
|
|
u2 = getProj((prx.circle.valueAt(prx.circle.LastParameter-arrowlength)).sub(prx.circle.valueAt(prx.circle.LastParameter)))
|
|
angle1 = -DraftVecUtils.angle(u1)
|
|
angle2 = -DraftVecUtils.angle(u2)
|
|
if hasattr(obj.ViewObject,"FlipArrows"):
|
|
if obj.ViewObject.FlipArrows:
|
|
angle1 = angle1+math.pi
|
|
angle2 = angle2+math.pi
|
|
svg += getArrow(obj.ViewObject.ArrowType,p2,arrowsize,stroke,linewidth,angle1)
|
|
svg += getArrow(obj.ViewObject.ArrowType,p3,arrowsize,stroke,linewidth,angle2)
|
|
|
|
# drawing text
|
|
if obj.ViewObject.DisplayMode == "2D":
|
|
t = prx.circle.tangentAt(prx.circle.FirstParameter+(prx.circle.LastParameter-prx.circle.FirstParameter)/2.0)
|
|
t = getProj(t)
|
|
tangle = DraftVecUtils.angle(t)
|
|
if (tangle <= -math.pi/2) or (tangle > math.pi/2):
|
|
tangle = tangle + math.pi
|
|
tbase = getProj(prx.circle.valueAt(prx.circle.FirstParameter+(prx.circle.LastParameter-prx.circle.FirstParameter)/2.0))
|
|
tbase = tbase.add(DraftVecUtils.rotate(Vector(0,2.0/scale,0),tangle))
|
|
#print(tbase)
|
|
else:
|
|
tangle = 0
|
|
tbase = getProj(prx.tbase)
|
|
svg += getText(stroke,fontsize,obj.ViewObject.FontName,tangle,tbase,prx.string)
|
|
|
|
elif getType(obj) == "Annotation":
|
|
"returns an svg representation of a document annotation"
|
|
n = obj.ViewObject.FontName
|
|
a = obj.ViewObject.Rotation.getValueAs("rad")
|
|
t = obj.LabelText
|
|
l = obj.ViewObject.LineSpacing/2.0
|
|
j = obj.ViewObject.Justification
|
|
svg += getText(stroke,fontsize,n,a,getProj(obj.Position),t,l,j)
|
|
|
|
elif getType(obj) == "Axis":
|
|
"returns the SVG representation of an Arch Axis system"
|
|
lorig = getLineStyle()
|
|
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="' + stroke + '" '
|
|
svg += 'font-size="' + str(rad) + '" '
|
|
svg += 'style="text-anchor:middle;'
|
|
svg += 'text-align:center;'
|
|
svg += 'font-family: sans;" '
|
|
svg += 'transform="translate(' + str(center.x+rad/4.0) + ',' + str(center.y-rad/3.0) + ') '
|
|
svg += 'scale(1,-1)"> '
|
|
svg += '<tspan>' + obj.ViewObject.Proxy.getNumber(n) + '</tspan>\n'
|
|
svg += '</text>\n'
|
|
n += 1
|
|
|
|
elif getType(obj) == "Space":
|
|
"returns an SVG fragment for the text of a space"
|
|
c = getrgb(obj.ViewObject.TextColor)
|
|
n = obj.ViewObject.FontName
|
|
a = 0
|
|
t1 = obj.ViewObject.Proxy.text1.string.getValues()
|
|
t2 = obj.ViewObject.Proxy.text2.string.getValues()
|
|
scale = obj.ViewObject.FirstLine.Value/obj.ViewObject.FontSize.Value
|
|
f1 = fontsize*scale
|
|
p2 = FreeCAD.Vector(obj.ViewObject.Proxy.coords.translation.getValue().getValue())
|
|
p1 = p2.add(FreeCAD.Vector(obj.ViewObject.Proxy.header.translation.getValue().getValue()))
|
|
l = obj.ViewObject.LineSpacing/2.0
|
|
j = obj.ViewObject.TextAlign
|
|
svg += getText(c,f1,n,a,getProj(p1),t1,l,j,flip=True)
|
|
if t2:
|
|
svg += getText(c,fontsize,n,a,getProj(p2),t2,l,j,flip=True)
|
|
|
|
elif obj.isDerivedFrom('Part::Feature'):
|
|
if obj.Shape.isNull():
|
|
return ''
|
|
# setting fill
|
|
if obj.Shape.Faces:
|
|
if gui:
|
|
try:
|
|
m = obj.ViewObject.DisplayMode
|
|
except AttributeError:
|
|
m = None
|
|
if (m != "Wireframe"):
|
|
if fillstyle == "shape color":
|
|
fill = getrgb(obj.ViewObject.ShapeColor,testbw=False)
|
|
else:
|
|
fill = 'url(#'+fillstyle+')'
|
|
svg += getPattern(fillstyle)
|
|
else:
|
|
fill = "none"
|
|
else:
|
|
fill = "#888888"
|
|
else:
|
|
fill = 'none'
|
|
lstyle = getLineStyle()
|
|
|
|
if len(obj.Shape.Vertexes) > 1:
|
|
wiredEdges = []
|
|
if obj.Shape.Faces:
|
|
for i,f in enumerate(obj.Shape.Faces):
|
|
svg += getPath(wires=f.Wires,pathname='%s_f%04d' % \
|
|
(obj.Name,i))
|
|
wiredEdges.extend(f.Edges)
|
|
else:
|
|
for i,w in enumerate(obj.Shape.Wires):
|
|
svg += getPath(w.Edges,pathname='%s_w%04d' % \
|
|
(obj.Name,i))
|
|
wiredEdges.extend(w.Edges)
|
|
if len(wiredEdges) != len(obj.Shape.Edges):
|
|
for i,e in enumerate(obj.Shape.Edges):
|
|
if (DraftGeomUtils.findEdge(e,wiredEdges) == None):
|
|
svg += getPath([e],pathname='%s_nwe%04d' % \
|
|
(obj.Name,i))
|
|
else:
|
|
# closed circle or spline
|
|
if isinstance(obj.Shape.Edges[0].Curve,Part.Circle):
|
|
svg = getCircle(obj.Shape.Edges[0])
|
|
else:
|
|
svg = getPath(obj.Shape.Edges)
|
|
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',True):
|
|
col = "#000000"
|
|
return col
|
|
|
|
def makeDrawingView(obj,page,lwmod=None,tmod=None,otherProjection=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
|
|
elif getType(obj) == "Panel":
|
|
import ArchPanel
|
|
viewobj = ArchPanel.makePanelView(obj,page)
|
|
else:
|
|
viewobj = FreeCAD.ActiveDocument.addObject("Drawing::FeatureViewPython","View"+obj.Name)
|
|
_DrawingView(viewobj)
|
|
page.addObject(viewobj)
|
|
if (otherProjection):
|
|
if hasattr(otherProjection,"Scale"):
|
|
viewobj.Scale = otherProjection.Scale
|
|
if hasattr(otherProjection,"X"):
|
|
viewobj.X = otherProjection.X
|
|
if hasattr(otherProjection,"Y"):
|
|
viewobj.Y = otherProjection.Y
|
|
if hasattr(otherProjection,"Rotation"):
|
|
viewobj.Rotation = otherProjection.Rotation
|
|
if hasattr(otherProjection,"Direction"):
|
|
viewobj.Direction = otherProjection.Direction
|
|
else:
|
|
if hasattr(page.ViewObject,"HintScale"):
|
|
viewobj.Scale = page.ViewObject.HintScale
|
|
if hasattr(page.ViewObject,"HintOffsetX"):
|
|
viewobj.X = page.ViewObject.HintOffsetX
|
|
if hasattr(page.ViewObject,"HintOffsetY"):
|
|
viewobj.Y = page.ViewObject.HintOffsetY
|
|
viewobj.Source = obj
|
|
if lwmod: viewobj.LineweightModifier = lwmod
|
|
if tmod: viewobj.TextModifier = tmod
|
|
if hasattr(obj.ViewObject,"Pattern"):
|
|
if str(obj.ViewObject.Pattern) in list(svgpatterns().keys()):
|
|
viewobj.FillStyle = str(obj.ViewObject.Pattern)
|
|
if hasattr(obj.ViewObject,"DrawStyle"):
|
|
viewobj.LineStyle = obj.ViewObject.DrawStyle
|
|
if hasattr(obj.ViewObject,"LineColor"):
|
|
viewobj.LineColor = obj.ViewObject.LineColor
|
|
elif hasattr(obj.ViewObject,"TextColor"):
|
|
viewobj.LineColor = obj.ViewObject.TextColor
|
|
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)
|
|
FreeCAD.ActiveDocument.recompute()
|
|
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
|
|
from DraftTools import translate
|
|
|
|
StartPoint = 1
|
|
EndPoint = 2
|
|
MiddlePoint = 3
|
|
deletable = None
|
|
|
|
if not isinstance(objectslist,list):
|
|
objectslist = [objectslist]
|
|
if addTo:
|
|
nobj = addTo
|
|
else:
|
|
nobj = FreeCAD.ActiveDocument.addObject("Sketcher::SketchObject",name)
|
|
deletable = nobj
|
|
nobj.ViewObject.Autoconstraints = False
|
|
for obj in objectslist:
|
|
ok = False
|
|
tp = getType(obj)
|
|
if tp in ["BSpline","BezCurve"]:
|
|
FreeCAD.Console.PrintError(translate("draft","BSplines and Bezier curves are not supported by this tool"))
|
|
if deletable: FreeCAD.ActiveDocument.removeObject(deletable.Name)
|
|
return None
|
|
elif tp in ["Circle","Ellipse"]:
|
|
g = (DraftGeomUtils.geom(obj.Shape.Edges[0],nobj.Placement))
|
|
nobj.addGeometry(g)
|
|
# TODO add Radius constraits
|
|
ok = True
|
|
elif tp == "Rectangle":
|
|
if obj.FilletRadius.Value == 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.Value == 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 = list(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):
|
|
FreeCAD.Console.PrintError(translate("draft","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) in ["BSplineCurve","BezierCurve"]:
|
|
FreeCAD.Console.PrintError(translate("draft","BSplines and Bezier curves are not supported by this tool"))
|
|
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):
|
|
''' makePoint(x,y,z ,[color(r,g,b),point_size]) or
|
|
makePoint(Vector,color(r,g,b),point_size]) -
|
|
creates a Point in the current document.
|
|
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)
|
|
if isinstance(X,FreeCAD.Vector):
|
|
Z = X.z
|
|
Y = X.y
|
|
X = X.x
|
|
_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 makeShapeString(String,FontFile,Size = 100,Tracking = 0):
|
|
'''ShapeString(Text,FontFile,Height,Track): Turns a text string
|
|
into a Compound Shape'''
|
|
obj = FreeCAD.ActiveDocument.addObject("Part::Part2DObjectPython","ShapeString")
|
|
_ShapeString(obj)
|
|
obj.String = String
|
|
obj.FontFile = FontFile
|
|
obj.Size = Size
|
|
obj.Tracking = Tracking
|
|
|
|
if gui:
|
|
_ViewProviderDraft(obj.ViewObject)
|
|
formatObject(obj)
|
|
obrep = obj.ViewObject
|
|
if "PointSize" in obrep.PropertiesList: obrep.PointSize = 1 # hide the segment end points
|
|
select(obj)
|
|
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]
|
|
if (len(obj) == 1) and obj[0].isDerivedFrom("Part::Part2DObject"):
|
|
cl = FreeCAD.ActiveDocument.addObject("Part::Part2DObjectPython","Clone2D")
|
|
cl.Label = "Clone of " + obj[0].Label + " (2D)"
|
|
elif (len(obj) == 1) and hasattr(obj[0],"CloneOf"):
|
|
# arch objects can be clones
|
|
import Arch
|
|
cl = getattr(Arch,"make"+obj[0].Proxy.Type)()
|
|
base = getCloneBase(obj[0])
|
|
cl.Label = "Clone of " + base.Label
|
|
cl.CloneOf = base
|
|
return cl
|
|
else:
|
|
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 getCloneBase(obj,strict=False):
|
|
'''getCloneBase(obj,[strict]): returns the object cloned by this object, if
|
|
any, or this object if it is no clone. If strict is True, if this object is
|
|
not a clone, this function returns False'''
|
|
if hasattr(obj,"CloneOf"):
|
|
if obj.CloneOf:
|
|
return getCloneBase(obj.CloneOf)
|
|
if getType(obj) == "Clone":
|
|
return obj.Objects[0]
|
|
if strict:
|
|
return False
|
|
return obj
|
|
|
|
|
|
def mirror(objlist,p1,p2):
|
|
'''mirror(objlist,p1,p2,[clone]): creates a mirrored version of the given object(s)
|
|
along an axis that passes through the two vectors p1 and p2.'''
|
|
|
|
if not objlist:
|
|
FreeCAD.Console.PrintError(translate("draft","No object given\n"))
|
|
return
|
|
if p1 == p2:
|
|
FreeCAD.Console.PrintError(translate("draft","The two points are coincident\n"))
|
|
return
|
|
if not isinstance(objlist,list):
|
|
objlist = [objlist]
|
|
|
|
result = []
|
|
|
|
for obj in objlist:
|
|
mir = FreeCAD.ActiveDocument.addObject("Part::Mirroring","mirror")
|
|
mir.Label = "Mirror of "+obj.Label
|
|
mir.Source = obj
|
|
if gui:
|
|
norm = FreeCADGui.ActiveDocument.ActiveView.getViewDirection().negative()
|
|
else:
|
|
norm = FreeCAD.Vector(0,0,1)
|
|
pnorm = p2.sub(p1).cross(norm).normalize()
|
|
mir.Base = p1
|
|
mir.Normal = pnorm
|
|
formatObject(mir,obj)
|
|
result.append(mir)
|
|
|
|
if len(result) == 1:
|
|
result = result[0]
|
|
return result
|
|
|
|
|
|
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.'''
|
|
|
|
auto = False
|
|
|
|
if not objlist:
|
|
objlist = FreeCAD.ActiveDocument.Objects
|
|
print("Automatic mode: Healing whole document...")
|
|
auto = True
|
|
else:
|
|
print("Manual mode: Force-healing selected objects...")
|
|
|
|
if not isinstance(objlist,list):
|
|
objlist = [objlist]
|
|
|
|
dellist = []
|
|
got = False
|
|
|
|
for obj in objlist:
|
|
dtype = getType(obj)
|
|
ftype = obj.TypeId
|
|
if ftype in ["Part::FeaturePython","App::FeaturePython","Part::Part2DObjectPython","Drawing::FeatureViewPython"]:
|
|
proxy = obj.Proxy
|
|
if hasattr(obj,"ViewObject"):
|
|
if hasattr(obj.ViewObject,"Proxy"):
|
|
proxy = obj.ViewObject.Proxy
|
|
if (proxy == 1) or (dtype in ["Unknown","Part"]) or (not auto):
|
|
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)
|
|
elif ("FillStyle" in props) and ("FontSize" in props):
|
|
nobj = makeCopy(obj,force="DrawingView",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 makeFacebinder(selectionset,name="Facebinder"):
|
|
"""makeFacebinder(selectionset,[name]): creates a Facebinder object from a selection set.
|
|
Only faces will be added."""
|
|
if not isinstance(selectionset,list):
|
|
selectionset = [selectionset]
|
|
fb = FreeCAD.ActiveDocument.addObject("Part::FeaturePython",name)
|
|
_Facebinder(fb)
|
|
if gui:
|
|
_ViewProviderDraft(fb.ViewObject)
|
|
faces = []
|
|
fb.Proxy.addSubobjects(fb,selectionset)
|
|
return fb
|
|
|
|
|
|
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 turnToLine(obj):
|
|
"""turns an edge into a Draft line"""
|
|
p1 = obj.Shape.Vertexes[0].Point
|
|
p2 = obj.Shape.Vertexes[-1].Point
|
|
newobj = makeLine(p1,p2)
|
|
addList.append(newobj)
|
|
deleteList.append(obj)
|
|
return newobj
|
|
|
|
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 Part.OCCError:
|
|
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(Part.__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.extend(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:
|
|
try:
|
|
f = Part.Face(w)
|
|
except Part.OCCError:
|
|
pass
|
|
else:
|
|
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 = Part.__sortEdges__(edges[:])
|
|
# for e in nedges: print("debug: ",e.Curve,e.Vertexes[0].Point,e.Vertexes[-1].Point)
|
|
w = Part.Wire(nedges)
|
|
except Part.OCCError:
|
|
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.TypeId == "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","turnToLine"]:
|
|
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: creating 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: creating 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 one object that contains one edge
|
|
elif (not faces) and (len(objects) == 1) and (len(edges) == 1):
|
|
# we have a closed sketch: Extract a face
|
|
if objects[0].isDerivedFrom("Sketcher::SketchObject") and (len(edges[0].Vertexes) == 1):
|
|
result = makeSketchFace(objects[0])
|
|
if result: msg(translate("draft", "Found 1 closed sketch object: creating a face from it\n"))
|
|
else:
|
|
# turn to Draft line
|
|
e = objects[0].Shape.Edges[0]
|
|
if isinstance(e.Curve,Part.Line):
|
|
result = turnToLine(objects[0])
|
|
if result: msg(translate("draft", "Found 1 linear object: converting to line\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"):
|
|
result = makeSketchFace(objects[0])
|
|
if result: msg(translate("draft", "Found 1 closed sketch object: creating a face from it\n"))
|
|
|
|
# only closed wires
|
|
else:
|
|
result = makeFaces(objects)
|
|
if result: msg(translate("draft", "Found closed wires: creating 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: creating 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, splitCompounds.
|
|
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, addList
|
|
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 splitCompounds(objects):
|
|
"""split solids contained in compound objects into new objects"""
|
|
result = False
|
|
for o in objects:
|
|
if o.Shape.Solids:
|
|
for s in o.Shape.Solids:
|
|
newobj = FreeCAD.ActiveDocument.addObject("Part::Feature","Solid")
|
|
newobj.Shape = s
|
|
addList.append(newobj)
|
|
result = True
|
|
deleteList.append(o)
|
|
return result
|
|
|
|
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.extend(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 = []
|
|
solids = []
|
|
result = None
|
|
|
|
for o in objects:
|
|
if o.isDerivedFrom("Part::Feature"):
|
|
for s in o.Shape.Solids:
|
|
solids.append(s)
|
|
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"))
|
|
|
|
# we have one multi-solids compound object: extract its solids
|
|
elif (len(objects) == 1) and (getType(objects[0]) == "Part") and (len(solids) > 1):
|
|
result = splitCompounds(objects)
|
|
#print(result)
|
|
if result: msg(translate("draft", "Found 1 multi-solids compound: 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"))
|
|
addList.append(result)
|
|
#deleteList.append(objects[0])
|
|
|
|
# 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, obj, prop):
|
|
pass
|
|
|
|
class _ViewProviderDraft:
|
|
"The base class for Draft Viewproviders"
|
|
|
|
def __init__(self, vobj):
|
|
from DraftTools import translate
|
|
vobj.Proxy = self
|
|
self.Object = vobj.Object
|
|
vobj.addProperty("App::PropertyEnumeration","Pattern",
|
|
"Draft","Defines a hatch pattern")
|
|
vobj.addProperty("App::PropertyFloat","PatternSize",
|
|
"Draft","Sets the size of the pattern")
|
|
vobj.Pattern = ["None"]+list(svgpatterns().keys())
|
|
vobj.PatternSize = 1
|
|
|
|
def __getstate__(self):
|
|
return None
|
|
|
|
def __setstate__(self, state):
|
|
return None
|
|
|
|
def attach(self,vobj):
|
|
self.texture = None
|
|
self.texcoords = None
|
|
self.Object = vobj.Object
|
|
self.onChanged(vobj,"Pattern")
|
|
return
|
|
|
|
def updateData(self, obj, prop):
|
|
return
|
|
|
|
def getDisplayModes(self, vobj):
|
|
modes=[]
|
|
return modes
|
|
|
|
def setDisplayMode(self, mode):
|
|
return mode
|
|
|
|
def onChanged(self, vobj, prop):
|
|
# treatment of patterns and image textures
|
|
if prop in ["TextureImage","Pattern"]:
|
|
if hasattr(self.Object,"Shape"):
|
|
if self.Object.Shape.Faces:
|
|
from pivy import coin
|
|
from PySide import QtCore
|
|
path = None
|
|
if hasattr(vobj,"TextureImage"):
|
|
if vobj.TextureImage:
|
|
path = vobj.TextureImage
|
|
if not path:
|
|
if hasattr(vobj,"Pattern"):
|
|
if str(vobj.Pattern) in list(svgpatterns().keys()):
|
|
path = svgpatterns()[vobj.Pattern][1]
|
|
if path and vobj.RootNode:
|
|
if vobj.RootNode.getChildren().getLength() > 2:
|
|
if vobj.RootNode.getChild(2).getChildren().getLength() > 0:
|
|
if vobj.RootNode.getChild(2).getChild(0).getChildren().getLength() > 2:
|
|
r = vobj.RootNode.getChild(2).getChild(0).getChild(2)
|
|
i = QtCore.QFileInfo(path)
|
|
if self.texture:
|
|
r.removeChild(self.texture)
|
|
self.texture = None
|
|
if self.texcoords:
|
|
r.removeChild(self.texcoords)
|
|
self.texcoords = None
|
|
if i.exists():
|
|
size = None
|
|
if ".SVG" in path.upper():
|
|
size = getParam("HatchPatternResolution",128)
|
|
if not size:
|
|
size = 128
|
|
im = loadTexture(path, size)
|
|
if im:
|
|
self.texture = coin.SoTexture2()
|
|
self.texture.image = im
|
|
r.insertChild(self.texture,1)
|
|
if size:
|
|
s =1
|
|
if hasattr(vobj,"PatternSize"):
|
|
if vobj.PatternSize:
|
|
s = vobj.PatternSize
|
|
self.texcoords = coin.SoTextureCoordinatePlane()
|
|
self.texcoords.directionS.setValue(s,0,0)
|
|
self.texcoords.directionT.setValue(0,s,0)
|
|
r.insertChild(self.texcoords,2)
|
|
elif prop == "PatternSize":
|
|
if hasattr(self,"texcoords"):
|
|
if self.texcoords:
|
|
s = 1
|
|
if vobj.PatternSize:
|
|
s = vobj.PatternSize
|
|
self.texcoords.directionS.setValue(s,0,0)
|
|
self.texcoords.directionT.setValue(0,s,0)
|
|
return
|
|
|
|
def execute(self,vobj):
|
|
return
|
|
|
|
def setEdit(self,vobj,mode=0):
|
|
FreeCADGui.runCommand("Draft_Edit")
|
|
return True
|
|
|
|
def unsetEdit(self,vobj,mode=0):
|
|
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::PropertyVectorDistance","Start","Draft","Startpoint of dimension")
|
|
obj.addProperty("App::PropertyVectorDistance","End","Draft","Endpoint of dimension")
|
|
obj.addProperty("App::PropertyVector","Normal","Draft","the normal direction of this dimension")
|
|
obj.addProperty("App::PropertyVector","Direction","Draft","the normal direction of this dimension")
|
|
obj.addProperty("App::PropertyVectorDistance","Dimline","Draft","Point through which the dimension line passes")
|
|
obj.addProperty("App::PropertyLink","Support","Draft","The object measured by this dimension")
|
|
obj.addProperty("App::PropertyLinkSubList","LinkedGeometry","Draft","The geometry this dimension is linked to")
|
|
obj.addProperty("App::PropertyLength","Distance","Draft","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):
|
|
if hasattr(obj,"Distance"):
|
|
obj.setEditorMode('Distance',1)
|
|
if hasattr(obj,"Normal"):
|
|
obj.setEditorMode('Normal',2)
|
|
if hasattr(obj,"Support"):
|
|
obj.setEditorMode('Support',2)
|
|
|
|
def execute(self, obj):
|
|
if obj.LinkedGeometry:
|
|
if "Edge" in obj.LinkedGeometry[0][1]:
|
|
n = int(obj.LinkedGeometry[0][1][4:])-1
|
|
if len(obj.LinkedGeometry) > 1:
|
|
c = obj.LinkedGeometry[0][0].Shape.Edges[n].Curve.Center
|
|
r = obj.LinkedGeometry[0][0].Shape.Edges[n].Curve.Radius
|
|
ray = DraftVecUtils.scaleTo(obj.Dimline.sub(c),r)
|
|
if "Center" in obj.LinkedGeometry[1][1]:
|
|
obj.Start = c
|
|
obj.End = c.add(ray)
|
|
elif "Diameter" in obj.LinkedGeometry[1][1]:
|
|
obj.Start = c.add(ray.negative())
|
|
obj.End = c.add(ray)
|
|
else:
|
|
obj.Start = obj.LinkedGeometry[0][0].Shape.Edges[n].Vertexes[0].Point
|
|
obj.End = obj.LinkedGeometry[0][0].Shape.Edges[n].Vertexes[-1].Point
|
|
elif "Vertex" in obj.LinkedGeometry[0][1]:
|
|
n = int(obj.LinkedGeometry[0][1][6:])-1
|
|
obj.Start = obj.LinkedGeometry[0][0].Shape.Vertexes[n].Point
|
|
if len(obj.LinkedGeometry) > 1:
|
|
if "Vertex" in obj.LinkedGeometry[1][1]:
|
|
n = int(obj.LinkedGeometry[1][1][6:])-1
|
|
obj.End = obj.LinkedGeometry[1][0].Shape.Vertexes[n].Point
|
|
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","Draft","Font size")
|
|
obj.addProperty("App::PropertyInteger","Decimals","Draft","The number of decimals to show")
|
|
obj.addProperty("App::PropertyLength","ArrowSize","Draft","Arrow size")
|
|
obj.addProperty("App::PropertyLength","TextSpacing","Draft","The spacing between the text and the dimension line")
|
|
obj.addProperty("App::PropertyEnumeration","ArrowType","Draft","Arrow type")
|
|
obj.addProperty("App::PropertyString","FontName","Draft","Font name")
|
|
obj.addProperty("App::PropertyFloat","LineWidth","Draft","Line width")
|
|
obj.addProperty("App::PropertyColor","LineColor","Draft","Line color")
|
|
obj.addProperty("App::PropertyDistance","ExtLines","Draft","Length of the extension lines")
|
|
obj.addProperty("App::PropertyBool","FlipArrows","Draft","Rotate the dimension arrows 180 degrees")
|
|
obj.addProperty("App::PropertyBool","ShowUnit","Draft","Show the unit suffix")
|
|
obj.addProperty("App::PropertyVectorDistance","TextPosition","Draft","The position of the text. Leave (0,0,0) for automatic position")
|
|
obj.addProperty("App::PropertyString","Override","Draft","Text override. Use $dim to insert the dimension length")
|
|
obj.FontSize = getParam("textheight",0.20)
|
|
obj.TextSpacing = getParam("dimspacing",0.05)
|
|
obj.FontName = getParam("textfont","")
|
|
obj.ArrowSize = getParam("arrowsize",0.1)
|
|
obj.ArrowType = arrowtypes
|
|
obj.ArrowType = arrowtypes[getParam("dimsymbol",0)]
|
|
obj.ExtLines = getParam("extlines",0.3)
|
|
obj.Decimals = getParam("dimPrecision",2)
|
|
obj.ShowUnit = getParam("showUnit",True)
|
|
_ViewProviderDraft.__init__(self,obj)
|
|
|
|
def attach(self, vobj):
|
|
"called on object creation"
|
|
from pivy import coin
|
|
self.Object = vobj.Object
|
|
self.color = coin.SoBaseColor()
|
|
self.font = coin.SoFont()
|
|
self.font3d = coin.SoFont()
|
|
self.text = coin.SoAsciiText()
|
|
self.text3d = coin.SoText2()
|
|
self.text.string = "d" # some versions of coin crash if string is not set
|
|
self.text3d.string = "d"
|
|
self.textpos = coin.SoTransform()
|
|
self.text.justification = self.text3d.justification = coin.SoAsciiText.CENTER
|
|
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.trans1 = coin.SoTransform()
|
|
self.coord2 = coin.SoCoordinate3()
|
|
self.trans2 = coin.SoTransform()
|
|
self.marks = coin.SoSeparator()
|
|
self.drawstyle = coin.SoDrawStyle()
|
|
self.line = coin.SoType.fromName("SoBrepEdgeSet").createInstance()
|
|
self.coords = coin.SoCoordinate3()
|
|
self.node = coin.SoGroup()
|
|
self.node.addChild(self.color)
|
|
self.node.addChild(self.drawstyle)
|
|
self.node.addChild(self.coords)
|
|
self.node.addChild(self.line)
|
|
self.node.addChild(self.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.line)
|
|
self.node3d.addChild(self.marks)
|
|
self.node3d.addChild(label3d)
|
|
vobj.addDisplayMode(self.node,"2D")
|
|
vobj.addDisplayMode(self.node3d,"3D")
|
|
self.updateData(vobj.Object,"Start")
|
|
self.onChanged(vobj,"FontSize")
|
|
self.onChanged(vobj,"FontName")
|
|
self.onChanged(vobj,"ArrowType")
|
|
self.onChanged(vobj,"LineColor")
|
|
|
|
def updateData(self, obj, prop):
|
|
"called when the base object is changed"
|
|
import DraftGui
|
|
if prop in ["Start","End","Dimline","Direction"]:
|
|
|
|
if obj.Start == obj.End:
|
|
return
|
|
|
|
if not hasattr(self,"node"):
|
|
return
|
|
|
|
import Part, DraftGeomUtils
|
|
from pivy import coin
|
|
|
|
# calculate the 4 points
|
|
self.p1 = obj.Start
|
|
self.p4 = obj.End
|
|
base = None
|
|
if hasattr(obj,"Direction"):
|
|
if not DraftVecUtils.isNull(obj.Direction):
|
|
v2 = self.p1.sub(obj.Dimline)
|
|
v3 = self.p4.sub(obj.Dimline)
|
|
v2 = DraftVecUtils.project(v2,obj.Direction)
|
|
v3 = DraftVecUtils.project(v3,obj.Direction)
|
|
self.p2 = obj.Dimline.add(v2)
|
|
self.p3 = obj.Dimline.add(v3)
|
|
if DraftVecUtils.equals(self.p2,self.p3):
|
|
base = None
|
|
proj = None
|
|
else:
|
|
base = Part.Line(self.p2,self.p3).toShape()
|
|
proj = DraftGeomUtils.findDistance(self.p1,base)
|
|
if not base:
|
|
if DraftVecUtils.equals(self.p1,self.p4):
|
|
base = None
|
|
proj = None
|
|
else:
|
|
base = Part.Line(self.p1,self.p4).toShape()
|
|
proj = DraftGeomUtils.findDistance(obj.Dimline,base)
|
|
if proj:
|
|
self.p2 = self.p1.add(proj.negative())
|
|
self.p3 = self.p4.add(proj.negative())
|
|
if hasattr(obj.ViewObject,"ExtLines"):
|
|
dmax = obj.ViewObject.ExtLines.Value
|
|
if dmax and (proj.Length > dmax):
|
|
if (dmax > 0):
|
|
self.p1 = self.p2.add(DraftVecUtils.scaleTo(proj,dmax))
|
|
self.p4 = self.p3.add(DraftVecUtils.scaleTo(proj,dmax))
|
|
else:
|
|
rest = proj.Length + dmax
|
|
self.p1 = self.p2.add(DraftVecUtils.scaleTo(proj,rest))
|
|
self.p4 = self.p3.add(DraftVecUtils.scaleTo(proj,rest))
|
|
else:
|
|
self.p2 = self.p1
|
|
self.p3 = self.p4
|
|
proj = (self.p3.sub(self.p2)).cross(Vector(0,0,1))
|
|
|
|
# calculate the arrows positions
|
|
self.trans1.translation.setValue((self.p2.x,self.p2.y,self.p2.z))
|
|
self.coord1.point.setValue((self.p2.x,self.p2.y,self.p2.z))
|
|
self.trans2.translation.setValue((self.p3.x,self.p3.y,self.p3.z))
|
|
self.coord2.point.setValue((self.p3.x,self.p3.y,self.p3.z))
|
|
|
|
# calculate the text position and orientation
|
|
if hasattr(obj,"Normal"):
|
|
if DraftVecUtils.isNull(obj.Normal):
|
|
if proj:
|
|
norm = (self.p3.sub(self.p2).cross(proj)).negative()
|
|
else:
|
|
norm = Vector(0,0,1)
|
|
else:
|
|
norm = obj.Normal
|
|
else:
|
|
if proj:
|
|
norm = (self.p3.sub(self.p2).cross(proj)).negative()
|
|
else:
|
|
norm = Vector(0,0,1)
|
|
if not DraftVecUtils.isNull(norm):
|
|
norm.normalize()
|
|
u = self.p3.sub(self.p2)
|
|
u.normalize()
|
|
v1 = norm.cross(u)
|
|
rot1 = FreeCAD.Placement(DraftVecUtils.getPlaneRotation(u,v1,norm)).Rotation.Q
|
|
if hasattr(obj.ViewObject,"FlipArrows"):
|
|
if obj.ViewObject.FlipArrows:
|
|
u = u.negative()
|
|
v2 = norm.cross(u)
|
|
rot2 = FreeCAD.Placement(DraftVecUtils.getPlaneRotation(u,v2,norm)).Rotation.Q
|
|
self.trans1.rotation.setValue((rot2[0],rot2[1],rot2[2],rot2[3]))
|
|
self.trans2.rotation.setValue((rot2[0],rot2[1],rot2[2],rot2[3]))
|
|
if hasattr(obj.ViewObject,"TextSpacing"):
|
|
offset = DraftVecUtils.scaleTo(v1,obj.ViewObject.TextSpacing.Value)
|
|
else:
|
|
offset = DraftVecUtils.scaleTo(v1,0.05)
|
|
|
|
# setting text
|
|
try:
|
|
m = obj.ViewObject.DisplayMode
|
|
except: # swallow all exceptions here since it always fails on first run (Displaymode enum no set yet)
|
|
m = ["2D","3D"][getParam("dimstyle",0)]
|
|
if m== "3D":
|
|
offset = offset.negative()
|
|
self.tbase = (self.p2.add((self.p3.sub(self.p2).multiply(0.5)))).add(offset)
|
|
if hasattr(obj.ViewObject,"TextPosition"):
|
|
if not DraftVecUtils.isNull(obj.ViewObject.TextPosition):
|
|
self.tbase = obj.ViewObject.TextPosition
|
|
self.textpos.translation.setValue([self.tbase.x,self.tbase.y,self.tbase.z])
|
|
self.textpos.rotation = coin.SbRotation(rot1[0],rot1[1],rot1[2],rot1[3])
|
|
su = True
|
|
if hasattr(obj.ViewObject,"ShowUnit"):
|
|
su = obj.ViewObject.ShowUnit
|
|
|
|
# set text value
|
|
l = self.p3.sub(self.p2).Length
|
|
if hasattr(obj.ViewObject,"Decimals"):
|
|
self.string = DraftGui.displayExternal(l,obj.ViewObject.Decimals,'Length',su)
|
|
else:
|
|
self.string = DraftGui.displayExternal(l,getParam("dimPrecision",2),'Length',su)
|
|
if hasattr(obj.ViewObject,"Override"):
|
|
if obj.ViewObject.Override:
|
|
self.string = obj.ViewObject.Override.replace("$dim",\
|
|
self.string)
|
|
self.text.string = self.text3d.string = stringencodecoin(self.string)
|
|
# set the distance property
|
|
if round(obj.Distance.Value,precision()) != round(l,precision()):
|
|
obj.Distance = l
|
|
|
|
# set the lines
|
|
if m == "3D":
|
|
# calculate the spacing of the text
|
|
textsize = (len(self.string)*obj.ViewObject.FontSize.Value)/4.0
|
|
spacing = ((self.p3.sub(self.p2)).Length/2.0) - textsize
|
|
self.p2a = self.p2.add(DraftVecUtils.scaleTo(self.p3.sub(self.p2),spacing))
|
|
self.p2b = self.p3.add(DraftVecUtils.scaleTo(self.p2.sub(self.p3),spacing))
|
|
self.coords.point.setValues([[self.p1.x,self.p1.y,self.p1.z],
|
|
[self.p2.x,self.p2.y,self.p2.z],
|
|
[self.p2a.x,self.p2a.y,self.p2a.z],
|
|
[self.p2b.x,self.p2b.y,self.p2b.z],
|
|
[self.p3.x,self.p3.y,self.p3.z],
|
|
[self.p4.x,self.p4.y,self.p4.z]])
|
|
#self.line.numVertices.setValues([3,3])
|
|
self.line.coordIndex.setValues(0,7,(0,1,2,-1,3,4,5))
|
|
else:
|
|
self.coords.point.setValues([[self.p1.x,self.p1.y,self.p1.z],
|
|
[self.p2.x,self.p2.y,self.p2.z],
|
|
[self.p3.x,self.p3.y,self.p3.z],
|
|
[self.p4.x,self.p4.y,self.p4.z]])
|
|
#self.line.numVertices.setValue(4)
|
|
self.line.coordIndex.setValues(0,4,(0,1,2,3))
|
|
|
|
def onChanged(self, vobj, prop):
|
|
"called when a view property has changed"
|
|
|
|
if (prop == "FontSize") and hasattr(vobj,"FontSize"):
|
|
if hasattr(self,"font"):
|
|
self.font.size = vobj.FontSize.Value
|
|
if hasattr(self,"font3d"):
|
|
self.font3d.size = vobj.FontSize.Value*100
|
|
elif (prop == "FontName") and hasattr(vobj,"FontName"):
|
|
if hasattr(self,"font") and hasattr(self,"font3d"):
|
|
self.font.name = self.font3d.name = str(vobj.FontName)
|
|
elif (prop == "LineColor") and hasattr(vobj,"LineColor"):
|
|
if hasattr(self,"color"):
|
|
c = vobj.LineColor
|
|
self.color.rgb.setValue(c[0],c[1],c[2])
|
|
elif (prop == "LineWidth") and hasattr(vobj,"LineWidth"):
|
|
if hasattr(self,"drawstyle"):
|
|
self.drawstyle.lineWidth = vobj.LineWidth
|
|
elif (prop in ["ArrowSize","ArrowType"]) and hasattr(vobj,"ArrowSize"):
|
|
if hasattr(self,"node") and hasattr(self,"p2"):
|
|
from pivy import coin
|
|
|
|
if not hasattr(vobj,"ArrowType"):
|
|
return
|
|
|
|
if self.p3.x < self.p2.x:
|
|
inv = False
|
|
else:
|
|
inv = True
|
|
|
|
# set scale
|
|
symbol = arrowtypes.index(vobj.ArrowType)
|
|
s = vobj.ArrowSize.Value
|
|
self.trans1.scaleFactor.setValue((s,s,s))
|
|
self.trans2.scaleFactor.setValue((s,s,s))
|
|
|
|
# remove existing nodes
|
|
self.node.removeChild(self.marks)
|
|
self.node3d.removeChild(self.marks)
|
|
|
|
# set new nodes
|
|
self.marks = coin.SoSeparator()
|
|
self.marks.addChild(self.color)
|
|
s1 = coin.SoSeparator()
|
|
if symbol == "Circle":
|
|
s1.addChild(self.coord1)
|
|
else:
|
|
s1.addChild(self.trans1)
|
|
s1.addChild(dimSymbol(symbol,invert=not(inv)))
|
|
self.marks.addChild(s1)
|
|
s2 = coin.SoSeparator()
|
|
if symbol == "Circle":
|
|
s2.addChild(self.coord2)
|
|
else:
|
|
s2.addChild(self.trans2)
|
|
s2.addChild(dimSymbol(symbol,invert=inv))
|
|
self.marks.addChild(s2)
|
|
self.node.insertChild(self.marks,2)
|
|
self.node3d.insertChild(self.marks,2)
|
|
else:
|
|
self.updateData(vobj.Object,"Start")
|
|
|
|
def doubleClicked(self,vobj):
|
|
self.setEdit(vobj)
|
|
|
|
def getDisplayModes(self,vobj):
|
|
return ["2D","3D"]
|
|
|
|
def getDefaultDisplayMode(self):
|
|
if hasattr(self,"defaultmode"):
|
|
return self.defaultmode
|
|
else:
|
|
return ["2D","3D"][getParam("dimstyle",0)]
|
|
|
|
def setDisplayMode(self,mode):
|
|
return mode
|
|
|
|
def getIcon(self):
|
|
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","Draft","Start angle of the dimension")
|
|
obj.addProperty("App::PropertyAngle","LastAngle","Draft","End angle of the dimension")
|
|
obj.addProperty("App::PropertyVectorDistance","Dimline","Draft","Point through which the dimension line passes")
|
|
obj.addProperty("App::PropertyVectorDistance","Center","Draft","The center point of this dimension")
|
|
obj.addProperty("App::PropertyVector","Normal","Draft","The normal direction of this dimension")
|
|
obj.addProperty("App::PropertyLink","Support","Draft","The object measured by this dimension")
|
|
obj.addProperty("App::PropertyLinkSubList","LinkedGeometry","Draft","The geometry this dimension is linked to")
|
|
obj.addProperty("App::PropertyAngle","Angle","Draft","The measurement of this dimension")
|
|
obj.FirstAngle = 0
|
|
obj.LastAngle = 90
|
|
obj.Dimline = FreeCAD.Vector(0,1,0)
|
|
obj.Center = FreeCAD.Vector(0,0,0)
|
|
|
|
def onChanged(self,obj,prop):
|
|
if hasattr(obj,"Angle"):
|
|
obj.setEditorMode('Angle',1)
|
|
if hasattr(obj,"Normal"):
|
|
obj.setEditorMode('Normal',2)
|
|
if hasattr(obj,"Support"):
|
|
obj.setEditorMode('Support',2)
|
|
|
|
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","Draft","Font size")
|
|
obj.addProperty("App::PropertyInteger","Decimals","Draft","The number of decimals to show")
|
|
obj.addProperty("App::PropertyString","FontName","Draft","Font name")
|
|
obj.addProperty("App::PropertyLength","ArrowSize","Draft","Arrow size")
|
|
obj.addProperty("App::PropertyLength","TextSpacing","Draft","The spacing between the text and the dimension line")
|
|
obj.addProperty("App::PropertyEnumeration","ArrowType","Draft","Arrow type")
|
|
obj.addProperty("App::PropertyFloat","LineWidth","Draft","Line width")
|
|
obj.addProperty("App::PropertyColor","LineColor","Draft","Line color")
|
|
obj.addProperty("App::PropertyBool","FlipArrows","Draft","Rotate the dimension arrows 180 degrees")
|
|
obj.addProperty("App::PropertyBool","ShowUnit","Draft","Show the unit suffix")
|
|
obj.addProperty("App::PropertyVectorDistance","TextPosition","Draft","The position of the text. Leave (0,0,0) for automatic position")
|
|
obj.addProperty("App::PropertyString","Override","Draft","Text override. Use 'dim' to insert the dimension length")
|
|
obj.FontSize = getParam("textheight",0.20)
|
|
obj.FontName = getParam("textfont","")
|
|
obj.TextSpacing = getParam("dimspacing",0.05)
|
|
obj.ArrowSize = getParam("arrowsize",0.1)
|
|
obj.ArrowType = arrowtypes
|
|
obj.ArrowType = arrowtypes[getParam("dimsymbol",0)]
|
|
obj.Override = ''
|
|
obj.Decimals = getParam("dimPrecision",2)
|
|
obj.ShowUnit = getParam("showUnit",True)
|
|
_ViewProviderDraft.__init__(self,obj)
|
|
|
|
def attach(self, vobj):
|
|
from pivy import coin
|
|
self.Object = 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.string = "d" # some versions of coin crash if string is not set
|
|
self.text3d.string = "d"
|
|
self.text.justification = self.text3d.justification = coin.SoAsciiText.CENTER
|
|
self.textpos = coin.SoTransform()
|
|
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.trans1 = coin.SoTransform()
|
|
self.coord2 = coin.SoCoordinate3()
|
|
self.trans2 = coin.SoTransform()
|
|
self.marks = coin.SoSeparator()
|
|
self.drawstyle = coin.SoDrawStyle()
|
|
self.coords = coin.SoCoordinate3()
|
|
self.arc = coin.SoType.fromName("SoBrepEdgeSet").createInstance()
|
|
self.node = coin.SoGroup()
|
|
self.node.addChild(self.color)
|
|
self.node.addChild(self.drawstyle)
|
|
self.node.addChild(self.coords)
|
|
self.node.addChild(self.arc)
|
|
self.node.addChild(self.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.arc)
|
|
self.node3d.addChild(self.marks)
|
|
self.node3d.addChild(label3d)
|
|
vobj.addDisplayMode(self.node,"2D")
|
|
vobj.addDisplayMode(self.node3d,"3D")
|
|
self.updateData(vobj.Object,None)
|
|
self.onChanged(vobj,"FontSize")
|
|
self.onChanged(vobj,"FontName")
|
|
self.onChanged(vobj,"ArrowType")
|
|
self.onChanged(vobj,"LineColor")
|
|
|
|
def updateData(self, obj, prop):
|
|
if hasattr(self,"arc"):
|
|
from pivy import coin
|
|
import Part, DraftGeomUtils
|
|
import DraftGui
|
|
text = None
|
|
ivob = None
|
|
arcsegs = 24
|
|
|
|
# calculate the arc data
|
|
if DraftVecUtils.isNull(obj.Normal):
|
|
norm = Vector(0,0,1)
|
|
else:
|
|
norm = obj.Normal
|
|
radius = (obj.Dimline.sub(obj.Center)).Length
|
|
self.circle = Part.makeCircle(radius,obj.Center,norm,obj.FirstAngle.Value,obj.LastAngle.Value)
|
|
self.p2 = self.circle.Vertexes[0].Point
|
|
self.p3 = self.circle.Vertexes[-1].Point
|
|
mp = DraftGeomUtils.findMidpoint(self.circle.Edges[0])
|
|
ray = mp.sub(obj.Center)
|
|
|
|
# set text value
|
|
if obj.LastAngle.Value > obj.FirstAngle.Value:
|
|
a = obj.LastAngle.Value - obj.FirstAngle.Value
|
|
else:
|
|
a = (360 - obj.FirstAngle.Value) + obj.LastAngle.Value
|
|
su = True
|
|
if hasattr(obj.ViewObject,"ShowUnit"):
|
|
su = obj.ViewObject.ShowUnit
|
|
if hasattr(obj.ViewObject,"Decimals"):
|
|
self.string = DraftGui.displayExternal(a,obj.ViewObject.Decimals,'Angle',su)
|
|
else:
|
|
self.string = DraftGui.displayExternal(a,getParam("dimPrecision",2),'Angle',su)
|
|
if obj.ViewObject.Override:
|
|
self.string = obj.ViewObject.Override.replace("$dim",\
|
|
self.string)
|
|
self.text.string = self.text3d.string = stringencodecoin(self.string)
|
|
|
|
# check display mode
|
|
try:
|
|
m = obj.ViewObject.DisplayMode
|
|
except: # swallow all exceptions here since it always fails on first run (Displaymode enum no set yet)
|
|
m = ["2D","3D"][getParam("dimstyle",0)]
|
|
|
|
# set the arc
|
|
if m == "3D":
|
|
# calculate the spacing of the text
|
|
spacing = (len(self.string)*obj.ViewObject.FontSize.Value)/8.0
|
|
pts1 = []
|
|
cut = None
|
|
pts2 = []
|
|
for i in range(arcsegs+1):
|
|
p = self.circle.valueAt(self.circle.FirstParameter+((self.circle.LastParameter-self.circle.FirstParameter)/arcsegs)*i)
|
|
if (p.sub(mp)).Length <= spacing:
|
|
if cut == None:
|
|
cut = i
|
|
else:
|
|
if cut == None:
|
|
pts1.append([p.x,p.y,p.z])
|
|
else:
|
|
pts2.append([p.x,p.y,p.z])
|
|
self.coords.point.setValues(pts1+pts2)
|
|
i1 = len(pts1)
|
|
i2 = i1+len(pts2)
|
|
self.arc.coordIndex.setValues(0,len(pts1)+len(pts2)+1,list(range(len(pts1)))+[-1]+list(range(i1,i2)))
|
|
if (len(pts1) >= 3) and (len(pts2) >= 3):
|
|
self.circle1 = Part.Arc(Vector(pts1[0][0],pts1[0][1],pts1[0][2]),Vector(pts1[1][0],pts1[1][1],pts1[1][2]),Vector(pts1[-1][0],pts1[-1][1],pts1[-1][2])).toShape()
|
|
self.circle2 = Part.Arc(Vector(pts2[0][0],pts2[0][1],pts2[0][2]),Vector(pts2[1][0],pts2[1][1],pts2[1][2]),Vector(pts2[-1][0],pts2[-1][1],pts2[-1][2])).toShape()
|
|
else:
|
|
pts = []
|
|
for i in range(arcsegs+1):
|
|
p = self.circle.valueAt(self.circle.FirstParameter+((self.circle.LastParameter-self.circle.FirstParameter)/arcsegs)*i)
|
|
pts.append([p.x,p.y,p.z])
|
|
self.coords.point.setValues(pts)
|
|
self.arc.coordIndex.setValues(0,arcsegs+1,list(range(arcsegs+1)))
|
|
|
|
# set the arrow coords and rotation
|
|
self.trans1.translation.setValue((self.p2.x,self.p2.y,self.p2.z))
|
|
self.coord1.point.setValue((self.p2.x,self.p2.y,self.p2.z))
|
|
self.trans2.translation.setValue((self.p3.x,self.p3.y,self.p3.z))
|
|
self.coord2.point.setValue((self.p3.x,self.p3.y,self.p3.z))
|
|
# calculate small chords to make arrows look better
|
|
arrowlength = 4*obj.ViewObject.ArrowSize.Value
|
|
u1 = (self.circle.valueAt(self.circle.FirstParameter+arrowlength)).sub(self.circle.valueAt(self.circle.FirstParameter)).normalize()
|
|
u2 = (self.circle.valueAt(self.circle.LastParameter)).sub(self.circle.valueAt(self.circle.LastParameter-arrowlength)).normalize()
|
|
if hasattr(obj.ViewObject,"FlipArrows"):
|
|
if obj.ViewObject.FlipArrows:
|
|
u1 = u1.negative()
|
|
u2 = u2.negative()
|
|
w2 = self.circle.Curve.Axis
|
|
w1 = w2.negative()
|
|
v1 = w1.cross(u1)
|
|
v2 = w2.cross(u2)
|
|
q1 = FreeCAD.Placement(DraftVecUtils.getPlaneRotation(u1,v1,w1)).Rotation.Q
|
|
q2 = FreeCAD.Placement(DraftVecUtils.getPlaneRotation(u2,v2,w2)).Rotation.Q
|
|
self.trans1.rotation.setValue((q1[0],q1[1],q1[2],q1[3]))
|
|
self.trans2.rotation.setValue((q2[0],q2[1],q2[2],q2[3]))
|
|
|
|
# setting text pos & rot
|
|
self.tbase = mp
|
|
if hasattr(obj.ViewObject,"TextPosition"):
|
|
if not DraftVecUtils.isNull(obj.ViewObject.TextPosition):
|
|
self.tbase = obj.ViewObject.TextPosition
|
|
|
|
u3 = ray.cross(norm).normalize()
|
|
v3 = norm.cross(u3)
|
|
r = FreeCAD.Placement(DraftVecUtils.getPlaneRotation(u3,v3,norm)).Rotation
|
|
offset = r.multVec(Vector(0,1,0))
|
|
|
|
if hasattr(obj.ViewObject,"TextSpacing"):
|
|
offset = DraftVecUtils.scaleTo(offset,obj.ViewObject.TextSpacing.Value)
|
|
else:
|
|
offset = DraftVecUtils.scaleTo(offset,0.05)
|
|
if m == "3D":
|
|
offset = offset.negative()
|
|
self.tbase = self.tbase.add(offset)
|
|
q = r.Q
|
|
self.textpos.translation.setValue([self.tbase.x,self.tbase.y,self.tbase.z])
|
|
self.textpos.rotation = coin.SbRotation(q[0],q[1],q[2],q[3])
|
|
|
|
# set the angle property
|
|
if round(obj.Angle,precision()) != round(a,precision()):
|
|
obj.Angle = a
|
|
|
|
def onChanged(self, vobj, prop):
|
|
if prop == "FontSize":
|
|
if hasattr(self,"font"):
|
|
self.font.size = vobj.FontSize.Value
|
|
if hasattr(self,"font3d"):
|
|
self.font3d.size = vobj.FontSize.Value*100
|
|
elif prop == "FontName":
|
|
if hasattr(self,"font") and hasattr(self,"font3d"):
|
|
self.font.name = self.font3d.name = str(vobj.FontName)
|
|
elif prop == "LineColor":
|
|
if hasattr(self,"color"):
|
|
c = vobj.LineColor
|
|
self.color.rgb.setValue(c[0],c[1],c[2])
|
|
elif prop == "LineWidth":
|
|
if hasattr(self,"drawstyle"):
|
|
self.drawstyle.lineWidth = vobj.LineWidth
|
|
elif prop in ["ArrowSize","ArrowType"]:
|
|
if hasattr(self,"node") and hasattr(self,"p2"):
|
|
from pivy import coin
|
|
|
|
if not hasattr(vobj,"ArrowType"):
|
|
return
|
|
|
|
# set scale
|
|
symbol = arrowtypes.index(vobj.ArrowType)
|
|
s = vobj.ArrowSize.Value
|
|
self.trans1.scaleFactor.setValue((s,s,s))
|
|
self.trans2.scaleFactor.setValue((s,s,s))
|
|
|
|
# remove existing nodes
|
|
self.node.removeChild(self.marks)
|
|
self.node3d.removeChild(self.marks)
|
|
|
|
# set new nodes
|
|
self.marks = coin.SoSeparator()
|
|
self.marks.addChild(self.color)
|
|
s1 = coin.SoSeparator()
|
|
if symbol == "Circle":
|
|
s1.addChild(self.coord1)
|
|
else:
|
|
s1.addChild(self.trans1)
|
|
s1.addChild(dimSymbol(symbol,invert=False))
|
|
self.marks.addChild(s1)
|
|
s2 = coin.SoSeparator()
|
|
if symbol == "Circle":
|
|
s2.addChild(self.coord2)
|
|
else:
|
|
s2.addChild(self.trans2)
|
|
s2.addChild(dimSymbol(symbol,invert=True))
|
|
self.marks.addChild(s2)
|
|
self.node.insertChild(self.marks,2)
|
|
self.node3d.insertChild(self.marks,2)
|
|
else:
|
|
self.updateData(vobj.Object, None)
|
|
|
|
def doubleClicked(self,vobj):
|
|
self.setEdit(vobj)
|
|
|
|
def getDisplayModes(self,obj):
|
|
modes=[]
|
|
modes.extend(["2D","3D"])
|
|
return modes
|
|
|
|
def getDefaultDisplayMode(self):
|
|
if hasattr(self,"defaultmode"):
|
|
return self.defaultmode
|
|
else:
|
|
return ["2D","3D"][getParam("dimstyle",0)]
|
|
|
|
def getIcon(self):
|
|
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 _Rectangle(_DraftObject):
|
|
"The Rectangle object"
|
|
|
|
def __init__(self, obj):
|
|
_DraftObject.__init__(self,obj,"Rectangle")
|
|
obj.addProperty("App::PropertyDistance","Length","Draft","Length of the rectangle")
|
|
obj.addProperty("App::PropertyDistance","Height","Draft","Height of the rectange")
|
|
obj.addProperty("App::PropertyLength","FilletRadius","Draft","Radius to use to fillet the corners")
|
|
obj.addProperty("App::PropertyLength","ChamferSize","Draft","Size of the chamfer to give to the corners")
|
|
obj.addProperty("App::PropertyBool","MakeFace","Draft","Create a face")
|
|
obj.MakeFace = getParam("fillmode",True)
|
|
obj.Length=1
|
|
obj.Height=1
|
|
|
|
def execute(self, obj):
|
|
if (obj.Length.Value != 0) and (obj.Height.Value != 0):
|
|
import Part, DraftGeomUtils
|
|
plm = obj.Placement
|
|
p1 = Vector(0,0,0)
|
|
p2 = Vector(p1.x+obj.Length.Value,p1.y,p1.z)
|
|
p3 = Vector(p1.x+obj.Length.Value,p1.y+obj.Height.Value,p1.z)
|
|
p4 = Vector(p1.x,p1.y+obj.Height.Value,p1.z)
|
|
shape = Part.makePolygon([p1,p2,p3,p4,p1])
|
|
if "ChamferSize" in obj.PropertiesList:
|
|
if obj.ChamferSize.Value != 0:
|
|
w = DraftGeomUtils.filletWire(shape,obj.ChamferSize.Value,chamfer=True)
|
|
if w:
|
|
shape = w
|
|
if "FilletRadius" in obj.PropertiesList:
|
|
if obj.FilletRadius.Value != 0:
|
|
w = DraftGeomUtils.filletWire(shape,obj.FilletRadius.Value)
|
|
if w:
|
|
shape = w
|
|
if hasattr(obj,"MakeFace"):
|
|
if obj.MakeFace:
|
|
shape = Part.Face(shape)
|
|
else:
|
|
shape = Part.Face(shape)
|
|
obj.Shape = shape
|
|
obj.Placement = plm
|
|
|
|
class _ViewProviderRectangle(_ViewProviderDraft):
|
|
def __init__(self,vobj):
|
|
_ViewProviderDraft.__init__(self,vobj)
|
|
vobj.addProperty("App::PropertyFile","TextureImage",
|
|
"Draft","Defines a texture image (overrides hatch patterns)")
|
|
|
|
class _Circle(_DraftObject):
|
|
"The Circle object"
|
|
|
|
def __init__(self, obj):
|
|
_DraftObject.__init__(self,obj,"Circle")
|
|
obj.addProperty("App::PropertyAngle","FirstAngle","Draft","Start angle of the arc")
|
|
obj.addProperty("App::PropertyAngle","LastAngle","Draft","End angle of the arc (for a full circle, give it same value as First Angle)")
|
|
obj.addProperty("App::PropertyLength","Radius","Draft","Radius of the circle")
|
|
obj.addProperty("App::PropertyBool","MakeFace","Draft","Create a face")
|
|
obj.MakeFace = getParam("fillmode",True)
|
|
|
|
def execute(self, obj):
|
|
import Part
|
|
plm = obj.Placement
|
|
shape = Part.makeCircle(obj.Radius.Value,Vector(0,0,0),Vector(0,0,1),obj.FirstAngle.Value,obj.LastAngle.Value)
|
|
if obj.FirstAngle.Value == obj.LastAngle.Value:
|
|
shape = Part.Wire(shape)
|
|
if hasattr(obj,"MakeFace"):
|
|
if obj.MakeFace:
|
|
shape = Part.Face(shape)
|
|
else:
|
|
shape = Part.Face(shape)
|
|
obj.Shape = shape
|
|
obj.Placement = plm
|
|
|
|
class _Ellipse(_DraftObject):
|
|
"The Circle object"
|
|
|
|
def __init__(self, obj):
|
|
_DraftObject.__init__(self,obj,"Ellipse")
|
|
obj.addProperty("App::PropertyAngle","FirstAngle","Draft","Start angle of the arc")
|
|
obj.addProperty("App::PropertyAngle","LastAngle","Draft","End angle of the arc (for a full circle, give it same value as First Angle)")
|
|
obj.addProperty("App::PropertyLength","MinorRadius","Draft","The minor radius of the ellipse")
|
|
obj.addProperty("App::PropertyLength","MajorRadius","Draft","The major radius of the ellipse")
|
|
obj.addProperty("App::PropertyBool","MakeFace","Draft","Create a face")
|
|
obj.MakeFace = getParam("fillmode",True)
|
|
|
|
def execute(self, obj):
|
|
import Part
|
|
plm = obj.Placement
|
|
if obj.MajorRadius.Value < obj.MinorRadius.Value:
|
|
msg(translate("Error: Major radius is smaller than the minor radius"))
|
|
return
|
|
if obj.MajorRadius.Value and obj.MinorRadius.Value:
|
|
ell = Part.Ellipse(Vector(0,0,0),obj.MajorRadius.Value,obj.MinorRadius.Value)
|
|
shape = ell.toShape()
|
|
if hasattr(obj,"FirstAngle"):
|
|
if obj.FirstAngle.Value != obj.LastAngle.Value:
|
|
a1 = obj.FirstAngle.getValueAs(FreeCAD.Units.Radian)
|
|
a2 = obj.LastAngle.getValueAs(FreeCAD.Units.Radian)
|
|
shape = Part.ArcOfEllipse(ell,a1,a2).toShape()
|
|
shape = Part.Wire(shape)
|
|
if shape.isClosed():
|
|
if hasattr(obj,"MakeFace"):
|
|
if obj.MakeFace:
|
|
shape = Part.Face(shape)
|
|
else:
|
|
shape = Part.Face(shape)
|
|
obj.Shape = shape
|
|
obj.Placement = plm
|
|
|
|
class _Wire(_DraftObject):
|
|
"The Wire object"
|
|
|
|
def __init__(self, obj):
|
|
_DraftObject.__init__(self,obj,"Wire")
|
|
obj.addProperty("App::PropertyVectorList","Points","Draft","The vertices of the wire")
|
|
obj.addProperty("App::PropertyBool","Closed","Draft","If the wire is closed or not")
|
|
obj.addProperty("App::PropertyLink","Base","Draft","The base object is the wire is formed from 2 objects")
|
|
obj.addProperty("App::PropertyLink","Tool","Draft","The tool object is the wire is formed from 2 objects")
|
|
obj.addProperty("App::PropertyVectorDistance","Start","Draft","The start point of this line")
|
|
obj.addProperty("App::PropertyVectorDistance","End","Draft","The end point of this line")
|
|
obj.addProperty("App::PropertyLength","Length","Draft","The length of this line")
|
|
obj.addProperty("App::PropertyLength","FilletRadius","Draft","Radius to use to fillet the corners")
|
|
obj.addProperty("App::PropertyLength","ChamferSize","Draft","Size of the chamfer to give to the corners")
|
|
obj.addProperty("App::PropertyBool","MakeFace","Draft","Create a face if this object is closed")
|
|
obj.MakeFace = getParam("fillmode",True)
|
|
obj.Closed = False
|
|
|
|
def execute(self, obj):
|
|
import Part, DraftGeomUtils
|
|
plm = obj.Placement
|
|
if obj.Base and (not obj.Tool):
|
|
if obj.Base.isDerivedFrom("Sketcher::SketchObject"):
|
|
shape = obj.Base.Shape.copy()
|
|
if obj.Base.Shape.isClosed():
|
|
if hasattr(obj,"MakeFace"):
|
|
if obj.MakeFace:
|
|
shape = Part.Face(shape)
|
|
else:
|
|
shape = Part.Face(shape)
|
|
obj.Shape = shape
|
|
elif obj.Base and obj.Tool:
|
|
if obj.Base.isDerivedFrom("Part::Feature") and obj.Tool.isDerivedFrom("Part::Feature"):
|
|
if (not obj.Base.Shape.isNull()) and (not obj.Tool.Shape.isNull()):
|
|
sh1 = obj.Base.Shape.copy()
|
|
sh2 = obj.Tool.Shape.copy()
|
|
shape = sh1.fuse(sh2)
|
|
if DraftGeomUtils.isCoplanar(shape.Faces):
|
|
shape = DraftGeomUtils.concatenate(shape)
|
|
obj.Shape = shape
|
|
p = []
|
|
for v in shape.Vertexes: p.append(v.Point)
|
|
if obj.Points != p: obj.Points = p
|
|
elif obj.Points:
|
|
if obj.Points[0] == obj.Points[-1]:
|
|
if not obj.Closed: obj.Closed = True
|
|
obj.Points.pop()
|
|
if obj.Closed and (len(obj.Points) > 2):
|
|
shape = Part.makePolygon(obj.Points+[obj.Points[0]])
|
|
if "FilletRadius" in obj.PropertiesList:
|
|
if obj.FilletRadius.Value != 0:
|
|
w = DraftGeomUtils.filletWire(shape,obj.FilletRadius.Value)
|
|
if w:
|
|
shape = w
|
|
try:
|
|
if hasattr(obj,"MakeFace"):
|
|
if obj.MakeFace:
|
|
shape = Part.Face(shape)
|
|
else:
|
|
shape = Part.Face(shape)
|
|
except Part.OCCError:
|
|
pass
|
|
else:
|
|
edges = []
|
|
pts = obj.Points[1:]
|
|
lp = obj.Points[0]
|
|
for p in pts:
|
|
if not DraftVecUtils.equals(lp,p):
|
|
edges.append(Part.Line(lp,p).toShape())
|
|
lp = p
|
|
try:
|
|
shape = Part.Wire(edges)
|
|
except Part.OCCError:
|
|
shape = None
|
|
if "ChamferSize" in obj.PropertiesList:
|
|
if obj.ChamferSize.Value != 0:
|
|
w = DraftGeomUtils.filletWire(shape,obj.ChamferSize.Value,chamfer=True)
|
|
if w:
|
|
shape = w
|
|
if "FilletRadius" in obj.PropertiesList:
|
|
if obj.FilletRadius.Value != 0:
|
|
w = DraftGeomUtils.filletWire(shape,obj.FilletRadius.Value)
|
|
if w:
|
|
shape = w
|
|
if shape:
|
|
obj.Shape = shape
|
|
if hasattr(obj,"Length"):
|
|
obj.Length = shape.Length
|
|
obj.Placement = plm
|
|
self.onChanged(obj,"Placement")
|
|
|
|
def onChanged(self, obj, prop):
|
|
if prop == "Start":
|
|
pts = obj.Points
|
|
invpl = FreeCAD.Placement(obj.Placement).inverse()
|
|
realfpstart = invpl.multVec(obj.Start)
|
|
if pts:
|
|
if pts[0] != realfpstart:
|
|
pts[0] = realfpstart
|
|
obj.Points = pts
|
|
elif prop == "End":
|
|
pts = obj.Points
|
|
invpl = FreeCAD.Placement(obj.Placement).inverse()
|
|
realfpend = invpl.multVec(obj.End)
|
|
if len(pts) > 1:
|
|
if pts[-1] != realfpend:
|
|
pts[-1] = realfpend
|
|
obj.Points = pts
|
|
elif prop == "Length":
|
|
if obj.Shape and not obj.Shape.isNull():
|
|
if obj.Length.Value != obj.Shape.Length:
|
|
if len(obj.Points) == 2:
|
|
v = obj.Points[-1].sub(obj.Points[0])
|
|
v = DraftVecUtils.scaleTo(v,obj.Length.Value)
|
|
obj.Points = [obj.Points[0],obj.Points[0].add(v)]
|
|
|
|
elif prop == "Placement":
|
|
pl = FreeCAD.Placement(obj.Placement)
|
|
if len(obj.Points) >= 2:
|
|
displayfpstart = pl.multVec(obj.Points[0])
|
|
displayfpend = pl.multVec(obj.Points[-1])
|
|
if obj.Start != displayfpstart:
|
|
obj.Start = displayfpstart
|
|
if obj.End != displayfpend:
|
|
obj.End = displayfpend
|
|
if len(obj.Points) > 2:
|
|
obj.setEditorMode('Start',2)
|
|
obj.setEditorMode('End',2)
|
|
if hasattr(obj,"Length"):
|
|
obj.setEditorMode('Length',2)
|
|
|
|
|
|
class _ViewProviderWire(_ViewProviderDraft):
|
|
"A View Provider for the Wire object"
|
|
def __init__(self, obj):
|
|
_ViewProviderDraft.__init__(self,obj)
|
|
obj.addProperty("App::PropertyBool","EndArrow","Draft","Displays a dim symbol at the end of the wire")
|
|
obj.addProperty("App::PropertyLength","ArrowSize","Draft","Arrow size")
|
|
obj.addProperty("App::PropertyEnumeration","ArrowType","Draft","Arrow type")
|
|
obj.ArrowSize = getParam("arrowsize",0.1)
|
|
obj.ArrowType = arrowtypes
|
|
obj.ArrowType = arrowtypes[getParam("dimsymbol",0)]
|
|
|
|
def attach(self, obj):
|
|
from pivy import coin
|
|
self.Object = obj.Object
|
|
col = coin.SoBaseColor()
|
|
col.rgb.setValue(obj.LineColor[0],obj.LineColor[1],obj.LineColor[2])
|
|
self.coords = coin.SoTransform()
|
|
self.pt = coin.SoSeparator()
|
|
self.pt.addChild(col)
|
|
self.pt.addChild(self.coords)
|
|
self.symbol = dimSymbol()
|
|
self.pt.addChild(self.symbol)
|
|
_ViewProviderDraft.attach(self,obj)
|
|
|
|
def updateData(self, obj, prop):
|
|
if prop == "Points":
|
|
if obj.Points:
|
|
p = obj.Points[-1]
|
|
if hasattr(self,"coords"):
|
|
self.coords.translation.setValue((p.x,p.y,p.z))
|
|
if len(obj.Points) >= 2:
|
|
v1 = obj.Points[-1].sub(obj.Points[-2])
|
|
v1.normalize()
|
|
import DraftGeomUtils
|
|
v2 = DraftGeomUtils.getNormal(obj.Shape)
|
|
v3 = v1.cross(v2)
|
|
q = FreeCAD.Placement(DraftVecUtils.getPlaneRotation(v1,v3,v2)).Rotation.Q
|
|
self.coords.rotation.setValue((q[0],q[1],q[2],q[3]))
|
|
return
|
|
|
|
def onChanged(self, vp, prop):
|
|
if prop == "EndArrow":
|
|
rn = vp.RootNode
|
|
if vp.EndArrow:
|
|
rn.addChild(self.pt)
|
|
self.onChanged(vp,"ArrowSize")
|
|
else:
|
|
rn.removeChild(self.pt)
|
|
elif prop == "ArrowSize":
|
|
if hasattr(vp,"ArrowSize"):
|
|
s = vp.ArrowSize
|
|
else:
|
|
s = getParam("arrowsize",0.1)
|
|
self.coords.scaleFactor.setValue((s,s,s))
|
|
elif prop == "ArrowType":
|
|
if hasattr(self,"pt"):
|
|
self.pt.removeChild(self.symbol)
|
|
s = arrowtypes.index(vp.ArrowType)
|
|
self.symbol = dimSymbol(s)
|
|
self.pt.addChild(self.symbol)
|
|
_ViewProviderDraft.onChanged(self,vp,prop)
|
|
return
|
|
|
|
def claimChildren(self):
|
|
if hasattr(self.Object,"Base"):
|
|
return [self.Object.Base,self.Object.Tool]
|
|
return []
|
|
|
|
class _Polygon(_DraftObject):
|
|
"The Polygon object"
|
|
|
|
def __init__(self, obj):
|
|
_DraftObject.__init__(self,obj,"Polygon")
|
|
obj.addProperty("App::PropertyInteger","FacesNumber","Draft","Number of faces")
|
|
obj.addProperty("App::PropertyLength","Radius","Draft","Radius of the control circle")
|
|
obj.addProperty("App::PropertyEnumeration","DrawMode","Draft","How the polygon must be drawn from the control circle")
|
|
obj.addProperty("App::PropertyLength","FilletRadius","Draft","Radius to use to fillet the corners")
|
|
obj.addProperty("App::PropertyLength","ChamferSize","Draft","Size of the chamfer to give to the corners")
|
|
obj.addProperty("App::PropertyBool","MakeFace","Draft","Create a face")
|
|
obj.MakeFace = getParam("fillmode",True)
|
|
obj.DrawMode = ['inscribed','circumscribed']
|
|
obj.FacesNumber = 0
|
|
obj.Radius = 1
|
|
|
|
def execute(self, obj):
|
|
if (obj.FacesNumber >= 3) and (obj.Radius.Value > 0):
|
|
import Part, DraftGeomUtils
|
|
plm = obj.Placement
|
|
angle = (math.pi*2)/obj.FacesNumber
|
|
if obj.DrawMode == 'inscribed':
|
|
delta = obj.Radius.Value
|
|
else:
|
|
delta = obj.Radius.Value/math.cos(angle/2.0)
|
|
pts = [Vector(delta,0,0)]
|
|
for i in range(obj.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 obj.PropertiesList:
|
|
if obj.ChamferSize.Value != 0:
|
|
w = DraftGeomUtils.filletWire(shape,obj.ChamferSize.Value,chamfer=True)
|
|
if w:
|
|
shape = w
|
|
if "FilletRadius" in obj.PropertiesList:
|
|
if obj.FilletRadius.Value != 0:
|
|
w = DraftGeomUtils.filletWire(shape,obj.FilletRadius.Value)
|
|
if w:
|
|
shape = w
|
|
if hasattr(obj,"MakeFace"):
|
|
if obj.MakeFace:
|
|
shape = Part.Face(shape)
|
|
else:
|
|
shape = Part.Face(shape)
|
|
obj.Shape = shape
|
|
obj.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","View Style","The width of the lines inside this object")
|
|
obj.addProperty("App::PropertyLength","FontSize","View Style","The size of the texts inside this object")
|
|
obj.addProperty("App::PropertyColor","LineColor","View Style","The color of the projected objects")
|
|
obj.addProperty("App::PropertyLink","Source","Base","The linked object")
|
|
obj.addProperty("App::PropertyEnumeration","FillStyle","View Style","Shape Fill Style")
|
|
obj.addProperty("App::PropertyEnumeration","LineStyle","View Style","Line Style")
|
|
obj.FillStyle = ['shape color'] + list(svgpatterns().keys())
|
|
obj.LineStyle = ['Solid','Dashed','Dotted','Dashdot']
|
|
obj.LineWidth = 0.35
|
|
obj.FontSize = 12
|
|
|
|
def execute(self, obj):
|
|
result = ""
|
|
if hasattr(obj,"Source"):
|
|
if obj.Source:
|
|
if hasattr(obj,"LineStyle"):
|
|
ls = obj.LineStyle
|
|
else:
|
|
ls = None
|
|
if hasattr(obj,"LineColor"):
|
|
lc = obj.LineColor
|
|
else:
|
|
lc = None
|
|
if obj.Source.isDerivedFrom("App::DocumentObjectGroup"):
|
|
svg = ""
|
|
shapes = []
|
|
others = []
|
|
objs = getGroupContents([obj.Source])
|
|
for o in objs:
|
|
if o.ViewObject.isVisible():
|
|
svg += getSVG(o,obj.Scale,obj.LineWidth,obj.FontSize.Value,obj.FillStyle,obj.Direction,ls,lc)
|
|
else:
|
|
svg = getSVG(obj.Source,obj.Scale,obj.LineWidth,obj.FontSize.Value,obj.FillStyle,obj.Direction,ls,lc)
|
|
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>'
|
|
obj.ViewResult = result
|
|
|
|
def getDXF(self,obj):
|
|
"returns a DXF fragment"
|
|
result = ""
|
|
if obj.Source.isDerivedFrom("App::DocumentObjectGroup"):
|
|
for o in obj.Source.Group:
|
|
if o.ViewObject.isVisible():
|
|
result += getDXF(o,obj.Direction)
|
|
else:
|
|
result += getDXF(o,obj.Direction)
|
|
return result
|
|
|
|
class _BSpline(_DraftObject):
|
|
"The BSpline object"
|
|
|
|
def __init__(self, obj):
|
|
_DraftObject.__init__(self,obj,"BSpline")
|
|
obj.addProperty("App::PropertyVectorList","Points","Draft", "The points of the b-spline")
|
|
obj.addProperty("App::PropertyBool","Closed","Draft","If the b-spline is closed or not")
|
|
obj.addProperty("App::PropertyBool","MakeFace","Draft","Create a face if this spline is closed")
|
|
obj.MakeFace = getParam("fillmode",True)
|
|
obj.Closed = False
|
|
obj.Points = []
|
|
|
|
def execute(self, obj):
|
|
import Part
|
|
from DraftTools import msg,translate
|
|
if obj.Points:
|
|
plm = obj.Placement
|
|
if obj.Closed and (len(obj.Points) > 2):
|
|
if obj.Points[0] == obj.Points[-1]: # should not occur, but OCC will crash
|
|
msg(translate('draft', "_BSpline.createGeometry: Closed with same first/last Point. Geometry not updated.\n"), "error")
|
|
return
|
|
spline = Part.BSplineCurve()
|
|
spline.interpolate(obj.Points, True)
|
|
# DNC: bug fix: convert to face if closed
|
|
shape = Part.Wire(spline.toShape())
|
|
# Creating a face from a closed spline cannot be expected to always work
|
|
# Usually, if the spline is not flat the call of Part.Face() fails
|
|
try:
|
|
if hasattr(obj,"MakeFace"):
|
|
if obj.MakeFace:
|
|
shape = Part.Face(shape)
|
|
else:
|
|
shape = Part.Face(shape)
|
|
except Part.OCCError:
|
|
pass
|
|
obj.Shape = shape
|
|
else:
|
|
spline = Part.BSplineCurve()
|
|
spline.interpolate(obj.Points, False)
|
|
obj.Shape = spline.toShape()
|
|
obj.Placement = plm
|
|
|
|
# for compatibility with older versions
|
|
_ViewProviderBSpline = _ViewProviderWire
|
|
|
|
class _BezCurve(_DraftObject):
|
|
"The BezCurve object"
|
|
|
|
def __init__(self, obj):
|
|
_DraftObject.__init__(self,obj,"BezCurve")
|
|
obj.addProperty("App::PropertyVectorList","Points","Draft",
|
|
"The points of the Bezier curve")
|
|
obj.addProperty("App::PropertyInteger","Degree","Draft",
|
|
"The degree of the Bezier function")
|
|
obj.addProperty("App::PropertyIntegerList","Continuity","Draft",
|
|
"Continuity")
|
|
obj.addProperty("App::PropertyBool","Closed","Draft",
|
|
"If the Bezier curve should be closed or not")
|
|
obj.addProperty("App::PropertyBool","MakeFace","Draft","Create a face if this curve is closed")
|
|
obj.MakeFace = getParam("fillmode",True)
|
|
obj.Closed = False
|
|
obj.Degree = 3
|
|
obj.Continuity = []
|
|
#obj.setEditorMode("Degree",2)#hide
|
|
obj.setEditorMode("Continuity",1)#ro
|
|
|
|
def execute(self, fp):
|
|
self.createGeometry(fp)
|
|
|
|
def _segpoleslst(self,fp):
|
|
"""split the points into segments"""
|
|
if not fp.Closed and len(fp.Points) >= 2: #allow lower degree segement
|
|
poles=fp.Points[1:]
|
|
elif fp.Closed and len(fp.Points) >= fp.Degree: #drawable
|
|
#poles=fp.Points[1:(fp.Degree*(len(fp.Points)//fp.Degree))]+fp.Points[0:1]
|
|
poles=fp.Points[1:]+fp.Points[0:1]
|
|
else:
|
|
poles=[]
|
|
return [poles[x:x+fp.Degree] for x in \
|
|
range(0, len(poles), (fp.Degree or 1))]
|
|
|
|
def resetcontinuity(self,fp):
|
|
fp.Continuity = [0]*(len(self._segpoleslst(fp))-1+1*fp.Closed)
|
|
#nump= len(fp.Points)-1+fp.Closed*1
|
|
#numsegments = (nump // fp.Degree) + 1 * (nump % fp.Degree > 0) -1
|
|
#fp.Continuity = [0]*numsegments
|
|
|
|
def onChanged(self, fp, prop):
|
|
if prop == 'Closed': # if remove the last entry when curve gets opened
|
|
oldlen = len(fp.Continuity)
|
|
newlen = (len(self._segpoleslst(fp))-1+1*fp.Closed)
|
|
if oldlen > newlen:
|
|
fp.Continuity = fp.Continuity[:newlen]
|
|
if oldlen < newlen:
|
|
fp.Continuity = fp.Continuity + [0]*(newlen-oldlen)
|
|
if hasattr(fp,'Closed') and fp.Closed and prop in ['Points','Degree','Closed'] and\
|
|
len(fp.Points) % fp.Degree: # the curve editing tools can't handle extra points
|
|
fp.Points=fp.Points[:(fp.Degree*(len(fp.Points)//fp.Degree))] #for closed curves
|
|
if prop in ["Degree"] and fp.Degree >= 1: #reset Continuity
|
|
self.resetcontinuity(fp)
|
|
if prop in ["Points","Degree","Continuity","Closed"]:
|
|
self.createGeometry(fp)
|
|
|
|
def createGeometry(self,fp):
|
|
import Part
|
|
plm = fp.Placement
|
|
if fp.Points:
|
|
startpoint=fp.Points[0]
|
|
edges = []
|
|
for segpoles in self._segpoleslst(fp):
|
|
# if len(segpoles) == fp.Degree # would skip additional poles
|
|
c = Part.BezierCurve() #last segment may have lower degree
|
|
c.increase(len(segpoles))
|
|
c.setPoles([startpoint]+segpoles)
|
|
edges.append(Part.Edge(c))
|
|
startpoint = segpoles[-1]
|
|
w = Part.Wire(edges)
|
|
if fp.Closed and w.isClosed():
|
|
try:
|
|
if hasattr(fp,"MakeFace"):
|
|
if fp.MakeFace:
|
|
w = Part.Face(w)
|
|
else:
|
|
w = Part.Face(w)
|
|
except Part.OCCError:
|
|
pass
|
|
fp.Shape = w
|
|
fp.Placement = plm
|
|
|
|
@classmethod
|
|
def symmetricpoles(cls,knot, p1, p2):
|
|
"""make two poles symmetric respective to the knot"""
|
|
p1h=FreeCAD.Vector(p1)
|
|
p2h=FreeCAD.Vector(p2)
|
|
p1h.multiply(0.5)
|
|
p2h.multiply(0.5)
|
|
return ( knot+p1h-p2h , knot+p2h-p1h)
|
|
|
|
@classmethod
|
|
def tangentpoles(cls,knot, p1, p2,allowsameside=False):
|
|
"""make two poles have the same tangent at knot"""
|
|
p12n=p2.sub(p1)
|
|
p12n.normalize()
|
|
p1k=knot-p1
|
|
p2k=knot-p2
|
|
p1k_= FreeCAD.Vector(p12n)
|
|
kon12=(p1k*p12n)
|
|
if allowsameside or not (kon12 < 0 or p2k*p12n > 0):# instead of moving
|
|
p1k_.multiply(kon12)
|
|
pk_k=knot-p1-p1k_
|
|
return (p1+pk_k,p2+pk_k)
|
|
else:
|
|
return cls.symmetricpoles(knot, p1, p2)
|
|
|
|
@staticmethod
|
|
def modifysymmetricpole(knot,p1):
|
|
"""calculate the coordinates of the opposite pole
|
|
of a symmetric knot"""
|
|
return knot+knot-p1
|
|
|
|
@staticmethod
|
|
def modifytangentpole(knot,p1,oldp2):
|
|
"""calculate the coordinates of the opposite pole
|
|
of a tangent knot"""
|
|
pn=knot-p1
|
|
pn.normalize()
|
|
pn.multiply((knot-oldp2).Length)
|
|
return pn+knot
|
|
|
|
# for compatibility with older versions ???????
|
|
_ViewProviderBezCurve = _ViewProviderWire
|
|
|
|
class _Block(_DraftObject):
|
|
"The Block object"
|
|
|
|
def __init__(self, obj):
|
|
_DraftObject.__init__(self,obj,"Block")
|
|
obj.addProperty("App::PropertyLinkList","Components","Draft","The components of this block")
|
|
|
|
def execute(self, obj):
|
|
import Part
|
|
plm = obj.Placement
|
|
shps = []
|
|
for c in obj.Components:
|
|
shps.append(c.Shape)
|
|
if shps:
|
|
shape = Part.makeCompound(shps)
|
|
obj.Shape = shape
|
|
obj.Placement = plm
|
|
|
|
class _Shape2DView(_DraftObject):
|
|
"The Shape2DView object"
|
|
|
|
def __init__(self,obj):
|
|
obj.addProperty("App::PropertyLink","Base","Draft","The base object this 2D view must represent")
|
|
obj.addProperty("App::PropertyVector","Projection","Draft","The projection vector of this object")
|
|
obj.addProperty("App::PropertyEnumeration","ProjectionMode","Draft","The way the viewed object must be projected")
|
|
obj.addProperty("App::PropertyIntegerList","FaceNumbers","Draft","The indices of the faces to be projected in Individual Faces mode")
|
|
obj.addProperty("App::PropertyBool","HiddenLines","Draft","Show hidden lines")
|
|
obj.addProperty("App::PropertyBool","Tessellation","Draft","Tessellate Ellipses and BSplines into line segments")
|
|
obj.addProperty("App::PropertyFloat","SegmentLength","Draft","Length of line segments if tessellating Ellipses or BSplines into line segments")
|
|
obj.Projection = Vector(0,0,1)
|
|
obj.ProjectionMode = ["Solid","Individual Faces","Cutlines","Cutfaces"]
|
|
obj.HiddenLines = False
|
|
obj.Tessellation = False
|
|
obj.SegmentLength = .05
|
|
_DraftObject.__init__(self,obj,"Shape2DView")
|
|
|
|
def getProjected(self,obj,shape,direction):
|
|
"returns projected edges from a shape and a direction"
|
|
import Part,Drawing,DraftGeomUtils
|
|
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)
|
|
if hasattr(obj,"Tessellation") and obj.Tessellation:
|
|
return DraftGeomUtils.cleanProjection(Part.makeCompound(edges),obj.Tessellation,obj.SegmentLength)
|
|
else:
|
|
return Part.makeCompound(edges)
|
|
#return DraftGeomUtils.cleanProjection(Part.makeCompound(edges))
|
|
|
|
def execute(self,obj):
|
|
import DraftGeomUtils
|
|
pl = obj.Placement
|
|
if obj.Base:
|
|
if getType(obj.Base) == "SectionPlane":
|
|
if obj.Base.Objects:
|
|
onlysolids = True
|
|
if hasattr(obj.Base,"OnlySolids"):
|
|
onlysolids = obj.Base.OnlySolids
|
|
import Arch, Part, Drawing
|
|
objs = getGroupContents(obj.Base.Objects,walls=True)
|
|
objs = removeHidden(objs)
|
|
shapes = []
|
|
for o in objs:
|
|
if o.isDerivedFrom("Part::Feature"):
|
|
if onlysolids:
|
|
shapes.extend(o.Shape.Solids)
|
|
else:
|
|
shapes.append(o.Shape.copy())
|
|
cutp,cutv,iv =Arch.getCutVolume(obj.Base.Shape,shapes)
|
|
cuts = []
|
|
if obj.ProjectionMode == "Solid":
|
|
for sh in shapes:
|
|
if cutv:
|
|
if sh.Volume < 0:
|
|
sh.reverse()
|
|
#if cutv.BoundBox.intersect(sh.BoundBox):
|
|
# c = sh.cut(cutv)
|
|
#else:
|
|
# c = sh.copy()
|
|
c = sh.cut(cutv)
|
|
if onlysolids:
|
|
cuts.extend(c.Solids)
|
|
else:
|
|
cuts.append(c)
|
|
else:
|
|
if onlysolids:
|
|
cuts.extend(sh.Solids)
|
|
else:
|
|
cuts.append(sh.copy())
|
|
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 in ["Cutlines","Cutfaces"]:
|
|
for sh in shapes:
|
|
if sh.Volume < 0:
|
|
sh.reverse()
|
|
c = sh.section(cutp)
|
|
if (obj.ProjectionMode == "Cutfaces") and (sh.ShapeType == "Solid"):
|
|
try:
|
|
c = Part.Wire(Part.__sortEdges__(c.Edges))
|
|
except Part.OCCError:
|
|
pass
|
|
else:
|
|
try:
|
|
c = Part.Face(c)
|
|
except Part.OCCError:
|
|
pass
|
|
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("App::DocumentObjectGroup"):
|
|
shapes = []
|
|
objs = getGroupContents(obj.Base)
|
|
for o in objs:
|
|
if o.isDerivedFrom("Part::Feature"):
|
|
if o.Shape:
|
|
if not o.Shape.isNull():
|
|
shapes.append(o.Shape)
|
|
if shapes:
|
|
import Part
|
|
comp = Part.makeCompound(shapes)
|
|
obj.Shape = self.getProjected(obj,comp,obj.Projection)
|
|
|
|
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","Draft","The base object that must be duplicated")
|
|
obj.addProperty("App::PropertyEnumeration","ArrayType","Draft","The type of array to create")
|
|
obj.addProperty("App::PropertyVector","Axis","Draft","The axis direction")
|
|
obj.addProperty("App::PropertyInteger","NumberX","Draft","Number of copies in X direction")
|
|
obj.addProperty("App::PropertyInteger","NumberY","Draft","Number of copies in Y direction")
|
|
obj.addProperty("App::PropertyInteger","NumberZ","Draft","Number of copies in Z direction")
|
|
obj.addProperty("App::PropertyInteger","NumberPolar","Draft","Number of copies")
|
|
obj.addProperty("App::PropertyVectorDistance","IntervalX","Draft","Distance and orientation of intervals in X direction")
|
|
obj.addProperty("App::PropertyVectorDistance","IntervalY","Draft","Distance and orientation of intervals in Y direction")
|
|
obj.addProperty("App::PropertyVectorDistance","IntervalZ","Draft","Distance and orientation of intervals in Z direction")
|
|
obj.addProperty("App::PropertyVectorDistance","IntervalAxis","Draft","Distance and orientation of intervals in Axis direction")
|
|
obj.addProperty("App::PropertyVectorDistance","Center","Draft","Center point")
|
|
obj.addProperty("App::PropertyAngle","Angle","Draft","Angle to cover with copies")
|
|
obj.addProperty("App::PropertyBool","Fuse","Draft","Specifies if copies must be fused (slower)")
|
|
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.IntervalZ = Vector(0,0,0)
|
|
obj.Angle = 360
|
|
obj.Axis = Vector(0,0,1)
|
|
obj.Fuse = False
|
|
|
|
def execute(self,obj):
|
|
import DraftGeomUtils
|
|
if hasattr(obj,"Fuse"):
|
|
fuse = obj.Fuse
|
|
else:
|
|
fuse = False
|
|
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,fuse)
|
|
else:
|
|
av = obj.IntervalAxis if hasattr(obj,"IntervalAxis") else None
|
|
sh = self.polarArray(obj.Base.Shape,obj.Center,obj.Angle.Value,obj.NumberPolar,obj.Axis,av,fuse)
|
|
obj.Shape = sh
|
|
if not DraftGeomUtils.isNull(pl):
|
|
obj.Placement = pl
|
|
|
|
def rectArray(self,shape,xvector,yvector,zvector,xnum,ynum,znum,fuse=False):
|
|
import Part
|
|
base = [shape.copy()]
|
|
for xcount in range(xnum):
|
|
currentxvector=Vector(xvector).multiply(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(Vector(yvector).multiply(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(Vector(zvector).multiply(zcount))
|
|
if not zcount==0:
|
|
nshape = shape.copy()
|
|
nshape.translate(currentzvector)
|
|
base.append(nshape)
|
|
if fuse and len(base) > 1:
|
|
return base[0].multiFuse(base[1:]).removeSplitter()
|
|
else:
|
|
return Part.makeCompound(base)
|
|
|
|
def polarArray(self,shape,center,angle,num,axis,axisvector,fuse=False):
|
|
#print("angle ",angle," num ",num)
|
|
import Part
|
|
if angle == 360:
|
|
fraction = float(angle)/num
|
|
else:
|
|
if num == 0:
|
|
return shape
|
|
fraction = float(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)
|
|
if axisvector:
|
|
if not DraftVecUtils.isNull(axisvector):
|
|
nshape.translate(FreeCAD.Vector(axisvector).multiply(i+1))
|
|
base.append(nshape)
|
|
if fuse and len(base) > 1:
|
|
return base[0].multiFuse(base[1:]).removeSplitter()
|
|
else:
|
|
return Part.makeCompound(base)
|
|
|
|
|
|
class _PathArray(_DraftObject):
|
|
"The Draft Path Array object"
|
|
|
|
def __init__(self,obj):
|
|
_DraftObject.__init__(self,obj,"PathArray")
|
|
obj.addProperty("App::PropertyLink","Base","Draft","The base object that must be duplicated")
|
|
obj.addProperty("App::PropertyLink","PathObj","Draft","The path object along which to distribute objects")
|
|
obj.addProperty("App::PropertyLinkSubList","PathSubs","Draft","Selected subobjects (edges) of PathObj")
|
|
obj.addProperty("App::PropertyInteger","Count","Draft","Number of copies")
|
|
obj.addProperty("App::PropertyVectorDistance","Xlate","Draft","Optional translation vector")
|
|
obj.addProperty("App::PropertyBool","Align","Draft","Orientation of Base along path")
|
|
obj.Count = 2
|
|
obj.PathSubs = []
|
|
obj.Xlate = FreeCAD.Vector(0,0,0)
|
|
obj.Align = False
|
|
|
|
def execute(self,obj):
|
|
import FreeCAD
|
|
import Part
|
|
import DraftGeomUtils
|
|
if obj.Base and obj.PathObj:
|
|
pl = obj.Placement
|
|
if obj.PathSubs:
|
|
w = self.getWireFromSubs(obj)
|
|
elif (hasattr(obj.PathObj.Shape,'Wires') and obj.PathObj.Shape.Wires):
|
|
w = obj.PathObj.Shape.Wires[0]
|
|
elif obj.PathObj.Shape.Edges:
|
|
w = Part.Wire(obj.PathObj.Shape.Edges)
|
|
else:
|
|
FreeCAD.Console.PrintLog ("_PathArray.createGeometry: path " + obj.PathObj.Name + " has no edges\n")
|
|
return
|
|
obj.Shape = self.pathArray(obj.Base.Shape,w,obj.Count,obj.Xlate,obj.Align)
|
|
if not DraftGeomUtils.isNull(pl):
|
|
obj.Placement = pl
|
|
|
|
def getWireFromSubs(self,obj):
|
|
'''Make a wire from PathObj subelements'''
|
|
import Part
|
|
sl = []
|
|
for sub in obj.PathSubs:
|
|
e = sub[0].Shape.getElement(sub[1])
|
|
sl.append(e)
|
|
return Part.Wire(sl)
|
|
|
|
def getParameterFromV0(self, edge, offset):
|
|
'''return parameter at distance offset from edge.Vertexes[0]'''
|
|
'''sb method in Part.TopoShapeEdge???'''
|
|
lpt = edge.valueAt(edge.getParameterByLength(0))
|
|
vpt = edge.Vertexes[0].Point
|
|
if not DraftVecUtils.equals(vpt,lpt):
|
|
# this edge is flipped
|
|
length = edge.Length - offset
|
|
else:
|
|
# this edge is right way around
|
|
length = offset
|
|
return(edge.getParameterByLength(length))
|
|
|
|
def orientShape(self,shape,edge,offset,RefPt,xlate,align,normal=None):
|
|
'''Orient shape to tangent at parm offset along edge.'''
|
|
# http://en.wikipedia.org/wiki/Euler_angles
|
|
import Part
|
|
import DraftGeomUtils
|
|
import math
|
|
z = FreeCAD.Vector(0,0,1) # unit +Z Probably defined elsewhere?
|
|
y = FreeCAD.Vector(0,1,0) # unit +Y
|
|
x = FreeCAD.Vector(1,0,0) # unit +X
|
|
nullv = FreeCAD.Vector(0,0,0)
|
|
nullPlace =FreeCAD.Placement()
|
|
ns = shape.copy()
|
|
ns.Placement.Base = nullPlace.Base # reset Placement point so translate goes to right place.
|
|
ns.Placement.Rotation = shape.Placement.Rotation # preserve global orientation
|
|
ns.translate(RefPt+xlate)
|
|
if not align:
|
|
return ns
|
|
|
|
# get local coord system - tangent, normal, binormal, if possible
|
|
t = edge.tangentAt(self.getParameterFromV0(edge,offset))
|
|
t.normalize()
|
|
try:
|
|
if normal:
|
|
n = normal
|
|
else:
|
|
n = edge.normalAt(self.getParameterFromV0(edge,offset))
|
|
n.normalize()
|
|
b = (t.cross(n))
|
|
b.normalize()
|
|
except FreeCAD.Base.FreeCADError: # no normal defined here
|
|
n = nullv
|
|
b = nullv
|
|
FreeCAD.Console.PrintLog ("Draft PathArray.orientShape - Cannot calculate Path normal.\n")
|
|
lnodes = z.cross(b)
|
|
if lnodes != nullv:
|
|
lnodes.normalize() # Can't normalize null vector.
|
|
# pathological cases:
|
|
if n == nullv: # 1) can't determine normal, don't align.
|
|
psi = 0.0
|
|
theta = 0.0
|
|
phi = 0.0
|
|
FreeCAD.Console.PrintWarning("Draft PathArray.orientShape - Path normal is Null. Cannot align.\n")
|
|
elif b == z: # 2) binormal is same as z
|
|
psi = math.degrees(DraftVecUtils.angle(x,t,z)) # align shape x to tangent
|
|
theta = 0.0
|
|
phi = 0.0
|
|
FreeCAD.Console.PrintLog ("Draft PathArray.orientShape - Aligned to tangent only (b == z).\n")
|
|
else: # regular case
|
|
psi = math.degrees(DraftVecUtils.angle(x,lnodes,z))
|
|
theta = math.degrees(DraftVecUtils.angle(z,b,lnodes))
|
|
phi = math.degrees(DraftVecUtils.angle(lnodes,t,b))
|
|
if psi != 0.0:
|
|
ns.rotate(RefPt,z,psi)
|
|
if theta != 0.0:
|
|
ns.rotate(RefPt,lnodes,theta)
|
|
if phi != 0.0:
|
|
ns.rotate(RefPt,b,phi)
|
|
return ns
|
|
|
|
def pathArray(self,shape,pathwire,count,xlate,align):
|
|
'''Distribute shapes along a path.'''
|
|
import Part
|
|
import DraftGeomUtils
|
|
closedpath = DraftGeomUtils.isReallyClosed(pathwire)
|
|
normal = DraftGeomUtils.getNormal(pathwire)
|
|
path = Part.__sortEdges__(pathwire.Edges)
|
|
ends = []
|
|
cdist = 0
|
|
for e in path: # find cumulative edge end distance
|
|
cdist += e.Length
|
|
ends.append(cdist)
|
|
base = []
|
|
pt = path[0].Vertexes[0].Point # place the start shape
|
|
ns = self.orientShape(shape,path[0],0,pt,xlate,align,normal)
|
|
base.append(ns)
|
|
if not(closedpath): # closed path doesn't need shape on last vertex
|
|
pt = path[-1].Vertexes[-1].Point # place the end shape
|
|
ns = self.orientShape(shape,path[-1],path[-1].Length,pt,xlate,align,normal)
|
|
base.append(ns)
|
|
if count < 3:
|
|
return(Part.makeCompound(base))
|
|
|
|
# place the middle shapes
|
|
if closedpath:
|
|
stop = count
|
|
else:
|
|
stop = count - 1
|
|
step = float(cdist)/stop
|
|
remain = 0
|
|
travel = step
|
|
for i in range(1,stop):
|
|
# which edge in path should contain this shape?
|
|
iend = len(ends) - 1 # avoids problems with float math travel > ends[-1]
|
|
for j in range(0,len(ends)):
|
|
if travel <= ends[j]:
|
|
iend = j
|
|
break
|
|
# place shape at proper spot on proper edge
|
|
remains = ends[iend] - travel
|
|
offset = path[iend].Length - remains
|
|
pt = path[iend].valueAt(self.getParameterFromV0(path[iend],offset))
|
|
ns = self.orientShape(shape,path[iend],offset,pt,xlate,align,normal)
|
|
base.append(ns)
|
|
travel += step
|
|
return(Part.makeCompound(base))
|
|
|
|
class _Point(_DraftObject):
|
|
"The Draft Point object"
|
|
def __init__(self, obj,x=0,y=0,z=0):
|
|
_DraftObject.__init__(self,obj,"Point")
|
|
obj.addProperty("App::PropertyDistance","X","Draft","Location").X = x
|
|
obj.addProperty("App::PropertyDistance","Y","Draft","Location").Y = y
|
|
obj.addProperty("App::PropertyDistance","Z","Draft","Location").Z = z
|
|
mode = 2
|
|
obj.setEditorMode('Placement',mode)
|
|
|
|
def execute(self, obj):
|
|
import Part
|
|
shape = Part.Vertex(Vector(obj.X.Value,obj.Y.Value,obj.Z.Value))
|
|
obj.Shape = shape
|
|
|
|
class _ViewProviderPoint(_ViewProviderDraft):
|
|
"A viewprovider for the Draft Point object"
|
|
def __init__(self, obj):
|
|
_ViewProviderDraft.__init__(self,obj)
|
|
|
|
def onChanged(self, vobj, prop):
|
|
mode = 2
|
|
vobj.setEditorMode('LineColor',mode)
|
|
vobj.setEditorMode('LineWidth',mode)
|
|
vobj.setEditorMode('BoundingBox',mode)
|
|
vobj.setEditorMode('Deviation',mode)
|
|
vobj.setEditorMode('DiffuseColor',mode)
|
|
vobj.setEditorMode('DisplayMode',mode)
|
|
vobj.setEditorMode('Lighting',mode)
|
|
vobj.setEditorMode('LineMaterial',mode)
|
|
vobj.setEditorMode('ShapeColor',mode)
|
|
vobj.setEditorMode('ShapeMaterial',mode)
|
|
vobj.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","Draft","The objects included in this scale object")
|
|
obj.addProperty("App::PropertyVector","Scale","Draft","The scale vector of this object")
|
|
obj.Scale = Vector(1,1,1)
|
|
|
|
def execute(self,obj):
|
|
import Part, DraftGeomUtils
|
|
pl = obj.Placement
|
|
shapes = []
|
|
if obj.isDerivedFrom("Part::Part2DObject"):
|
|
# if our clone is 2D, make sure all its linked geometry is 2D too
|
|
for o in obj.Objects:
|
|
if not o.isDerivedFrom("Part::Part2DObject"):
|
|
FreeCAD.Console.PrintWarning("Warning 2D Clone "+obj.Name+" contains 3D geometry")
|
|
return
|
|
objs = getGroupContents(obj.Objects)
|
|
for o in objs:
|
|
if o.isDerivedFrom("Part::Feature"):
|
|
if o.Shape.isNull():
|
|
return
|
|
sh = o.Shape.copy()
|
|
m = FreeCAD.Matrix()
|
|
if hasattr(obj,"Scale") and not sh.isNull():
|
|
sx,sy,sz = obj.Scale
|
|
if not DraftVecUtils.equals(obj.Scale,Vector(1,1,1)):
|
|
m.scale(obj.Scale)
|
|
if sx == sy == sz:
|
|
sh.transformShape(m)
|
|
else:
|
|
sh = sh.transformGeometry(m)
|
|
if not sh.isNull():
|
|
shapes.append(sh)
|
|
if shapes:
|
|
if len(shapes) == 1:
|
|
obj.Shape = shapes[0]
|
|
obj.Placement = shapes[0].Placement
|
|
else:
|
|
obj.Shape = Part.makeCompound(shapes)
|
|
if not DraftGeomUtils.isNull(pl):
|
|
obj.Placement = pl
|
|
|
|
def getSubVolume(self,obj,placement=None):
|
|
# this allows clones of arch windows to return a subvolume too
|
|
if obj.Objects:
|
|
if hasattr(obj.Objects[0],"Proxy"):
|
|
if hasattr(obj.Objects[0].Proxy,"getSubVolume"):
|
|
if not placement:
|
|
# clones must displace the original subvolume too
|
|
placement = obj.Placement
|
|
return obj.Objects[0].Proxy.getSubVolume(obj.Objects[0],placement)
|
|
return None
|
|
|
|
class _ViewProviderClone(_ViewProviderDraftAlt):
|
|
"a view provider that displays a Clone icon instead of a Draft icon"
|
|
|
|
def __init__(self,vobj):
|
|
_ViewProviderDraftAlt.__init__(self,vobj)
|
|
|
|
def getIcon(self):
|
|
return ":/icons/Draft_Clone.svg"
|
|
|
|
class _ViewProviderDraftArray(_ViewProviderDraft):
|
|
"a view provider that displays a Array icon instead of a Draft icon"
|
|
|
|
def __init__(self,vobj):
|
|
_ViewProviderDraft.__init__(self,vobj)
|
|
|
|
def getIcon(self):
|
|
return ":/icons/Draft_Array.svg"
|
|
|
|
class _ShapeString(_DraftObject):
|
|
"The ShapeString object"
|
|
|
|
def __init__(self, obj):
|
|
_DraftObject.__init__(self,obj,"ShapeString")
|
|
obj.addProperty("App::PropertyString","String","Draft","Text string")
|
|
obj.addProperty("App::PropertyFile","FontFile","Draft","Font file name")
|
|
obj.addProperty("App::PropertyLength","Size","Draft","Height of text")
|
|
obj.addProperty("App::PropertyLength","Tracking","Draft",
|
|
"Inter-character spacing")
|
|
|
|
def execute(self, obj):
|
|
import Part
|
|
# import OpenSCAD2Dgeom
|
|
import os
|
|
if obj.String and obj.FontFile:
|
|
if obj.Placement:
|
|
plm = obj.Placement
|
|
CharList = Part.makeWireString(obj.String,obj.FontFile,obj.Size,obj.Tracking)
|
|
SSChars = []
|
|
|
|
# test a simple letter to know if we have a sticky font or not
|
|
sticky = False
|
|
testWire = Part.makeWireString("L",obj.FontFile,obj.Size,obj.Tracking)[0][0]
|
|
if testWire.isClosed:
|
|
try:
|
|
testFace = Part.Face(testWire)
|
|
except Part.OCCError:
|
|
sticky = True
|
|
else:
|
|
if not testFace.isValid():
|
|
sticky = True
|
|
else:
|
|
sticky = True
|
|
|
|
for char in CharList:
|
|
if sticky:
|
|
for CWire in char:
|
|
SSChars.append(CWire)
|
|
else:
|
|
CharFaces = []
|
|
for CWire in char:
|
|
f = Part.Face(CWire)
|
|
if f:
|
|
CharFaces.append(f)
|
|
# whitespace (ex: ' ') has no faces. This breaks OpenSCAD2Dgeom...
|
|
if CharFaces:
|
|
# s = OpenSCAD2Dgeom.Overlappingfaces(CharFaces).makeshape()
|
|
# s = self.makeGlyph(CharFaces)
|
|
s = self.makeFaces(char)
|
|
SSChars.append(s)
|
|
shape = Part.Compound(SSChars)
|
|
obj.Shape = shape
|
|
if plm:
|
|
obj.Placement = plm
|
|
|
|
def makeFaces(self, wireChar):
|
|
import Part
|
|
compFaces=[]
|
|
allEdges = []
|
|
wirelist=sorted(wireChar,key=(lambda shape: shape.BoundBox.DiagonalLength),reverse=True)
|
|
fixedwire = []
|
|
for w in wirelist:
|
|
compEdges = Part.Compound(w.Edges)
|
|
compEdges = compEdges.connectEdgesToWires()
|
|
fixedwire.append(compEdges.Wires[0])
|
|
wirelist = fixedwire
|
|
sep_wirelist = []
|
|
while len(wirelist) > 0:
|
|
wire2Face = [wirelist[0]]
|
|
face = Part.Face(wirelist[0])
|
|
for w in wirelist[1:]:
|
|
p = w.Vertexes[0].Point
|
|
u,v = face.Surface.parameter(p)
|
|
if face.isPartOfDomain(u,v):
|
|
f = Part.Face(w)
|
|
if face.Orientation == f.Orientation:
|
|
if f.Surface.Axis * face.Surface.Axis < 0:
|
|
w.reverse()
|
|
else:
|
|
if f.Surface.Axis * face.Surface.Axis > 0:
|
|
w.reverse()
|
|
wire2Face.append(w)
|
|
else:
|
|
sep_wirelist.append(w)
|
|
wirelist = sep_wirelist
|
|
sep_wirelist = []
|
|
face = Part.Face(wire2Face)
|
|
face.validate()
|
|
if face.Surface.Axis.z < 0.0:
|
|
face.reverse()
|
|
compFaces.append(face)
|
|
ret = Part.Compound(compFaces)
|
|
return ret
|
|
|
|
def makeGlyph(self, facelist):
|
|
''' turn list of simple contour faces into a compound shape representing a glyph '''
|
|
''' remove cuts, fuse overlapping contours, retain islands '''
|
|
import Part
|
|
if len(facelist) == 1:
|
|
return(facelist[0])
|
|
|
|
sortedfaces = sorted(facelist,key=(lambda shape: shape.Area),reverse=True)
|
|
|
|
biggest = sortedfaces[0]
|
|
result = biggest
|
|
islands =[]
|
|
for face in sortedfaces[1:]:
|
|
bcfA = biggest.common(face).Area
|
|
fA = face.Area
|
|
difA = abs(bcfA - fA)
|
|
eps = epsilon()
|
|
# if biggest.common(face).Area == face.Area:
|
|
if difA <= eps: # close enough to zero
|
|
# biggest completely overlaps current face ==> cut
|
|
result = result.cut(face)
|
|
# elif biggest.common(face).Area == 0:
|
|
elif bcfA <= eps:
|
|
# island
|
|
islands.append(face)
|
|
else:
|
|
# partial overlap - (font designer error?)
|
|
result = result.fuse(face)
|
|
#glyphfaces = [result]
|
|
wl = result.Wires
|
|
for w in wl:
|
|
w.fixWire()
|
|
glyphfaces = [Part.Face(wl)]
|
|
glyphfaces.extend(islands)
|
|
ret = Part.Compound(glyphfaces) # should we fuse these instead of making compound?
|
|
return ret
|
|
|
|
|
|
class _Facebinder(_DraftObject):
|
|
"The Draft Facebinder object"
|
|
def __init__(self,obj):
|
|
_DraftObject.__init__(self,obj,"Facebinder")
|
|
obj.addProperty("App::PropertyLinkSubList","Faces","Draft","Linked faces")
|
|
|
|
def execute(self,obj):
|
|
pl = obj.Placement
|
|
if not obj.Faces:
|
|
return
|
|
faces = []
|
|
for f in obj.Faces:
|
|
if "Face" in f[1]:
|
|
try:
|
|
fnum = int(f[1][4:])-1
|
|
faces.append(f[0].Shape.Faces[fnum])
|
|
except(IndexError,Part.OCCError):
|
|
print("Draft: wrong face index")
|
|
return
|
|
if not faces:
|
|
return
|
|
import Part
|
|
try:
|
|
if len(faces) > 1:
|
|
sh = faces.pop()
|
|
sh = sh.multiFuse(faces)
|
|
sh = sh.removeSplitter()
|
|
else:
|
|
sh = faces[0]
|
|
sh.transformShape(sh.Matrix, True)
|
|
except Part.OCCError:
|
|
print("Draft: error building facebinder")
|
|
return
|
|
obj.Shape = sh
|
|
obj.Placement = pl
|
|
|
|
def addSubobjects(self,obj,facelinks):
|
|
"adds facelinks to this facebinder"
|
|
objs = obj.Faces
|
|
for o in facelinks:
|
|
if isinstance(o,tuple) or isinstance(o,list):
|
|
if o[0].Name != obj.Name:
|
|
objs.append(tuple(o))
|
|
else:
|
|
for el in o.SubElementNames:
|
|
if "Face" in el:
|
|
if o.Object.Name != obj.Name:
|
|
objs.append((o.Object,el))
|
|
obj.Faces = objs
|
|
self.execute(obj)
|
|
|
|
class _VisGroup:
|
|
"The VisGroup object"
|
|
def __init__(self,obj):
|
|
self.Type = "VisGroup"
|
|
obj.Proxy = self
|
|
self.Object = obj
|
|
|
|
def __getstate__(self):
|
|
return self.Type
|
|
|
|
def __setstate__(self,state):
|
|
if state:
|
|
self.Type = state
|
|
|
|
def execute(self,obj):
|
|
pass
|
|
|
|
class _ViewProviderVisGroup:
|
|
"A View Provider for the VisGroup object"
|
|
def __init__(self,vobj):
|
|
vobj.addProperty("App::PropertyColor","LineColor","Base","")
|
|
vobj.addProperty("App::PropertyColor","ShapeColor","Base","")
|
|
vobj.addProperty("App::PropertyFloat","LineWidth","Base","")
|
|
vobj.addProperty("App::PropertyEnumeration","DrawStyle","Base","")
|
|
vobj.addProperty("App::PropertyInteger","Transparency","Base","")
|
|
vobj.DrawStyle = ["Solid","Dashed","Dotted","Dashdot"]
|
|
vobj.LineWidth = 1
|
|
vobj.LineColor = (0.13,0.15,0.37)
|
|
vobj.DrawStyle = "Solid"
|
|
vobj.Proxy = self
|
|
|
|
def getIcon(self):
|
|
import Arch_rc
|
|
return ":/icons/Draft_VisGroup.svg"
|
|
|
|
def attach(self,vobj):
|
|
self.Object = vobj.Object
|
|
return
|
|
|
|
def claimChildren(self):
|
|
return self.Object.Group
|
|
|
|
def __getstate__(self):
|
|
return None
|
|
|
|
def __setstate__(self,state):
|
|
return None
|
|
|
|
def updateData(self,obj,prop):
|
|
if prop == "Group":
|
|
if obj.ViewObject:
|
|
obj.ViewObject.Proxy.onChanged(obj.ViewObject,"LineColor")
|
|
|
|
def onChanged(self,vobj,prop):
|
|
if hasattr(vobj,"Object"):
|
|
if vobj.Object:
|
|
if hasattr(vobj.Object,"Group"):
|
|
if vobj.Object.Group:
|
|
for o in vobj.Object.Group:
|
|
if o.ViewObject:
|
|
for p in ["LineColor","ShapeColor","LineWidth","DrawStyle","Transparency"]:
|
|
if hasattr(o.ViewObject,p):
|
|
setattr(o.ViewObject,p,getattr(vobj,p))
|
|
elif hasattr(o,p):
|
|
# for Drawing views
|
|
setattr(o,p,getattr(vobj,p))
|
|
elif (p == "DrawStyle") and hasattr(o,"LineStyle"):
|
|
# Special case in Drawing views
|
|
setattr(o,"LineStyle",getattr(vobj,p))
|
|
if vobj.Object.InList:
|
|
# touch the page if something was changed
|
|
if vobj.Object.InList[0].isDerivedFrom("Drawing::FeaturePage"):
|
|
vobj.Object.InList[0].touch()
|