3836 lines
155 KiB
Python
3836 lines
155 KiB
Python
# -*- coding: utf-8 -*-
|
|
# ***************************************************************************
|
|
# * Copyright (c) 2009, 2010 Yorik van Havre <yorik@uncreated.net> *
|
|
# * Copyright (c) 2009, 2010 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 *
|
|
# * *
|
|
# ***************************************************************************
|
|
"""Provide the Draft Workbench public programming interface.
|
|
|
|
The Draft module offers tools to create and manipulate 2D objects.
|
|
The functions in this file must be usable without requiring the
|
|
graphical user interface.
|
|
These functions can be used as the backend for the graphical commands
|
|
defined in `DraftTools.py`.
|
|
"""
|
|
## \addtogroup DRAFT
|
|
# \brief Create and manipulate basic 2D objects
|
|
#
|
|
# This module offers 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
|
|
# visual interface, and corresponding python functions, that can perform
|
|
# the same operation programmatically.
|
|
#
|
|
# @{
|
|
|
|
import math
|
|
import sys
|
|
from PySide.QtCore import QT_TRANSLATE_NOOP
|
|
|
|
import FreeCAD
|
|
from FreeCAD import Vector
|
|
|
|
import DraftVecUtils
|
|
import WorkingPlane
|
|
from draftutils.translate import translate
|
|
|
|
if FreeCAD.GuiUp:
|
|
import FreeCADGui
|
|
import Draft_rc
|
|
gui = True
|
|
# To prevent complaints from code checkers (flake8)
|
|
True if Draft_rc.__name__ else False
|
|
else:
|
|
gui = False
|
|
|
|
__title__ = "FreeCAD Draft Workbench"
|
|
__author__ = ("Yorik van Havre, Werner Mayer, Martin Burbaum, Ken Cline, "
|
|
"Dmitry Chigrin, Daniel Falck")
|
|
__url__ = "https://www.freecadweb.org"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Backwards compatibility
|
|
# ---------------------------------------------------------------------------
|
|
from DraftLayer import Layer as _VisGroup
|
|
from DraftLayer import ViewProviderLayer as _ViewProviderVisGroup
|
|
from DraftLayer import makeLayer
|
|
|
|
# import DraftFillet
|
|
# Fillet = DraftFillet.Fillet
|
|
# makeFillet = DraftFillet.makeFillet
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# General functions
|
|
# ---------------------------------------------------------------------------
|
|
from draftutils.utils import ARROW_TYPES as arrowtypes
|
|
|
|
from draftutils.utils import stringencodecoin
|
|
from draftutils.utils import string_encode_coin
|
|
|
|
from draftutils.utils import typecheck
|
|
from draftutils.utils import type_check
|
|
|
|
from draftutils.utils import getParamType
|
|
from draftutils.utils import get_param_type
|
|
|
|
from draftutils.utils import getParam
|
|
from draftutils.utils import get_param
|
|
|
|
from draftutils.utils import setParam
|
|
from draftutils.utils import set_param
|
|
|
|
from draftutils.utils import precision
|
|
from draftutils.utils import tolerance
|
|
from draftutils.utils import epsilon
|
|
|
|
from draftutils.utils import getRealName
|
|
from draftutils.utils import get_real_name
|
|
|
|
from draftutils.utils import getType
|
|
from draftutils.utils import get_type
|
|
|
|
from draftutils.utils import getObjectsOfType
|
|
from draftutils.utils import get_objects_of_type
|
|
|
|
from draftutils.utils import isClone
|
|
from draftutils.utils import is_clone
|
|
|
|
from draftutils.utils import getGroupNames
|
|
from draftutils.utils import get_group_names
|
|
|
|
from draftutils.utils import ungroup
|
|
|
|
from draftutils.utils import getGroupContents
|
|
from draftutils.utils import get_group_contents
|
|
|
|
from draftutils.utils import printShape
|
|
from draftutils.utils import print_shape
|
|
|
|
from draftutils.utils import compareObjects
|
|
from draftutils.utils import compare_objects
|
|
|
|
from draftutils.utils import shapify
|
|
|
|
from draftutils.utils import loadSvgPatterns
|
|
from draftutils.utils import load_svg_patterns
|
|
|
|
from draftutils.utils import svgpatterns
|
|
from draftutils.utils import svg_patterns
|
|
|
|
from draftutils.utils import getMovableChildren
|
|
from draftutils.utils import get_movable_children
|
|
|
|
from draftutils.gui_utils import get3DView
|
|
from draftutils.gui_utils import get_3d_view
|
|
|
|
from draftutils.gui_utils import autogroup
|
|
|
|
from draftutils.gui_utils import dimSymbol
|
|
from draftutils.gui_utils import dim_symbol
|
|
|
|
from draftutils.gui_utils import dimDash
|
|
from draftutils.gui_utils import dim_dash
|
|
|
|
from draftutils.gui_utils import removeHidden
|
|
from draftutils.gui_utils import remove_hidden
|
|
|
|
from draftutils.gui_utils import formatObject
|
|
from draftutils.gui_utils import format_object
|
|
|
|
from draftutils.gui_utils import getSelection
|
|
from draftutils.gui_utils import get_selection
|
|
|
|
from draftutils.gui_utils import getSelectionEx
|
|
from draftutils.gui_utils import get_selection_ex
|
|
|
|
from draftutils.gui_utils import select
|
|
|
|
from draftutils.gui_utils import loadTexture
|
|
from draftutils.gui_utils import load_texture
|
|
|
|
#---------------------------------------------------------------------------
|
|
# Draft functions
|
|
#---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
#---------------------------------------------------------------------------
|
|
# Draft objects
|
|
#---------------------------------------------------------------------------
|
|
|
|
# base object
|
|
from draftobjects.base import DraftObject
|
|
from draftobjects.base import _DraftObject
|
|
|
|
# base viewprovider
|
|
from draftviewproviders.view_base import ViewProviderDraft
|
|
from draftviewproviders.view_base import _ViewProviderDraft
|
|
from draftviewproviders.view_base import ViewProviderDraftAlt
|
|
from draftviewproviders.view_base import _ViewProviderDraftAlt
|
|
from draftviewproviders.view_base import ViewProviderDraftPart
|
|
from draftviewproviders.view_base import _ViewProviderDraftPart
|
|
|
|
# circle
|
|
from draftmake.make_circle import make_circle, makeCircle
|
|
from draftobjects.circle import Circle, _Circle
|
|
|
|
# ellipse
|
|
from draftmake.make_ellipse import make_ellipse, makeEllipse
|
|
from draftobjects.ellipse import Ellipse, _Ellipse
|
|
|
|
# rectangle
|
|
from draftmake.make_rectangle import make_rectangle, makeRectangle
|
|
from draftobjects.rectangle import Rectangle, _Rectangle
|
|
if FreeCAD.GuiUp:
|
|
from draftviewproviders.view_rectangle import ViewProviderRectangle
|
|
from draftviewproviders.view_rectangle import _ViewProviderRectangle
|
|
|
|
# polygon
|
|
from draftmake.make_polygon import make_polygon, makePolygon
|
|
from draftobjects.polygon import Polygon, _Polygon
|
|
|
|
# wire and line
|
|
from draftmake.make_line import make_line, makeLine
|
|
from draftmake.make_wire import make_wire, makeWire
|
|
from draftobjects.wire import Wire, _Wire
|
|
if FreeCAD.GuiUp:
|
|
from draftviewproviders.view_wire import ViewProviderWire
|
|
from draftviewproviders.view_wire import _ViewProviderWire
|
|
|
|
# bspline
|
|
from draftmake.make_bspline import make_bspline, makeBSpline
|
|
from draftobjects.bspline import BSpline, _BSpline
|
|
if FreeCAD.GuiUp:
|
|
# for compatibility with older versions
|
|
_ViewProviderBSpline = ViewProviderWire
|
|
|
|
# bezcurve
|
|
from draftmake.make_bezcurve import make_bezcurve, makeBezCurve
|
|
from draftobjects.bezcurve import BezCurve, _BezCurve
|
|
if FreeCAD.GuiUp:
|
|
# for compatibility with older versions
|
|
_ViewProviderBezCurve = ViewProviderWire
|
|
|
|
# clone
|
|
from draftmake.make_clone import make_clone, clone
|
|
from draftobjects.clone import Clone, _Clone
|
|
if FreeCAD.GuiUp:
|
|
from draftviewproviders.view_clone import ViewProviderClone
|
|
from draftviewproviders.view_clone import _ViewProviderClone
|
|
|
|
# point
|
|
from draftmake.make_point import make_point, makePoint
|
|
from draftobjects.point import Point, _Point
|
|
if FreeCAD.GuiUp:
|
|
from draftviewproviders.view_point import ViewProviderPoint
|
|
from draftviewproviders.view_point import _ViewProviderPoint
|
|
|
|
# working plane proxy
|
|
from draftmake.make_wpproxy import make_workingplaneproxy
|
|
from draftmake.make_wpproxy import makeWorkingPlaneProxy
|
|
from draftobjects.wpproxy import WorkingPlaneProxy
|
|
if FreeCAD.GuiUp:
|
|
from draftviewproviders.view_wpproxy import ViewProviderWorkingPlaneProxy
|
|
|
|
#---------------------------------------------------------------------------
|
|
# Draft annotation objects
|
|
#---------------------------------------------------------------------------
|
|
|
|
from draftobjects.dimension import make_dimension, make_angular_dimension
|
|
from draftobjects.dimension import LinearDimension, AngularDimension
|
|
|
|
makeDimension = make_dimension
|
|
makeAngularDimension = make_angular_dimension
|
|
_Dimension = LinearDimension
|
|
_AngularDimension = AngularDimension
|
|
|
|
if gui:
|
|
from draftviewproviders.view_dimension import ViewProviderLinearDimension
|
|
from draftviewproviders.view_dimension import ViewProviderAngularDimension
|
|
_ViewProviderDimension = ViewProviderLinearDimension
|
|
_ViewProviderAngularDimension = ViewProviderAngularDimension
|
|
|
|
|
|
from draftobjects.label import make_label
|
|
from draftobjects.label import Label
|
|
|
|
makeLabel = make_label
|
|
DraftLabel = Label
|
|
|
|
if gui:
|
|
from draftviewproviders.view_label import ViewProviderLabel
|
|
ViewProviderDraftLabel = ViewProviderLabel
|
|
|
|
|
|
from draftobjects.text import make_text
|
|
from draftobjects.text import Text
|
|
makeText = make_text
|
|
DraftText = Text
|
|
|
|
if gui:
|
|
from draftviewproviders.view_text import ViewProviderText
|
|
ViewProviderDraftText = ViewProviderText
|
|
|
|
def convertDraftTexts(textslist=[]):
|
|
"""
|
|
converts the given Draft texts (or all that is found
|
|
in the active document) to the new object
|
|
This function was already present at splitting time during v 0.19
|
|
"""
|
|
if not isinstance(textslist,list):
|
|
textslist = [textslist]
|
|
if not textslist:
|
|
for o in FreeCAD.ActiveDocument.Objects:
|
|
if o.TypeId == "App::Annotation":
|
|
textslist.append(o)
|
|
todelete = []
|
|
for o in textslist:
|
|
l = o.Label
|
|
o.Label = l+".old"
|
|
obj = makeText(o.LabelText,point=o.Position)
|
|
obj.Label = l
|
|
todelete.append(o.Name)
|
|
for p in o.InList:
|
|
if p.isDerivedFrom("App::DocumentObjectGroup"):
|
|
if o in p.Group:
|
|
g = p.Group
|
|
g.append(obj)
|
|
p.Group = g
|
|
for n in todelete:
|
|
FreeCAD.ActiveDocument.removeObject(n)
|
|
|
|
|
|
def makeWire(pointslist,closed=False,placement=None,face=None,support=None,bs2wire=False):
|
|
"""makeWire(pointslist,[closed],[placement]): Creates a Wire object
|
|
from the given list of vectors. If closed is True or first
|
|
and last points are identical, the wire is closed. If face is
|
|
true (and wire is closed), the wire will appear filled. Instead of
|
|
a pointslist, you can also pass a Part Wire."""
|
|
if not FreeCAD.ActiveDocument:
|
|
FreeCAD.Console.PrintError("No active document. Aborting\n")
|
|
return
|
|
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")
|
|
ipl = placement.inverse()
|
|
if not bs2wire:
|
|
pointslist = [ipl.multVec(p) for p in pointslist]
|
|
if len(pointslist) == 2: fname = "Line"
|
|
else: fname = "Wire"
|
|
obj = FreeCAD.ActiveDocument.addObject("Part::Part2DObjectPython",fname)
|
|
_Wire(obj)
|
|
obj.Points = pointslist
|
|
obj.Closed = closed
|
|
obj.Support = support
|
|
if face != None:
|
|
obj.MakeFace = face
|
|
if placement: obj.Placement = placement
|
|
if gui:
|
|
_ViewProviderWire(obj.ViewObject)
|
|
formatObject(obj)
|
|
select(obj)
|
|
|
|
return obj
|
|
|
|
|
|
def makeCopy(obj,force=None,reparent=False):
|
|
"""makeCopy(object): returns an exact copy of an object"""
|
|
if not FreeCAD.ActiveDocument:
|
|
FreeCAD.Console.PrintError("No active document. Aborting\n")
|
|
return
|
|
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) in ["Dimension","LinearDimension"]) 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 hasattr(obj, 'Shape'):
|
|
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","ExpressionEngine"]:
|
|
if p in newobj.PropertiesList:
|
|
if not "ReadOnly" in 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)
|
|
|
|
formatObject(newobj,obj)
|
|
return newobj
|
|
|
|
def makeBlock(objectslist):
|
|
"""makeBlock(objectslist): Creates a Draft Block from the given objects"""
|
|
if not FreeCAD.ActiveDocument:
|
|
FreeCAD.Console.PrintError("No active document. Aborting\n")
|
|
return
|
|
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,arg5=None,arg6=None,name="Array",use_link=False):
|
|
"""makeArray(object,xvector,yvector,xnum,ynum,[name]) for rectangular array, or
|
|
makeArray(object,xvector,yvector,zvector,xnum,ynum,znum,[name]) for rectangular array, or
|
|
makeArray(object,center,totalangle,totalnum,[name]) for polar array, or
|
|
makeArray(object,rdistance,tdistance,axis,center,ncircles,symmetry,[name]) for circular 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, same for y direction with yvector and ynum,
|
|
same for z direction with zvector and znum. 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. In case of a circular array, rdistance is the distance of the
|
|
circles, tdistance is the distance within circles, axis the rotation-axes, center the
|
|
center of rotation, ncircles the number of circles and symmetry the number
|
|
of symmetry-axis of the distribution. The result is a parametric Draft Array.
|
|
"""
|
|
|
|
if not FreeCAD.ActiveDocument:
|
|
FreeCAD.Console.PrintError("No active document. Aborting\n")
|
|
return
|
|
if use_link:
|
|
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython",name,_Array(None),None,True)
|
|
else:
|
|
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython",name)
|
|
_Array(obj)
|
|
obj.Base = baseobject
|
|
if arg6:
|
|
if isinstance(arg1, (int, float, FreeCAD.Units.Quantity)):
|
|
obj.ArrayType = "circular"
|
|
obj.RadialDistance = arg1
|
|
obj.TangentialDistance = arg2
|
|
obj.Axis = arg3
|
|
obj.Center = arg4
|
|
obj.NumberCircles = arg5
|
|
obj.Symmetry = arg6
|
|
else:
|
|
obj.ArrayType = "ortho"
|
|
obj.IntervalX = arg1
|
|
obj.IntervalY = arg2
|
|
obj.IntervalZ = arg3
|
|
obj.NumberX = arg4
|
|
obj.NumberY = arg5
|
|
obj.NumberZ = arg6
|
|
elif 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:
|
|
if use_link:
|
|
_ViewProviderDraftLink(obj.ViewObject)
|
|
else:
|
|
_ViewProviderDraftArray(obj.ViewObject)
|
|
formatObject(obj,obj.Base)
|
|
if len(obj.Base.ViewObject.DiffuseColor) > 1:
|
|
obj.ViewObject.Proxy.resetColors(obj.ViewObject)
|
|
baseobject.ViewObject.hide()
|
|
select(obj)
|
|
return obj
|
|
|
|
def makePathArray(baseobject,pathobject,count,xlate=None,align=False,pathobjsubs=[],use_link=False):
|
|
"""makePathArray(docobj,path,count,xlate,align,pathobjsubs,use_link): 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."""
|
|
if not FreeCAD.ActiveDocument:
|
|
FreeCAD.Console.PrintError("No active document. Aborting\n")
|
|
return
|
|
if use_link:
|
|
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython","PathArray",_PathArray(None),None,True)
|
|
else:
|
|
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:
|
|
if use_link:
|
|
_ViewProviderDraftLink(obj.ViewObject)
|
|
else:
|
|
_ViewProviderDraftArray(obj.ViewObject)
|
|
formatObject(obj,obj.Base)
|
|
if len(obj.Base.ViewObject.DiffuseColor) > 1:
|
|
obj.ViewObject.Proxy.resetColors(obj.ViewObject)
|
|
baseobject.ViewObject.hide()
|
|
select(obj)
|
|
return obj
|
|
|
|
def makePointArray(base, ptlst):
|
|
"""makePointArray(base,pointlist):"""
|
|
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython","PointArray")
|
|
_PointArray(obj, base, ptlst)
|
|
obj.Base = base
|
|
obj.PointList = ptlst
|
|
if gui:
|
|
_ViewProviderDraftArray(obj.ViewObject)
|
|
base.ViewObject.hide()
|
|
formatObject(obj,obj.Base)
|
|
if len(obj.Base.ViewObject.DiffuseColor) > 1:
|
|
obj.ViewObject.Proxy.resetColors(obj.ViewObject)
|
|
select(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."""
|
|
if not FreeCAD.ActiveDocument:
|
|
FreeCAD.Console.PrintError("No active document. Aborting\n")
|
|
return
|
|
newobj = FreeCAD.ActiveDocument.addObject("Part::Extrusion","Extrusion")
|
|
newobj.Base = obj
|
|
newobj.Dir = vector
|
|
newobj.Solid = solid
|
|
if gui:
|
|
obj.ViewObject.Visibility = False
|
|
formatObject(newobj,obj)
|
|
select(newobj)
|
|
|
|
return newobj
|
|
|
|
def joinWires(wires, joinAttempts = 0):
|
|
"""joinWires(objects): merges a set of wires where possible, if any of those
|
|
wires have a coincident start and end point"""
|
|
if joinAttempts > len(wires):
|
|
return
|
|
joinAttempts += 1
|
|
for wire1Index, wire1 in enumerate(wires):
|
|
for wire2Index, wire2 in enumerate(wires):
|
|
if wire2Index <= wire1Index:
|
|
continue
|
|
if joinTwoWires(wire1, wire2):
|
|
wires.pop(wire2Index)
|
|
break
|
|
joinWires(wires, joinAttempts)
|
|
|
|
def joinTwoWires(wire1, wire2):
|
|
"""joinTwoWires(object, object): joins two wires if they share a common
|
|
point as a start or an end.
|
|
|
|
BUG: it occasionally fails to join lines even if the lines
|
|
visually share a point.
|
|
This is a rounding error in the comparison of the shared point;
|
|
a small difference will result in the points being considered different
|
|
and thus the lines not joining.
|
|
Test properly using `DraftVecUtils.equals` because then it will consider
|
|
the precision set in the Draft preferences.
|
|
"""
|
|
wire1AbsPoints = [wire1.Placement.multVec(point) for point in wire1.Points]
|
|
wire2AbsPoints = [wire2.Placement.multVec(point) for point in wire2.Points]
|
|
if (wire1AbsPoints[0] == wire2AbsPoints[-1] and wire1AbsPoints[-1] == wire2AbsPoints[0]) \
|
|
or (wire1AbsPoints[0] == wire2AbsPoints[0] and wire1AbsPoints[-1] == wire2AbsPoints[-1]):
|
|
wire2AbsPoints.pop()
|
|
wire1.Closed = True
|
|
elif wire1AbsPoints[0] == wire2AbsPoints[0]:
|
|
wire1AbsPoints = list(reversed(wire1AbsPoints))
|
|
elif wire1AbsPoints[0] == wire2AbsPoints[-1]:
|
|
wire1AbsPoints = list(reversed(wire1AbsPoints))
|
|
wire2AbsPoints = list(reversed(wire2AbsPoints))
|
|
elif wire1AbsPoints[-1] == wire2AbsPoints[-1]:
|
|
wire2AbsPoints = list(reversed(wire2AbsPoints))
|
|
elif wire1AbsPoints[-1] == wire2AbsPoints[0]:
|
|
pass
|
|
else:
|
|
return False
|
|
wire2AbsPoints.pop(0)
|
|
wire1.Points = [wire1.Placement.inverse().multVec(point) for point in wire1AbsPoints] + [wire1.Placement.inverse().multVec(point) for point in wire2AbsPoints]
|
|
FreeCAD.ActiveDocument.removeObject(wire2.Name)
|
|
return True
|
|
|
|
def split(wire, newPoint, edgeIndex):
|
|
if getType(wire) != "Wire":
|
|
return
|
|
elif wire.Closed:
|
|
splitClosedWire(wire, edgeIndex)
|
|
else:
|
|
splitOpenWire(wire, newPoint, edgeIndex)
|
|
|
|
def splitClosedWire(wire, edgeIndex):
|
|
wire.Closed = False
|
|
if edgeIndex == len(wire.Points):
|
|
makeWire([wire.Placement.multVec(wire.Points[0]),
|
|
wire.Placement.multVec(wire.Points[-1])], placement=wire.Placement)
|
|
else:
|
|
makeWire([wire.Placement.multVec(wire.Points[edgeIndex-1]),
|
|
wire.Placement.multVec(wire.Points[edgeIndex])], placement=wire.Placement)
|
|
wire.Points = list(reversed(wire.Points[0:edgeIndex])) + list(reversed(wire.Points[edgeIndex:]))
|
|
|
|
def splitOpenWire(wire, newPoint, edgeIndex):
|
|
wire1Points = []
|
|
wire2Points = []
|
|
for index, point in enumerate(wire.Points):
|
|
if index == edgeIndex:
|
|
wire1Points.append(wire.Placement.inverse().multVec(newPoint))
|
|
wire2Points.append(newPoint)
|
|
wire2Points.append(wire.Placement.multVec(point))
|
|
elif index < edgeIndex:
|
|
wire1Points.append(point)
|
|
elif index > edgeIndex:
|
|
wire2Points.append(wire.Placement.multVec(point))
|
|
wire.Points = wire1Points
|
|
makeWire(wire2Points, placement=wire.Placement)
|
|
|
|
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."""
|
|
if not FreeCAD.ActiveDocument:
|
|
FreeCAD.Console.PrintError("No active document. Aborting\n")
|
|
return
|
|
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)
|
|
select(obj)
|
|
|
|
return obj
|
|
|
|
def cut(object1,object2):
|
|
"""cut(oject1,object2): returns a cut object made from
|
|
the difference of the 2 given objects."""
|
|
if not FreeCAD.ActiveDocument:
|
|
FreeCAD.Console.PrintError("No active document. Aborting\n")
|
|
return
|
|
obj = FreeCAD.ActiveDocument.addObject("Part::Cut","Cut")
|
|
obj.Base = object1
|
|
obj.Tool = object2
|
|
object1.ViewObject.Visibility = False
|
|
object2.ViewObject.Visibility = False
|
|
formatObject(obj,object1)
|
|
select(obj)
|
|
|
|
return obj
|
|
|
|
def moveVertex(object, vertex_index, vector):
|
|
points = object.Points
|
|
points[vertex_index] = points[vertex_index].add(vector)
|
|
object.Points = points
|
|
|
|
def moveEdge(object, edge_index, vector):
|
|
moveVertex(object, edge_index, vector)
|
|
if isClosedEdge(edge_index, object):
|
|
moveVertex(object, 0, vector)
|
|
else:
|
|
moveVertex(object, edge_index+1, vector)
|
|
|
|
def copyMovedEdges(arguments):
|
|
copied_edges = []
|
|
for argument in arguments:
|
|
copied_edges.append(copyMovedEdge(argument[0], argument[1], argument[2]))
|
|
joinWires(copied_edges)
|
|
|
|
def copyMovedEdge(object, edge_index, vector):
|
|
vertex1 = object.Placement.multVec(object.Points[edge_index]).add(vector)
|
|
if isClosedEdge(edge_index, object):
|
|
vertex2 = object.Placement.multVec(object.Points[0]).add(vector)
|
|
else:
|
|
vertex2 = object.Placement.multVec(object.Points[edge_index+1]).add(vector)
|
|
return makeLine(vertex1, vertex2)
|
|
|
|
def copyRotatedEdges(arguments):
|
|
copied_edges = []
|
|
for argument in arguments:
|
|
copied_edges.append(copyRotatedEdge(argument[0], argument[1],
|
|
argument[2], argument[3], argument[4]))
|
|
joinWires(copied_edges)
|
|
|
|
def copyRotatedEdge(object, edge_index, angle, center, axis):
|
|
vertex1 = rotateVectorFromCenter(
|
|
object.Placement.multVec(object.Points[edge_index]),
|
|
angle, axis, center)
|
|
if isClosedEdge(edge_index, object):
|
|
vertex2 = rotateVectorFromCenter(
|
|
object.Placement.multVec(object.Points[0]),
|
|
angle, axis, center)
|
|
else:
|
|
vertex2 = rotateVectorFromCenter(
|
|
object.Placement.multVec(object.Points[edge_index+1]),
|
|
angle, axis, center)
|
|
return makeLine(vertex1, vertex2)
|
|
|
|
def isClosedEdge(edge_index, object):
|
|
return edge_index + 1 >= len(object.Points)
|
|
|
|
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. The objects (or their copies) are returned."""
|
|
typecheck([(vector,Vector), (copy,bool)], "move")
|
|
if not isinstance(objectslist,list): objectslist = [objectslist]
|
|
objectslist.extend(getMovableChildren(objectslist))
|
|
newobjlist = []
|
|
newgroups = {}
|
|
objectslist = filterObjectsForModifiers(objectslist, copy)
|
|
for obj in objectslist:
|
|
newobj = None
|
|
# real_vector have been introduced to take into account
|
|
# the possibility that object is inside an App::Part
|
|
if hasattr(obj, "getGlobalPlacement"):
|
|
v_minus_global = obj.getGlobalPlacement().inverse().Rotation.multVec(vector)
|
|
real_vector = obj.Placement.Rotation.multVec(v_minus_global)
|
|
else:
|
|
real_vector = vector
|
|
if getType(obj) == "Point":
|
|
v = Vector(obj.X,obj.Y,obj.Z)
|
|
v = v.add(real_vector)
|
|
if copy:
|
|
newobj = makeCopy(obj)
|
|
else:
|
|
newobj = obj
|
|
newobj.X = v.x
|
|
newobj.Y = v.y
|
|
newobj.Z = v.z
|
|
elif obj.isDerivedFrom("App::DocumentObjectGroup"):
|
|
pass
|
|
elif hasattr(obj,'Shape'):
|
|
if copy:
|
|
newobj = makeCopy(obj)
|
|
else:
|
|
newobj = obj
|
|
pla = newobj.Placement
|
|
pla.move(real_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(real_vector)
|
|
elif getType(obj) == "DraftText":
|
|
if copy:
|
|
newobj = FreeCAD.ActiveDocument.addObject("App::FeaturePython",getRealName(obj.Name))
|
|
DraftText(newobj)
|
|
if gui:
|
|
ViewProviderDraftText(newobj.ViewObject)
|
|
formatObject(newobj,obj)
|
|
newobj.Text = obj.Text
|
|
newobj.Placement = obj.Placement
|
|
if gui:
|
|
formatObject(newobj,obj)
|
|
else:
|
|
newobj = obj
|
|
newobj.Placement.Base = obj.Placement.Base.add(real_vector)
|
|
elif getType(obj) in ["Dimension","LinearDimension"]:
|
|
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(real_vector)
|
|
newobj.End = obj.End.add(real_vector)
|
|
newobj.Dimline = obj.Dimline.add(real_vector)
|
|
else:
|
|
if copy and obj.isDerivedFrom("Mesh::Feature"):
|
|
print("Mesh copy not supported at the moment") # TODO
|
|
newobj = obj
|
|
if "Placement" in obj.PropertiesList:
|
|
pla = obj.Placement
|
|
pla.move(real_vector)
|
|
if newobj is not None:
|
|
newobjlist.append(newobj)
|
|
if copy:
|
|
for p in obj.InList:
|
|
if p.isDerivedFrom("App::DocumentObjectGroup") and (p in objectslist):
|
|
g = newgroups.setdefault(p.Name,FreeCAD.ActiveDocument.addObject(p.TypeId,p.Name))
|
|
g.addObject(newobj)
|
|
break
|
|
if getType(p) == "Layer":
|
|
p.Proxy.addObject(p,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,arg5=None,arg6=None):
|
|
"""array(objectslist,xvector,yvector,xnum,ynum) for rectangular array,
|
|
array(objectslist,xvector,yvector,zvector,xnum,ynum,znum) 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 and z directions with yvector
|
|
and ynum and zvector and znum. 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 rectArray2(objectslist,xvector,yvector,zvector,xnum,ynum,znum):
|
|
typecheck([(xvector,Vector), (yvector,Vector), (zvector,Vector),(xnum,int), (ynum,int),(znum,int)], "rectArray2")
|
|
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)
|
|
for zcount in range(znum):
|
|
currentzvector=currentyvector.add(Vector(zvector).multiply(zcount))
|
|
if not zcount==0:
|
|
move(objectslist,currentzvector,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 arg6:
|
|
rectArray2(objectslist,arg1,arg2,arg3,arg4,arg5,arg6)
|
|
elif arg4:
|
|
rectArray(objectslist,arg1,arg2,arg3,arg4)
|
|
else:
|
|
polarArray(objectslist,arg1,arg2,arg3)
|
|
|
|
def filterObjectsForModifiers(objects, isCopied=False):
|
|
filteredObjects = []
|
|
for object in objects:
|
|
if hasattr(object, "MoveBase") and object.MoveBase and object.Base:
|
|
parents = []
|
|
for parent in object.Base.InList:
|
|
if parent.isDerivedFrom("Part::Feature"):
|
|
parents.append(parent.Name)
|
|
if len(parents) > 1:
|
|
warningMessage = translate("draft","%s shares a base with %d other objects. Please check if you want to modify this.") % (object.Name,len(parents) - 1)
|
|
FreeCAD.Console.PrintError(warningMessage)
|
|
if FreeCAD.GuiUp:
|
|
FreeCADGui.getMainWindow().showMessage(warningMessage, 0)
|
|
filteredObjects.append(object.Base)
|
|
elif hasattr(object,"Placement") and object.getEditorMode("Placement") == ["ReadOnly"] and not isCopied:
|
|
FreeCAD.Console.PrintError(translate("Draft","%s cannot be modified because its placement is readonly.") % obj.Name)
|
|
continue
|
|
else:
|
|
filteredObjects.append(object)
|
|
return filteredObjects
|
|
|
|
def rotateVertex(object, vertex_index, angle, center, axis):
|
|
points = object.Points
|
|
points[vertex_index] = object.Placement.inverse().multVec(
|
|
rotateVectorFromCenter(
|
|
object.Placement.multVec(points[vertex_index]),
|
|
angle, axis, center))
|
|
object.Points = points
|
|
|
|
def rotateVectorFromCenter(vector, angle, axis, center):
|
|
rv = vector.sub(center)
|
|
rv = DraftVecUtils.rotate(rv, math.radians(angle), axis)
|
|
return center.add(rv)
|
|
|
|
def rotateEdge(object, edge_index, angle, center, axis):
|
|
rotateVertex(object, edge_index, angle, center, axis)
|
|
if isClosedEdge(edge_index, object):
|
|
rotateVertex(object, 0, angle, center, axis)
|
|
else:
|
|
rotateVertex(object, edge_index+1, angle, center, axis)
|
|
|
|
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 = []
|
|
newgroups = {}
|
|
objectslist = filterObjectsForModifiers(objectslist, copy)
|
|
for obj in objectslist:
|
|
newobj = None
|
|
# real_center and real_axis are introduced to take into account
|
|
# the possibility that object is inside an App::Part
|
|
if hasattr(obj, "getGlobalPlacement"):
|
|
ci = obj.getGlobalPlacement().inverse().multVec(center)
|
|
real_center = obj.Placement.multVec(ci)
|
|
ai = obj.getGlobalPlacement().inverse().Rotation.multVec(axis)
|
|
real_axis = obj.Placement.Rotation.multVec(ai)
|
|
else:
|
|
real_center = center
|
|
real_axis = axis
|
|
|
|
if copy:
|
|
newobj = makeCopy(obj)
|
|
else:
|
|
newobj = obj
|
|
if 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 getType(obj) == "Point":
|
|
v = Vector(obj.X,obj.Y,obj.Z)
|
|
rv = v.sub(real_center)
|
|
rv = DraftVecUtils.rotate(rv,math.radians(angle),real_axis)
|
|
v = real_center.add(rv)
|
|
newobj.X = v.x
|
|
newobj.Y = v.y
|
|
newobj.Z = v.z
|
|
elif obj.isDerivedFrom("App::DocumentObjectGroup"):
|
|
pass
|
|
elif hasattr(obj,"Placement"):
|
|
#FreeCAD.Console.PrintMessage("placement rotation\n")
|
|
shape = Part.Shape()
|
|
shape.Placement = obj.Placement
|
|
shape.rotate(DraftVecUtils.tup(real_center), DraftVecUtils.tup(real_axis), angle)
|
|
newobj.Placement = shape.Placement
|
|
elif hasattr(obj,'Shape') and (getType(obj) not in ["WorkingPlaneProxy","BuildingPart"]):
|
|
#think it make more sense to try first to rotate placement and later to try with shape. no?
|
|
shape = obj.Shape.copy()
|
|
shape.rotate(DraftVecUtils.tup(real_center), DraftVecUtils.tup(real_axis), angle)
|
|
newobj.Shape = shape
|
|
if copy:
|
|
formatObject(newobj,obj)
|
|
if newobj is not None:
|
|
newobjlist.append(newobj)
|
|
if copy:
|
|
for p in obj.InList:
|
|
if p.isDerivedFrom("App::DocumentObjectGroup") and (p in objectslist):
|
|
g = newgroups.setdefault(p.Name,FreeCAD.ActiveDocument.addObject(p.TypeId,p.Name))
|
|
g.addObject(newobj)
|
|
break
|
|
if copy and getParam("selectBaseObjects",False):
|
|
select(objectslist)
|
|
else:
|
|
select(newobjlist)
|
|
if len(newobjlist) == 1: return newobjlist[0]
|
|
return newobjlist
|
|
|
|
def scaleVectorFromCenter(vector, scale, center):
|
|
return vector.sub(center).scale(scale.x, scale.y, scale.z).add(center)
|
|
|
|
def scaleVertex(object, vertex_index, scale, center):
|
|
points = object.Points
|
|
points[vertex_index] = object.Placement.inverse().multVec(
|
|
scaleVectorFromCenter(
|
|
object.Placement.multVec(points[vertex_index]),
|
|
scale, center))
|
|
object.Points = points
|
|
|
|
def scaleEdge(object, edge_index, scale, center):
|
|
scaleVertex(object, edge_index, scale, center)
|
|
if isClosedEdge(edge_index, object):
|
|
scaleVertex(object, 0, scale, center)
|
|
else:
|
|
scaleVertex(object, edge_index+1, scale, center)
|
|
|
|
def copyScaledEdges(arguments):
|
|
copied_edges = []
|
|
for argument in arguments:
|
|
copied_edges.append(copyScaledEdge(argument[0], argument[1],
|
|
argument[2], argument[3]))
|
|
joinWires(copied_edges)
|
|
|
|
def copyScaledEdge(object, edge_index, scale, center):
|
|
vertex1 = scaleVectorFromCenter(
|
|
object.Placement.multVec(object.Points[edge_index]),
|
|
scale, center)
|
|
if isClosedEdge(edge_index, object):
|
|
vertex2 = scaleVectorFromCenter(
|
|
object.Placement.multVec(object.Points[0]),
|
|
scale, center)
|
|
else:
|
|
vertex2 = scaleVectorFromCenter(
|
|
object.Placement.multVec(object.Points[edge_index+1]),
|
|
scale, center)
|
|
return makeLine(vertex1, vertex2)
|
|
|
|
def scale(objectslist,scale=Vector(1,1,1),center=Vector(0,0,0),copy=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 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]
|
|
newobjlist = []
|
|
for obj in objectslist:
|
|
if copy:
|
|
newobj = makeCopy(obj)
|
|
else:
|
|
newobj = obj
|
|
if hasattr(obj,'Shape'):
|
|
scaled_shape = obj.Shape.copy()
|
|
m = FreeCAD.Matrix()
|
|
m.move(obj.Placement.Base.negative())
|
|
m.move(center.negative())
|
|
m.scale(scale.x,scale.y,scale.z)
|
|
m.move(center)
|
|
m.move(obj.Placement.Base)
|
|
scaled_shape = scaled_shape.transformGeometry(m)
|
|
if getType(obj) == "Rectangle":
|
|
p = []
|
|
for v in scaled_shape.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" or getType(obj) == "BSpline":
|
|
for index, point in enumerate(newobj.Points):
|
|
scaleVertex(newobj, index, scale, center)
|
|
elif hasattr(obj,'Shape'):
|
|
newobj.Shape = scaled_shape
|
|
elif (obj.TypeId == "App::Annotation"):
|
|
factor = scale.y * obj.ViewObject.FontSize
|
|
newobj.ViewObject.FontSize = factor
|
|
d = obj.Position.sub(center)
|
|
newobj.Position = center.add(Vector(d.x*scale.x,d.y*scale.y,d.z*scale.z))
|
|
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 offset(obj,delta,copy=False,bind=False,sym=False,occ=False):
|
|
"""offset(object,delta,[copymode],[bind]): offsets the given wire by
|
|
applying the given delta Vector to its first vertex. If copymode is
|
|
True, another object is created, otherwise the same object gets
|
|
offset. If bind is True, and provided the wire is open, the original
|
|
and the offset 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. If offsetting a
|
|
BSpline, the delta must not be a Vector but a list of Vectors, one for
|
|
each node of the spline."""
|
|
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,height,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)
|
|
|
|
newwire = None
|
|
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
|
|
if s1 and s2:
|
|
w1 = s1.Edges
|
|
w2 = s2.Edges
|
|
w3 = Part.LineSegment(s1.Vertexes[0].Point,s2.Vertexes[0].Point).toShape()
|
|
w4 = Part.LineSegment(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:
|
|
print("Draft.offset: Unable to bind wires")
|
|
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":
|
|
if p:
|
|
newobj = makeWire(p)
|
|
newobj.Closed = obj.Closed
|
|
elif newwire:
|
|
newobj = FreeCAD.ActiveDocument.addObject("Part::Feature","Offset")
|
|
newobj.Shape = newwire
|
|
else:
|
|
print("Draft.offset: Unable to duplicate this object")
|
|
elif getType(obj) == "Rectangle":
|
|
if p:
|
|
length,height,plac = getRect(p,obj)
|
|
newobj = makeRectangle(length,height,plac)
|
|
elif newwire:
|
|
newobj = FreeCAD.ActiveDocument.addObject("Part::Feature","Offset")
|
|
newobj.Shape = newwire
|
|
else:
|
|
print("Draft.offset: Unable to duplicate this object")
|
|
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("Draft.offset: 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 hasattr(obj,'Shape'):
|
|
for cluster in Part.getSortedClusters(obj.Shape.Edges):
|
|
w = Part.Wire(cluster)
|
|
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!
|
|
if FreeCAD.GuiUp:
|
|
nobj.ViewObject.DisplayMode = "Flat Lines"
|
|
if delete:
|
|
FreeCAD.ActiveDocument.removeObject(obj.Name)
|
|
|
|
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 obj.isDerivedFrom("Drawing::View") or obj.isDerivedFrom("TechDraw::DrawView"):
|
|
if obj.Source.isDerivedFrom("App::DocumentObjectGroup"):
|
|
for o in obj.Source.Group:
|
|
result += getDXF(o,obj.Direction)
|
|
else:
|
|
result += getDXF(obj.Source,obj.Direction)
|
|
return 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) in ["Dimension","LinearDimension"]:
|
|
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 hasattr(obj,'Shape'):
|
|
# TODO do this the Draft way, for ex. using polylines and rectangles
|
|
import Drawing
|
|
if not direction:
|
|
direction = FreeCAD.Vector(0,0,-1)
|
|
if DraftVecUtils.isNull(direction):
|
|
direction = FreeCAD.Vector(0,0,-1)
|
|
try:
|
|
d = Drawing.projectToDXF(obj.Shape,direction)
|
|
except:
|
|
print("Draft.getDXF: Unable to project ",obj.Label," to ",direction)
|
|
else:
|
|
result += d
|
|
|
|
else:
|
|
print("Draft.getDXF: Unsupported object: ",obj.Label)
|
|
|
|
return result
|
|
|
|
|
|
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
|
|
|
|
|
|
import getSVG as svg
|
|
|
|
|
|
getSVG = svg.getSVG
|
|
|
|
|
|
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 not FreeCAD.ActiveDocument:
|
|
FreeCAD.Console.PrintError("No active document. Aborting\n")
|
|
return
|
|
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.
|
|
"""
|
|
if not FreeCAD.ActiveDocument:
|
|
FreeCAD.Console.PrintError("No active document. Aborting\n")
|
|
return
|
|
obj = FreeCAD.ActiveDocument.addObject("Part::Part2DObjectPython","Shape2DView")
|
|
_Shape2DView(obj)
|
|
if gui:
|
|
_ViewProviderDraftAlt(obj.ViewObject)
|
|
obj.Base = baseobj
|
|
if projectionVector:
|
|
obj.Projection = projectionVector
|
|
if facenumbers:
|
|
obj.FaceNumbers = facenumbers
|
|
select(obj)
|
|
|
|
return obj
|
|
|
|
def makeSketch(objectslist,autoconstraints=False,addTo=None,
|
|
delete=False,name="Sketch",radiusPrecision=-1):
|
|
"""makeSketch(objectslist,[autoconstraints],[addTo],[delete],[name],[radiusPrecision]):
|
|
|
|
Makes a Sketch objectslist with the given Draft objects.
|
|
|
|
* objectlist: can be single or list of objects of Draft type objects,
|
|
Part::Feature, Part.Shape, or mix of them.
|
|
|
|
* autoconstraints(False): if True, constraints will be automatically added to
|
|
wire nodes, rectangles and circles.
|
|
|
|
* addTo(None) : if set to an existing sketch, geometry will be added to it
|
|
instead of creating a new one.
|
|
|
|
* delete(False): if True, the original object will be deleted.
|
|
If set to a string 'all' the object and all its linked object will be
|
|
deleted
|
|
|
|
* name('Sketch'): the name for the new sketch object
|
|
|
|
* radiusPrecision(-1): If <0, disable radius constraint. If =0, add indiviaul
|
|
radius constraint. If >0, the radius will be rounded according to this
|
|
precision, and 'Equal' constraint will be added to curve with equal
|
|
radius within precision."""
|
|
|
|
if not FreeCAD.ActiveDocument:
|
|
FreeCAD.Console.PrintError("No active document. Aborting\n")
|
|
return
|
|
|
|
import Part, DraftGeomUtils
|
|
from Sketcher import Constraint
|
|
import Sketcher
|
|
import math
|
|
|
|
StartPoint = 1
|
|
EndPoint = 2
|
|
MiddlePoint = 3
|
|
deletable = None
|
|
|
|
if not isinstance(objectslist,(list,tuple)):
|
|
objectslist = [objectslist]
|
|
for obj in objectslist:
|
|
if isinstance(obj,Part.Shape):
|
|
shape = obj
|
|
elif not hasattr(obj,'Shape'):
|
|
FreeCAD.Console.PrintError(translate("draft","not shape found"))
|
|
return None
|
|
else:
|
|
shape = obj.Shape
|
|
if not DraftGeomUtils.isPlanar(shape):
|
|
FreeCAD.Console.PrintError(translate("draft","All Shapes must be co-planar"))
|
|
return None
|
|
if addTo:
|
|
nobj = addTo
|
|
else:
|
|
nobj = FreeCAD.ActiveDocument.addObject("Sketcher::SketchObject", name)
|
|
deletable = nobj
|
|
if FreeCAD.GuiUp:
|
|
nobj.ViewObject.Autoconstraints = False
|
|
|
|
# Collect constraints and add in one go to improve performance
|
|
constraints = []
|
|
radiuses = {}
|
|
|
|
def addRadiusConstraint(edge):
|
|
try:
|
|
if radiusPrecision<0:
|
|
return
|
|
if radiusPrecision==0:
|
|
constraints.append(Constraint('Radius',
|
|
nobj.GeometryCount-1, edge.Curve.Radius))
|
|
return
|
|
r = round(edge.Curve.Radius,radiusPrecision)
|
|
constraints.append(Constraint('Equal',
|
|
radiuses[r],nobj.GeometryCount-1))
|
|
except KeyError:
|
|
radiuses[r] = nobj.GeometryCount-1
|
|
constraints.append(Constraint('Radius',nobj.GeometryCount-1, r))
|
|
except AttributeError:
|
|
pass
|
|
|
|
def convertBezier(edge):
|
|
if DraftGeomUtils.geomType(edge) == "BezierCurve":
|
|
return(edge.Curve.toBSpline(edge.FirstParameter,edge.LastParameter).toShape())
|
|
else:
|
|
return(edge)
|
|
|
|
rotation = None
|
|
for obj in objectslist:
|
|
ok = False
|
|
tp = getType(obj)
|
|
if tp in ["Circle","Ellipse"]:
|
|
if obj.Shape.Edges:
|
|
if rotation is None:
|
|
rotation = obj.Placement.Rotation
|
|
edge = obj.Shape.Edges[0]
|
|
if len(edge.Vertexes) == 1:
|
|
newEdge = DraftGeomUtils.orientEdge(edge)
|
|
nobj.addGeometry(newEdge)
|
|
else:
|
|
# make new ArcOfCircle
|
|
circle = DraftGeomUtils.orientEdge(edge)
|
|
angle = edge.Placement.Rotation.Angle
|
|
axis = edge.Placement.Rotation.Axis
|
|
circle.Center = DraftVecUtils.rotate(edge.Curve.Center, -angle, axis)
|
|
first = math.radians(obj.FirstAngle)
|
|
last = math.radians(obj.LastAngle)
|
|
arc = Part.ArcOfCircle(circle, first, last)
|
|
nobj.addGeometry(arc)
|
|
addRadiusConstraint(edge)
|
|
ok = True
|
|
elif tp == "Rectangle":
|
|
if rotation is None:
|
|
rotation = obj.Placement.Rotation
|
|
if obj.FilletRadius.Value == 0:
|
|
for edge in obj.Shape.Edges:
|
|
nobj.addGeometry(DraftGeomUtils.orientEdge(edge))
|
|
if autoconstraints:
|
|
last = nobj.GeometryCount - 1
|
|
segs = [last-3,last-2,last-1,last]
|
|
if obj.Placement.Rotation.Q == (0,0,0,1):
|
|
constraints.append(Constraint("Coincident",last-3,EndPoint,last-2,StartPoint))
|
|
constraints.append(Constraint("Coincident",last-2,EndPoint,last-1,StartPoint))
|
|
constraints.append(Constraint("Coincident",last-1,EndPoint,last,StartPoint))
|
|
constraints.append(Constraint("Coincident",last,EndPoint,last-3,StartPoint))
|
|
constraints.append(Constraint("Horizontal",last-3))
|
|
constraints.append(Constraint("Vertical",last-2))
|
|
constraints.append(Constraint("Horizontal",last-1))
|
|
constraints.append(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
|
|
|
|
if obj.Shape.Edges:
|
|
if (len(obj.Shape.Vertexes) < 3):
|
|
e = obj.Shape.Edges[0]
|
|
nobj.addGeometry(Part.LineSegment(e.Curve,e.FirstParameter,e.LastParameter))
|
|
else:
|
|
# Use the first three points to make a working plane. We've already
|
|
# checked to make sure everything is coplanar
|
|
plane = Part.Plane(*[i.Point for i in obj.Shape.Vertexes[:3]])
|
|
normal = plane.Axis
|
|
if rotation is None:
|
|
axis = FreeCAD.Vector(0,0,1).cross(normal)
|
|
angle = DraftVecUtils.angle(normal, FreeCAD.Vector(0,0,1)) * FreeCAD.Units.Radian
|
|
rotation = FreeCAD.Rotation(axis, angle)
|
|
for edge in obj.Shape.Edges:
|
|
# edge.rotate(FreeCAD.Vector(0,0,0), rotAxis, rotAngle)
|
|
edge = DraftGeomUtils.orientEdge(edge, normal)
|
|
nobj.addGeometry(edge)
|
|
if autoconstraints:
|
|
last = nobj.GeometryCount
|
|
segs = list(range(last-len(obj.Shape.Edges),last-1))
|
|
for seg in segs:
|
|
constraints.append(Constraint("Coincident",seg,EndPoint,seg+1,StartPoint))
|
|
if DraftGeomUtils.isAligned(nobj.Geometry[seg],"x"):
|
|
constraints.append(Constraint("Vertical",seg))
|
|
elif DraftGeomUtils.isAligned(nobj.Geometry[seg],"y"):
|
|
constraints.append(Constraint("Horizontal",seg))
|
|
if closed:
|
|
constraints.append(Constraint("Coincident",last-1,EndPoint,segs[0],StartPoint))
|
|
ok = True
|
|
elif tp == "BSpline":
|
|
if obj.Shape.Edges:
|
|
nobj.addGeometry(obj.Shape.Edges[0].Curve)
|
|
nobj.exposeInternalGeometry(nobj.GeometryCount-1)
|
|
ok = True
|
|
elif tp == "BezCurve":
|
|
if obj.Shape.Edges:
|
|
bez = obj.Shape.Edges[0].Curve
|
|
bsp = bez.toBSpline(bez.FirstParameter,bez.LastParameter)
|
|
nobj.addGeometry(bsp)
|
|
nobj.exposeInternalGeometry(nobj.GeometryCount-1)
|
|
ok = True
|
|
elif tp == 'Shape' or hasattr(obj,'Shape'):
|
|
shape = obj if tp == 'Shape' else obj.Shape
|
|
|
|
if not DraftGeomUtils.isPlanar(shape):
|
|
FreeCAD.Console.PrintError(translate("draft","The given object is not planar and cannot be converted into a sketch."))
|
|
return None
|
|
if rotation is None:
|
|
#rotation = obj.Placement.Rotation
|
|
norm = DraftGeomUtils.getNormal(shape)
|
|
if norm:
|
|
rotation = FreeCAD.Rotation(FreeCAD.Vector(0,0,1),norm)
|
|
else:
|
|
FreeCAD.Console.PrintWarning(translate("draft","Unable to guess the normal direction of this object"))
|
|
rotation = FreeCAD.Rotation()
|
|
norm = obj.Placement.Rotation.Axis
|
|
if not shape.Wires:
|
|
for e in shape.Edges:
|
|
# unconnected edges
|
|
newedge = convertBezier(e)
|
|
nobj.addGeometry(DraftGeomUtils.orientEdge(newedge,norm,make_arc=True))
|
|
addRadiusConstraint(newedge)
|
|
|
|
# if not addTo:
|
|
# nobj.Placement.Rotation = DraftGeomUtils.calculatePlacement(shape).Rotation
|
|
|
|
if autoconstraints:
|
|
for wire in shape.Wires:
|
|
last_count = nobj.GeometryCount
|
|
edges = wire.OrderedEdges
|
|
for edge in edges:
|
|
newedge = convertBezier(edge)
|
|
nobj.addGeometry(DraftGeomUtils.orientEdge(
|
|
newedge,norm,make_arc=True))
|
|
addRadiusConstraint(newedge)
|
|
for i,g in enumerate(nobj.Geometry[last_count:]):
|
|
if edges[i].Closed:
|
|
continue
|
|
seg = last_count+i
|
|
|
|
if DraftGeomUtils.isAligned(g,"x"):
|
|
constraints.append(Constraint("Vertical",seg))
|
|
elif DraftGeomUtils.isAligned(g,"y"):
|
|
constraints.append(Constraint("Horizontal",seg))
|
|
|
|
if seg == nobj.GeometryCount-1:
|
|
if not wire.isClosed():
|
|
break
|
|
g2 = nobj.Geometry[last_count]
|
|
seg2 = last_count
|
|
else:
|
|
seg2 = seg+1
|
|
g2 = nobj.Geometry[seg2]
|
|
|
|
end1 = g.value(g.LastParameter)
|
|
start2 = g2.value(g2.FirstParameter)
|
|
if DraftVecUtils.equals(end1,start2) :
|
|
constraints.append(Constraint(
|
|
"Coincident",seg,EndPoint,seg2,StartPoint))
|
|
continue
|
|
end2 = g2.value(g2.LastParameter)
|
|
start1 = g.value(g.FirstParameter)
|
|
if DraftVecUtils.equals(end2,start1):
|
|
constraints.append(Constraint(
|
|
"Coincident",seg,StartPoint,seg2,EndPoint))
|
|
elif DraftVecUtils.equals(start1,start2):
|
|
constraints.append(Constraint(
|
|
"Coincident",seg,StartPoint,seg2,StartPoint))
|
|
elif DraftVecUtils.equals(end1,end2):
|
|
constraints.append(Constraint(
|
|
"Coincident",seg,EndPoint,seg2,EndPoint))
|
|
else:
|
|
for wire in shape.Wires:
|
|
for edge in wire.OrderedEdges:
|
|
newedge = convertBezier(edge)
|
|
nobj.addGeometry(DraftGeomUtils.orientEdge(
|
|
newedge,norm,make_arc=True))
|
|
ok = True
|
|
formatObject(nobj,obj)
|
|
if ok and delete and hasattr(obj,'Shape'):
|
|
doc = obj.Document
|
|
def delObj(obj):
|
|
if obj.InList:
|
|
FreeCAD.Console.PrintWarning(translate("draft",
|
|
"Cannot delete object {} with dependency".format(obj.Label))+"\n")
|
|
else:
|
|
doc.removeObject(obj.Name)
|
|
try:
|
|
if delete == 'all':
|
|
objs = [obj]
|
|
while objs:
|
|
obj = objs[0]
|
|
objs = objs[1:] + obj.OutList
|
|
delObj(obj)
|
|
else:
|
|
delObj(obj)
|
|
except Exception as ex:
|
|
FreeCAD.Console.PrintWarning(translate("draft",
|
|
"Failed to delete object {}: {}".format(obj.Label,ex))+"\n")
|
|
if rotation:
|
|
nobj.Placement.Rotation = rotation
|
|
else:
|
|
print("-----error!!! rotation is still None...")
|
|
nobj.addConstraint(constraints)
|
|
|
|
return nobj
|
|
|
|
def makeShapeString(String,FontFile,Size = 100,Tracking = 0):
|
|
"""ShapeString(Text,FontFile,Height,Track): Turns a text string
|
|
into a Compound Shape"""
|
|
if not FreeCAD.ActiveDocument:
|
|
FreeCAD.Console.PrintError("No active document. Aborting\n")
|
|
return
|
|
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)
|
|
obj.recompute()
|
|
return obj
|
|
|
|
|
|
def mirror(objlist, p1, p2):
|
|
"""mirror(objlist, p1, p2)
|
|
creates a Part::Mirror of the given object(s), along a plane defined
|
|
by the 2 given points and the draft working plane normal.
|
|
"""
|
|
|
|
if not objlist:
|
|
_err = "No object given"
|
|
FreeCAD.Console.PrintError(translate("draft", _err) + "\n")
|
|
return
|
|
if p1 == p2:
|
|
_err = "The two points are coincident"
|
|
FreeCAD.Console.PrintError(translate("draft", _err) + "\n")
|
|
return
|
|
if not isinstance(objlist,list):
|
|
objlist = [objlist]
|
|
|
|
if hasattr(FreeCAD, "DraftWorkingPlane"):
|
|
norm = FreeCAD.DraftWorkingPlane.getNormal()
|
|
elif gui:
|
|
norm = FreeCADGui.ActiveDocument.ActiveView.getViewDirection().negative()
|
|
else:
|
|
norm = FreeCAD.Vector(0,0,1)
|
|
|
|
pnorm = p2.sub(p1).cross(norm).normalize()
|
|
|
|
result = []
|
|
|
|
for obj in objlist:
|
|
mir = FreeCAD.ActiveDocument.addObject("Part::Mirroring","mirror")
|
|
mir.Label = "Mirror of " + obj.Label
|
|
mir.Source = obj
|
|
mir.Base = p1
|
|
mir.Normal = pnorm
|
|
formatObject(mir, obj)
|
|
result.append(mir)
|
|
|
|
if len(result) == 1:
|
|
result = result[0]
|
|
select(result)
|
|
|
|
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 FreeCAD.ActiveDocument:
|
|
FreeCAD.Console.PrintError("No active document. Aborting\n")
|
|
return
|
|
if not isinstance(selectionset,list):
|
|
selectionset = [selectionset]
|
|
fb = FreeCAD.ActiveDocument.addObject("Part::FeaturePython",name)
|
|
_Facebinder(fb)
|
|
if gui:
|
|
_ViewProviderFacebinder(fb.ViewObject)
|
|
faces = []
|
|
fb.Proxy.addSubobjects(fb,selectionset)
|
|
select(fb)
|
|
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 dictionary containing two lists, a list of new objects and a list
|
|
of objects to be deleted"""
|
|
|
|
import Part, DraftGeomUtils
|
|
|
|
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 newobj
|
|
|
|
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.LineSegment(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"""
|
|
params = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft")
|
|
preserveFaceColor = params.GetBool("preserveFaceColor") # True
|
|
preserveFaceNames = params.GetBool("preserveFaceNames") # True
|
|
faces = []
|
|
facecolors = [[], []] if (preserveFaceColor) else None
|
|
for obj in objectslist:
|
|
faces.extend(obj.Shape.Faces)
|
|
if (preserveFaceColor):
|
|
""" at this point, obj.Shape.Faces are not in same order as the
|
|
original faces we might have gotten as a result of downgrade, nor do they
|
|
have the same hashCode(); but they still keep reference to their original
|
|
colors - capture that in facecolors.
|
|
Also, cannot w/ .ShapeColor here, need a whole array matching the colors
|
|
of the array of faces per object, only DiffuseColor has that """
|
|
facecolors[0].extend(obj.ViewObject.DiffuseColor)
|
|
facecolors[1] = faces
|
|
sh = Part.makeShell(faces)
|
|
if sh:
|
|
if sh.Faces:
|
|
newobj = FreeCAD.ActiveDocument.addObject("Part::Feature","Shell")
|
|
newobj.Shape = sh
|
|
if (preserveFaceNames):
|
|
import re
|
|
firstName = objectslist[0].Label
|
|
nameNoTrailNumbers = re.sub("\d+$", "", firstName)
|
|
newobj.Label = "{} {}".format(newobj.Label, nameNoTrailNumbers)
|
|
if (preserveFaceColor):
|
|
""" At this point, sh.Faces are completely new, with different hashCodes
|
|
and different ordering from obj.Shape.Faces; since we cannot compare
|
|
via hashCode(), we have to iterate and use a different criteria to find
|
|
the original matching color """
|
|
colarray = []
|
|
for ind, face in enumerate(newobj.Shape.Faces):
|
|
for fcind, fcface in enumerate(facecolors[1]):
|
|
if ((face.Area == fcface.Area) and (face.CenterOfMass == fcface.CenterOfMass)):
|
|
colarray.append(facecolors[0][fcind])
|
|
break
|
|
newobj.ViewObject.DiffuseColor = colarray;
|
|
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 become 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 hasattr(ob,'Shape'):
|
|
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:
|
|
FreeCAD.Console.PrintMessage(translate("Draft","Upgrade: Unknown 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:
|
|
FreeCAD.Console.PrintMessage(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:
|
|
FreeCAD.Console.PrintMessage(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:
|
|
FreeCAD.Console.PrintMessage(translate("draft", "Found 1 solidifiable 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:
|
|
FreeCAD.Console.PrintMessage(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:
|
|
FreeCAD.Console.PrintMessage(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:
|
|
FreeCAD.Console.PrintMessage(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:
|
|
FreeCAD.Console.PrintMessage(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:
|
|
FreeCAD.Console.PrintMessage(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.LineSegment,Part.Line)):
|
|
result = turnToLine(objects[0])
|
|
if result:
|
|
FreeCAD.Console.PrintMessage(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:
|
|
FreeCAD.Console.PrintMessage(translate("draft", "Found 1 closed sketch object: creating a face from it")+"\n")
|
|
|
|
# only closed wires
|
|
else:
|
|
result = makeFaces(objects)
|
|
if result:
|
|
FreeCAD.Console.PrintMessage(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:
|
|
FreeCAD.Console.PrintMessage(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:
|
|
FreeCAD.Console.PrintMessage(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:
|
|
FreeCAD.Console.PrintMessage(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:
|
|
FreeCAD.Console.PrintMessage(translate("draft", "Found several non-treatable objects: creating compound")+"\n")
|
|
|
|
# no result has been obtained
|
|
if not result:
|
|
FreeCAD.Console.PrintMessage(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)
|
|
select(addList)
|
|
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 dictionary containing two lists, a list of new objects and a list
|
|
of objects to be deleted"""
|
|
|
|
import Part, DraftGeomUtils
|
|
|
|
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
|
|
params = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft")
|
|
preserveFaceColor = params.GetBool("preserveFaceColor") # True
|
|
preserveFaceNames = params.GetBool("preserveFaceNames") # True
|
|
for o in objects:
|
|
voDColors = o.ViewObject.DiffuseColor if (preserveFaceColor and hasattr(o,'ViewObject')) else None
|
|
oLabel = o.Label if hasattr(o,'Label') else ""
|
|
if o.Shape.Faces:
|
|
for ind, f in enumerate(o.Shape.Faces):
|
|
newobj = FreeCAD.ActiveDocument.addObject("Part::Feature","Face")
|
|
newobj.Shape = f
|
|
if preserveFaceNames:
|
|
newobj.Label = "{} {}".format(oLabel, newobj.Label)
|
|
if preserveFaceColor:
|
|
""" At this point, some single-color objects might have
|
|
just a single entry in voDColors for all their faces; handle that"""
|
|
tcolor = voDColors[ind] if ind<len(voDColors) else voDColors[0]
|
|
newobj.ViewObject.DiffuseColor[0] = tcolor # does is not applied visually on its own; left just in case
|
|
newobj.ViewObject.ShapeColor = tcolor # this gets applied, works by itself too
|
|
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 hasattr(o, 'Shape'):
|
|
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:
|
|
FreeCAD.Console.PrintMessage(translate("Draft","Upgrade: Unknown 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:
|
|
FreeCAD.Console.PrintMessage(translate("draft", "Found 1 block: exploding it")+"\n")
|
|
|
|
# we have one multi-solids compound object: extract its solids
|
|
elif (len(objects) == 1) and hasattr(objects[0],'Shape') and (len(solids) > 1):
|
|
result = splitCompounds(objects)
|
|
#print(result)
|
|
if result:
|
|
FreeCAD.Console.PrintMessage(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 hasattr(objects[0],'Shape') and hasattr(objects[0], 'Base'):
|
|
result = shapify(objects[0])
|
|
if result:
|
|
FreeCAD.Console.PrintMessage(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:
|
|
FreeCAD.Console.PrintMessage(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:
|
|
FreeCAD.Console.PrintMessage(translate("draft", "Found several faces: splitting them")+"\n")
|
|
|
|
# several objects: remove all the faces from the first one
|
|
else:
|
|
result = subtr(objects)
|
|
if result:
|
|
FreeCAD.Console.PrintMessage(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:
|
|
FreeCAD.Console.PrintMessage(translate("draft", "Found 1 face: extracting its wires")+"\n")
|
|
|
|
# no faces: split wire into single edges
|
|
elif not onlyedges:
|
|
result = splitWires(objects)
|
|
if result:
|
|
FreeCAD.Console.PrintMessage(translate("draft", "Found only wires: extracting their edges")+"\n")
|
|
|
|
# no result has been obtained
|
|
if not result:
|
|
FreeCAD.Console.PrintMessage(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)
|
|
select(addList)
|
|
return [addList,deleteList]
|
|
|
|
|
|
def getParameterFromV0(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 calculatePlacement(globalRotation, edge, offset, RefPt, xlate, align, normal=None):
|
|
"""Orient shape to tangent at parm offset along edge."""
|
|
import functools
|
|
# http://en.wikipedia.org/wiki/Euler_angles
|
|
# start with null Placement point so translate goes to right place.
|
|
placement = FreeCAD.Placement()
|
|
# preserve global orientation
|
|
placement.Rotation = globalRotation
|
|
|
|
placement.move(RefPt + xlate)
|
|
|
|
if not align:
|
|
return placement
|
|
|
|
# unit +Z Probably defined elsewhere?
|
|
z = FreeCAD.Vector(0, 0, 1)
|
|
# y = FreeCAD.Vector(0, 1, 0) # unit +Y
|
|
x = FreeCAD.Vector(1, 0, 0) # unit +X
|
|
nullv = FreeCAD.Vector(0, 0, 0)
|
|
|
|
# get local coord system - tangent, normal, binormal, if possible
|
|
t = edge.tangentAt(getParameterFromV0(edge, offset))
|
|
t.normalize()
|
|
|
|
try:
|
|
if normal:
|
|
n = normal
|
|
else:
|
|
n = edge.normalAt(getParameterFromV0(edge, offset))
|
|
n.normalize()
|
|
b = (t.cross(n))
|
|
b.normalize()
|
|
# no normal defined here
|
|
except FreeCAD.Base.FreeCADError:
|
|
n = nullv
|
|
b = nullv
|
|
FreeCAD.Console.PrintLog(
|
|
"Draft PathArray.orientShape - Cannot calculate Path normal.\n")
|
|
|
|
lnodes = z.cross(b)
|
|
|
|
try:
|
|
# Can't normalize null vector.
|
|
lnodes.normalize()
|
|
except:
|
|
# pathological cases:
|
|
pass
|
|
# 1) can't determine normal, don't align.
|
|
if n == nullv:
|
|
psi = 0.0
|
|
theta = 0.0
|
|
phi = 0.0
|
|
FreeCAD.Console.PrintWarning(
|
|
"Draft PathArray.orientShape - Path normal is Null. Cannot align.\n")
|
|
elif abs(b.dot(z)) == 1.0: # 2) binormal is || z
|
|
# align shape to tangent only
|
|
psi = math.degrees(DraftVecUtils.angle(x, t, z))
|
|
theta = 0.0
|
|
phi = 0.0
|
|
FreeCAD.Console.PrintWarning(
|
|
"Draft PathArray.orientShape - Gimbal lock. Infinite lnodes. Change Path or Base.\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))
|
|
|
|
rotations = [placement.Rotation]
|
|
|
|
if psi != 0.0:
|
|
rotations.insert(0, FreeCAD.Rotation(z, psi))
|
|
if theta != 0.0:
|
|
rotations.insert(0, FreeCAD.Rotation(lnodes, theta))
|
|
if phi != 0.0:
|
|
rotations.insert(0, FreeCAD.Rotation(b, phi))
|
|
|
|
if len(rotations) == 1:
|
|
finalRotation = rotations[0]
|
|
else:
|
|
finalRotation = functools.reduce(
|
|
lambda rot1, rot2: rot1.multiply(rot2), rotations)
|
|
|
|
placement.Rotation = finalRotation
|
|
|
|
return placement
|
|
|
|
|
|
def calculatePlacementsOnPath(shapeRotation, pathwire, count, xlate, align):
|
|
"""Calculates the placements of a shape along a given path so that each copy will be distributed evenly"""
|
|
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)
|
|
|
|
placements = []
|
|
|
|
# place the start shape
|
|
pt = path[0].Vertexes[0].Point
|
|
placements.append(calculatePlacement(
|
|
shapeRotation, path[0], 0, pt, xlate, align, normal))
|
|
|
|
# closed path doesn't need shape on last vertex
|
|
if not(closedpath):
|
|
# place the end shape
|
|
pt = path[-1].Vertexes[-1].Point
|
|
placements.append(calculatePlacement(
|
|
shapeRotation, path[-1], path[-1].Length, pt, xlate, align, normal))
|
|
|
|
if count < 3:
|
|
return placements
|
|
|
|
# place the middle shapes
|
|
if closedpath:
|
|
stop = count
|
|
else:
|
|
stop = count - 1
|
|
step = float(cdist) / stop
|
|
remains = 0
|
|
travel = step
|
|
for i in range(1, stop):
|
|
# which edge in path should contain this shape?
|
|
# avoids problems with float math travel > ends[-1]
|
|
iend = len(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(getParameterFromV0(path[iend], offset))
|
|
|
|
placements.append(calculatePlacement(
|
|
shapeRotation, path[iend], offset, pt, xlate, align, normal))
|
|
|
|
travel += step
|
|
|
|
return placements
|
|
|
|
#---------------------------------------------------------------------------
|
|
# Python Features definitions
|
|
#---------------------------------------------------------------------------
|
|
import draftobjects.base
|
|
_DraftObject = draftobjects.base.DraftObject
|
|
|
|
class _ViewProviderDraftLink:
|
|
"a view provider for link type object"
|
|
|
|
def __init__(self,vobj):
|
|
self.Object = vobj.Object
|
|
vobj.Proxy = self
|
|
|
|
def attach(self,vobj):
|
|
self.Object = vobj.Object
|
|
|
|
def __getstate__(self):
|
|
return None
|
|
|
|
def __setstate__(self, state):
|
|
return None
|
|
|
|
def getIcon(self):
|
|
tp = self.Object.Proxy.Type
|
|
if tp == 'Array':
|
|
return ":/icons/Draft_LinkArray.svg"
|
|
elif tp == 'PathArray':
|
|
return ":/icons/Draft_PathLinkArray.svg"
|
|
|
|
def claimChildren(self):
|
|
obj = self.Object
|
|
if hasattr(obj,'ExpandArray'):
|
|
expand = obj.ExpandArray
|
|
else:
|
|
expand = obj.ShowElement
|
|
if not expand:
|
|
return [obj.Base]
|
|
else:
|
|
return obj.ElementList
|
|
|
|
|
|
class _DrawingView(_DraftObject):
|
|
"""The Draft DrawingView object"""
|
|
def __init__(self, obj):
|
|
_DraftObject.__init__(self,obj,"DrawingView")
|
|
obj.addProperty("App::PropertyVector","Direction","Shape View",QT_TRANSLATE_NOOP("App::Property","Projection direction"))
|
|
obj.addProperty("App::PropertyFloat","LineWidth","View Style",QT_TRANSLATE_NOOP("App::Property","The width of the lines inside this object"))
|
|
obj.addProperty("App::PropertyLength","FontSize","View Style",QT_TRANSLATE_NOOP("App::Property","The size of the texts inside this object"))
|
|
obj.addProperty("App::PropertyLength","LineSpacing","View Style",QT_TRANSLATE_NOOP("App::Property","The spacing between lines of text"))
|
|
obj.addProperty("App::PropertyColor","LineColor","View Style",QT_TRANSLATE_NOOP("App::Property","The color of the projected objects"))
|
|
obj.addProperty("App::PropertyLink","Source","Base",QT_TRANSLATE_NOOP("App::Property","The linked object"))
|
|
obj.addProperty("App::PropertyEnumeration","FillStyle","View Style",QT_TRANSLATE_NOOP("App::Property","Shape Fill Style"))
|
|
obj.addProperty("App::PropertyEnumeration","LineStyle","View Style",QT_TRANSLATE_NOOP("App::Property","Line Style"))
|
|
obj.addProperty("App::PropertyBool","AlwaysOn","View Style",QT_TRANSLATE_NOOP("App::Property","If checked, source objects are displayed regardless of being visible in the 3D model"))
|
|
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 hasattr(obj,"LineSpacing"):
|
|
lp = obj.LineSpacing
|
|
else:
|
|
lp = None
|
|
if obj.Source.isDerivedFrom("App::DocumentObjectGroup"):
|
|
svg = ""
|
|
shapes = []
|
|
others = []
|
|
objs = getGroupContents([obj.Source])
|
|
for o in objs:
|
|
v = o.ViewObject.isVisible()
|
|
if hasattr(obj,"AlwaysOn"):
|
|
if obj.AlwaysOn:
|
|
v = True
|
|
if v:
|
|
svg += getSVG(o,obj.Scale,obj.LineWidth,obj.FontSize.Value,obj.FillStyle,obj.Direction,ls,lc,lp)
|
|
else:
|
|
svg = getSVG(obj.Source,obj.Scale,obj.LineWidth,obj.FontSize.Value,obj.FillStyle,obj.Direction,ls,lc,lp)
|
|
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"
|
|
return getDXF(obj)
|
|
|
|
|
|
class _Block(_DraftObject):
|
|
"""The Block object"""
|
|
|
|
def __init__(self, obj):
|
|
_DraftObject.__init__(self,obj,"Block")
|
|
obj.addProperty("App::PropertyLinkList","Components","Draft",QT_TRANSLATE_NOOP("App::Property","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
|
|
obj.positionBySupport()
|
|
|
|
class _Shape2DView(_DraftObject):
|
|
"""The Shape2DView object"""
|
|
|
|
def __init__(self,obj):
|
|
obj.addProperty("App::PropertyLink","Base","Draft",QT_TRANSLATE_NOOP("App::Property","The base object this 2D view must represent"))
|
|
obj.addProperty("App::PropertyVector","Projection","Draft",QT_TRANSLATE_NOOP("App::Property","The projection vector of this object"))
|
|
obj.addProperty("App::PropertyEnumeration","ProjectionMode","Draft",QT_TRANSLATE_NOOP("App::Property","The way the viewed object must be projected"))
|
|
obj.addProperty("App::PropertyIntegerList","FaceNumbers","Draft",QT_TRANSLATE_NOOP("App::Property","The indices of the faces to be projected in Individual Faces mode"))
|
|
obj.addProperty("App::PropertyBool","HiddenLines","Draft",QT_TRANSLATE_NOOP("App::Property","Show hidden lines"))
|
|
obj.addProperty("App::PropertyBool","FuseArch","Draft",QT_TRANSLATE_NOOP("App::Property","Fuse wall and structure objects of same type and material"))
|
|
obj.addProperty("App::PropertyBool","Tessellation","Draft",QT_TRANSLATE_NOOP("App::Property","Tessellate Ellipses and B-splines into line segments"))
|
|
obj.addProperty("App::PropertyBool","InPlace","Draft",QT_TRANSLATE_NOOP("App::Property","For Cutlines and Cutfaces modes, this leaves the faces at the cut location"))
|
|
obj.addProperty("App::PropertyFloat","SegmentLength","Draft",QT_TRANSLATE_NOOP("App::Property","Length of line segments if tessellating Ellipses or B-splines into line segments"))
|
|
obj.addProperty("App::PropertyBool","VisibleOnly","Draft",QT_TRANSLATE_NOOP("App::Property","If this is True, this object will be recomputed only if it is visible"))
|
|
obj.Projection = Vector(0,0,1)
|
|
obj.ProjectionMode = ["Solid","Individual Faces","Cutlines","Cutfaces"]
|
|
obj.HiddenLines = False
|
|
obj.Tessellation = False
|
|
obj.VisibleOnly = False
|
|
obj.InPlace = True
|
|
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):
|
|
if hasattr(obj,"VisibleOnly"):
|
|
if obj.VisibleOnly:
|
|
if obj.ViewObject:
|
|
if obj.ViewObject.Visibility == False:
|
|
return False
|
|
import Part, DraftGeomUtils
|
|
obj.positionBySupport()
|
|
pl = obj.Placement
|
|
if obj.Base:
|
|
if getType(obj.Base) in ["BuildingPart","SectionPlane"]:
|
|
objs = []
|
|
if getType(obj.Base) == "SectionPlane":
|
|
objs = obj.Base.Objects
|
|
cutplane = obj.Base.Shape
|
|
else:
|
|
objs = obj.Base.Group
|
|
cutplane = Part.makePlane(1000,1000,FreeCAD.Vector(-500,-500,0))
|
|
m = 1
|
|
if obj.Base.ViewObject and hasattr(obj.Base.ViewObject,"CutMargin"):
|
|
m = obj.Base.ViewObject.CutMargin.Value
|
|
cutplane.translate(FreeCAD.Vector(0,0,m))
|
|
cutplane.Placement = cutplane.Placement.multiply(obj.Base.Placement)
|
|
if objs:
|
|
onlysolids = True
|
|
if hasattr(obj.Base,"OnlySolids"):
|
|
onlysolids = obj.Base.OnlySolids
|
|
import Arch, Part, Drawing
|
|
objs = getGroupContents(objs,walls=True)
|
|
objs = removeHidden(objs)
|
|
shapes = []
|
|
if hasattr(obj,"FuseArch") and obj.FuseArch:
|
|
shtypes = {}
|
|
for o in objs:
|
|
if getType(o) in ["Wall","Structure"]:
|
|
if onlysolids:
|
|
shtypes.setdefault(o.Material.Name if (hasattr(o,"Material") and o.Material) else "None",[]).extend(o.Shape.Solids)
|
|
else:
|
|
shtypes.setdefault(o.Material.Name if (hasattr(o,"Material") and o.Material) else "None",[]).append(o.Shape.copy())
|
|
elif hasattr(o,'Shape'):
|
|
if onlysolids:
|
|
shapes.extend(o.Shape.Solids)
|
|
else:
|
|
shapes.append(o.Shape.copy())
|
|
for k,v in shtypes.items():
|
|
v1 = v.pop()
|
|
if v:
|
|
v1 = v1.multiFuse(v)
|
|
v1 = v1.removeSplitter()
|
|
if v1.Solids:
|
|
shapes.extend(v1.Solids)
|
|
else:
|
|
print("Shape2DView: Fusing Arch objects produced non-solid results")
|
|
shapes.append(v1)
|
|
else:
|
|
for o in objs:
|
|
if hasattr(o,'Shape'):
|
|
if onlysolids:
|
|
shapes.extend(o.Shape.Solids)
|
|
else:
|
|
shapes.append(o.Shape.copy())
|
|
clip = False
|
|
if hasattr(obj.Base,"Clip"):
|
|
clip = obj.Base.Clip
|
|
cutp,cutv,iv = Arch.getCutVolume(cutplane,shapes,clip)
|
|
cuts = []
|
|
opl = FreeCAD.Placement(obj.Base.Placement)
|
|
proj = opl.Rotation.multVec(FreeCAD.Vector(0,0,1))
|
|
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)
|
|
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)
|
|
faces = []
|
|
if (obj.ProjectionMode == "Cutfaces") and (sh.ShapeType == "Solid"):
|
|
if hasattr(obj,"InPlace"):
|
|
if not obj.InPlace:
|
|
c = self.getProjected(obj,c,proj)
|
|
wires = DraftGeomUtils.findWires(c.Edges)
|
|
for w in wires:
|
|
if w.isClosed():
|
|
faces.append(Part.Face(w))
|
|
if faces:
|
|
cuts.extend(faces)
|
|
else:
|
|
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 hasattr(o,'Shape'):
|
|
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 hasattr(obj.Base,'Shape'):
|
|
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)
|
|
else:
|
|
FreeCAD.Console.PrintWarning(obj.ProjectionMode+" mode not implemented\n")
|
|
if not DraftGeomUtils.isNull(pl):
|
|
obj.Placement = pl
|
|
|
|
class _DraftLink(_DraftObject):
|
|
|
|
def __init__(self,obj,tp):
|
|
self.use_link = False if obj else True
|
|
_DraftObject.__init__(self,obj,tp)
|
|
if obj:
|
|
self.attach(obj)
|
|
|
|
def __getstate__(self):
|
|
return self.__dict__
|
|
|
|
def __setstate__(self,state):
|
|
if isinstance(state,dict):
|
|
self.__dict__ = state
|
|
else:
|
|
self.use_link = False
|
|
_DraftObject.__setstate__(self,state)
|
|
|
|
def attach(self,obj):
|
|
if self.use_link:
|
|
obj.addExtension('App::LinkExtensionPython', None)
|
|
self.linkSetup(obj)
|
|
|
|
def canLinkProperties(self,_obj):
|
|
return False
|
|
|
|
def linkSetup(self,obj):
|
|
obj.configLinkProperty('Placement',LinkedObject='Base')
|
|
if hasattr(obj,'ShowElement'):
|
|
# rename 'ShowElement' property to 'ExpandArray' to avoid conflict
|
|
# with native App::Link
|
|
obj.configLinkProperty('ShowElement')
|
|
showElement = obj.ShowElement
|
|
obj.addProperty("App::PropertyBool","ExpandArray","Draft",
|
|
QT_TRANSLATE_NOOP("App::Property","Show array element as children object"))
|
|
obj.ExpandArray = showElement
|
|
obj.configLinkProperty(ShowElement='ExpandArray')
|
|
obj.removeProperty('ShowElement')
|
|
else:
|
|
obj.configLinkProperty(ShowElement='ExpandArray')
|
|
if getattr(obj,'ExpandArray',False):
|
|
obj.setPropertyStatus('PlacementList','Immutable')
|
|
else:
|
|
obj.setPropertyStatus('PlacementList','-Immutable')
|
|
if not hasattr(obj,'LinkTransform'):
|
|
obj.addProperty('App::PropertyBool','LinkTransform',' Link')
|
|
if not hasattr(obj,'ColoredElements'):
|
|
obj.addProperty('App::PropertyLinkSubHidden','ColoredElements',' Link')
|
|
obj.setPropertyStatus('ColoredElements','Hidden')
|
|
obj.configLinkProperty('LinkTransform','ColoredElements')
|
|
|
|
def getViewProviderName(self,_obj):
|
|
if self.use_link:
|
|
return 'Gui::ViewProviderLinkPython'
|
|
return ''
|
|
|
|
def migrate_attributes(self, obj):
|
|
"""Migrate old attribute names to new names if they exist.
|
|
|
|
This is done to comply with Python guidelines or fix small issues
|
|
in older code.
|
|
"""
|
|
if hasattr(self, "useLink"):
|
|
# This is only needed for some models created in 0.19
|
|
# while it was in development. Afterwards,
|
|
# all models should use 'use_link' by default
|
|
# and this won't be run.
|
|
self.use_link = bool(self.useLink)
|
|
FreeCAD.Console.PrintWarning("Migrating 'useLink' to 'use_link', "
|
|
"{} ({})\n".format(obj.Label,
|
|
obj.TypeId))
|
|
del self.useLink
|
|
|
|
def onDocumentRestored(self, obj):
|
|
self.migrate_attributes(obj)
|
|
if self.use_link:
|
|
self.linkSetup(obj)
|
|
else:
|
|
obj.setPropertyStatus('Shape','-Transient')
|
|
if obj.Shape.isNull():
|
|
if getattr(obj,'PlacementList',None):
|
|
self.buildShape(obj,obj.Placement,obj.PlacementList)
|
|
else:
|
|
self.execute(obj)
|
|
|
|
def buildShape(self,obj,pl,pls):
|
|
import Part
|
|
import DraftGeomUtils
|
|
|
|
if self.use_link:
|
|
if not getattr(obj,'ExpandArray',True) or obj.Count != len(pls):
|
|
obj.setPropertyStatus('PlacementList','-Immutable')
|
|
obj.PlacementList = pls
|
|
obj.setPropertyStatus('PlacementList','Immutable')
|
|
obj.Count = len(pls)
|
|
|
|
if obj.Base:
|
|
shape = Part.getShape(obj.Base)
|
|
if shape.isNull():
|
|
raise RuntimeError("'{}' cannot build shape of '{}'\n".format(
|
|
obj.Name,obj.Base.Name))
|
|
else:
|
|
shape = shape.copy()
|
|
shape.Placement = FreeCAD.Placement()
|
|
base = []
|
|
for i,pla in enumerate(pls):
|
|
vis = getattr(obj,'VisibilityList',[])
|
|
if len(vis)>i and not vis[i]:
|
|
continue;
|
|
# 'I' is a prefix for disambiguation when mapping element names
|
|
base.append(shape.transformed(pla.toMatrix(),op='I{}'.format(i)))
|
|
if getattr(obj,'Fuse',False) and len(base) > 1:
|
|
obj.Shape = base[0].multiFuse(base[1:]).removeSplitter()
|
|
else:
|
|
obj.Shape = Part.makeCompound(base)
|
|
|
|
if not DraftGeomUtils.isNull(pl):
|
|
obj.Placement = pl
|
|
|
|
if self.use_link:
|
|
return False # return False to call LinkExtension::execute()
|
|
|
|
def onChanged(self, obj, prop):
|
|
if not getattr(self,'use_link',False):
|
|
return
|
|
if prop == 'Fuse':
|
|
if obj.Fuse:
|
|
obj.setPropertyStatus('Shape','-Transient')
|
|
else:
|
|
obj.setPropertyStatus('Shape','Transient')
|
|
elif prop == 'ExpandArray':
|
|
if hasattr(obj,'PlacementList'):
|
|
obj.setPropertyStatus('PlacementList',
|
|
'-Immutable' if obj.ExpandArray else 'Immutable')
|
|
|
|
|
|
class _Array(_DraftLink):
|
|
"The Draft Array object"
|
|
|
|
def __init__(self,obj):
|
|
_DraftLink.__init__(self,obj,"Array")
|
|
|
|
def attach(self, obj):
|
|
obj.addProperty("App::PropertyLink","Base","Draft",QT_TRANSLATE_NOOP("App::Property","The base object that must be duplicated"))
|
|
obj.addProperty("App::PropertyEnumeration","ArrayType","Draft",QT_TRANSLATE_NOOP("App::Property","The type of array to create"))
|
|
obj.addProperty("App::PropertyLinkGlobal","AxisReference","Draft",QT_TRANSLATE_NOOP("App::Property","The axis (e.g. DatumLine) overriding Axis/Center"))
|
|
obj.addProperty("App::PropertyVector","Axis","Draft",QT_TRANSLATE_NOOP("App::Property","The axis direction"))
|
|
obj.addProperty("App::PropertyInteger","NumberX","Draft",QT_TRANSLATE_NOOP("App::Property","Number of copies in X direction"))
|
|
obj.addProperty("App::PropertyInteger","NumberY","Draft",QT_TRANSLATE_NOOP("App::Property","Number of copies in Y direction"))
|
|
obj.addProperty("App::PropertyInteger","NumberZ","Draft",QT_TRANSLATE_NOOP("App::Property","Number of copies in Z direction"))
|
|
obj.addProperty("App::PropertyInteger","NumberPolar","Draft",QT_TRANSLATE_NOOP("App::Property","Number of copies"))
|
|
obj.addProperty("App::PropertyVectorDistance","IntervalX","Draft",QT_TRANSLATE_NOOP("App::Property","Distance and orientation of intervals in X direction"))
|
|
obj.addProperty("App::PropertyVectorDistance","IntervalY","Draft",QT_TRANSLATE_NOOP("App::Property","Distance and orientation of intervals in Y direction"))
|
|
obj.addProperty("App::PropertyVectorDistance","IntervalZ","Draft",QT_TRANSLATE_NOOP("App::Property","Distance and orientation of intervals in Z direction"))
|
|
obj.addProperty("App::PropertyVectorDistance","IntervalAxis","Draft",QT_TRANSLATE_NOOP("App::Property","Distance and orientation of intervals in Axis direction"))
|
|
obj.addProperty("App::PropertyVectorDistance","Center","Draft",QT_TRANSLATE_NOOP("App::Property","Center point"))
|
|
obj.addProperty("App::PropertyAngle","Angle","Draft",QT_TRANSLATE_NOOP("App::Property","Angle to cover with copies"))
|
|
obj.addProperty("App::PropertyDistance","RadialDistance","Draft",QT_TRANSLATE_NOOP("App::Property","Distance between copies in a circle"))
|
|
obj.addProperty("App::PropertyDistance","TangentialDistance","Draft",QT_TRANSLATE_NOOP("App::Property","Distance between circles"))
|
|
obj.addProperty("App::PropertyInteger","NumberCircles","Draft",QT_TRANSLATE_NOOP("App::Property","number of circles"))
|
|
obj.addProperty("App::PropertyInteger","Symmetry","Draft",QT_TRANSLATE_NOOP("App::Property","number of circles"))
|
|
obj.addProperty("App::PropertyBool","Fuse","Draft",QT_TRANSLATE_NOOP("App::Property","Specifies if copies must be fused (slower)"))
|
|
obj.Fuse = False
|
|
if self.use_link:
|
|
obj.addProperty("App::PropertyInteger","Count","Draft",'')
|
|
obj.addProperty("App::PropertyBool","ExpandArray","Draft",
|
|
QT_TRANSLATE_NOOP("App::Property","Show array element as children object"))
|
|
obj.ExpandArray = False
|
|
|
|
obj.ArrayType = ['ortho','polar','circular']
|
|
obj.NumberX = 1
|
|
obj.NumberY = 1
|
|
obj.NumberZ = 1
|
|
obj.NumberPolar = 1
|
|
obj.IntervalX = Vector(1,0,0)
|
|
obj.IntervalY = Vector(0,1,0)
|
|
obj.IntervalZ = Vector(0,0,1)
|
|
obj.Angle = 360
|
|
obj.Axis = Vector(0,0,1)
|
|
obj.RadialDistance = 1.0
|
|
obj.TangentialDistance = 1.0
|
|
obj.NumberCircles = 2
|
|
obj.Symmetry = 1
|
|
|
|
_DraftLink.attach(self,obj)
|
|
|
|
def linkSetup(self,obj):
|
|
_DraftLink.linkSetup(self,obj)
|
|
obj.configLinkProperty(ElementCount='Count')
|
|
obj.setPropertyStatus('Count','Hidden')
|
|
|
|
def onChanged(self,obj,prop):
|
|
_DraftLink.onChanged(self,obj,prop)
|
|
if prop == "AxisReference":
|
|
if obj.AxisReference:
|
|
obj.setEditorMode("Center", 1)
|
|
obj.setEditorMode("Axis", 1)
|
|
else:
|
|
obj.setEditorMode("Center", 0)
|
|
obj.setEditorMode("Axis", 0)
|
|
|
|
def execute(self,obj):
|
|
if obj.Base:
|
|
pl = obj.Placement
|
|
axis = obj.Axis
|
|
center = obj.Center
|
|
if hasattr(obj,"AxisReference") and obj.AxisReference:
|
|
if hasattr(obj.AxisReference,"Placement"):
|
|
axis = obj.AxisReference.Placement.Rotation * Vector(0,0,1)
|
|
center = obj.AxisReference.Placement.Base
|
|
else:
|
|
raise TypeError("AxisReference has no Placement attribute. Please select a different AxisReference.")
|
|
if obj.ArrayType == "ortho":
|
|
pls = self.rectArray(obj.Base.Placement,obj.IntervalX,obj.IntervalY,
|
|
obj.IntervalZ,obj.NumberX,obj.NumberY,obj.NumberZ)
|
|
elif obj.ArrayType == "circular":
|
|
pls = self.circArray(obj.Base.Placement,obj.RadialDistance,obj.TangentialDistance,
|
|
axis,center,obj.NumberCircles,obj.Symmetry)
|
|
else:
|
|
av = obj.IntervalAxis if hasattr(obj,"IntervalAxis") else None
|
|
pls = self.polarArray(obj.Base.Placement,center,obj.Angle.Value,obj.NumberPolar,axis,av)
|
|
|
|
return _DraftLink.buildShape(self,obj,pl,pls)
|
|
|
|
def rectArray(self,pl,xvector,yvector,zvector,xnum,ynum,znum):
|
|
import Part
|
|
base = [pl.copy()]
|
|
for xcount in range(xnum):
|
|
currentxvector=Vector(xvector).multiply(xcount)
|
|
if not xcount==0:
|
|
npl = pl.copy()
|
|
npl.translate(currentxvector)
|
|
base.append(npl)
|
|
for ycount in range(ynum):
|
|
currentyvector=FreeCAD.Vector(currentxvector)
|
|
currentyvector=currentyvector.add(Vector(yvector).multiply(ycount))
|
|
if not ycount==0:
|
|
npl = pl.copy()
|
|
npl.translate(currentyvector)
|
|
base.append(npl)
|
|
for zcount in range(znum):
|
|
currentzvector=FreeCAD.Vector(currentyvector)
|
|
currentzvector=currentzvector.add(Vector(zvector).multiply(zcount))
|
|
if not zcount==0:
|
|
npl = pl.copy()
|
|
npl.translate(currentzvector)
|
|
base.append(npl)
|
|
return base
|
|
|
|
def circArray(self,pl,rdist,tdist,axis,center,cnum,sym):
|
|
import Part
|
|
sym = max(1, sym)
|
|
lead = (0,1,0)
|
|
if axis.x == 0 and axis.z == 0: lead = (1,0,0)
|
|
direction = axis.cross(Vector(lead)).normalize()
|
|
base = [pl.copy()]
|
|
for xcount in range(1, cnum):
|
|
rc = xcount*rdist
|
|
c = 2*rc*math.pi
|
|
n = math.floor(c/tdist)
|
|
n = int(math.floor(n/sym)*sym)
|
|
if n == 0: continue
|
|
angle = 360.0/n
|
|
for ycount in range(0, n):
|
|
npl = pl.copy()
|
|
trans = FreeCAD.Vector(direction).multiply(rc)
|
|
npl.translate(trans)
|
|
npl.rotate(npl.Rotation.inverted().multVec(center-trans), axis, ycount*angle)
|
|
base.append(npl)
|
|
return base
|
|
|
|
def polarArray(self,spl,center,angle,num,axis,axisvector):
|
|
#print("angle ",angle," num ",num)
|
|
import Part
|
|
spin = FreeCAD.Placement(Vector(), spl.Rotation)
|
|
pl = FreeCAD.Placement(spl.Base, FreeCAD.Rotation())
|
|
center = center.sub(spl.Base)
|
|
base = [spl.copy()]
|
|
if angle == 360:
|
|
fraction = float(angle)/num
|
|
else:
|
|
if num == 0:
|
|
return base
|
|
fraction = float(angle)/(num-1)
|
|
ctr = DraftVecUtils.tup(center)
|
|
axs = DraftVecUtils.tup(axis)
|
|
for i in range(num-1):
|
|
currangle = fraction + (i*fraction)
|
|
npl = pl.copy()
|
|
npl.rotate(ctr, axs, currangle)
|
|
npl = npl.multiply(spin)
|
|
if axisvector:
|
|
if not DraftVecUtils.isNull(axisvector):
|
|
npl.translate(FreeCAD.Vector(axisvector).multiply(i+1))
|
|
base.append(npl)
|
|
return base
|
|
|
|
class _PathArray(_DraftLink):
|
|
"""The Draft Path Array object"""
|
|
|
|
def __init__(self,obj):
|
|
_DraftLink.__init__(self,obj,"PathArray")
|
|
|
|
def attach(self,obj):
|
|
obj.addProperty("App::PropertyLinkGlobal","Base","Draft",QT_TRANSLATE_NOOP("App::Property","The base object that must be duplicated"))
|
|
obj.addProperty("App::PropertyLinkGlobal","PathObj","Draft",QT_TRANSLATE_NOOP("App::Property","The path object along which to distribute objects"))
|
|
obj.addProperty("App::PropertyLinkSubListGlobal","PathSubs",QT_TRANSLATE_NOOP("App::Property","Selected subobjects (edges) of PathObj"))
|
|
obj.addProperty("App::PropertyInteger","Count","Draft",QT_TRANSLATE_NOOP("App::Property","Number of copies"))
|
|
obj.addProperty("App::PropertyVectorDistance","Xlate","Draft",QT_TRANSLATE_NOOP("App::Property","Optional translation vector"))
|
|
obj.addProperty("App::PropertyBool","Align","Draft",QT_TRANSLATE_NOOP("App::Property","Orientation of Base along path"))
|
|
obj.Count = 2
|
|
obj.PathSubs = []
|
|
obj.Xlate = FreeCAD.Vector(0,0,0)
|
|
obj.Align = False
|
|
|
|
if self.use_link:
|
|
obj.addProperty("App::PropertyBool","ExpandArray","Draft",
|
|
QT_TRANSLATE_NOOP("App::Property","Show array element as children object"))
|
|
obj.ExpandArray = False
|
|
obj.setPropertyStatus('Shape','Transient')
|
|
|
|
_DraftLink.attach(self,obj)
|
|
|
|
def linkSetup(self,obj):
|
|
_DraftLink.linkSetup(self,obj)
|
|
obj.configLinkProperty(ElementCount='Count')
|
|
|
|
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
|
|
base = calculatePlacementsOnPath(
|
|
obj.Base.Shape.Placement.Rotation,w,obj.Count,obj.Xlate,obj.Align)
|
|
return _DraftLink.buildShape(self,obj,pl,base)
|
|
|
|
def getWireFromSubs(self,obj):
|
|
'''Make a wire from PathObj subelements'''
|
|
import Part
|
|
sl = []
|
|
for sub in obj.PathSubs:
|
|
edgeNames = sub[1]
|
|
for n in edgeNames:
|
|
e = sub[0].Shape.getElement(n)
|
|
sl.append(e)
|
|
return Part.Wire(sl)
|
|
|
|
def pathArray(self,shape,pathwire,count,xlate,align):
|
|
'''Distribute shapes along a path.'''
|
|
import Part
|
|
|
|
placements = calculatePlacementsOnPath(
|
|
shape.Placement.Rotation, pathwire, count, xlate, align)
|
|
|
|
base = []
|
|
|
|
for placement in placements:
|
|
ns = shape.copy()
|
|
ns.Placement = placement
|
|
|
|
base.append(ns)
|
|
|
|
return (Part.makeCompound(base))
|
|
|
|
class _PointArray(_DraftObject):
|
|
"""The Draft Point Array object"""
|
|
def __init__(self, obj, bobj, ptlst):
|
|
_DraftObject.__init__(self,obj,"PointArray")
|
|
obj.addProperty("App::PropertyLink","Base","Draft",QT_TRANSLATE_NOOP("App::Property","Base")).Base = bobj
|
|
obj.addProperty("App::PropertyLink","PointList","Draft",QT_TRANSLATE_NOOP("App::Property","PointList")).PointList = ptlst
|
|
obj.addProperty("App::PropertyInteger","Count","Draft",QT_TRANSLATE_NOOP("App::Property","Count")).Count = 0
|
|
obj.setEditorMode("Count", 1)
|
|
|
|
def execute(self, obj):
|
|
import Part
|
|
from FreeCAD import Base, Vector
|
|
pls = []
|
|
opl = obj.PointList
|
|
while getType(opl) == 'Clone':
|
|
opl = opl.Objects[0]
|
|
if hasattr(opl, 'Geometry'):
|
|
place = opl.Placement
|
|
for pts in opl.Geometry:
|
|
if hasattr(pts, 'X') and hasattr(pts, 'Y') and hasattr(pts, 'Z'):
|
|
pn = pts.copy()
|
|
pn.translate(place.Base)
|
|
pn.rotate(place)
|
|
pls.append(pn)
|
|
elif hasattr(opl, 'Links'):
|
|
pls = opl.Links
|
|
elif hasattr(opl, 'Components'):
|
|
pls = opl.Components
|
|
|
|
base = []
|
|
i = 0
|
|
if hasattr(obj.Base, 'Shape'):
|
|
for pts in pls:
|
|
#print pts # inspect the objects
|
|
if hasattr(pts, 'X') and hasattr(pts, 'Y') and hasattr(pts, 'Z'):
|
|
nshape = obj.Base.Shape.copy()
|
|
if hasattr(pts, 'Placement'):
|
|
place = pts.Placement
|
|
nshape.translate(place.Base)
|
|
nshape.rotate(place.Base, place.Rotation.Axis, place.Rotation.Angle * 180 / math.pi )
|
|
else:
|
|
nshape.translate(Base.Vector(pts.X,pts.Y,pts.Z))
|
|
i += 1
|
|
base.append(nshape)
|
|
obj.Count = i
|
|
if i > 0:
|
|
obj.Shape = Part.makeCompound(base)
|
|
else:
|
|
FreeCAD.Console.PrintError(translate("draft","No point found\n"))
|
|
obj.Shape = obj.Base.Shape.copy()
|
|
|
|
|
|
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):
|
|
if hasattr(self.Object,"ArrayType"):
|
|
return ":/icons/Draft_Array.svg"
|
|
elif hasattr(self.Object,"PointList"):
|
|
return ":/icons/Draft_PointArray.svg"
|
|
return ":/icons/Draft_PathArray.svg"
|
|
|
|
def resetColors(self, vobj):
|
|
colors = []
|
|
if vobj.Object.Base:
|
|
if vobj.Object.Base.isDerivedFrom("Part::Feature"):
|
|
if len(vobj.Object.Base.ViewObject.DiffuseColor) > 1:
|
|
colors = vobj.Object.Base.ViewObject.DiffuseColor
|
|
else:
|
|
c = vobj.Object.Base.ViewObject.ShapeColor
|
|
c = (c[0],c[1],c[2],vobj.Object.Base.ViewObject.Transparency/100.0)
|
|
for f in vobj.Object.Base.Shape.Faces:
|
|
colors.append(c)
|
|
if colors:
|
|
n = 1
|
|
if hasattr(vobj.Object,"ArrayType"):
|
|
if vobj.Object.ArrayType == "ortho":
|
|
n = vobj.Object.NumberX * vobj.Object.NumberY * vobj.Object.NumberZ
|
|
else:
|
|
n = vobj.Object.NumberPolar
|
|
elif hasattr(vobj.Object,"Count"):
|
|
n = vobj.Object.Count
|
|
colors = colors * n
|
|
vobj.DiffuseColor = colors
|
|
|
|
class _ShapeString(_DraftObject):
|
|
"""The ShapeString object"""
|
|
|
|
def __init__(self, obj):
|
|
_DraftObject.__init__(self,obj,"ShapeString")
|
|
obj.addProperty("App::PropertyString","String","Draft",QT_TRANSLATE_NOOP("App::Property","Text string"))
|
|
obj.addProperty("App::PropertyFile","FontFile","Draft",QT_TRANSLATE_NOOP("App::Property","Font file name"))
|
|
obj.addProperty("App::PropertyLength","Size","Draft",QT_TRANSLATE_NOOP("App::Property","Height of text"))
|
|
obj.addProperty("App::PropertyLength","Tracking","Draft",QT_TRANSLATE_NOOP("App::Property","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
|
|
ff8 = obj.FontFile.encode('utf8') # 1947 accents in filepath
|
|
# TODO: change for Py3?? bytes?
|
|
# Part.makeWireString uses FontFile as char* string
|
|
if sys.version_info.major < 3:
|
|
CharList = Part.makeWireString(obj.String,ff8,obj.Size,obj.Tracking)
|
|
else:
|
|
CharList = Part.makeWireString(obj.String,obj.FontFile,obj.Size,obj.Tracking)
|
|
if len(CharList) == 0:
|
|
FreeCAD.Console.PrintWarning(translate("draft","ShapeString: string has no wires")+"\n")
|
|
return
|
|
SSChars = []
|
|
|
|
# test a simple letter to know if we have a sticky font or not
|
|
sticky = False
|
|
if sys.version_info.major < 3:
|
|
testWire = Part.makeWireString("L",ff8,obj.Size,obj.Tracking)[0][0]
|
|
else:
|
|
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
|
|
obj.positionBySupport()
|
|
|
|
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()
|
|
try:
|
|
# some fonts fail here
|
|
if face.Surface.Axis.z < 0.0:
|
|
face.reverse()
|
|
except:
|
|
pass
|
|
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",QT_TRANSLATE_NOOP("App::Property","Linked faces"))
|
|
obj.addProperty("App::PropertyBool","RemoveSplitter","Draft",QT_TRANSLATE_NOOP("App::Property","Specifies if splitter lines must be removed"))
|
|
obj.addProperty("App::PropertyDistance","Extrusion","Draft",QT_TRANSLATE_NOOP("App::Property","An optional extrusion value to be applied to all faces"))
|
|
obj.addProperty("App::PropertyBool","Sew","Draft",QT_TRANSLATE_NOOP("App::Property","This specifies if the shapes sew"))
|
|
|
|
|
|
def execute(self,obj):
|
|
import Part
|
|
pl = obj.Placement
|
|
if not obj.Faces:
|
|
return
|
|
faces = []
|
|
for sel in obj.Faces:
|
|
for f in sel[1]:
|
|
if "Face" in f:
|
|
try:
|
|
fnum = int(f[4:])-1
|
|
faces.append(sel[0].Shape.Faces[fnum])
|
|
except(IndexError,Part.OCCError):
|
|
print("Draft: wrong face index")
|
|
return
|
|
if not faces:
|
|
return
|
|
try:
|
|
if len(faces) > 1:
|
|
sh = None
|
|
if hasattr(obj,"Extrusion"):
|
|
if obj.Extrusion.Value:
|
|
for f in faces:
|
|
f = f.extrude(f.normalAt(0,0).multiply(obj.Extrusion.Value))
|
|
if sh:
|
|
sh = sh.fuse(f)
|
|
else:
|
|
sh = f
|
|
if not sh:
|
|
sh = faces.pop()
|
|
sh = sh.multiFuse(faces)
|
|
if hasattr(obj,"Sew"):
|
|
if obj.Sew:
|
|
sh = sh.copy()
|
|
sh.sewShape()
|
|
if hasattr(obj,"RemoveSplitter"):
|
|
if obj.RemoveSplitter:
|
|
sh = sh.removeSplitter()
|
|
else:
|
|
sh = sh.removeSplitter()
|
|
else:
|
|
sh = faces[0]
|
|
if hasattr(obj,"Extrusion"):
|
|
if obj.Extrusion.Value:
|
|
sh = sh.extrude(sh.normalAt(0,0).multiply(obj.Extrusion.Value))
|
|
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 _ViewProviderFacebinder(_ViewProviderDraft):
|
|
def __init__(self,vobj):
|
|
_ViewProviderDraft.__init__(self,vobj)
|
|
|
|
def getIcon(self):
|
|
return ":/icons/Draft_Facebinder_Provider.svg"
|
|
|
|
def setEdit(self,vobj,mode):
|
|
import DraftGui
|
|
taskd = DraftGui.FacebinderTaskPanel()
|
|
taskd.obj = vobj.Object
|
|
taskd.update()
|
|
FreeCADGui.Control.showDialog(taskd)
|
|
return True
|
|
|
|
def unsetEdit(self,vobj,mode):
|
|
FreeCADGui.Control.closeDialog()
|
|
return False
|
|
|
|
|
|
## @}
|