Draft: clean up code, PEP8, and docstrings in PathArray
Test the inputs to the `make_path_array` function
and return `None` if there is a problem.
Now the make function accepts as input a `"String"` which must be
the `Label` of an object in the document, so it is easier to create
arrays quickly from the Python console.
Add the new parameters to the make function, `align_mode`,
`tan_vector`, `force_vertical`, and `vertical_vector`.
These properties were added to the proxy object in ff323ebdb5.
Add message deprecating the older call `makePathArray`.
Adjust the GuiCommand accordingly. Now it uses the commit
mechanism of the parent `Modifier` class so that the executed
functions are recorded in the Python console.
Clean up the `PathArray` class as well.
This commit is contained in:
@@ -1,7 +1,10 @@
|
||||
# ***************************************************************************
|
||||
# * (c) 2009, 2010 Yorik van Havre <yorik@uncreated.net> *
|
||||
# * (c) 2009, 2010 Ken Cline <cline@frii.com> *
|
||||
# * (c) 2020 Eliud Cabrera Castillo <e.cabrera-castillo@tum.de> *
|
||||
# * Copyright (c) 2009, 2010 Yorik van Havre <yorik@uncreated.net> *
|
||||
# * Copyright (c) 2009, 2010 Ken Cline <cline@frii.com> *
|
||||
# * Copyright (c) 2013 Wandererfan <wandererfan@gmail.com> *
|
||||
# * Copyright (c) 2019 Zheng, Lei (realthunder)<realthunder.dev@gmail.com>*
|
||||
# * Copyright (c) 2020 Carlo Pavan <carlopav@gmail.com> *
|
||||
# * Copyright (c) 2020 Eliud Cabrera Castillo <e.cabrera-castillo@tum.de> *
|
||||
# * *
|
||||
# * This file is part of the FreeCAD CAx development system. *
|
||||
# * *
|
||||
@@ -11,13 +14,13 @@
|
||||
# * the License, or (at your option) any later version. *
|
||||
# * for detail see the LICENCE text file. *
|
||||
# * *
|
||||
# * FreeCAD is distributed in the hope that it will be useful, *
|
||||
# * 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 FreeCAD; if not, write to the Free Software *
|
||||
# * License along with this program; if not, write to the Free Software *
|
||||
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
||||
# * USA *
|
||||
# * *
|
||||
@@ -35,12 +38,12 @@ from PySide.QtCore import QT_TRANSLATE_NOOP
|
||||
|
||||
import FreeCAD as App
|
||||
import FreeCADGui as Gui
|
||||
import Draft
|
||||
import Draft_rc
|
||||
import DraftVecUtils
|
||||
import draftguitools.gui_base_original as gui_base_original
|
||||
import draftguitools.gui_tool_utils as gui_tool_utils
|
||||
from draftutils.messages import _msg
|
||||
from draftutils.translate import translate, _tr
|
||||
|
||||
from draftutils.messages import _err
|
||||
from draftutils.translate import _tr
|
||||
|
||||
# The module is used to prevent complaints from code checkers (flake8)
|
||||
True if Draft_rc.__name__ else False
|
||||
@@ -59,13 +62,16 @@ class PathArray(gui_base_original.Modifier):
|
||||
def __init__(self, use_link=False):
|
||||
super(PathArray, self).__init__()
|
||||
self.use_link = use_link
|
||||
self.call = None
|
||||
|
||||
def GetResources(self):
|
||||
"""Set icon, menu and tooltip."""
|
||||
_menu = "Path array"
|
||||
_tip = ("Creates copies of a selected object along a selected path.\n"
|
||||
_tip = ("Creates copies of the selected object "
|
||||
"along a selected path.\n"
|
||||
"First select the object, and then select the path.\n"
|
||||
"The path can be a polyline, B-spline or Bezier curve.")
|
||||
"The path can be a polyline, B-spline, Bezier curve, "
|
||||
"or even edges from other objects.")
|
||||
|
||||
return {'Pixmap': 'Draft_PathArray',
|
||||
'MenuText': QT_TRANSLATE_NOOP("Draft_PathArray", _menu),
|
||||
@@ -74,15 +80,24 @@ class PathArray(gui_base_original.Modifier):
|
||||
def Activated(self, name=_tr("Path array")):
|
||||
"""Execute when the command is called."""
|
||||
super(PathArray, self).Activated(name=name)
|
||||
if not Gui.Selection.getSelectionEx():
|
||||
if self.ui:
|
||||
self.ui.selectUi()
|
||||
_msg(translate("draft", "Please select base and path objects"))
|
||||
self.call = \
|
||||
self.view.addEventCallback("SoEvent",
|
||||
gui_tool_utils.selectObject)
|
||||
else:
|
||||
self.proceed()
|
||||
self.name = name
|
||||
# This was deactivated becuase it doesn't work correctly;
|
||||
# the selection needs to be made on two objects, but currently
|
||||
# it only selects one.
|
||||
|
||||
# if not Gui.Selection.getSelectionEx():
|
||||
# if self.ui:
|
||||
# self.ui.selectUi()
|
||||
# _msg(translate("draft",
|
||||
# "Please select exactly two objects, "
|
||||
# "the base object and the path object, "
|
||||
# "before calling this command."))
|
||||
# self.call = \
|
||||
# self.view.addEventCallback("SoEvent",
|
||||
# gui_tool_utils.selectObject)
|
||||
# else:
|
||||
# self.proceed()
|
||||
self.proceed()
|
||||
|
||||
def proceed(self):
|
||||
"""Proceed with the command if one object was selected."""
|
||||
@@ -90,21 +105,54 @@ class PathArray(gui_base_original.Modifier):
|
||||
self.view.removeEventCallback("SoEvent", self.call)
|
||||
|
||||
sel = Gui.Selection.getSelectionEx()
|
||||
if sel:
|
||||
base = sel[0].Object
|
||||
path = sel[1].Object
|
||||
if len(sel) != 2:
|
||||
_err(_tr("Please select exactly two objects, "
|
||||
"the base object and the path object, "
|
||||
"before calling this command."))
|
||||
else:
|
||||
base_object = sel[0].Object
|
||||
path_object = sel[1].Object
|
||||
|
||||
defCount = 4
|
||||
defXlate = App.Vector(0, 0, 0)
|
||||
defAlign = False
|
||||
pathsubs = list(sel[1].SubElementNames)
|
||||
count = 4
|
||||
xlate = App.Vector(0, 0, 0)
|
||||
subelements = list(sel[1].SubElementNames)
|
||||
align = False
|
||||
align_mode = "Original"
|
||||
tan_vector = App.Vector(1, 0, 0)
|
||||
force_vertical = False
|
||||
vertical_vector = App.Vector(0, 0, 1)
|
||||
use_link = self.use_link
|
||||
|
||||
App.ActiveDocument.openTransaction("PathArray")
|
||||
Draft.makePathArray(base, path,
|
||||
defCount, defXlate, defAlign, pathsubs,
|
||||
use_link=self.use_link)
|
||||
App.ActiveDocument.commitTransaction()
|
||||
App.ActiveDocument.recompute()
|
||||
_edge_list_str = list()
|
||||
_edge_list_str = ["'" + edge + "'" for edge in subelements]
|
||||
_sub_str = ", ".join(_edge_list_str)
|
||||
subelements_list_str = "[" + _sub_str + "]"
|
||||
|
||||
vertical_vector_str = DraftVecUtils.toString(vertical_vector)
|
||||
|
||||
Gui.addModule("Draft")
|
||||
_cmd = "Draft.make_path_array"
|
||||
_cmd += "("
|
||||
_cmd += "App.ActiveDocument." + base_object.Name + ", "
|
||||
_cmd += "App.ActiveDocument." + path_object.Name + ", "
|
||||
_cmd += "count=" + str(count) + ", "
|
||||
_cmd += "xlate=" + DraftVecUtils.toString(xlate) + ", "
|
||||
_cmd += "subelements=" + subelements_list_str + ", "
|
||||
_cmd += "align=" + str(align) + ", "
|
||||
_cmd += "align_mode=" + "'" + align_mode + "', "
|
||||
_cmd += "tan_vector=" + DraftVecUtils.toString(tan_vector) + ", "
|
||||
_cmd += "force_vertical=" + str(force_vertical) + ", "
|
||||
_cmd += "vertical_vector=" + vertical_vector_str + ", "
|
||||
_cmd += "use_link=" + str(use_link)
|
||||
_cmd += ")"
|
||||
|
||||
_cmd_list = ["_obj_ = " + _cmd,
|
||||
"Draft.autogroup(_obj_)",
|
||||
"App.ActiveDocument.recompute()"]
|
||||
self.commit(_tr(self.name), _cmd_list)
|
||||
|
||||
# Commit the transaction and execute the commands
|
||||
# through the parent class
|
||||
self.finish()
|
||||
|
||||
|
||||
@@ -131,7 +179,7 @@ class PathLinkArray(PathArray):
|
||||
|
||||
def Activated(self):
|
||||
"""Execute when the command is called."""
|
||||
super(PathLinkArray, self).Activated(name=_tr("Link path array"))
|
||||
super(PathLinkArray, self).Activated(name=_tr("Path link array"))
|
||||
|
||||
|
||||
Gui.addCommand('Draft_PathLinkArray', PathLinkArray())
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
# ***************************************************************************
|
||||
# * Copyright (c) 2009, 2010 Yorik van Havre <yorik@uncreated.net> *
|
||||
# * Copyright (c) 2009, 2010 Ken Cline <cline@frii.com> *
|
||||
# * Copyright (c) 2020 FreeCAD Developers *
|
||||
# * Copyright (c) 2013 Wandererfan <wandererfan@gmail.com> *
|
||||
# * Copyright (c) 2019 Zheng, Lei (realthunder)<realthunder.dev@gmail.com>*
|
||||
# * Copyright (c) 2020 Carlo Pavan <carlopav@gmail.com> *
|
||||
# * Copyright (c) 2020 Eliud Cabrera Castillo <e.cabrera-castillo@tum.de> *
|
||||
# * *
|
||||
# * This file is part of the FreeCAD CAx development system. *
|
||||
# * *
|
||||
# * This program is free software; you can redistribute it and/or modify *
|
||||
# * it under the terms of the GNU Lesser General Public License (LGPL) *
|
||||
@@ -20,98 +25,293 @@
|
||||
# * USA *
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
"""This module provides the code for Draft make_path_array function.
|
||||
"""Provides functions for creating path arrays.
|
||||
|
||||
The copies will be placed along a path like a polyline, spline, or bezier
|
||||
curve.
|
||||
"""
|
||||
## @package make_patharray
|
||||
# \ingroup DRAFT
|
||||
# \brief This module provides the code for Draft make_path_array function.
|
||||
# \brief Provides functions for creating path arrays.
|
||||
|
||||
import FreeCAD as App
|
||||
|
||||
import draftutils.utils as utils
|
||||
import draftutils.gui_utils as gui_utils
|
||||
|
||||
from draftutils.translate import _tr, translate
|
||||
from draftutils.messages import _msg, _err
|
||||
from draftutils.translate import _tr
|
||||
from draftobjects.patharray import PathArray
|
||||
|
||||
from draftviewproviders.view_draftlink import ViewProviderDraftLink
|
||||
if App.GuiUp:
|
||||
from draftviewproviders.view_array import ViewProviderDraftArray
|
||||
from draftviewproviders.view_draftlink import ViewProviderDraftLink
|
||||
|
||||
|
||||
def make_path_array(baseobject,pathobject,count,xlate=None,align=False,pathobjsubs=[],use_link=False):
|
||||
"""make_path_array(docobj, path, count, xlate, align, pathobjsubs, use_link)
|
||||
|
||||
Make a Draft PathArray object.
|
||||
|
||||
Distribute count copies of a document baseobject along a pathobject
|
||||
or subobjects of a pathobject.
|
||||
def make_path_array(base_object, path_object, count=4,
|
||||
xlate=App.Vector(0, 0, 0), subelements=None,
|
||||
align=False, align_mode="Original",
|
||||
tan_vector=App.Vector(1, 0, 0),
|
||||
force_vertical=False,
|
||||
vertical_vector=App.Vector(0, 0, 1),
|
||||
use_link=True):
|
||||
"""Make a Draft PathArray object.
|
||||
|
||||
Distribute copies of a `base_object` along `path_object`
|
||||
or `subelements` from `path_object`.
|
||||
|
||||
|
||||
Parameters
|
||||
----------
|
||||
docobj :
|
||||
Object to array
|
||||
base_object: Part::Feature or str
|
||||
Any of object that has a `Part::TopoShape` that can be duplicated.
|
||||
This means most 2D and 3D objects produced with any workbench.
|
||||
If it is a string, it must be the `Label` of that object.
|
||||
Since a label is not guaranteed to be unique in a document,
|
||||
it will use the first object found with this label.
|
||||
|
||||
path :
|
||||
Path object
|
||||
path_object: Part::Feature or str
|
||||
Path object like a polyline, B-Spline, or bezier curve that should
|
||||
contain edges.
|
||||
Just like `base_object` it can also be `Label`.
|
||||
|
||||
pathobjsubs :
|
||||
TODO: Complete documentation
|
||||
count: int, float, optional
|
||||
It defaults to 4.
|
||||
Number of copies to create along the `path_object`.
|
||||
It must be at least 2.
|
||||
If a `float` is provided, it will be truncated by `int(count)`.
|
||||
|
||||
align :
|
||||
Optionally aligns baseobject to tangent/normal/binormal of path. TODO: verify
|
||||
xlate: Base.Vector3, optional
|
||||
It defaults to `App.Vector(0, 0, 0)`.
|
||||
It translates each copy by the value of `xlate`.
|
||||
This is useful to adjust for the difference between shape centre
|
||||
and shape reference point.
|
||||
|
||||
count :
|
||||
TODO: Complete documentation
|
||||
subelements: list or tuple of str, optional
|
||||
It defaults to `None`.
|
||||
It should be a list of names of edges that must exist in `path_object`.
|
||||
Then the path array will be created along these edges only,
|
||||
and not the entire `path_object`.
|
||||
::
|
||||
subelements = ['Edge1', 'Edge2']
|
||||
|
||||
xlate : Base.Vector
|
||||
Optionally translates each copy by FreeCAD.Vector xlate direction
|
||||
and distance to adjust for difference in shape centre vs shape reference point.
|
||||
|
||||
use_link :
|
||||
TODO: Complete documentation
|
||||
The edges must be contiguous, meaning that it is not allowed to
|
||||
input `'Edge1'` and `'Edge3'` if they do not touch each other.
|
||||
|
||||
A single string value is also allowed.
|
||||
::
|
||||
subelements = 'Edge1'
|
||||
|
||||
align: bool, optional
|
||||
It defaults to `False`.
|
||||
If it is `True` it will align `base_object` to tangent, normal,
|
||||
or binormal to the `path_object`, depending on the value
|
||||
of `tan_vector`.
|
||||
|
||||
align_mode: str, optional
|
||||
It defaults to `'Original'` which is the traditional alignment.
|
||||
It can also be `'Frenet'` or `'Tangent'`.
|
||||
|
||||
- Original. It does not calculate curve normal.
|
||||
`X` is curve tangent, `Y` is normal parameter, Z is the cross
|
||||
product `X` x `Y`.
|
||||
- Frenet. It defines a local coordinate system along the path.
|
||||
`X` is tanget to curve, `Y` is curve normal, `Z` is curve binormal.
|
||||
If normal cannot be computed, for example, in a straight path,
|
||||
a default is used.
|
||||
- Tangent. It is similar to `'Original'` but includes a pre-rotation
|
||||
to align the base object's `X` to the value of `tan_vector`,
|
||||
then `X` follows curve tangent.
|
||||
|
||||
tan_vector: Base::Vector3, optional
|
||||
It defaults to `App.Vector(1, 0, 0)` or the +X axis.
|
||||
It aligns the tangent of the path to this local unit vector
|
||||
of the object.
|
||||
|
||||
force_vertical: Base::Vector3, optional
|
||||
It defaults to `False`.
|
||||
If it is `True`, the value of `vertical_vector`
|
||||
will be used when `align_mode` is `'Original'` or `'Tangent'`.
|
||||
|
||||
vertical_vector: Base::Vector3, optional
|
||||
It defaults to `App.Vector(0, 0, 1)` or the +Z axis.
|
||||
It will force this vector to be the vertical direction
|
||||
when `force_vertical` is `True`.
|
||||
|
||||
use_link: bool, optional
|
||||
It defaults to `True`, in which case the copies are `App::Link`
|
||||
elements. Otherwise, the copies are shape copies which makes
|
||||
the resulting array heavier.
|
||||
|
||||
Returns
|
||||
-------
|
||||
Part::FeaturePython
|
||||
The scripted object of type `'PathArray'`.
|
||||
Its `Shape` is a compound of the copies of the original object.
|
||||
|
||||
None
|
||||
If there is a problem it will return `None`.
|
||||
"""
|
||||
_name = "make_path_array"
|
||||
utils.print_header(_name, "Path array")
|
||||
|
||||
if not App.ActiveDocument:
|
||||
App.Console.PrintError("No active document. Aborting\n")
|
||||
return
|
||||
found, doc = utils.find_doc(App.activeDocument())
|
||||
if not found:
|
||||
_err(_tr("No active document. Aborting."))
|
||||
return None
|
||||
|
||||
if isinstance(base_object, str):
|
||||
base_object_str = base_object
|
||||
|
||||
found, base_object = utils.find_object(base_object, doc)
|
||||
if not found:
|
||||
_msg("base_object: {}".format(base_object_str))
|
||||
_err(_tr("Wrong input: object not in document."))
|
||||
return None
|
||||
|
||||
_msg("base_object: {}".format(base_object.Label))
|
||||
|
||||
if isinstance(path_object, str):
|
||||
path_object_str = path_object
|
||||
|
||||
found, path_object = utils.find_object(path_object, doc)
|
||||
if not found:
|
||||
_msg("path_object: {}".format(path_object_str))
|
||||
_err(_tr("Wrong input: object not in document."))
|
||||
return None
|
||||
|
||||
_msg("path_object: {}".format(path_object.Label))
|
||||
|
||||
_msg("count: {}".format(count))
|
||||
try:
|
||||
utils.type_check([(count, (int, float))],
|
||||
name=_name)
|
||||
except TypeError:
|
||||
_err(_tr("Wrong input: must be a number."))
|
||||
return None
|
||||
count = int(count)
|
||||
|
||||
_msg("xlate: {}".format(xlate))
|
||||
try:
|
||||
utils.type_check([(xlate, App.Vector)],
|
||||
name=_name)
|
||||
except TypeError:
|
||||
_err(_tr("Wrong input: must be a vector."))
|
||||
return None
|
||||
|
||||
_msg("subelements: {}".format(subelements))
|
||||
if subelements:
|
||||
try:
|
||||
# Make a list
|
||||
if isinstance(subelements, str):
|
||||
subelements = [subelements]
|
||||
|
||||
utils.type_check([(subelements, (list, tuple, str))],
|
||||
name=_name)
|
||||
except TypeError:
|
||||
_err(_tr("Wrong input: must be a list or tuple of strings. "
|
||||
"Or a single string."))
|
||||
return None
|
||||
|
||||
# The subelements list is used to build a special list
|
||||
# called a LinkSubList, which includes the path_object.
|
||||
# Old style: [(path_object, "Edge1"), (path_object, "Edge2")]
|
||||
# New style: [(path_object, ("Edge1", "Edge2"))]
|
||||
#
|
||||
# If a simple list is given ["a", "b"], this will create an old-style
|
||||
# SubList.
|
||||
# If a nested list is given [["a", "b"]], this will create a new-style
|
||||
# SubList.
|
||||
# In any case, the property of the object accepts both styles.
|
||||
#
|
||||
# If the old style is deprecated then this code should be updated
|
||||
# to create new style lists exclusively.
|
||||
sub_list = list()
|
||||
for sub in subelements:
|
||||
sub_list.append((path_object, sub))
|
||||
else:
|
||||
sub_list = None
|
||||
|
||||
align = bool(align)
|
||||
_msg("align: {}".format(align))
|
||||
|
||||
_msg("align_mode: {}".format(align_mode))
|
||||
try:
|
||||
utils.type_check([(align_mode, str)],
|
||||
name=_name)
|
||||
|
||||
if align_mode not in ("Original", "Frenet", "Tangent"):
|
||||
raise TypeError
|
||||
except TypeError:
|
||||
_err(_tr("Wrong input: must be "
|
||||
"'Original', 'Frenet', or 'Tangent'."))
|
||||
return None
|
||||
|
||||
_msg("tan_vector: {}".format(tan_vector))
|
||||
try:
|
||||
utils.type_check([(tan_vector, App.Vector)],
|
||||
name=_name)
|
||||
except TypeError:
|
||||
_err(_tr("Wrong input: must be a vector."))
|
||||
return None
|
||||
|
||||
force_vertical = bool(force_vertical)
|
||||
_msg("force_vertical: {}".format(force_vertical))
|
||||
|
||||
_msg("vertical_vector: {}".format(vertical_vector))
|
||||
try:
|
||||
utils.type_check([(vertical_vector, App.Vector)],
|
||||
name=_name)
|
||||
except TypeError:
|
||||
_err(_tr("Wrong input: must be a vector."))
|
||||
return None
|
||||
|
||||
use_link = bool(use_link)
|
||||
_msg("use_link: {}".format(use_link))
|
||||
|
||||
if use_link:
|
||||
obj = App.ActiveDocument.addObject("Part::FeaturePython","PathArray", PathArray(None), None, True)
|
||||
# The PathArray class must be called in this special way
|
||||
# to make it a PathLinkArray
|
||||
new_obj = doc.addObject("Part::FeaturePython", "PathArray",
|
||||
PathArray(None), None, True)
|
||||
else:
|
||||
obj = App.ActiveDocument.addObject("Part::FeaturePython","PathArray")
|
||||
PathArray(obj)
|
||||
new_obj = doc.addObject("Part::FeaturePython", "PathArray")
|
||||
PathArray(new_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
|
||||
new_obj.Base = base_object
|
||||
new_obj.PathObj = path_object
|
||||
new_obj.Count = count
|
||||
new_obj.Xlate = xlate
|
||||
new_obj.PathSubs = sub_list
|
||||
new_obj.Align = align
|
||||
new_obj.AlignMode = align_mode
|
||||
new_obj.TangentVector = tan_vector
|
||||
new_obj.ForceVertical = force_vertical
|
||||
new_obj.VerticalVector = vertical_vector
|
||||
|
||||
if App.GuiUp:
|
||||
if use_link:
|
||||
ViewProviderDraftLink(obj.ViewObject)
|
||||
ViewProviderDraftLink(new_obj.ViewObject)
|
||||
else:
|
||||
ViewProviderDraftArray(obj.ViewObject)
|
||||
gui_utils.formatObject(obj,obj.Base)
|
||||
if hasattr(obj.Base.ViewObject, "DiffuseColor"):
|
||||
if len(obj.Base.ViewObject.DiffuseColor) > 1:
|
||||
obj.ViewObject.Proxy.resetColors(obj.ViewObject)
|
||||
baseobject.ViewObject.hide()
|
||||
gui_utils.select(obj)
|
||||
return obj
|
||||
ViewProviderDraftArray(new_obj.ViewObject)
|
||||
gui_utils.formatObject(new_obj, new_obj.Base)
|
||||
|
||||
if hasattr(new_obj.Base.ViewObject, "DiffuseColor"):
|
||||
if len(new_obj.Base.ViewObject.DiffuseColor) > 1:
|
||||
new_obj.ViewObject.Proxy.resetColors(new_obj.ViewObject)
|
||||
|
||||
new_obj.Base.ViewObject.hide()
|
||||
gui_utils.select(new_obj)
|
||||
|
||||
return new_obj
|
||||
|
||||
|
||||
makePathArray = make_path_array
|
||||
def makePathArray(baseobject, pathobject, count,
|
||||
xlate=None, align=False,
|
||||
pathobjsubs=[],
|
||||
use_link=False):
|
||||
"""Create PathArray. DEPRECATED. Use 'make_path_array'."""
|
||||
utils.use_instead('make_path_array')
|
||||
|
||||
return make_path_array(baseobject, pathobject, count,
|
||||
xlate, pathobjsubs,
|
||||
align,
|
||||
use_link)
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
# ***************************************************************************
|
||||
# * Copyright (c) 2009, 2010 Yorik van Havre <yorik@uncreated.net> *
|
||||
# * Copyright (c) 2009, 2010 Ken Cline <cline@frii.com> *
|
||||
# * Copyright (c) 2020 FreeCAD Developers *
|
||||
# * Copyright (c) 2013 Wandererfan <wandererfan@gmail.com> *
|
||||
# * Copyright (c) 2019 Zheng, Lei (realthunder)<realthunder.dev@gmail.com>*
|
||||
# * Copyright (c) 2020 Carlo Pavan <carlopav@gmail.com> *
|
||||
# * Copyright (c) 2020 Eliud Cabrera Castillo <e.cabrera-castillo@tum.de> *
|
||||
# * *
|
||||
# * This file is part of the FreeCAD CAx development system. *
|
||||
# * *
|
||||
# * This program is free software; you can redistribute it and/or modify *
|
||||
# * it under the terms of the GNU Lesser General Public License (LGPL) *
|
||||
@@ -20,150 +25,270 @@
|
||||
# * USA *
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
"""This module provides the object code for the Draft PathArray object.
|
||||
"""Provides the object code for the Draft PathArray object.
|
||||
|
||||
The copies will be placed along a path like a polyline, spline, or bezier
|
||||
curve.
|
||||
"""
|
||||
## @package patharray
|
||||
# \ingroup DRAFT
|
||||
# \brief This module provides the object code for the Draft PathArray object.
|
||||
# \brief Provides the object code for the Draft PathArray object.
|
||||
|
||||
import FreeCAD as App
|
||||
import DraftVecUtils
|
||||
import lazy_loader.lazy_loader as lz
|
||||
|
||||
from draftutils.utils import get_param
|
||||
from draftutils.messages import _msg, _wrn
|
||||
from draftutils.translate import _tr, translate
|
||||
from draftutils.messages import _msg, _wrn, _err
|
||||
from draftutils.translate import _tr
|
||||
|
||||
from draftobjects.draftlink import DraftLink
|
||||
|
||||
# Delay import of module until first use because it is heavy
|
||||
Part = lz.LazyLoader("Part", globals(), "Part")
|
||||
DraftGeomUtils = lz.LazyLoader("DraftGeomUtils", globals(), "DraftGeomUtils")
|
||||
|
||||
|
||||
class PathArray(DraftLink):
|
||||
"""The Draft Path Array object - distributes copies of an object along a path.
|
||||
Original mode is the historic "Align" for old (v0.18) documents. It is not
|
||||
really the Fernat alignment. Uses the normal parameter from getNormal (or the
|
||||
default) as a constant - it does not calculate curve normal.
|
||||
X is curve tangent, Y is normal parameter, Z is (X x Y)
|
||||
"""The Draft Path Array object.
|
||||
|
||||
Tangent mode is similar to Original, but includes a pre-rotation (in execute) to
|
||||
align the Base object's X to the TangentVector, then X follows curve tangent,
|
||||
normal input parameter is the Z component.
|
||||
The object distributes copies of an object along a path like a polyline,
|
||||
spline, or bezier curve.
|
||||
|
||||
If the ForceVertical option is applied, the normal parameter from getNormal is
|
||||
ignored, and X is curve tangent, Z is VerticalVector, Y is (X x Z)
|
||||
Attributes
|
||||
----------
|
||||
Align: bool
|
||||
It defaults to `False`.
|
||||
It sets whether the object will be specially aligned to the path.
|
||||
|
||||
AlignMode: str
|
||||
It defaults to `'Original'`.
|
||||
Indicates the type of alignment that will be calculated when
|
||||
`Align` is `True`.
|
||||
|
||||
`'Original'` mode is the historic `'Align'` for old (v0.18) documents.
|
||||
It is not really the Fernat alignment. It uses the normal parameter
|
||||
from `getNormal` (or the default) as a constant, it does not calculate
|
||||
curve normal.
|
||||
`X` is curve tangent, `Y` is normal parameter, `Z` is the cross product
|
||||
`X` x `Y`.
|
||||
|
||||
`'Tangent'` mode is similar to `Original`, but includes a pre-rotation
|
||||
(in execute) to align the `Base` object's `X` to `TangentVector`,
|
||||
then `X` follows curve tangent, normal input parameter
|
||||
is the Z component.
|
||||
|
||||
If `ForceVertical` is `True`, the normal parameter from `getNormal`
|
||||
is ignored, and `X` is curve tangent, `Z` is `VerticalVector`,
|
||||
and `Y` is the cross product `X` x `Z`.
|
||||
|
||||
`'Frenet'` mode orients the copies to a coordinate system
|
||||
along the path.
|
||||
`X` is tangent to curve, `Y` is curve normal, `Z` is curve binormal.
|
||||
If normal cannot be computed, for example, in a straight line,
|
||||
the default is used.
|
||||
|
||||
ForceVertical: bool
|
||||
It defaults to `False`.
|
||||
If it is `True`, and `AlignMode` is `'Original'` or `'Tangent'`,
|
||||
it will use the vector in `VerticalVector` as the `Z` axis.
|
||||
"""
|
||||
|
||||
Frenet mode orients the copies to a coordinate system along the path.
|
||||
X is tangent to curve, Y is curve normal, Z is curve binormal.
|
||||
if normal can not be computed (ex a straight line), the default is used."""
|
||||
|
||||
def __init__(self, obj):
|
||||
super(PathArray, self).__init__(obj, "PathArray")
|
||||
|
||||
#For PathLinkArray, DraftLink.attach creates the link to the Base object.
|
||||
def attach(self,obj):
|
||||
self.setProperties(obj)
|
||||
super(PathArray, self).attach(obj)
|
||||
def attach(self, obj):
|
||||
"""Set up the properties when the object is attached.
|
||||
|
||||
def setProperties(self,obj):
|
||||
Note: we don't exactly know why the properties are added
|
||||
in the `attach` method. They should probably be added in the `__init__`
|
||||
method. Maybe this is related to the link behavior of this class.
|
||||
|
||||
For PathLinkArray, DraftLink.attach creates the link to the Base.
|
||||
|
||||
Realthunder: before the big merge, there was only the attach() method
|
||||
in the view object proxy, not the object proxy.
|
||||
I added that to allow the proxy to override the C++ view provider
|
||||
type. The view provider type is normally determined by object's
|
||||
C++ API getViewProviderName(), and cannot be overridden by the proxy.
|
||||
I introduced the attach() method in proxy to allow the core
|
||||
to attach the proxy before creating the C++ view provider.
|
||||
"""
|
||||
self.set_properties(obj)
|
||||
super(PathArray, self).attach(obj)
|
||||
|
||||
def set_properties(self, obj):
|
||||
"""Set properties only if they don't exist."""
|
||||
if not obj:
|
||||
return
|
||||
|
||||
if hasattr(obj, "PropertiesList"):
|
||||
pl = obj.PropertiesList
|
||||
properties = obj.PropertiesList
|
||||
else:
|
||||
pl = []
|
||||
properties = []
|
||||
|
||||
if not "Base" in pl:
|
||||
_tip = _tr("The base object that must be duplicated")
|
||||
obj.addProperty("App::PropertyLinkGlobal", "Base", "Objects", _tip)
|
||||
if "Base" not in properties:
|
||||
_tip = _tr("The base object that will be duplicated")
|
||||
obj.addProperty("App::PropertyLinkGlobal",
|
||||
"Base",
|
||||
"Objects",
|
||||
_tip)
|
||||
obj.Base = None
|
||||
|
||||
if not "PathObj" in pl:
|
||||
_tip = _tr("The path object along which to distribute objects")
|
||||
obj.addProperty("App::PropertyLinkGlobal", "PathObj", "Objects", _tip)
|
||||
if "PathObj" not in properties:
|
||||
_tip = _tr("The object along which "
|
||||
"the copies will be distributed. "
|
||||
"It must contain 'Edges'.")
|
||||
obj.addProperty("App::PropertyLinkGlobal",
|
||||
"PathObj",
|
||||
"Objects",
|
||||
_tip)
|
||||
obj.PathObj = None
|
||||
|
||||
if not "PathSubs" in pl:
|
||||
_tip = _tr("Selected subobjects (edges) of PathObj")
|
||||
obj.addProperty("App::PropertyLinkSubListGlobal", "PathSubs", "Objects", _tip)
|
||||
if "PathSubs" not in properties:
|
||||
_tip = _tr("List of connected edges in the 'Path Object'.\n"
|
||||
"If these are present, the copies will be created "
|
||||
"along these subelements only.\n"
|
||||
"Leave this property empty to create copies along "
|
||||
"the entire 'Path Object'.")
|
||||
obj.addProperty("App::PropertyLinkSubListGlobal",
|
||||
"PathSubs",
|
||||
"Objects",
|
||||
_tip)
|
||||
obj.PathSubs = []
|
||||
|
||||
if not "Count" in pl:
|
||||
_tip = _tr("Number of copies")
|
||||
obj.addProperty("App::PropertyInteger", "Count", "Parameters", _tip)
|
||||
obj.Count = 2
|
||||
|
||||
# copy alignment properties
|
||||
if not "Align" in pl:
|
||||
_tip = _tr("Orient the copies along path")
|
||||
obj.addProperty("App::PropertyBool", "Align", "Alignment", _tip)
|
||||
if "Count" not in properties:
|
||||
_tip = _tr("Number of copies to create")
|
||||
obj.addProperty("App::PropertyInteger",
|
||||
"Count",
|
||||
"General",
|
||||
_tip)
|
||||
obj.Count = 4
|
||||
|
||||
if "Align" not in properties:
|
||||
_tip = _tr("Orient the copies along the path depending "
|
||||
"on the 'Align Mode'.\n"
|
||||
"Otherwise the copies will have the same orientation "
|
||||
"as the original Base object.")
|
||||
obj.addProperty("App::PropertyBool",
|
||||
"Align",
|
||||
"Alignment",
|
||||
_tip)
|
||||
obj.Align = False
|
||||
|
||||
if not "AlignMode" in pl:
|
||||
_tip = _tr("How to orient copies on path")
|
||||
obj.addProperty("App::PropertyEnumeration","AlignMode","Alignment", _tip)
|
||||
obj.AlignMode = ['Original','Frenet','Tangent']
|
||||
obj.AlignMode = 'Original'
|
||||
|
||||
if not "Xlate" in pl:
|
||||
_tip = _tr("Optional translation vector")
|
||||
obj.addProperty("App::PropertyVectorDistance","Xlate","Alignment", _tip)
|
||||
obj.Xlate = App.Vector(0,0,0)
|
||||
|
||||
if not "TangentVector" in pl:
|
||||
_tip = _tr("Alignment vector for Tangent mode")
|
||||
obj.addProperty("App::PropertyVector","TangentVector","Alignment", _tip)
|
||||
obj.TangentVector = App.Vector(1,0,0)
|
||||
|
||||
if not "ForceVertical" in pl:
|
||||
_tip = _tr("Force Original/Tangent modes to use VerticalVector as Z")
|
||||
obj.addProperty("App::PropertyBool","ForceVertical","Alignment", _tip)
|
||||
if "AlignMode" not in properties:
|
||||
_tip = _tr("Method to orient the copies along the path.\n"
|
||||
"- Original, X is curve tangent, Y is normal, "
|
||||
"and Z is the cross product.\n"
|
||||
"- Frenet uses a local coordinate system along "
|
||||
"the path.\n"
|
||||
"- Tangent is similar to 'Original' but the local X "
|
||||
"axis is pre-aligned to 'Tangent Vector'.\n"
|
||||
"To get better results with 'Original' and 'Tangent' "
|
||||
"you may have to set 'Force Vertical' to true.")
|
||||
obj.addProperty("App::PropertyEnumeration",
|
||||
"AlignMode",
|
||||
"Alignment",
|
||||
_tip)
|
||||
obj.AlignMode = ['Original', 'Frenet', 'Tangent']
|
||||
obj.AlignMode = 'Original'
|
||||
|
||||
if "Xlate" not in properties:
|
||||
_tip = _tr("Additional translation "
|
||||
"that will be applied to each copy.\n"
|
||||
"This is useful to adjust for the difference "
|
||||
"between shape centre and shape reference point.")
|
||||
obj.addProperty("App::PropertyVectorDistance",
|
||||
"Xlate",
|
||||
"Alignment",
|
||||
_tip)
|
||||
obj.Xlate = App.Vector(0, 0, 0)
|
||||
|
||||
if "TangentVector" not in properties:
|
||||
_tip = _tr("Alignment vector for 'Tangent' mode")
|
||||
obj.addProperty("App::PropertyVector",
|
||||
"TangentVector",
|
||||
"Alignment",
|
||||
_tip)
|
||||
obj.TangentVector = App.Vector(1, 0, 0)
|
||||
|
||||
if "ForceVertical" not in properties:
|
||||
_tip = _tr("Force use of 'Vertical Vector' as Z direction "
|
||||
"when using 'Original' or 'Tangent' align mode")
|
||||
obj.addProperty("App::PropertyBool",
|
||||
"ForceVertical",
|
||||
"Alignment",
|
||||
_tip)
|
||||
obj.ForceVertical = False
|
||||
|
||||
if not "VerticalVector" in pl:
|
||||
_tip = _tr("ForceVertical direction")
|
||||
obj.addProperty("App::PropertyVector","VerticalVector","Alignment", _tip)
|
||||
obj.VerticalVector = App.Vector(0,0,1)
|
||||
if "VerticalVector" not in properties:
|
||||
_tip = _tr("Direction of the local Z axis "
|
||||
"when 'Force Vertical' is true")
|
||||
obj.addProperty("App::PropertyVector",
|
||||
"VerticalVector",
|
||||
"Alignment",
|
||||
_tip)
|
||||
obj.VerticalVector = App.Vector(0, 0, 1)
|
||||
|
||||
if self.use_link and "ExpandArray" not in pl:
|
||||
_tip = _tr("Show array element as children object")
|
||||
obj.addProperty("App::PropertyBool","ExpandArray", "Parameters", _tip)
|
||||
if self.use_link and "ExpandArray" not in properties:
|
||||
_tip = _tr("Show the individual array elements "
|
||||
"(only for Link arrays)")
|
||||
obj.addProperty("App::PropertyBool",
|
||||
"ExpandArray",
|
||||
"General",
|
||||
_tip)
|
||||
obj.ExpandArray = False
|
||||
obj.setPropertyStatus('Shape','Transient')
|
||||
obj.setPropertyStatus('Shape', 'Transient')
|
||||
|
||||
def linkSetup(self,obj):
|
||||
def linkSetup(self, obj):
|
||||
"""Set up the object as a link object."""
|
||||
super(PathArray, self).linkSetup(obj)
|
||||
obj.configLinkProperty(ElementCount='Count')
|
||||
|
||||
def execute(self,obj):
|
||||
import Part
|
||||
import DraftGeomUtils
|
||||
def execute(self, obj):
|
||||
"""Execute when the object is created or recomputed."""
|
||||
if obj.Base and obj.PathObj:
|
||||
pl = obj.Placement #placement of whole pathArray
|
||||
pl = obj.Placement # placement of entire PathArray object
|
||||
if obj.PathSubs:
|
||||
w = self.getWireFromSubs(obj)
|
||||
elif (hasattr(obj.PathObj.Shape,'Wires') and obj.PathObj.Shape.Wires):
|
||||
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:
|
||||
App.Console.PrintLog ("PathArray.execute: path " + obj.PathObj.Name + " has no edges\n")
|
||||
_err(obj.PathObj.Name
|
||||
+ _tr(", path object doesn't have 'Edges'."))
|
||||
return
|
||||
if (hasattr(obj, "TangentVector")) and (obj.AlignMode == "Tangent") and (obj.Align):
|
||||
|
||||
if (hasattr(obj, "TangentVector")
|
||||
and obj.AlignMode == "Tangent" and obj.Align):
|
||||
basePlacement = obj.Base.Shape.Placement
|
||||
baseRotation = basePlacement.Rotation
|
||||
stdX = App.Vector(1.0, 0.0, 0.0) #default TangentVector
|
||||
if (not DraftVecUtils.equals(stdX, obj.TangentVector)):
|
||||
preRotation = App.Rotation(obj.TangentVector, stdX) #make rotation from TangentVector to X
|
||||
stdX = App.Vector(1.0, 0.0, 0.0) # default TangentVector
|
||||
|
||||
if not DraftVecUtils.equals(stdX, obj.TangentVector):
|
||||
# make rotation from TangentVector to X
|
||||
preRotation = App.Rotation(obj.TangentVector, stdX)
|
||||
netRotation = baseRotation.multiply(preRotation)
|
||||
else:
|
||||
netRotation = baseRotation
|
||||
base = calculatePlacementsOnPath(
|
||||
netRotation,w,obj.Count,obj.Xlate,obj.Align, obj.AlignMode,
|
||||
obj.ForceVertical, obj.VerticalVector)
|
||||
|
||||
base = placements_on_path(netRotation,
|
||||
w, obj.Count, obj.Xlate,
|
||||
obj.Align, obj.AlignMode,
|
||||
obj.ForceVertical,
|
||||
obj.VerticalVector)
|
||||
else:
|
||||
base = calculatePlacementsOnPath(
|
||||
obj.Base.Shape.Placement.Rotation,w,obj.Count,obj.Xlate,obj.Align, obj.AlignMode,
|
||||
obj.ForceVertical, obj.VerticalVector)
|
||||
base = placements_on_path(obj.Base.Shape.Placement.Rotation,
|
||||
w, obj.Count, obj.Xlate,
|
||||
obj.Align, obj.AlignMode,
|
||||
obj.ForceVertical,
|
||||
obj.VerticalVector)
|
||||
|
||||
return super(PathArray, self).buildShape(obj, pl, base)
|
||||
|
||||
def getWireFromSubs(self,obj):
|
||||
'''Make a wire from PathObj subelements'''
|
||||
import Part
|
||||
def getWireFromSubs(self, obj):
|
||||
"""Make a wire from PathObj subelements."""
|
||||
sl = []
|
||||
for sub in obj.PathSubs:
|
||||
edgeNames = sub[1]
|
||||
@@ -173,37 +298,46 @@ class PathArray(DraftLink):
|
||||
return Part.Wire(sl)
|
||||
|
||||
def onDocumentRestored(self, obj):
|
||||
"""Execute code when the document is restored.
|
||||
|
||||
Add properties that don't exist.
|
||||
"""
|
||||
self.migrate_attributes(obj)
|
||||
self.setProperties(obj)
|
||||
self.set_properties(obj)
|
||||
|
||||
if self.use_link:
|
||||
self.linkSetup(obj)
|
||||
else:
|
||||
obj.setPropertyStatus('Shape','-Transient')
|
||||
obj.setPropertyStatus('Shape', '-Transient')
|
||||
|
||||
if obj.Shape.isNull():
|
||||
if getattr(obj,'PlacementList',None):
|
||||
self.buildShape(obj,obj.Placement,obj.PlacementList)
|
||||
if getattr(obj, 'PlacementList', None):
|
||||
self.buildShape(obj, obj.Placement, obj.PlacementList)
|
||||
else:
|
||||
self.execute(obj)
|
||||
|
||||
|
||||
_PathArray = PathArray
|
||||
|
||||
def calculatePlacementsOnPath(shapeRotation, pathwire, count, xlate, align,
|
||||
mode = 'Original', forceNormal=False, normalOverride=None):
|
||||
"""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)
|
||||
|
||||
def placements_on_path(shapeRotation, pathwire, count, xlate, align,
|
||||
mode='Original', forceNormal=False,
|
||||
normalOverride=None):
|
||||
"""Calculate the placements of a shape along a given path.
|
||||
|
||||
Each copy will be distributed evenly.
|
||||
"""
|
||||
closedpath = DraftGeomUtils.isReallyClosed(pathwire)
|
||||
normal = DraftGeomUtils.getNormal(pathwire)
|
||||
|
||||
if forceNormal and normalOverride:
|
||||
normal = normalOverride
|
||||
normal = normalOverride
|
||||
|
||||
path = Part.__sortEdges__(pathwire.Edges)
|
||||
ends = []
|
||||
cdist = 0
|
||||
|
||||
for e in path: # find cumulative edge end distance
|
||||
for e in path: # find cumulative edge end distance
|
||||
cdist += e.Length
|
||||
ends.append(cdist)
|
||||
|
||||
@@ -211,15 +345,20 @@ def calculatePlacementsOnPath(shapeRotation, pathwire, count, xlate, align,
|
||||
|
||||
# place the start shape
|
||||
pt = path[0].Vertexes[0].Point
|
||||
placements.append(calculatePlacement(
|
||||
shapeRotation, path[0], 0, pt, xlate, align, normal, mode, forceNormal))
|
||||
_place = calculate_placement(shapeRotation,
|
||||
path[0], 0, pt, xlate, align, normal,
|
||||
mode, forceNormal)
|
||||
placements.append(_place)
|
||||
|
||||
# closed path doesn't need shape on last vertex
|
||||
if not(closedpath):
|
||||
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, mode, forceNormal))
|
||||
_place = calculate_placement(shapeRotation,
|
||||
path[-1], path[-1].Length,
|
||||
pt, xlate, align, normal,
|
||||
mode, forceNormal)
|
||||
placements.append(_place)
|
||||
|
||||
if count < 3:
|
||||
return placements
|
||||
@@ -245,24 +384,35 @@ def calculatePlacementsOnPath(shapeRotation, pathwire, count, xlate, align,
|
||||
# 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))
|
||||
pt = path[iend].valueAt(get_parameter_from_v0(path[iend], offset))
|
||||
|
||||
placements.append(calculatePlacement(
|
||||
shapeRotation, path[iend], offset, pt, xlate, align, normal, mode, forceNormal))
|
||||
_place = calculate_placement(shapeRotation,
|
||||
path[iend], offset,
|
||||
pt, xlate, align, normal,
|
||||
mode, forceNormal)
|
||||
placements.append(_place)
|
||||
|
||||
travel += step
|
||||
|
||||
return placements
|
||||
|
||||
def calculatePlacement(globalRotation, edge, offset, RefPt, xlate, align, normal=None,
|
||||
mode = 'Original', overrideNormal=False):
|
||||
"""Orient shape to a local coord system (tangent, normal, binormal) at parameter offset (normally length)"""
|
||||
import functools
|
||||
# http://en.wikipedia.org/wiki/Euler_angles (previous version)
|
||||
# http://en.wikipedia.org/wiki/Quaternions
|
||||
# start with null Placement point so _tr goes to right place.
|
||||
|
||||
calculatePlacementsOnPath = placements_on_path
|
||||
|
||||
|
||||
def calculate_placement(globalRotation,
|
||||
edge, offset, RefPt, xlate, align, normal=None,
|
||||
mode='Original', overrideNormal=False):
|
||||
"""Orient shape to a local coordinate system (tangent, normal, binormal).
|
||||
|
||||
Orient shape at parameter offset, normally length.
|
||||
|
||||
http://en.wikipedia.org/wiki/Euler_angles (previous version)
|
||||
http://en.wikipedia.org/wiki/Quaternions
|
||||
"""
|
||||
# Start with a null Placement so the translation goes to the right place.
|
||||
# Then apply the global orientation.
|
||||
placement = App.Placement()
|
||||
# preserve global orientation
|
||||
placement.Rotation = globalRotation
|
||||
|
||||
placement.move(RefPt + xlate)
|
||||
@@ -271,65 +421,77 @@ def calculatePlacement(globalRotation, edge, offset, RefPt, xlate, align, normal
|
||||
|
||||
nullv = App.Vector(0, 0, 0)
|
||||
defNormal = App.Vector(0.0, 0.0, 1.0)
|
||||
if not normal is None:
|
||||
if normal:
|
||||
defNormal = normal
|
||||
|
||||
try:
|
||||
t = edge.tangentAt(getParameterFromV0(edge, offset))
|
||||
t = edge.tangentAt(get_parameter_from_v0(edge, offset))
|
||||
t.normalize()
|
||||
except:
|
||||
_msg("Draft CalculatePlacement - Cannot calculate Path tangent. Copy not aligned\n")
|
||||
_wrn(_tr("Cannot calculate path tangent. Copy not aligned."))
|
||||
return placement
|
||||
|
||||
if (mode == 'Original') or (mode == 'Tangent'):
|
||||
if mode in ('Original', 'Tangent'):
|
||||
if normal is None:
|
||||
n = defNormal
|
||||
n = defNormal
|
||||
else:
|
||||
n = normal
|
||||
n.normalize()
|
||||
|
||||
try:
|
||||
b = t.cross(n)
|
||||
b.normalize()
|
||||
except: # weird special case. tangent & normal parallel
|
||||
except:
|
||||
# weird special case, tangent and normal parallel
|
||||
b = nullv
|
||||
_msg("PathArray computePlacement - parallel tangent, normal. Copy not aligned\n")
|
||||
_wrn(_tr("Tangent and normal are parallel. Copy not aligned."))
|
||||
return placement
|
||||
|
||||
if overrideNormal:
|
||||
priority = "XZY"
|
||||
newRot = App.Rotation(t, b, n, priority); #t/x, b/y, n/z
|
||||
else:
|
||||
priority = "XZY" #must follow X, try to follow Z, Y is what it is
|
||||
newRot = App.Rotation(t, n, b, priority);
|
||||
newRot = App.Rotation(t, b, n, priority) # t/x, b/y, n/z
|
||||
else:
|
||||
# must follow X, try to follow Z, Y is what it is
|
||||
priority = "XZY"
|
||||
newRot = App.Rotation(t, n, b, priority)
|
||||
|
||||
elif mode == 'Frenet':
|
||||
try:
|
||||
n = edge.normalAt(getParameterFromV0(edge, offset))
|
||||
n = edge.normalAt(get_parameter_from_v0(edge, offset))
|
||||
n.normalize()
|
||||
except App.Base.FreeCADError: # no/infinite normals here
|
||||
except App.Base.FreeCADError: # no/infinite normals here
|
||||
n = defNormal
|
||||
_msg("PathArray computePlacement - Cannot calculate Path normal, using default\n")
|
||||
_msg(_tr("Cannot calculate path normal, using default."))
|
||||
|
||||
try:
|
||||
b = t.cross(n)
|
||||
b.normalize()
|
||||
except:
|
||||
b = nullv
|
||||
_msg("Draft PathArray.orientShape - Cannot calculate Path biNormal. Copy not aligned\n")
|
||||
_wrn(_tr("Cannot calculate path binormal. Copy not aligned."))
|
||||
return placement
|
||||
priority = "XZY"
|
||||
newRot = App.Rotation(t, n, b, priority); #t/x, n/y, b/z
|
||||
|
||||
priority = "XZY"
|
||||
newRot = App.Rotation(t, n, b, priority) # t/x, n/y, b/z
|
||||
else:
|
||||
_msg(_tr("AlignMode {} is not implemented".format(mode)))
|
||||
return placement
|
||||
|
||||
#have valid t, n, b
|
||||
|
||||
# Have valid tangent, normal, binormal
|
||||
newGRot = newRot.multiply(globalRotation)
|
||||
|
||||
placement.Rotation = newGRot
|
||||
return placement
|
||||
|
||||
def getParameterFromV0(edge, offset):
|
||||
"""return parameter at distance offset from edge.Vertexes[0]
|
||||
sb method in Part.TopoShapeEdge???"""
|
||||
|
||||
calculatePlacement = calculate_placement
|
||||
|
||||
|
||||
def get_parameter_from_v0(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
|
||||
|
||||
@@ -340,4 +502,7 @@ def getParameterFromV0(edge, offset):
|
||||
# this edge is right way around
|
||||
length = offset
|
||||
|
||||
return (edge.getParameterByLength(length))
|
||||
return edge.getParameterByLength(length)
|
||||
|
||||
|
||||
getParameterFromV0 = get_parameter_from_v0
|
||||
|
||||
@@ -468,11 +468,13 @@ class DraftModification(unittest.TestCase):
|
||||
|
||||
number = 4
|
||||
translation = Vector(0, 1, 0)
|
||||
subelements = "Edge1"
|
||||
align = False
|
||||
_msg(" Path Array")
|
||||
_msg(" number={}, translation={}".format(number, translation))
|
||||
_msg(" align={}".format(align))
|
||||
obj = Draft.make_path_array(poly, wire, number, translation, align)
|
||||
_msg(" subelements={}, align={}".format(subelements, align))
|
||||
obj = Draft.make_path_array(poly, wire, number,
|
||||
translation, subelements, align)
|
||||
self.assertTrue(obj, "'{}' failed".format(operation))
|
||||
|
||||
def test_point_array(self):
|
||||
|
||||
Reference in New Issue
Block a user