Added wire(s)ForPath functions with test.

This commit is contained in:
Markus Lampert 2016-11-28 15:52:17 -08:00
parent fbc75d9797
commit 0807eaf597
9 changed files with 62 additions and 1907 deletions

View File

@ -26,7 +26,6 @@ SET(PathScripts_SRCS
PathScripts/PathCopy.py
PathScripts/PathCustom.py
PathScripts/PathDressup.py
PathScripts/PathDressupHoldingTags.py
PathScripts/PathDrilling.py
PathScripts/PathEngrave.py
PathScripts/PathFacePocket.py
@ -73,7 +72,6 @@ SET(PathScripts_SRCS
PathScripts/opensbp_pre.py
PathScripts/rml_post.py
PathScripts/slic3r_pre.py
PathTests/TestPathDressupHoldingTags.py
PathTests/TestPathGeom.py
PathTests/TestPathPost.py
PathTests/__init__.py

View File

@ -53,7 +53,6 @@
<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>

View File

@ -1,240 +0,0 @@
<?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>352</width>
<height>387</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>334</width>
<height>311</height>
</rect>
</property>
<attribute name="label">
<string>Tags</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTableWidget" name="twTags">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="sortingEnabled">
<bool>true</bool>
</property>
<attribute name="horizontalHeaderDefaultSectionSize">
<number>80</number>
</attribute>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
<column>
<property name="text">
<string>X</string>
</property>
</column>
<column>
<property name="text">
<string>Y</string>
</property>
</column>
<column>
<property name="text">
<string>Width</string>
</property>
</column>
<column>
<property name="text">
<string>Height</string>
</property>
</column>
<column>
<property name="text">
<string>Angle</string>
</property>
</column>
</widget>
</item>
<item>
<widget class="QWidget" name="widget" native="true">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="pbDelete">
<property name="text">
<string>Delete</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pbDisable">
<property name="text">
<string>Disable</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pbAdd">
<property name="text">
<string>Add</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="tbpGenerate">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>334</width>
<height>311</height>
</rect>
</property>
<attribute name="label">
<string>Generate</string>
</attribute>
<layout class="QFormLayout" name="formLayout_2">
<item row="4" column="0">
<widget class="QLabel" name="lWidth">
<property name="text">
<string>Width</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QDoubleSpinBox" name="dsbWidth">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Width of each tag.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="lHeight">
<property name="text">
<string>Height</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QDoubleSpinBox" name="dsbHeight">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The height of the holding tag measured from the bottom of the path. By default this is set to the (estimated) height of the path.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="lAngle">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>Angle </string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QDoubleSpinBox" name="dsbAngle">
<property name="enabled">
<bool>true</bool>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Angle of tag walls.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Apply|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
<item row="8" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="0" colspan="2">
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Layout</string>
</property>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="lCount">
<property name="text">
<string>Count</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QSpinBox" name="sbCount">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Enter the number of tags you wish to have.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Note that sometimes it's necessary to enter a larger than desired count number and disable the ones tags you don't want in order to get the holding tag layout you want.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Spacing </string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QDoubleSpinBox" name="dsbSpacing"/>
</item>
</layout>
</widget>
</item>
<item row="0" column="0">
<widget class="QCheckBox" name="cbAutoApply">
<property name="text">
<string>Auto Apply</string>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -74,7 +74,6 @@ class PathWorkbench (Workbench):
from PathScripts import PathProfileEdges
from PathScripts import DogboneDressup
from PathScripts import PathMillFace
from PathScripts import PathDressupHoldingTags
import PathCommands
# build commands list
@ -84,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 = ["Dogbone_Dressup", "DragKnife_Dressup", "PathDressup_HoldingTags"]
dressupcmdlist = ["Dogbone_Dressup", "DragKnife_Dressup"]
extracmdlist = ["Path_SelectLoop"]
#modcmdmore = ["Path_Hop",]
#remotecmdlist = ["Path_Remote"]

File diff suppressed because it is too large Load Diff

View File

@ -65,11 +65,12 @@ 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']
CmdMoveStraight = ['G1', 'G01']
CmdMoveCW = ['G2', 'G02']
CmdMoveCCW = ['G3', 'G03']
CmdMoveArc = CmdMoveCW + CmdMoveCCW
CmdMove = CmdMoveStraight + CmdMoveArc
CmdMoveCW = ['G2', 'G02']
CmdMoveCCW = ['G3', 'G03']
CmdMoveArc = CmdMoveCW + CmdMoveCCW
CmdMove = CmdMoveStraight + CmdMoveArc
@classmethod
def getAngle(cls, vertex):
@ -108,11 +109,11 @@ class PathGeom:
return Vector(pt.x, pt.y, 0)
@classmethod
def edgeForCmd(cls, cmd, startPoint):
def edgeForCmd(cls, cmd, startPoint, includeFastMoves = False):
"""Returns a Curve representing the givne command, assuming a given startinPoint."""
endPoint = cls.commandEndPoint(cmd, startPoint)
if cmd.Name in cls.CmdMoveStraight:
if (cmd.Name in cls.CmdMoveStraight) or (includeFastMoves and cmd.Name in cls.CmdMoveFast):
return Part.Edge(Part.Line(startPoint, endPoint))
if cmd.Name in cls.CmdMoveArc:
@ -155,5 +156,35 @@ class PathGeom:
e = helix.Edges[0]
helix.translate(startPoint - e.valueAt(e.FirstParameter))
return helix.Edges[0]
return None
@classmethod
def wireForPath(cls, path, startPoint = FreeCAD.Vector(0, 0, 0)):
"""Returns a wire representing all move commands found in the given path."""
edges = []
if hasattr(path, "Commands"):
for cmd in path.Commands:
edge = cls.edgeForCmd(cmd, startPoint, True)
if edge:
edges.append(edge)
startPoint = cls.commandEndPoint(cmd, startPoint)
return Part.Wire(edges)
@classmethod
def wiresForPath(cls, path, startPoint = FreeCAD.Vector(0, 0, 0)):
"""Returns a collection of wires, each representing a continuous cutting Path in path."""
wires = []
if hasattr(path, "Commands"):
edges = []
for cmd in path.Commands:
if cmd.Name in cls.CmdMove:
edges.append(cls.edgeForCmd(cmd, startPoint, False))
startPoint = cls.commandEndPoint(cmd, startPoint)
elif cmd.Name in cls.CmdMoveFast:
wires.append(Part.Wire(edges))
edges = []
startPoint = cls.commandEndPoint(cmd, startPoint)
if edges:
wires.append(Part.Wire(edges))
return wires

View File

@ -1,639 +0,0 @@
# -*- 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 *
def pointsCoincide(pt1, pt2):
pt = pt1 - pt2
if math.fabs(pt.x) > slack:
return False
if math.fabs(pt.y) > slack:
return False
if math.fabs(pt.z) > slack:
return False
return True
class TagTestCaseBase(unittest.TestCase):
"""Base class for all tag test cases providing additional assert functions."""
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)
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.assertAbout(curve.Radius, r)
def assertLine(self, edge, pt1, pt2):
"""Verify that edge is a line from pt1 to pt2."""
curve = edge.Curve
self.assertIs(type(curve), Part.Line)
self.assertCoincide(curve.StartPoint, pt1)
self.assertCoincide(curve.EndPoint, pt2)
def assertCoincide(self, pt1, pt2):
"""Verify that 2 points coincide (with tolerance)."""
self.assertAbout(pt1.x, pt2.x)
self.assertAbout(pt1.y, pt2.y)
self.assertAbout(pt1.z, pt2.z)
def assertAbout(self, v1, v2):
"""Verify that 2 values are the same (accounting for float imprecision)."""
if math.fabs(v1 - v2) > slack:
self.fail("%f != %f" % (v1, v2))
def assertLines(self, edgs, tail, points):
"""Check that there are 5 edges forming a trapezoid."""
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])
class TestTag00BasicHolding(TagTestCaseBase):
"""Some basid test cases."""
def test00(self,x=1, y=1):
"""Test getAngle."""
self.assertAbout(getAngle(FreeCAD.Vector( 1*x, 0*y, 0)), 0)
self.assertAbout(getAngle(FreeCAD.Vector( 1*x, 1*y, 0)), math.pi/4)
self.assertAbout(getAngle(FreeCAD.Vector( 0*x, 1*y, 0)), math.pi/2)
self.assertAbout(getAngle(FreeCAD.Vector(-1*x, 1*y, 0)), 3*math.pi/4)
self.assertAbout(getAngle(FreeCAD.Vector(-1*x, 0*y, 0)), math.pi)
self.assertAbout(getAngle(FreeCAD.Vector(-1*x,-1*y, 0)), -3*math.pi/4)
self.assertAbout(getAngle(FreeCAD.Vector( 0*x,-1*y, 0)), -math.pi/2)
self.assertAbout(getAngle(FreeCAD.Vector( 1*x,-1*y, 0)), -math.pi/4)
def test01(self):
"""Test class Side."""
self.assertEqual(Side.of(FreeCAD.Vector( 1, 0, 0), FreeCAD.Vector( 1, 0, 0)), Side.On)
self.assertEqual(Side.of(FreeCAD.Vector( 1, 0, 0), FreeCAD.Vector(-1, 0, 0)), Side.On)
self.assertEqual(Side.of(FreeCAD.Vector( 1, 0, 0), FreeCAD.Vector( 0, 1, 0)), Side.Left)
self.assertEqual(Side.of(FreeCAD.Vector( 1, 0, 0), FreeCAD.Vector( 0,-1, 0)), Side.Right)
class TestTag01BasicTag(TagTestCaseBase): # =============
"""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 and core for a 90 degree tag are identical cylinders."""
tag = Tag(100, 200, 4, 5, 90, True)
tag.createSolidsAt(17)
self.assertIsNotNone(tag.solid)
self.assertCylinderAt(tag.solid, Vector(100, 200, 17), 2, 5)
self.assertIsNotNone(tag.core)
self.assertCylinderAt(tag.core, Vector(100, 200, 17), 2, 5)
def test02(self):
"""Verify trapezoidal tag has a cone shape with a lid, and cylinder core."""
tag = Tag(0, 0, 18, 5, 45, True)
tag.createSolidsAt(0)
self.assertIsNotNone(tag.solid)
self.assertConeAt(tag.solid, Vector(0,0,0), 9, 4, 5)
self.assertIsNotNone(tag.core)
self.assertCylinderAt(tag.core, Vector(0,0,0), 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)
self.assertIsNotNone(tag.solid)
self.assertConeAt(tag.solid, Vector(0,0,0), 5, 0, 5)
self.assertIsNone(tag.core)
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)
self.assertIsNotNone(tag.solid)
self.assertConeAt(tag.solid, Vector(0,0,0), 2.5, 0, 2.5 * math.tan((60/180.0)*math.pi))
self.assertIsNone(tag.core)
class TestTag02SquareTag(TagTestCaseBase): # =============
"""Unit tests for square tags."""
def test00(self):
"""Verify no intersection."""
tag = Tag( 0, 0, 4, 7, 90, True, 0)
pt1 = Vector(+5, 5, 0)
pt2 = Vector(-5, 5, 0)
edge = Part.Edge(Part.Line(pt1, pt2))
i = tag.intersect(edge)
self.assertIsNotNone(i)
self.assertTrue(i.isComplete())
self.assertIsNotNone(i.edges)
self.assertFalse(i.edges)
self.assertLine(i.tail, pt1, pt2)
def test01(self):
"""Verify intersection of square tag with line ending at tag start."""
tag = Tag( 0, 0, 8, 3, 90, True, 0)
edge = Part.Edge(Part.Line(Vector(5, 0, 0), Vector(4, 0, 0)))
i = tag.intersect(edge)
self.assertEqual(i.state, Tag.Intersection.P0)
self.assertEqual(len(i.edges), 1)
self.assertLine(i.edges[0], edge.Curve.StartPoint, edge.Curve.EndPoint)
self.assertIsNone(i.tail)
def test02(self):
"""Verify intersection of square tag with line ending between P1 and P2."""
tag = Tag( 0, 0, 8, 3, 90, True, 0)
edge = Part.Edge(Part.Line(Vector(5, 0, 0), Vector(1, 0, 0)))
i = tag.intersect(edge)
self.assertEqual(i.state, Tag.Intersection.P1)
self.assertEqual(len(i.edges), 3)
p1 = Vector(4, 0, 0)
p2 = Vector(4, 0, 3)
p3 = Vector(1, 0, 3)
self.assertLine(i.edges[0], edge.Curve.StartPoint, p1)
self.assertLine(i.edges[1], p1, p2)
self.assertLine(i.edges[2], p2, p3)
self.assertIsNone(i.tail)
# verify we stay in P1 if we add another segment
edge = Part.Edge(Part.Line(edge.Curve.EndPoint, Vector(0, 0, 0)))
i = i.intersect(edge)
self.assertEqual(i.state, Tag.Intersection.P1)
self.assertEqual(len(i.edges), 4)
p4 = Vector(0, 0, 3)
self.assertLine(i.edges[3], p3, p4)
self.assertIsNone(i.tail)
def test03(self):
"""Verify intesection of square tag with line ending on P2."""
tag = Tag( 0, 0, 8, 3, 90, True, 0)
edge = Part.Edge(Part.Line(Vector(5, 0, 0), Vector(-4, 0, 0)))
i = tag.intersect(edge)
self.assertEqual(i.state, Tag.Intersection.P2)
self.assertEqual(len(i.edges), 3)
p0 = edge.Curve.StartPoint
p1 = Vector( 4, 0, 0)
p2 = Vector( 4, 0, 3)
p3 = Vector(-4, 0, 3)
self.assertLine(i.edges[0], p0, p1)
self.assertLine(i.edges[1], p1, p2)
self.assertLine(i.edges[2], p2, p3)
self.assertIsNone(i.tail)
# make sure it also works if we get there not directly
edge = Part.Edge(Part.Line(Vector(5, 0, 0), Vector(0, 0, 0)))
i = tag.intersect(edge)
edge = Part.Edge(Part.Line(edge.Curve.EndPoint, Vector(-4, 0, 0)))
i = i.intersect(edge)
self.assertEqual(i.state, Tag.Intersection.P2)
self.assertEqual(len(i.edges), 4)
p2a = Vector( 0, 0, 3)
self.assertLine(i.edges[0], p0, p1)
self.assertLine(i.edges[1], p1, p2)
self.assertLine(i.edges[2], p2, p2a)
self.assertLine(i.edges[3], p2a, p3)
self.assertIsNone(i.tail)
def test04(self):
"""Verify plunge down is inserted for square tag on exit."""
tag = Tag( 0, 0, 8, 3, 90, True, 0)
edge = Part.Edge(Part.Line(Vector(5, 0, 0), Vector(-5, 0, 0)))
i = tag.intersect(edge)
self.assertEqual(i.state, Tag.Intersection.P3)
self.assertTrue(i.isComplete())
self.assertEqual(len(i.edges), 4)
p0 = edge.Curve.StartPoint
p1 = Vector( 4, 0, 0)
p2 = Vector( 4, 0, 3)
p3 = Vector(-4, 0, 3)
p4 = Vector(-4, 0, 0)
p5 = edge.Curve.EndPoint
self.assertLine(i.edges[0], p0, p1)
self.assertLine(i.edges[1], p1, p2)
self.assertLine(i.edges[2], p2, p3)
self.assertLine(i.edges[3], p3, p4)
self.assertIsNotNone(i.tail)
self.assertLine(i.tail, p4, p5)
def test05(self):
"""Verify all lines between P0 and P3 are added."""
tag = Tag( 0, 0, 4, 7, 90, True, 0)
e0 = Part.Edge(Part.Line(Vector(5, 0, 0), Vector(+2, 0, 0)))
e1 = Part.Edge(Part.Line(e0.Curve.EndPoint, Vector(+1, 0, 0)))
e2 = Part.Edge(Part.Line(e1.Curve.EndPoint, Vector(+0.5, 0, 0)))
e3 = Part.Edge(Part.Line(e2.Curve.EndPoint, Vector(-0.5, 0, 0)))
e4 = Part.Edge(Part.Line(e3.Curve.EndPoint, Vector(-1, 0, 0)))
e5 = Part.Edge(Part.Line(e4.Curve.EndPoint, Vector(-2, 0, 0)))
e6 = Part.Edge(Part.Line(e5.Curve.EndPoint, Vector(-5, 0, 0)))
i = tag
for e in [e0, e1, e2, e3, e4, e5]:
i = i.intersect(e)
self.assertFalse(i.isComplete())
i = i.intersect(e6)
self.assertTrue(i.isComplete())
pt0 = Vector(2, 0, 0)
pt1 = Vector(2, 0, 7)
pt2 = Vector(1, 0, 7)
pt3 = Vector(0.5, 0, 7)
pt4 = Vector(-0.5, 0, 7)
pt5 = Vector(-1, 0, 7)
pt6 = Vector(-2, 0, 7)
self.assertEqual(len(i.edges), 8)
self.assertLines(i.edges, i.tail, [e0.Curve.StartPoint, pt0, pt1, pt2, pt3, pt4, pt5, pt6, e6.Curve.StartPoint, e6.Curve.EndPoint])
self.assertIsNotNone(i.tail)
def test06(self):
"""Verify intersection of different z levels."""
tag = Tag( 0, 0, 4, 7, 90, True, 0)
# for all lines below 7 we get the trapezoid
for i in range(0, 7):
p0 = Vector(5, 0, i)
p1 = Vector(2, 0, i)
p2 = Vector(2, 0, 7)
p3 = Vector(-2, 0, 7)
p4 = Vector(-2, 0, i)
p5 = Vector(-5, 0, i)
edge = Part.Edge(Part.Line(p0, p5))
s = tag.intersect(edge)
self.assertTrue(s.isComplete())
self.assertLines(s.edges, s.tail, [p0, p1, p2, p3, p4, p5])
# for all edges at height or above the original line is used
for i in range(7, 9):
edge = Part.Edge(Part.Line(Vector(5, 0, i), Vector(-5, 0, i)))
s = tag.intersect(edge)
self.assertTrue(s.isComplete())
self.assertLine(s.tail, edge.Curve.StartPoint, edge.Curve.EndPoint)
class TestTag03TrapezoidTag(TagTestCaseBase): # =============
"""Unit tests for trapezoid tags."""
def test00(self):
"""Verify no intersection."""
tag = Tag( 0, 0, 8, 3, 45, True, 0)
pt1 = Vector(+5, 5, 0)
pt2 = Vector(-5, 5, 0)
edge = Part.Edge(Part.Line(pt1, pt2))
i = tag.intersect(edge)
self.assertIsNotNone(i)
self.assertTrue(i.isComplete())
self.assertIsNotNone(i.edges)
self.assertFalse(i.edges)
self.assertLine(i.tail, pt1, pt2)
def test01(self):
"""Veify intersection of trapezoid tag with line ending before P1."""
tag = Tag( 0, 0, 8, 3, 45, True, 0)
edge = Part.Edge(Part.Line(Vector(5, 0, 0), Vector(4, 0, 0)))
i = tag.intersect(edge)
self.assertEqual(i.state, Tag.Intersection.P0)
self.assertEqual(len(i.edges), 1)
self.assertLine(i.edges[0], edge.Curve.StartPoint, edge.Curve.EndPoint)
self.assertIsNone(i.tail)
# now add another segment that doesn't reach the top of the cone
edge = Part.Edge(Part.Line(edge.Curve.EndPoint, Vector(3, 0, 0)))
i = i.intersect(edge)
# still a P0 and edge fully consumed
p1 = Vector(edge.Curve.StartPoint)
p1.z = 0
p2 = Vector(edge.Curve.EndPoint)
p2.z = 1 # height of cone @ (3,0)
self.assertEqual(i.state, Tag.Intersection.P0)
self.assertEqual(len(i.edges), 2)
self.assertLine(i.edges[1], p1, p2)
self.assertIsNone(i.tail)
# add another segment to verify starting point offset
edge = Part.Edge(Part.Line(edge.Curve.EndPoint, Vector(2, 0, 0)))
i = i.intersect(edge)
# still a P0 and edge fully consumed
p3 = Vector(edge.Curve.EndPoint)
p3.z = 2 # height of cone @ (2,0)
self.assertEqual(i.state, Tag.Intersection.P0)
self.assertEqual(len(i.edges), 3)
self.assertLine(i.edges[2], p2, p3)
self.assertIsNone(i.tail)
def test02(self):
"""Verify intersection of trapezoid tag with line ending between P1 and P2"""
tag = Tag( 0, 0, 8, 3, 45, True, 0)
edge = Part.Edge(Part.Line(Vector(5, 0, 0), Vector(1, 0, 0)))
i = tag.intersect(edge)
self.assertEqual(i.state, Tag.Intersection.P1)
self.assertEqual(len(i.edges), 2)
p1 = Vector(4, 0, 0)
p2 = Vector(1, 0, 3)
self.assertLine(i.edges[0], edge.Curve.StartPoint, p1)
self.assertLine(i.edges[1], p1, p2)
self.assertIsNone(i.tail)
# verify we stay in P1 if we add another segment
edge = Part.Edge(Part.Line(edge.Curve.EndPoint, Vector(0, 0, 0)))
i = i.intersect(edge)
self.assertEqual(i.state, Tag.Intersection.P1)
self.assertEqual(len(i.edges), 3)
p3 = Vector(0, 0, 3)
self.assertLine(i.edges[2], p2, p3)
self.assertIsNone(i.tail)
def test03(self):
"""Verify intersection of trapezoid tag with edge ending on P2."""
tag = Tag( 0, 0, 8, 3, 45, True, 0)
edge = Part.Edge(Part.Line(Vector(5, 0, 0), Vector(-1, 0, 0)))
i = tag.intersect(edge)
self.assertEqual(i.state, Tag.Intersection.P2)
p0 = Vector(edge.Curve.StartPoint)
p1 = Vector(4, 0, 0)
p2 = Vector(1, 0, 3)
p3 = Vector(-1, 0, 3)
self.assertLines(i.edges, i.tail, [p0, p1, p2, p3])
self.assertIsNone(i.tail)
# make sure we get the same result if there's another edge
edge = Part.Edge(Part.Line(Vector(5, 0, 0), Vector(1, 0, 0)))
i = tag.intersect(edge)
edge = Part.Edge(Part.Line(edge.Curve.EndPoint, Vector(-1, 0, 0)))
i = i.intersect(edge)
self.assertEqual(i.state, Tag.Intersection.P2)
self.assertLines(i.edges, i.tail, [p0, p1, p2, p3])
self.assertIsNone(i.tail)
# and also if the last segment doesn't cross the entire plateau
edge = Part.Edge(Part.Line(Vector(5, 0, 0), Vector(0.5, 0, 0)))
i = tag.intersect(edge)
edge = Part.Edge(Part.Line(edge.Curve.EndPoint, Vector(-1, 0, 0)))
i = i.intersect(edge)
self.assertEqual(i.state, Tag.Intersection.P2)
p2a = Vector(0.5, 0, 3)
self.assertLines(i.edges, i.tail, [p0, p1, p2, p2a, p3])
self.assertIsNone(i.tail)
def test04(self):
"""Verify proper down plunge on trapezoid tag exit."""
tag = Tag( 0, 0, 8, 3, 45, True, 0)
edge = Part.Edge(Part.Line(Vector(5, 0, 0), Vector(-2, 0, 0)))
i = tag.intersect(edge)
self.assertEqual(i.state, Tag.Intersection.P2)
p0 = Vector(5, 0, 0)
p1 = Vector(4, 0, 0)
p2 = Vector(1, 0, 3)
p3 = Vector(-1, 0, 3)
p4 = Vector(-2, 0, 2)
self.assertLines(i.edges, i.tail, [p0, p1, p2, p3, p4])
self.assertIsNone(i.tail)
# make sure adding another segment doesn't change the state
edge = Part.Edge(Part.Line(edge.Curve.EndPoint, Vector(-3, 0, 0)))
i = i.intersect(edge)
self.assertEqual(i.state, Tag.Intersection.P2)
self.assertEqual(len(i.edges), 5)
p5 = Vector(-3, 0, 1)
self.assertLine(i.edges[4], p4, p5)
self.assertIsNone(i.tail)
# now if we complete to P3 ....
edge = Part.Edge(Part.Line(edge.Curve.EndPoint, Vector(-4, 0, 0)))
i = i.intersect(edge)
self.assertEqual(i.state, Tag.Intersection.P3)
self.assertTrue(i.isComplete())
self.assertEqual(len(i.edges), 6)
p6 = Vector(-4, 0, 0)
self.assertLine(i.edges[5], p5, p6)
self.assertIsNone(i.tail)
# verify proper operation if there is a single edge going through all
edge = Part.Edge(Part.Line(Vector(5, 0, 0), Vector(-4, 0, 0)))
i = tag.intersect(edge)
self.assertEqual(i.state, Tag.Intersection.P3)
self.assertTrue(i.isComplete())
self.assertLines(i.edges, i.tail, [p0, p1, p2, p3, p6])
self.assertIsNone(i.tail)
# verify tail is added as well
edge = Part.Edge(Part.Line(Vector(5, 0, 0), Vector(-5, 0, 0)))
i = tag.intersect(edge)
self.assertEqual(i.state, Tag.Intersection.P3)
self.assertTrue(i.isComplete())
self.assertLines(i.edges, i.tail, [p0, p1, p2, p3, p6, edge.Curve.EndPoint])
self.assertIsNotNone(i.tail)
def test05(self):
"""Verify all lines between P0 and P3 are added."""
tag = Tag( 0, 0, 8, 3, 45, True, 0)
e0 = Part.Edge(Part.Line(Vector(5, 0, 0), Vector(+4, 0, 0)))
e1 = Part.Edge(Part.Line(e0.Curve.EndPoint, Vector(+2, 0, 0)))
e2 = Part.Edge(Part.Line(e1.Curve.EndPoint, Vector(+0.5, 0, 0)))
e3 = Part.Edge(Part.Line(e2.Curve.EndPoint, Vector(-0.5, 0, 0)))
e4 = Part.Edge(Part.Line(e3.Curve.EndPoint, Vector(-1, 0, 0)))
e5 = Part.Edge(Part.Line(e4.Curve.EndPoint, Vector(-2, 0, 0)))
e6 = Part.Edge(Part.Line(e5.Curve.EndPoint, Vector(-5, 0, 0)))
i = tag
for e in [e0, e1, e2, e3, e4, e5]:
i = i.intersect(e)
self.assertFalse(i.isComplete())
i = i.intersect(e6)
self.assertTrue(i.isComplete())
p0 = Vector(4, 0, 0)
p1 = Vector(2, 0, 2)
p2 = Vector(1, 0, 3)
p3 = Vector(0.5, 0, 3)
p4 = Vector(-0.5, 0, 3)
p5 = Vector(-1, 0, 3)
p6 = Vector(-2, 0, 2)
p7 = Vector(-4, 0, 0)
self.assertLines(i.edges, i.tail, [e0.Curve.StartPoint, p0, p1, p2, p3, p4, p5, p6, p7, e6.Curve.EndPoint])
self.assertIsNotNone(i.tail)
def test06(self):
"""Verify intersection for different z levels."""
tag = Tag( 0, 0, 8, 3, 45, True, 0)
# for all lines below 3 we get the trapezoid
for i in range(0, 3):
p0 = Vector(5, 0, i)
p1 = Vector(4-i, 0, i)
p2 = Vector(1, 0, 3)
p3 = Vector(-1, 0, 3)
p4 = Vector(-4+i, 0, i)
p5 = Vector(-5, 0, i)
edge = Part.Edge(Part.Line(p0, p5))
s = tag.intersect(edge)
self.assertTrue(s.isComplete())
self.assertLines(s.edges, s.tail, [p0, p1, p2, p3, p4, p5])
# for all edges at height or above the original line is used
for i in range(3, 5):
edge = Part.Edge(Part.Line(Vector(5, 0, i), Vector(-5, 0, i)))
s = tag.intersect(edge)
self.assertTrue(s.isComplete())
self.assertLine(s.tail, edge.Curve.StartPoint, edge.Curve.EndPoint)
class TestTag04TriangularTag(TagTestCaseBase): # ========================
"""Unit tests for tags that take on a triangular shape."""
def test00(self):
"""Verify intersection of triangular tag with line ending at tag start."""
tag = Tag( 0, 0, 8, 7, 45, True, 0)
edge = Part.Edge(Part.Line(Vector(5, 0, 0), Vector(4, 0, 0)))
i = tag.intersect(edge)
self.assertEqual(i.state, Tag.Intersection.P0)
self.assertEqual(len(i.edges), 1)
self.assertLine(i.edges[0], edge.Curve.StartPoint, edge.Curve.EndPoint)
self.assertIsNone(i.tail)
def test01(self):
"""Verify intersection of triangular tag with line ending between P0 and P1."""
tag = Tag( 0, 0, 8, 7, 45, True, 0)
edge = Part.Edge(Part.Line(Vector(5, 0, 0), Vector(3, 0, 0)))
i = tag.intersect(edge)
self.assertEqual(i.state, Tag.Intersection.P0)
p1 = Vector(4, 0, 0)
p2 = Vector(3, 0, 1)
self.assertLines(i.edges, i.tail, [edge.Curve.StartPoint, p1, p2])
self.assertIsNone(i.tail)
# verify we stay in P1 if we add another segment
edge = Part.Edge(Part.Line(edge.Curve.EndPoint, Vector(1, 0, 0)))
i = i.intersect(edge)
self.assertEqual(i.state, Tag.Intersection.P0)
self.assertEqual(len(i.edges), 3)
p3 = Vector(1, 0, 3)
self.assertLine(i.edges[2], p2, p3)
self.assertIsNone(i.tail)
def test02(self):
"""Verify proper down plunge on exit of triangular tag."""
tag = Tag( 0, 0, 8, 7, 45, True, 0)
p0 = Vector(5, 0, 0)
p1 = Vector(4, 0, 0)
p2 = Vector(0, 0, 4)
edge = Part.Edge(Part.Line(p0, FreeCAD.Vector(0,0,0)))
i = tag.intersect(edge)
self.assertEqual(i.state, Tag.Intersection.P2)
self.assertEqual(len(i.edges), 2)
self.assertLines(i.edges, i.tail, [p0, p1, p2])
# adding another segment doesn't make a difference
edge = Part.Edge(Part.Line(edge.Curve.EndPoint, FreeCAD.Vector(-3,0,0)))
i = i.intersect(edge)
self.assertEqual(i.state, Tag.Intersection.P2)
self.assertEqual(len(i.edges), 3)
p3 = Vector(-3, 0, 1)
self.assertLines(i.edges, i.tail, [p0, p1, p2, p3])
# same result if all is one line
edge = Part.Edge(Part.Line(p0, edge.Curve.EndPoint))
i = tag.intersect(edge)
self.assertEqual(i.state, Tag.Intersection.P2)
self.assertLines(i.edges, i.tail, [p0, p1, p2, p3])
def test03(self):
"""Verify triangular tag shap on intersection."""
tag = Tag( 0, 0, 8, 7, 45, True, 0)
p0 = Vector(5, 0, 0)
p1 = Vector(4, 0, 0)
p2 = Vector(0, 0, 4)
p3 = Vector(-4, 0, 0)
edge = Part.Edge(Part.Line(p0, p3))
i = tag.intersect(edge)
self.assertTrue(i.isComplete())
self.assertLines(i.edges, i.tail, [p0, p1, p2, p3])
self.assertIsNone(i.tail)
# this should also work if there is some excess, aka tail
p4 = Vector(-5, 0, 0)
edge = Part.Edge(Part.Line(p0, p4))
i = tag.intersect(edge)
self.assertTrue(i.isComplete())
self.assertLines(i.edges, i.tail, [p0, p1, p2, p3, p4])
self.assertIsNotNone(i.tail)

