Merge pull request #412 from mlampert/HoldingTags
Path: Holding tags dressup, partially fixes issue 2751
This commit is contained in:
commit
7943ce4ce4
|
@ -454,7 +454,7 @@ def findIntersection(edge1,edge2,infinite1=False,infinite2=False,ex1=False,ex2=F
|
|||
|
||||
return int
|
||||
else:
|
||||
# print("DraftGeomUtils: Unsupported curve type: (" + str(edge1.Curve) + ", " + str(edge2.Curve) + ")")
|
||||
print("DraftGeomUtils: Unsupported curve type: (" + str(edge1.Curve) + ", " + str(edge2.Curve) + ")")
|
||||
return []
|
||||
|
||||
def wiresIntersect(wire1,wire2):
|
||||
|
|
|
@ -26,6 +26,7 @@ SET(PathScripts_SRCS
|
|||
PathScripts/PathDressup.py
|
||||
PathScripts/PathDressupDogbone.py
|
||||
PathScripts/PathDressupDragknife.py
|
||||
PathScripts/PathDressupHoldingTags.py
|
||||
PathScripts/PathDrilling.py
|
||||
PathScripts/PathEngrave.py
|
||||
PathScripts/PathFacePocket.py
|
||||
|
@ -73,6 +74,7 @@ SET(PathScripts_SRCS
|
|||
PathScripts/slic3r_pre.py
|
||||
PathTests/PathTestUtils.py
|
||||
PathTests/TestPathDepthParams.py
|
||||
PathTests/TestPathDressupHoldingTags.py
|
||||
PathTests/TestPathGeom.py
|
||||
PathTests/TestPathPost.py
|
||||
PathTests/__init__.py
|
||||
|
|
|
@ -53,6 +53,7 @@
|
|||
<file>panels/DogboneEdit.ui</file>
|
||||
<file>panels/DrillingEdit.ui</file>
|
||||
<file>panels/EngraveEdit.ui</file>
|
||||
<file>panels/HoldingTagsEdit.ui</file>
|
||||
<file>panels/JobEdit.ui</file>
|
||||
<file>panels/MillFaceEdit.ui</file>
|
||||
<file>panels/PocketEdit.ui</file>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>352</width>
|
||||
<width>376</width>
|
||||
<height>387</height>
|
||||
</rect>
|
||||
</property>
|
||||
|
@ -27,8 +27,8 @@
|
|||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>334</width>
|
||||
<height>340</height>
|
||||
<width>358</width>
|
||||
<height>333</height>
|
||||
</rect>
|
||||
</property>
|
||||
<attribute name="label">
|
||||
|
|
159
src/Mod/Path/Gui/Resources/panels/HoldingTagsEdit.ui
Normal file
159
src/Mod/Path/Gui/Resources/panels/HoldingTagsEdit.ui
Normal file
|
@ -0,0 +1,159 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>TaskPanel</class>
|
||||
<widget class="QWidget" name="TaskPanel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>363</width>
|
||||
<height>530</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Holding Tags</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="2" column="0">
|
||||
<widget class="QToolBox" name="toolBox">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::NoFrame</enum>
|
||||
</property>
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="tbpTags">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>345</width>
|
||||
<height>476</height>
|
||||
</rect>
|
||||
</property>
|
||||
<attribute name="label">
|
||||
<string>Tags</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QWidget" name="widget_2" native="true">
|
||||
<layout class="QFormLayout">
|
||||
<property name="fieldGrowthPolicy">
|
||||
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Width</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QDoubleSpinBox" name="dsbWidth">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Specify the resulting width of tags at the base.</p><p>The initial default width is based on the longest edge found in the base path.</p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Height</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QDoubleSpinBox" name="dsbHeight">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Height of holding tags.</p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Angle </string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QDoubleSpinBox" name="dsbAngle">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Angle of ascend and descend of the tool for the holding tag cutout.</p><p><br/></p><p>If the angle is too flat for the given width to reach the specified height the resulting tag will have a triangular shape and not as high as specified.</p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QListWidget" name="lwTags">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>List of current tags.</p><p>Edit coordinates to move tag or invoker snapper tool.</p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="widget" native="true">
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QPushButton" name="pbDelete">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Delete</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QPushButton" name="pbAdd">
|
||||
<property name="text">
|
||||
<string>Add...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>Auto Generate</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_3">
|
||||
<item row="1" column="1">
|
||||
<widget class="QSpinBox" name="sbCount"/>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QPushButton" name="pbGenerate">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Replace All</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Number of Tags</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
|
@ -73,6 +73,7 @@ class PathWorkbench (Workbench):
|
|||
from PathScripts import PathProfileEdges
|
||||
from PathScripts import PathDressupDogbone
|
||||
from PathScripts import PathMillFace
|
||||
from PathScripts import PathDressupHoldingTags
|
||||
import PathCommands
|
||||
|
||||
# build commands list
|
||||
|
@ -82,7 +83,7 @@ class PathWorkbench (Workbench):
|
|||
twodopcmdlist = ["Path_Contour", "Path_Profile", "Path_Profile_Edges", "Path_Pocket", "Path_Drilling", "Path_Engrave", "Path_MillFace"]
|
||||
threedopcmdlist = ["Path_Surfacing"]
|
||||
modcmdlist = ["Path_Copy", "Path_CompoundExtended", "Path_Array", "Path_SimpleCopy" ]
|
||||
dressupcmdlist = ["PathDressup_Dogbone", "PathDressup_DragKnife"]
|
||||
dressupcmdlist = ["PathDressup_Dogbone", "PathDressup_DragKnife", "PathDressup_HoldingTags"]
|
||||
extracmdlist = ["Path_SelectLoop"]
|
||||
#modcmdmore = ["Path_Hop",]
|
||||
#remotecmdlist = ["Path_Remote"]
|
||||
|
@ -134,8 +135,8 @@ class PathWorkbench (Workbench):
|
|||
if len(FreeCADGui.Selection.getSelection()) == 1:
|
||||
if FreeCADGui.Selection.getSelection()[0].isDerivedFrom("Path::Feature"):
|
||||
self.appendContextMenu("", ["Path_Inspect"])
|
||||
if FreeCADGui.Selection.getSelection()[0].Name in ["Profile", "Contour"]:
|
||||
self.appendContextMenu("", ["Add_Tag"])
|
||||
if "Profile" or "Contour" in FreeCADGui.Selection.getSelection()[0].Name:
|
||||
#self.appendContextMenu("", ["Add_Tag"])
|
||||
self.appendContextMenu("", ["Set_StartPoint"])
|
||||
self.appendContextMenu("", ["Set_EndPoint"])
|
||||
if "Remote" in FreeCADGui.Selection.getSelection()[0].Name:
|
||||
|
|
1042
src/Mod/Path/PathScripts/PathDressupHoldingTags.py
Normal file
1042
src/Mod/Path/PathScripts/PathDressupHoldingTags.py
Normal file
File diff suppressed because it is too large
Load Diff
|
@ -63,7 +63,7 @@ class Side:
|
|||
class PathGeom:
|
||||
"""Class to transform Path Commands into Edges and Wire and back again.
|
||||
The interface might eventuallly become part of Path itself."""
|
||||
CmdMoveFast = ['G0', 'G00']
|
||||
CmdMoveRapid = ['G0', 'G00']
|
||||
CmdMoveStraight = ['G1', 'G01']
|
||||
CmdMoveCW = ['G2', 'G02']
|
||||
CmdMoveCCW = ['G3', 'G03']
|
||||
|
@ -82,6 +82,18 @@ class PathGeom:
|
|||
Return True if two points are roughly identical (see also isRoughly)."""
|
||||
return cls.isRoughly(p1.x, p2.x, error) and cls.isRoughly(p1.y, p2.y, error) and cls.isRoughly(p1.z, p2.z, error)
|
||||
|
||||
@classmethod
|
||||
def edgesMatch(cls, e0, e1, error=0.0000001):
|
||||
"""(e0, e1, [error=0.0000001]
|
||||
Return true if the edges start and end at the same point and have the same type of curve."""
|
||||
if type(e0.Curve) != type(e1.Curve):
|
||||
return False
|
||||
if not cls.pointsCoincide(e0.valueAt(e0.FirstParameter), e1.valueAt(e1.FirstParameter)):
|
||||
return False
|
||||
if not cls.pointsCoincide(e0.valueAt(e0.LastParameter), e1.valueAt(e1.LastParameter)):
|
||||
return False
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def edgeConnectsTo(cls, edge, vector):
|
||||
"""(edge, vector)
|
||||
|
@ -89,13 +101,13 @@ class PathGeom:
|
|||
return cls.pointsCoincide(edge.valueAt(edge.FirstParameter), vector) or cls.pointsCoincide(edge.valueAt(edge.LastParameter), vector)
|
||||
|
||||
@classmethod
|
||||
def getAngle(cls, vertex):
|
||||
"""(vertex)
|
||||
Returns the angle [-pi,pi] of a vertex using the X-axis as the reference.
|
||||
def getAngle(cls, vector):
|
||||
"""(vector)
|
||||
Returns the angle [-pi,pi] of a vector using the X-axis as the reference.
|
||||
Positive angles for vertexes in the upper hemishpere (positive y values)
|
||||
and negative angles for the lower hemishpere."""
|
||||
a = vertex.getAngle(FreeCAD.Vector(1,0,0))
|
||||
if vertex.y < 0:
|
||||
a = vector.getAngle(FreeCAD.Vector(1,0,0))
|
||||
if vector.y < 0:
|
||||
return -a
|
||||
return a
|
||||
|
||||
|
@ -128,13 +140,76 @@ class PathGeom:
|
|||
Convenience function to return the projection of the Vector in the XY-plane."""
|
||||
return Vector(point.x, point.y, 0)
|
||||
|
||||
@classmethod
|
||||
def cmdsForEdge(cls, edge, flip = False, useHelixForBSpline = True):
|
||||
"""(edge, flip = False, useHelixForBSpline = True) -> List(Path.Command)
|
||||
Returns a list of Path.Command representing the given edge.
|
||||
If flip is True the edge is considered to be backwards.
|
||||
If useHelixForBSpline is True an Edge based on a BSplineCurve is considered
|
||||
to represent a helix and results in G2 or G3 command. Otherwise edge has
|
||||
no direct Path.Command mapping and will be approximated by straight segments.
|
||||
Approximation is also the approach for edges that are neither straight lines
|
||||
nor arcs (nor helixes)."""
|
||||
pt = edge.valueAt(edge.LastParameter) if not flip else edge.valueAt(edge.FirstParameter)
|
||||
params = {'X': pt.x, 'Y': pt.y, 'Z': pt.z}
|
||||
if type(edge.Curve) == Part.Line or type(edge.Curve) == Part.LineSegment:
|
||||
commands = [Path.Command('G1', params)]
|
||||
else:
|
||||
if not flip:
|
||||
p1 = edge.valueAt(edge.FirstParameter)
|
||||
p3 = pt
|
||||
else:
|
||||
p1 = pt
|
||||
p3 = edge.valueAt(edge.LastParameter)
|
||||
p2 = edge.valueAt((edge.FirstParameter + edge.LastParameter)/2)
|
||||
if type(edge.Curve) == Part.Circle or (useHelixForBSpline and type(edge.Curve) == Part.BSplineCurve):
|
||||
if Side.Left == Side.of(p2 - p1, p3 - p2):
|
||||
cmd = 'G3'
|
||||
else:
|
||||
cmd = 'G2'
|
||||
#print("**** (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)" % (p1.x, p1.y, p1.z, p2.x, p2.y, p2.z, p3.x, p3.y, p3.z))
|
||||
pd = Part.Circle(PathGeom.xy(p1), PathGeom.xy(p2), PathGeom.xy(p3)).Center
|
||||
|
||||
pa = PathGeom.xy(p1)
|
||||
pb = PathGeom.xy(p2)
|
||||
pc = PathGeom.xy(p3)
|
||||
#print("**** (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)" % (pa.x, pa.y, pa.z, pc.x, pc.y, pc.z))
|
||||
#print("**** (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)" % (pb.x, pb.y, pb.z, pd.x, pd.y, pd.z))
|
||||
offset = Part.Circle(PathGeom.xy(p1), PathGeom.xy(p2), PathGeom.xy(p3)).Center - p1
|
||||
#print("**** (%.2f, %.2f, %.2f)" % (offset.x, offset.y, offset.z))
|
||||
params.update({'I': offset.x, 'J': offset.y, 'K': (p3.z - p1.z)/2})
|
||||
commands = [ Path.Command(cmd, params) ]
|
||||
else:
|
||||
eStraight = Part.Edge(Part.LineSegment(p1, p3))
|
||||
esP2 = eStraight.valueAt((eStraight.FirstParameter + eStraight.LastParameter)/2)
|
||||
deviation = (p2 - esP2).Length
|
||||
if cls.isRoughly(deviation, 0):
|
||||
return [ Path.Command('G1', {'X': p3.x, 'Y': p3.y, 'Z': p3.z}) ]
|
||||
# at this point pixellation is all we can do
|
||||
commands = []
|
||||
segments = int(math.ceil((deviation / eStraight.Length) * 1000))
|
||||
#print("**** pixellation with %d segments" % segments)
|
||||
dParameter = (edge.LastParameter - edge.FirstParameter) / segments
|
||||
for i in range(0, segments):
|
||||
if flip:
|
||||
p = edge.valueAt(edge.LastParameter - (i + 1) * dParameter)
|
||||
else:
|
||||
p = edge.valueAt(edge.FirstParameter + (i + 1) * dParameter)
|
||||
cmd = Path.Command('G1', {'X': p.x, 'Y': p.y, 'Z': p.z})
|
||||
#print("***** %s" % cmd)
|
||||
commands.append(cmd)
|
||||
#print commands
|
||||
return commands
|
||||
|
||||
@classmethod
|
||||
def edgeForCmd(cls, cmd, startPoint):
|
||||
"""(cmd, startPoint).
|
||||
Returns an Edge representing the given command, assuming a given startPoint."""
|
||||
|
||||
endPoint = cls.commandEndPoint(cmd, startPoint)
|
||||
if (cmd.Name in cls.CmdMoveStraight) or (cmd.Name in cls.CmdMoveFast):
|
||||
if (cmd.Name in cls.CmdMoveStraight) or (cmd.Name in cls.CmdMoveRapid):
|
||||
if cls.pointsCoincide(startPoint, endPoint):
|
||||
return None
|
||||
return Part.Edge(Part.LineSegment(startPoint, endPoint))
|
||||
|
||||
if cmd.Name in cls.CmdMoveArc:
|
||||
|
@ -184,13 +259,16 @@ class PathGeom:
|
|||
"""(path, [startPoint=Vector(0,0,0)])
|
||||
Returns a wire representing all move commands found in the given path."""
|
||||
edges = []
|
||||
rapid = []
|
||||
if hasattr(path, "Commands"):
|
||||
for cmd in path.Commands:
|
||||
edge = cls.edgeForCmd(cmd, startPoint)
|
||||
if edge:
|
||||
if cmd.Name in cls.CmdMoveRapid:
|
||||
rapid.append(edge)
|
||||
edges.append(edge)
|
||||
startPoint = cls.commandEndPoint(cmd, startPoint)
|
||||
return Part.Wire(edges)
|
||||
return (Part.Wire(edges), rapid)
|
||||
|
||||
@classmethod
|
||||
def wiresForPath(cls, path, startPoint = FreeCAD.Vector(0, 0, 0)):
|
||||
|
@ -203,7 +281,7 @@ class PathGeom:
|
|||
if cmd.Name in cls.CmdMove:
|
||||
edges.append(cls.edgeForCmd(cmd, startPoint))
|
||||
startPoint = cls.commandEndPoint(cmd, startPoint)
|
||||
elif cmd.Name in cls.CmdMoveFast:
|
||||
elif cmd.Name in cls.CmdMoveRapid:
|
||||
wires.append(Part.Wire(edges))
|
||||
edges = []
|
||||
startPoint = cls.commandEndPoint(cmd, startPoint)
|
||||
|
@ -211,3 +289,81 @@ class PathGeom:
|
|||
wires.append(Part.Wire(edges))
|
||||
return wires
|
||||
|
||||
@classmethod
|
||||
def arcToHelix(cls, edge, z0, z1):
|
||||
"""(edge, z0, z1)
|
||||
Assuming edge is an arc it'll return a helix matching the arc starting at z0 and rising/falling to z1."""
|
||||
|
||||
|
||||
p1 = edge.valueAt(edge.FirstParameter)
|
||||
p2 = edge.valueAt(edge.LastParameter)
|
||||
|
||||
cmd = cls.cmdsForEdge(edge)[0]
|
||||
params = cmd.Parameters
|
||||
params.update({'Z': z1, 'K': (z1 - z0)/2})
|
||||
command = Path.Command(cmd.Name, params)
|
||||
|
||||
#print("- (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f): %.2f:%.2f" % (edge.Vertexes[0].X, edge.Vertexes[0].Y, edge.Vertexes[0].Z, edge.Vertexes[1].X, edge.Vertexes[1].Y, edge.Vertexes[1].Z, z0, z1))
|
||||
#print("- %s -> %s" % (cmd, command))
|
||||
|
||||
return cls.edgeForCmd(command, FreeCAD.Vector(p1.x, p1.y, z0))
|
||||
|
||||
|
||||
@classmethod
|
||||
def helixToArc(cls, edge, z = 0):
|
||||
"""(edge, z=0)
|
||||
Returns the projection of the helix onto the XY-plane with a given offset."""
|
||||
p1 = edge.valueAt(edge.FirstParameter)
|
||||
p2 = edge.valueAt((edge.FirstParameter + edge.LastParameter)/2)
|
||||
p3 = edge.valueAt(edge.LastParameter)
|
||||
p01 = FreeCAD.Vector(p1.x, p1.y, z)
|
||||
p02 = FreeCAD.Vector(p2.x, p2.y, z)
|
||||
p03 = FreeCAD.Vector(p3.x, p3.y, z)
|
||||
return Part.Edge(Part.Arc(p01, p02, p03))
|
||||
|
||||
@classmethod
|
||||
def splitArcAt(cls, edge, pt):
|
||||
"""(edge, pt)
|
||||
Returns a list of 2 edges which together form the original arc split at the given point.
|
||||
The Vector pt has to represnt a point on the given arc."""
|
||||
p1 = edge.valueAt(edge.FirstParameter)
|
||||
p2 = pt
|
||||
p3 = edge.valueAt(edge.LastParameter)
|
||||
edges = []
|
||||
|
||||
p = edge.Curve.parameter(p2)
|
||||
#print("splitArcAt(%.2f, %.2f, %.2f): %.2f - %.2f - %.2f" % (pt.x, pt.y, pt.z, edge.FirstParameter, p, edge.LastParameter))
|
||||
|
||||
p12 = edge.Curve.value((edge.FirstParameter + p)/2)
|
||||
p23 = edge.Curve.value((p + edge.LastParameter)/2)
|
||||
#print("splitArcAt: p12=(%.2f, %.2f, %.2f) p23=(%.2f, %.2f, %.2f)" % (p12.x, p12.y, p12.z, p23.x, p23.y, p23.z))
|
||||
|
||||
edges.append(Part.Edge(Part.Arc(p1, p12, p2)))
|
||||
edges.append(Part.Edge(Part.Arc(p2, p23, p3)))
|
||||
|
||||
return edges
|
||||
|
||||
@classmethod
|
||||
def splitEdgeAt(cls, edge, pt):
|
||||
"""(edge, pt)
|
||||
Returns a list of 2 edges, forming the original edge split at the given point.
|
||||
The results are undefined if the Vector representing the point is not part of the edge."""
|
||||
# I could not get the OCC parameterAt and split to work ...
|
||||
# pt HAS to be on the edge, otherwise the results are undefined
|
||||
p1 = edge.valueAt(edge.FirstParameter)
|
||||
p2 = pt
|
||||
p3 = edge.valueAt(edge.LastParameter)
|
||||
edges = []
|
||||
|
||||
if type(edge.Curve) == Part.Line or type(edge.Curve) == Part.LineSegment:
|
||||
# it's a line
|
||||
return [Part.Edge(Part.LineSegment(p1, p2)), Part.Edge(Part.LineSegment(p2, p3))]
|
||||
elif type(edge.Curve) == Part.Circle:
|
||||
# it's an arc
|
||||
return cls.splitArcAt(edge, pt)
|
||||
else:
|
||||
# it's a helix
|
||||
arc = cls.helixToArc(edge, 0)
|
||||
aes = cls.splitArcAt(arc, FreeCAD.Vector(pt.x, pt.y, 0))
|
||||
return [cls.arcToHelix(aes[0], p1.z, p2.z), cls.arcToHelix(aes[1], p2.z, p3.z)]
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@ import Part
|
|||
import math
|
||||
import unittest
|
||||
|
||||
from FreeCAD import Vector
|
||||
from PathScripts.PathGeom import Side
|
||||
|
||||
class PathTestBase(unittest.TestCase):
|
||||
|
@ -44,28 +45,39 @@ class PathTestBase(unittest.TestCase):
|
|||
|
||||
def assertLine(self, edge, pt1, pt2):
|
||||
"""Verify that edge is a line from pt1 to pt2."""
|
||||
self.assertIs(type(edge.Curve), Part.LineSegment)
|
||||
self.assertCoincide(edge.Curve.StartPoint, pt1)
|
||||
self.assertCoincide(edge.Curve.EndPoint, pt2)
|
||||
self.assertIs(type(edge.Curve), Part.Line)
|
||||
self.assertCoincide(edge.valueAt(edge.FirstParameter), pt1)
|
||||
self.assertCoincide(edge.valueAt(edge.LastParameter), pt2)
|
||||
|
||||
def assertLines(self, edgs, tail, points):
|
||||
"""Verify that the edges match the polygon resulting from points."""
|
||||
edges = list(edgs)
|
||||
if tail:
|
||||
edges.append(tail)
|
||||
self.assertEqual(len(edges), len(points) - 1)
|
||||
|
||||
for i in range(0, len(edges)):
|
||||
self.assertLine(edges[i], points[i], points[i+1])
|
||||
|
||||
def assertArc(self, edge, pt1, pt2, direction = 'CW'):
|
||||
"""Verify that edge is an arc between pt1 and pt2 with the given direction."""
|
||||
# If an Arc is wrapped into edge, then it's curve is represented as a circle
|
||||
# and not as an Arc (GeomTrimmedCurve)
|
||||
#self.assertIs(type(edge.Curve), Part.Arc)
|
||||
self.assertIs(type(edge.Curve), Part.Circle)
|
||||
self.assertCoincide(edge.valueAt(edge.FirstParameter), pt1)
|
||||
self.assertCoincide(edge.valueAt(edge.LastParameter), pt2)
|
||||
ptm = edge.valueAt((edge.LastParameter + edge.FirstParameter)/2)
|
||||
side = Side.of(pt2 - pt1, ptm - pt1)
|
||||
#print("(%.2f, %.2f) (%.2f, %.2f) (%.2f, %.2f)" % (pt1.x, pt1.y, ptm.x, ptm.y, pt2.x, pt2.y))
|
||||
#print(" (%.2f, %.2f) (%.2f, %.2f) -> %s" % ((pt2-pt1).x, (pt2-pt1).y, (ptm-pt1).x, (ptm-pt1).y, Side.toString(side)))
|
||||
#print(" (%.2f, %.2f) (%.2f, %.2f) -> (%.2f, %.2f)" % (pf.x,pf.y, pl.x,pl.y, pm.x, pmy))
|
||||
if 'CW' == direction:
|
||||
self.assertEqual(side, Side.Left)
|
||||
else:
|
||||
self.assertEqual(side, Side.Right)
|
||||
|
||||
def assertCircle(self, edge, pt, r):
|
||||
"""Verivy that edge is a circle at given location."""
|
||||
curve = edge.Curve
|
||||
self.assertIs(type(curve), Part.Circle)
|
||||
self.assertCoincide(curve.Center, Vector(pt.x, pt.y, pt.z))
|
||||
self.assertRoughly(curve.Radius, r)
|
||||
|
||||
|
||||
def assertCurve(self, edge, p1, p2, p3):
|
||||
"""Verify that the edge goes through the given 3 points, representing start, mid and end point respectively."""
|
||||
|
@ -73,3 +85,27 @@ class PathTestBase(unittest.TestCase):
|
|||
self.assertCoincide(edge.valueAt(edge.LastParameter), p3)
|
||||
self.assertCoincide(edge.valueAt((edge.FirstParameter + edge.LastParameter)/2), p2)
|
||||
|
||||
def assertCylinderAt(self, solid, pt, r, h):
|
||||
"""Verify that solid is a cylinder at the specified location."""
|
||||
self.assertEqual(len(solid.Edges), 3)
|
||||
|
||||
lid = solid.Edges[0]
|
||||
hull = solid.Edges[1]
|
||||
base = solid.Edges[2]
|
||||
|
||||
self.assertCircle(lid, Vector(pt.x, pt.y, pt.z+h), r)
|
||||
self.assertLine(hull, Vector(pt.x+r, pt.y, pt.z), Vector(pt.x+r, pt.y, pt.z+h))
|
||||
self.assertCircle(base, Vector(pt.x, pt.y, pt.z), r)
|
||||
|
||||
def assertConeAt(self, solid, pt, r1, r2, h):
|
||||
"""Verify that solid is a cone at the specified location."""
|
||||
self.assertEqual(len(solid.Edges), 3)
|
||||
|
||||
lid = solid.Edges[0]
|
||||
hull = solid.Edges[1]
|
||||
base = solid.Edges[2]
|
||||
|
||||
self.assertCircle(lid, Vector(pt.x, pt.y, pt.z+h), r2)
|
||||
self.assertLine(hull, Vector(pt.x+r1, pt.y, pt.z), Vector(pt.x+r2, pt.y, pt.z+h))
|
||||
self.assertCircle(base, Vector(pt.x, pt.y, pt.z), r1)
|
||||
|
||||
|
|
81
src/Mod/Path/PathTests/TestPathDressupHoldingTags.py
Normal file
81
src/Mod/Path/PathTests/TestPathDressupHoldingTags.py
Normal file
|
@ -0,0 +1,81 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# ***************************************************************************
|
||||
# * *
|
||||
# * Copyright (c) 2016 sliptonic <shopinthewoods@gmail.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 *
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
import FreeCAD
|
||||
import Part
|
||||
import Path
|
||||
import PathScripts
|
||||
import math
|
||||
import unittest
|
||||
|
||||
from FreeCAD import Vector
|
||||
from PathScripts.PathDressupHoldingTags import *
|
||||
from PathTests.PathTestUtils import PathTestBase
|
||||
|
||||
class TestHoldingTags(PathTestBase):
|
||||
"""Unit tests for the HoldingTags dressup."""
|
||||
|
||||
def test00(self):
|
||||
"""Check Tag origin, serialization and de-serialization."""
|
||||
tag = Tag(77, 13, 4, 5, 90, True)
|
||||
self.assertCoincide(tag.originAt(3), Vector(77, 13, 3))
|
||||
s = tag.toString()
|
||||
tagCopy = Tag.FromString(s)
|
||||
self.assertEqual(tag.x, tagCopy.x)
|
||||
self.assertEqual(tag.y, tagCopy.y)
|
||||
self.assertEqual(tag.height, tagCopy.height)
|
||||
self.assertEqual(tag.width, tagCopy.width)
|
||||
self.assertEqual(tag.enabled, tagCopy.enabled)
|
||||
|
||||
|
||||
def test01(self):
|
||||
"""Verify solid for a 90 degree tag is a cylinder."""
|
||||
tag = Tag(100, 200, 4, 5, 90, True)
|
||||
tag.createSolidsAt(17, 0)
|
||||
|
||||
self.assertIsNotNone(tag.solid)
|
||||
self.assertCylinderAt(tag.solid, Vector(100, 200, 17), 2, 5)
|
||||
|
||||
def test02(self):
|
||||
"""Verify trapezoidal tag has a cone shape with a lid."""
|
||||
tag = Tag(0, 0, 18, 5, 45, True)
|
||||
tag.createSolidsAt(0, 0)
|
||||
|
||||
self.assertIsNotNone(tag.solid)
|
||||
self.assertConeAt(tag.solid, Vector(0,0,0), 9, 4, 5)
|
||||
|
||||
def test03(self):
|
||||
"""Verify pointy cone shape of tag with pointy end if width, angle and height match up."""
|
||||
tag = Tag(0, 0, 10, 5, 45, True)
|
||||
tag.createSolidsAt(0, 0)
|
||||
self.assertIsNotNone(tag.solid)
|
||||
self.assertConeAt(tag.solid, Vector(0,0,0), 5, 0, 5)
|
||||
|
||||
def test04(self):
|
||||
"""Verify height adjustment if tag isn't wide eough for angle."""
|
||||
tag = Tag(0, 0, 5, 17, 60, True)
|
||||
tag.createSolidsAt(0, 0)
|
||||
self.assertIsNotNone(tag.solid)
|
||||
self.assertConeAt(tag.solid, Vector(0,0,0), 2.5, 0, 2.5 * math.tan((60/180.0)*math.pi))
|
||||
|
|
@ -130,12 +130,14 @@ class TestPathGeom(PathTestBase):
|
|||
commands.append(Path.Command('G0', {'X': 0}))
|
||||
commands.append(Path.Command('G1', {'Y': 0}))
|
||||
|
||||
wire = PathGeom.wireForPath(Path.Path(commands))
|
||||
wire,rapid = PathGeom.wireForPath(Path.Path(commands))
|
||||
self.assertEqual(len(wire.Edges), 4)
|
||||
self.assertLine(wire.Edges[0], Vector(0,0,0), Vector(1,0,0))
|
||||
self.assertLine(wire.Edges[1], Vector(1,0,0), Vector(1,1,0))
|
||||
self.assertLine(wire.Edges[2], Vector(1,1,0), Vector(0,1,0))
|
||||
self.assertLine(wire.Edges[3], Vector(0,1,0), Vector(0,0,0))
|
||||
self.assertEqual(len(rapid), 1)
|
||||
self.assertTrue(PathGeom.edgesMatch(rapid[0], wire.Edges[2]))
|
||||
|
||||
wires = PathGeom.wiresForPath(Path.Path(commands))
|
||||
self.assertEqual(len(wires), 2)
|
||||
|
@ -145,3 +147,96 @@ class TestPathGeom(PathTestBase):
|
|||
self.assertEqual(len(wires[1].Edges), 1)
|
||||
self.assertLine(wires[1].Edges[0], Vector(0,1,0), Vector(0,0,0))
|
||||
|
||||
|
||||
def test60(self):
|
||||
"""Verify arcToHelix returns proper helix."""
|
||||
p1 = Vector(10,-10,0)
|
||||
p2 = Vector(0,0,0)
|
||||
p3 = Vector(10,10,0)
|
||||
|
||||
e = PathGeom.arcToHelix(Part.Edge(Part.Arc(p1, p2, p3)), 0, 2)
|
||||
self.assertCurve(e, p1, p2 + Vector(0,0,1), p3 + Vector(0,0,2))
|
||||
|
||||
e = PathGeom.arcToHelix(Part.Edge(Part.Arc(p1, p2, p3)), 3, 7)
|
||||
self.assertCurve(e, p1 + Vector(0,0,3), p2 + Vector(0,0,5), p3 + Vector(0,0,7))
|
||||
|
||||
e = PathGeom.arcToHelix(Part.Edge(Part.Arc(p1, p2, p3)), 9, 1)
|
||||
self.assertCurve(e, p1 + Vector(0,0,9), p2 + Vector(0,0,5), p3 + Vector(0,0,1))
|
||||
|
||||
dz = Vector(0,0,3)
|
||||
p11 = p1 + dz
|
||||
p12 = p2 + dz
|
||||
p13 = p3 + dz
|
||||
|
||||
e = PathGeom.arcToHelix(Part.Edge(Part.Arc(p11, p12, p13)), 0, 8)
|
||||
self.assertCurve(e, p1, p2 + Vector(0,0,4), p3 + Vector(0,0,8))
|
||||
|
||||
e = PathGeom.arcToHelix(Part.Edge(Part.Arc(p11, p12, p13)), 2, -2)
|
||||
self.assertCurve(e, p1 + Vector(0,0,2), p2, p3 + Vector(0,0,-2))
|
||||
|
||||
o = 10*math.sin(math.pi/4)
|
||||
p1 = Vector(10, -10, 1)
|
||||
p2 = Vector(10 - 10*math.sin(math.pi/4), -10*math.cos(math.pi/4), 1)
|
||||
p3 = Vector(0, 0, 1)
|
||||
e = PathGeom.arcToHelix(Part.Edge(Part.Arc(p1, p2, p3)), 0, 5)
|
||||
self.assertCurve(e, Vector(10,-10,0), Vector(p2.x,p2.y,2.5), Vector(0, 0, 5))
|
||||
|
||||
|
||||
def test62(self):
|
||||
"""Verify splitArcAt returns proper subarcs."""
|
||||
p1 = Vector(10,-10,0)
|
||||
p2 = Vector(0,0,0)
|
||||
p3 = Vector(10,10,0)
|
||||
|
||||
arc = Part.Edge(Part.Arc(p1, p2, p3))
|
||||
|
||||
o = 10*math.sin(math.pi/4)
|
||||
p12 = Vector(10 - o, -o, 0)
|
||||
p23 = Vector(10 - o, +o, 0)
|
||||
|
||||
e = PathGeom.splitArcAt(arc, p2)
|
||||
self.assertCurve(e[0], p1, p12, p2)
|
||||
self.assertCurve(e[1], p2, p23, p3)
|
||||
|
||||
p34 = Vector(10 - 10*math.sin(1*math.pi/8), -10*math.cos(1*math.pi/8), 0)
|
||||
p45 = Vector(10 - 10*math.sin(5*math.pi/8), -10*math.cos(5*math.pi/8), 0)
|
||||
|
||||
e = PathGeom.splitArcAt(arc, p12)
|
||||
self.assertCurve(e[0], p1, p34, p12)
|
||||
self.assertCurve(e[1], p12, p45, p3)
|
||||
|
||||
|
||||
def test65(self):
|
||||
"""Verify splitEdgeAt."""
|
||||
e = PathGeom.splitEdgeAt(Part.Edge(Part.LineSegment(Vector(), Vector(2, 4, 6))), Vector(1, 2, 3))
|
||||
self.assertLine(e[0], Vector(), Vector(1,2,3))
|
||||
self.assertLine(e[1], Vector(1,2,3), Vector(2,4,6))
|
||||
|
||||
# split an arc
|
||||
p1 = Vector(10,-10,1)
|
||||
p2 = Vector(0,0,1)
|
||||
p3 = Vector(10,10,1)
|
||||
arc = Part.Edge(Part.Arc(p1, p2, p3))
|
||||
e = PathGeom.splitEdgeAt(arc, p2)
|
||||
o = 10*math.sin(math.pi/4)
|
||||
p12 = Vector(10 - o, -o, 1)
|
||||
p23 = Vector(10 - o, +o, 1)
|
||||
self.assertCurve(e[0], p1, p12, p2)
|
||||
self.assertCurve(e[1], p2, p23, p3)
|
||||
|
||||
|
||||
# split a helix
|
||||
p1 = Vector(10,-10,0)
|
||||
p2 = Vector(0,0,5)
|
||||
p3 = Vector(10,10,10)
|
||||
h = PathGeom.arcToHelix(arc, 0, 10)
|
||||
self.assertCurve(h, p1, p2, p3)
|
||||
|
||||
e = PathGeom.splitEdgeAt(h, p2)
|
||||
o = 10*math.sin(math.pi/4)
|
||||
p12 = Vector(10 - o, -o, 2.5)
|
||||
p23 = Vector(10 - o, +o, 7.5)
|
||||
pf = e[0].valueAt((e[0].FirstParameter + e[0].LastParameter)/2)
|
||||
pl = e[1].valueAt((e[1].FirstParameter + e[1].LastParameter)/2)
|
||||
self.assertCurve(e[0], p1, p12, p2)
|
||||
self.assertCurve(e[1], p2, p23, p3)
|
||||
|
|
|
@ -29,3 +29,4 @@ from PathTests.TestPathPost import PathPostTestCases
|
|||
from PathTests.TestPathGeom import TestPathGeom
|
||||
from PathTests.TestPathDepthParams import depthTestCases
|
||||
|
||||
from PathTests.TestPathDressupHoldingTags import TestHoldingTags
|
||||
|
|
Loading…
Reference in New Issue
Block a user