Merge pull request #7506 from Roy-043/Draft-Fix-several-path-array-issues

Draft: Fix several path array issues
This commit is contained in:
Yorik van Havre
2022-09-21 10:21:55 +02:00
committed by GitHub

View File

@@ -336,35 +336,40 @@ class PathArray(DraftLink):
self.show_and_hide(obj, prop)
def show_and_hide(self, obj, prop):
"""Show and hide the properties depending on the touched property."""
# The minus sign removes the Hidden property (show)
if prop == "Align":
"""Show and hide the properties depending on the touched property.
Note that when the array is created, some properties will change
more than once in a seemingly random order.
"""
# The minus sign removes the Hidden property (show).
if prop in ("Align", "AlignMode"):
# Check if all referenced properties are available:
for pr in ("Align", "AlignMode", "ForceVertical",
"VerticalVector", "TangentVector"):
if not hasattr(obj, pr):
return
if obj.Align:
for pr in ("AlignMode", "ForceVertical", "VerticalVector",
"TangentVector"):
obj.setPropertyStatus(pr, "-Hidden")
obj.setPropertyStatus("AlignMode", "-Hidden")
if obj.AlignMode == "Frenet":
for pr in ("ForceVertical", "VerticalVector"):
obj.setPropertyStatus(pr, "Hidden")
else:
for pr in ("ForceVertical", "VerticalVector"):
obj.setPropertyStatus(pr, "-Hidden")
if obj.AlignMode == "Tangent":
obj.setPropertyStatus("TangentVector", "-Hidden")
else:
obj.setPropertyStatus("TangentVector", "Hidden")
else:
for pr in ("AlignMode", "ForceVertical", "VerticalVector",
"TangentVector"):
for pr in ("AlignMode", "ForceVertical",
"VerticalVector", "TangentVector"):
obj.setPropertyStatus(pr, "Hidden")
if prop == "AlignMode":
if obj.AlignMode == "Original":
for pr in ("ForceVertical", "VerticalVector"):
obj.setPropertyStatus(pr, "-Hidden")
obj.setPropertyStatus("TangentVector", "Hidden")
elif obj.AlignMode == "Frenet":
for pr in ("ForceVertical", "VerticalVector",
"TangentVector"):
obj.setPropertyStatus(pr, "Hidden")
elif obj.AlignMode == "Tangent":
for pr in ("ForceVertical", "VerticalVector",
"TangentVector"):
obj.setPropertyStatus(pr, "-Hidden")
def onDocumentRestored(self, obj):
"""Execute code when the document is restored.
@@ -412,79 +417,55 @@ _PathArray = PathArray
def placements_on_path(shapeRotation, pathwire, count, xlate, align,
mode='Original', forceNormal=False,
mode="Original", forceNormal=False,
normalOverride=None):
"""Calculate the placements of a shape along a given path.
Each copy will be distributed evenly.
Copies will be distributed evenly.
"""
closedpath = DraftGeomUtils.isReallyClosed(pathwire)
normal = DraftGeomUtils.get_normal(pathwire)
# for backward compatibility with previous getNormal implementation
if normal is None:
normal = App.Vector(0, 0, 1)
if mode == "Frenet":
forceNormal = False
if forceNormal and normalOverride:
normal = normalOverride
else:
normal = DraftGeomUtils.get_normal(pathwire)
if normal is None:
normal = App.Vector(0, 0, 1)
path = Part.__sortEdges__(pathwire.Edges)
ends = []
cdist = 0
for e in path: # find cumulative edge end distance
# find cumulative edge end distance
cdist = 0
ends = []
for e in path:
cdist += e.Length
ends.append(cdist)
step = cdist / (count if DraftGeomUtils.isReallyClosed(pathwire) else count - 1)
remains = 0
travel = 0
placements = []
# place the start shape
pt = path[0].Vertexes[0].Point
_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:
# place the end shape
pt = path[-1].Vertexes[-1].Point
_place = calculate_placement(shapeRotation,
path[-1], path[-1].Length,
pt, xlate, align, normal,
mode, forceNormal)
placements.append(_place)
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):
for i in range(0, count):
# 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
else:
# avoids problems with float math travel > ends[-1]
iend = len(ends) - 1
# place shape at proper spot on proper edge
remains = ends[iend] - travel
offset = path[iend].Length - remains
pt = path[iend].valueAt(get_parameter_from_v0(path[iend], offset))
_place = calculate_placement(shapeRotation,
path[iend], offset,
pt, xlate, align, normal,
mode, forceNormal)
placements.append(_place)
place = calculate_placement(shapeRotation,
path[iend], offset,
pt, xlate, align, normal,
mode, forceNormal)
placements.append(place)
travel += step
@@ -495,86 +476,81 @@ 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.
edge, offset, RefPt, xlate, align,
normal=App.Vector(0.0, 0.0, 1.0),
mode="Original", overrideNormal=False):
"""Orient shape in the local coordinate system at parameter offset.
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.
# Default Placement:
placement = App.Placement()
placement.Rotation = globalRotation
placement.Base = RefPt + xlate
placement.move(RefPt + xlate)
if not align:
return placement
nullv = App.Vector(0, 0, 0)
defNormal = App.Vector(0.0, 0.0, 1.0)
if normal:
defNormal = normal
tol = 1e-6 # App.Rotation() tolerance is 1e-7. Shorter vectors are ignored.
nullv = App.Vector()
try:
t = edge.tangentAt(get_parameter_from_v0(edge, offset))
t.normalize()
except Exception:
_wrn(translate("draft","Cannot calculate path tangent. Copy not aligned."))
t = edge.tangentAt(get_parameter_from_v0(edge, offset))
if t.isEqual(nullv, tol):
_wrn(translate("draft", "Length of tangent vector is zero. Copy not aligned."))
return placement
if mode in ('Original', 'Tangent'):
if normal is None:
n = defNormal
else:
n = normal
n.normalize()
# If the length of the normal is zero or if it is parallel to the tangent,
# we make the vectors equal (n = t). The App.Rotation() algorithm will
# then replace the normal with a default axis.
# For the vector with the lowest App.Rotation() priority we use a null
# vector. Calculating this binormal would not make sense in the mentioned
# cases. And in all other cases calculating it is not necessary as
# App.Rotation() will ignore it.
try:
b = t.cross(n)
b.normalize()
except Exception:
# weird special case, tangent and normal parallel
b = nullv
_wrn(translate("draft","Tangent and normal are parallel. Copy not aligned."))
return placement
if mode in ("Original", "Tangent"):
n = normal
if n.isEqual(nullv, tol):
_wrn(translate("draft", "Length of normal vector is zero. Using a default axis instead."))
n = t
else:
n_nor = n.normalize()
t_nor = t.normalize()
if n_nor.isEqual(t_nor, tol) or n_nor.isEqual(t_nor.negative(), tol):
_wrn(translate("draft", "Tangent and normal vectors are parallel. Normal replaced by a default axis."))
n = t
if overrideNormal:
priority = "XZY"
newRot = App.Rotation(t, b, n, priority) # t/x, b/y, n/z
newRot = App.Rotation(t, nullv, n, "XZY") # priority = "XZY"
else:
# must follow X, try to follow Z, Y is what it is
priority = "XZY"
newRot = App.Rotation(t, n, b, priority)
newRot = App.Rotation(t, n, nullv, "XYZ") # priority = "XYZ"
elif mode == 'Frenet':
elif mode == "Frenet":
try:
n = edge.normalAt(get_parameter_from_v0(edge, offset))
n.normalize()
except App.Base.FreeCADError: # no/infinite normals here
n = defNormal
_msg(translate("draft","Cannot calculate path normal, using default."))
except App.Base.FreeCADError: # no/infinite normals here
_wrn(translate("draft", "Cannot calculate normal vector. Using the default normal instead."))
n = normal
try:
b = t.cross(n)
b.normalize()
except Exception:
b = nullv
_wrn(translate("draft","Cannot calculate path binormal. Copy not aligned."))
return placement
if n.isEqual(nullv, tol):
_wrn(translate("draft", "Length of normal vector is zero. Using a default axis instead."))
n = t
else:
n_nor = n.normalize()
t_nor = t.normalize()
if n_nor.isEqual(t_nor, tol) or n_nor.isEqual(t_nor.negative(), tol):
_wrn(translate("draft", "Tangent and normal vectors are parallel. Normal replaced by a default axis."))
n = t
newRot = App.Rotation(t, n, nullv, "XYZ") # priority = "XYZ"
priority = "XZY"
newRot = App.Rotation(t, n, b, priority) # t/x, n/y, b/z
else:
_msg(translate("draft","AlignMode {} is not implemented").format(mode))
_msg(translate("draft", "AlignMode {} is not implemented").format(mode))
return placement
# Have valid tangent, normal, binormal
newGRot = newRot.multiply(globalRotation)
placement.Rotation = newGRot
placement.Base = RefPt + newRot.multVec(xlate)
return placement