View File

@ -121,3 +121,27 @@ class TestPathGeom(PathTestBase):
PathGeom.edgeForCmd(
Path.Command('G3', {'X': p2.x, 'Y': p2.y, 'Z': p2.z, 'I': 1, 'J': 0, 'K': -1}), p1),
p1, Vector(-1/math.sqrt(2), -1/math.sqrt(2), 1), p2)
def test50(self):
"""Verify proper wire(s) aggregation from a Path."""
commands = []
commands.append(Path.Command('G1', {'X': 1}))
commands.append(Path.Command('G1', {'Y': 1}))
commands.append(Path.Command('G0', {'X': 0}))
commands.append(Path.Command('G1', {'Y': 0}))
wire = 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))
wires = PathGeom.wiresForPath(Path.Path(commands))
self.assertEqual(len(wires), 2)
self.assertEqual(len(wires[0].Edges), 2)
self.assertLine(wires[0].Edges[0], Vector(0,0,0), Vector(1,0,0))
self.assertLine(wires[0].Edges[1], Vector(1,0,0), Vector(1,1,0))
self.assertEqual(len(wires[1].Edges), 1)
self.assertLine(wires[1].Edges[0], Vector(0,1,0), Vector(0,0,0))

View File

@ -26,10 +26,4 @@ import TestApp
from PathTests.TestPathPost import PathPostTestCases
from PathTests.TestPathDressupHoldingTags import TestTag00BasicHolding
from PathTests.TestPathDressupHoldingTags import TestTag01BasicTag
from PathTests.TestPathDressupHoldingTags import TestTag02SquareTag
from PathTests.TestPathDressupHoldingTags import TestTag03TrapezoidTag
from PathTests.TestPathDressupHoldingTags import TestTag04TriangularTag
from PathTests.TestPathGeom import TestPathGeom