Fixed alignment issue and unit tests.
The trick is really to over-extend edges before creationg shapes for the common operation, and trying to avoid alignment of the edge with the cone's seam.
This commit is contained in:
parent
27b71ab1ae
commit
cb85072bbd
|
@ -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">
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>352</width>
|
||||
<height>387</height>
|
||||
<width>363</width>
|
||||
<height>530</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
|
@ -27,8 +27,8 @@
|
|||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>334</width>
|
||||
<height>311</height>
|
||||
<width>345</width>
|
||||
<height>476</height>
|
||||
</rect>
|
||||
</property>
|
||||
<attribute name="label">
|
||||
|
@ -36,199 +36,118 @@
|
|||
</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>
|
||||
<widget class="QWidget" name="widget_2" native="true">
|
||||
<layout class="QFormLayout">
|
||||
<property name="fieldGrowthPolicy">
|
||||
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
|
||||
</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">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Delete</string>
|
||||
<string>Width</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pbDisable">
|
||||
<property name="text">
|
||||
<string>Disable</string>
|
||||
<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>
|
||||
<widget class="QPushButton" name="pbAdd">
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Add</string>
|
||||
<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>
|
||||
</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">
|
||||
<item>
|
||||
<widget class="QListWidget" name="lwTags">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Width of each tag.</p></body></html></string>
|
||||
<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 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><html><head/><body><p>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.</p></body></html></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><html><head/><body><p>Angle of tag walls.</p></body></html></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>
|
||||
<widget class="QWidget" name="widget" native="true">
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="lCount">
|
||||
<widget class="QPushButton" name="pbDelete">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Count</string>
|
||||
<string>Delete</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QSpinBox" name="sbCount">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Enter the number of tags you wish to have.</p><p><br/></p><p>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.</p></body></html></string>
|
||||
<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>Spacing </string>
|
||||
<string>Number of Tags</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</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>
|
||||
|
|
|
@ -29,6 +29,9 @@ import Part
|
|||
import copy
|
||||
import math
|
||||
|
||||
import cProfile
|
||||
import time
|
||||
|
||||
from PathScripts import PathUtils
|
||||
from PathScripts.PathGeom import *
|
||||
from PySide import QtCore, QtGui
|
||||
|
@ -57,7 +60,11 @@ def debugEdge(edge, prefix, force = False):
|
|||
pf = edge.valueAt(edge.FirstParameter)
|
||||
pl = edge.valueAt(edge.LastParameter)
|
||||
if force or debugDressup:
|
||||
print("%s %s((%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f))" % (prefix, type(edge.Curve), pf.x, pf.y, pf.z, pl.x, pl.y, pl.z))
|
||||
if type(edge.Curve) == Part.Line or type(edge.Curve) == Part.LineSegment:
|
||||
print("%s %s((%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f))" % (prefix, type(edge.Curve), pf.x, pf.y, pf.z, pl.x, pl.y, pl.z))
|
||||
else:
|
||||
pm = edge.valueAt((edge.FirstParameter+edge.LastParameter)/2)
|
||||
print("%s %s((%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f))" % (prefix, type(edge.Curve), pf.x, pf.y, pf.z, pm.x, pm.y, pm.z, pl.x, pl.y, pl.z))
|
||||
|
||||
def debugMarker(vector, label, color = None, radius = 0.5):
|
||||
if debugDressup:
|
||||
|
@ -104,18 +111,18 @@ class Tag:
|
|||
def toString(self):
|
||||
return str((self.x, self.y, self.width, self.height, self.angle, self.enabled))
|
||||
|
||||
def __init__(self, x, y, width, height, angle, enabled=True, z=None):
|
||||
debugPrint("Tag(%.2f, %.2f, %.2f, %.2f, %.2f, %d, %s)" % (x, y, width, height, angle/math.pi, enabled, z))
|
||||
def __init__(self, x, y, width, height, angle, enabled=True):
|
||||
debugPrint("Tag(%.2f, %.2f, %.2f, %.2f, %.2f, %d)" % (x, y, width, height, angle/math.pi, enabled))
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.z = z
|
||||
self.width = math.fabs(width)
|
||||
self.height = math.fabs(height)
|
||||
self.actualHeight = self.height
|
||||
self.angle = math.fabs(angle)
|
||||
self.enabled = enabled
|
||||
if z is not None:
|
||||
self.createSolidsAt(z)
|
||||
|
||||
def fullWidth(self):
|
||||
return 2 * self.toolRadius + self.width
|
||||
|
||||
def originAt(self, z):
|
||||
return FreeCAD.Vector(self.x, self.y, z)
|
||||
|
@ -126,14 +133,16 @@ class Tag:
|
|||
def top(self):
|
||||
return self.z + self.actualHeight
|
||||
|
||||
def createSolidsAt(self, z):
|
||||
def createSolidsAt(self, z, R):
|
||||
self.z = z
|
||||
r1 = self.width / 2
|
||||
self.toolRadius = R
|
||||
r1 = self.fullWidth() / 2
|
||||
self.r1 = r1
|
||||
self.r2 = r1
|
||||
height = self.height
|
||||
if self.angle == 90 and height > 0:
|
||||
self.solid = Part.makeCylinder(r1, height)
|
||||
print("Part.makeCone(%f, %f)" % (r1, height))
|
||||
elif self.angle > 0.0 and height > 0.0:
|
||||
tangens = math.tan(math.radians(self.angle))
|
||||
dr = height / tangens
|
||||
|
@ -144,10 +153,17 @@ class Tag:
|
|||
height = r1 * tangens
|
||||
self.actualHeight = height
|
||||
self.r2 = r2
|
||||
print("Part.makeCone(%f, %f, %f)" % (r1, r2, height))
|
||||
self.solid = Part.makeCone(r1, r2, height)
|
||||
else:
|
||||
# degenerated case - no tag
|
||||
print("Part.makeSphere(%f / 10000)" % (r1))
|
||||
self.solid = Part.makeSphere(r1 / 10000)
|
||||
if not R == 0: # testing is easier if the solid is not rotated
|
||||
angle = -PathGeom.getAngle(self.originAt(0)) * 180 / math.pi
|
||||
print("solid.rotate(%f)" % angle)
|
||||
self.solid.rotate(FreeCAD.Vector(0,0,0), FreeCAD.Vector(0,0,1), angle)
|
||||
print("solid.translate(%s)" % self.originAt(z))
|
||||
self.solid.translate(self.originAt(z))
|
||||
|
||||
def filterIntersections(self, pts, face):
|
||||
|
@ -206,8 +222,9 @@ class Tag:
|
|||
return None
|
||||
|
||||
def intersects(self, edge, param):
|
||||
if edge.valueAt(edge.FirstParameter).z < self.top() or edge.valueAt(edge.LastParameter).z < self.top():
|
||||
return self.nextIntersectionClosestTo(edge, self.solid, edge.valueAt(param))
|
||||
if self.enabled:
|
||||
if edge.valueAt(edge.FirstParameter).z < self.top() or edge.valueAt(edge.LastParameter).z < self.top():
|
||||
return self.nextIntersectionClosestTo(edge, self.solid, edge.valueAt(param))
|
||||
return None
|
||||
|
||||
class MapWireToTag:
|
||||
|
@ -231,105 +248,131 @@ class MapWireToTag:
|
|||
self.edges = []
|
||||
self.entry = i
|
||||
self.complete = False
|
||||
self.wire = None
|
||||
self.haveProblem = False
|
||||
|
||||
def addEdge(self, edge):
|
||||
debugEdge(edge, '..........')
|
||||
if self.wire:
|
||||
self.wire.add(edge)
|
||||
else:
|
||||
self.wire = Part.Wire(edge)
|
||||
self.edges.append(edge)
|
||||
|
||||
def needToFlipEdge(self, edge, p):
|
||||
if PathGeom.pointsCoincide(edge.valueAt(edge.LastParameter), p):
|
||||
return True, edge.valueAt(edge.FirstParameter)
|
||||
return False, edge.valueAt(edge.LastParameter)
|
||||
|
||||
def isEntryOrExitStrut(self, p1, p2):
|
||||
p = PathGeom.xy(p1)
|
||||
pEntry0 = PathGeom.xy(self.entry)
|
||||
pExit0 = PathGeom.xy(self.exit)
|
||||
# it can only be an entry strut if the strut coincides with the entry point and is above it
|
||||
if PathGeom.pointsCoincide(p, pEntry0) and p1.z >= self.entry.z and p2.z >= self.entry.z:
|
||||
return True
|
||||
if PathGeom.pointsCoincide(p, pExit0) and p1.z >= self.exit.z and p2.z >= self.exit.z:
|
||||
return True
|
||||
return False
|
||||
def isEntryOrExitStrut(self, e):
|
||||
p1 = e.valueAt(e.FirstParameter)
|
||||
p2 = e.valueAt(e.LastParameter)
|
||||
if PathGeom.pointsCoincide(p1, self.entry) and p2.z >= self.entry.z:
|
||||
return 1
|
||||
if PathGeom.pointsCoincide(p2, self.entry) and p1.z >= self.entry.z:
|
||||
return 1
|
||||
if PathGeom.pointsCoincide(p1, self.exit) and p2.z >= self.exit.z:
|
||||
return 2
|
||||
if PathGeom.pointsCoincide(p2, self.exit) and p1.z >= self.exit.z:
|
||||
return 2
|
||||
return 0
|
||||
|
||||
|
||||
def cleanupEdges(self, edges):
|
||||
# first remove all internal struts
|
||||
debugEdge(Part.Edge(Part.LineSegment(self.entry, self.exit)), '------> cleanupEdges')
|
||||
inputEdges = copy.copy(edges)
|
||||
plinths = []
|
||||
def cleanupEdges(self, edges, baseEdge):
|
||||
# want to remove all edges from the wire itself, and all internal struts
|
||||
print("+cleanupEdges")
|
||||
print(" base:")
|
||||
debugEdge(baseEdge, ' ', True)
|
||||
print(" edges:")
|
||||
for e in edges:
|
||||
debugEdge(e, '........ cleanup')
|
||||
p1 = e.valueAt(e.FirstParameter)
|
||||
p2 = e.valueAt(e.LastParameter)
|
||||
if PathGeom.pointsCoincide(PathGeom.xy(p1), PathGeom.xy(p2)):
|
||||
#it's a strut
|
||||
if not self.isEntryOrExitStrut(p1, p2):
|
||||
debugEdge(e, '......... X0 %d/%d' % (PathGeom.edgeConnectsTo(e, self.entry), PathGeom.edgeConnectsTo(e, self.exit)))
|
||||
inputEdges.remove(e)
|
||||
if p1.z > p2.z:
|
||||
plinths.append(p2)
|
||||
else:
|
||||
plinths.append(p1)
|
||||
# remove all edges that are connected to the plinths of the (former) internal struts
|
||||
for e in copy.copy(inputEdges):
|
||||
for p in plinths:
|
||||
if PathGeom.edgeConnectsTo(e, p):
|
||||
debugEdge(e, '......... X1')
|
||||
inputEdges.remove(e)
|
||||
break
|
||||
# if there are any edges beside a direct edge remaining, the direct edge between
|
||||
# entry and exit is redundant
|
||||
if len(inputEdges) > 1:
|
||||
for e in copy.copy(inputEdges):
|
||||
if PathGeom.edgeConnectsTo(e, self.entry) and PathGeom.edgeConnectsTo(e, self.exit):
|
||||
debugEdge(e, '......... X2')
|
||||
inputEdges.remove(e)
|
||||
debugEdge(e, ' ', True)
|
||||
print(":")
|
||||
|
||||
# the remaining edges form a walk around the tag
|
||||
# they need to be ordered and potentially flipped though
|
||||
haveEntry = False
|
||||
for e in copy.copy(edges):
|
||||
if PathGeom.edgesMatch(e, baseEdge):
|
||||
debugEdge(e, '......... X0')
|
||||
edges.remove(e)
|
||||
elif self.isStrut(e):
|
||||
typ = self.isEntryOrExitStrut(e)
|
||||
debugEdge(e, '......... |%d' % typ)
|
||||
if 0 == typ: # neither entry nor exit
|
||||
debugEdge(e, '......... X1')
|
||||
edges.remove(e)
|
||||
elif 1 == typ:
|
||||
haveEntry = True
|
||||
|
||||
print("entry(%.2f, %.2f, %.2f), exit(%.2f, %.2f, %.2f)" % (self.entry.x, self.entry.y, self.entry.z, self.exit.x, self.exit.y, self.exit.z))
|
||||
# the remaininng edges for the path from xy(baseEdge) along the tags surface
|
||||
outputEdges = []
|
||||
p = self.entry
|
||||
lastP = p
|
||||
while inputEdges:
|
||||
for e in inputEdges:
|
||||
p0 = baseEdge.valueAt(baseEdge.FirstParameter)
|
||||
ignoreZ = False
|
||||
if not haveEntry:
|
||||
ignoreZ = True
|
||||
p0 = PathGeom.xy(p0)
|
||||
lastP = p0
|
||||
while edges:
|
||||
print("(%.2f, %.2f, %.2f) %d %d" % (p0.x, p0.y, p0.z, haveEntry, ignoreZ))
|
||||
for e in edges:
|
||||
p1 = e.valueAt(e.FirstParameter)
|
||||
p2 = e.valueAt(e.LastParameter)
|
||||
if PathGeom.pointsCoincide(p1, p):
|
||||
outputEdges.append((e,False))
|
||||
inputEdges.remove(e)
|
||||
lastP = p
|
||||
p = p2
|
||||
if PathGeom.pointsCoincide(PathGeom.xy(p1) if ignoreZ else p1, p0):
|
||||
outputEdges.append((e, False))
|
||||
edges.remove(e)
|
||||
lastP = None
|
||||
ignoreZ = False
|
||||
p0 = p2
|
||||
debugEdge(e, ">>>>> no flip")
|
||||
break
|
||||
elif PathGeom.pointsCoincide(p2, p):
|
||||
outputEdges.append((e,True))
|
||||
inputEdges.remove(e)
|
||||
lastP = p
|
||||
p = p1
|
||||
elif PathGeom.pointsCoincide(PathGeom.xy(p2) if ignoreZ else p2, p0):
|
||||
outputEdges.append((e, True))
|
||||
edges.remove(e)
|
||||
lastP = None
|
||||
ignoreZ = False
|
||||
p0 = p1
|
||||
debugEdge(e, ">>>>> flip")
|
||||
break
|
||||
#else:
|
||||
# debugEdge(e, "<<<<< (%.2f, %.2f, %.2f)" % (p.x, p.y, p.z))
|
||||
if lastP == p:
|
||||
raise ValueError("No connection to %s" % (p))
|
||||
#else:
|
||||
# print("xxxxxx (%.2f, %.2f, %.2f)" % (p.x, p.y, p.z))
|
||||
else:
|
||||
debugEdge(e, "<<<<< (%.2f, %.2f, %.2f)" % (p0.x, p0.y, p0.z))
|
||||
if lastP == p0:
|
||||
raise ValueError("No connection to %s" % (p0))
|
||||
elif lastP:
|
||||
print("xxxxxx (%.2f, %.2f, %.2f) (%.2f, %.2f, %.2f)" % (p0.x, p0.y, p0.z, lastP.x, lastP.y, lastP.z))
|
||||
else:
|
||||
print("xxxxxx (%.2f, %.2f, %.2f) -" % (p0.x, p0.y, p0.z))
|
||||
lastP = p0
|
||||
print("-cleanupEdges")
|
||||
return outputEdges
|
||||
|
||||
def shell(self):
|
||||
shell = self.wire.extrude(FreeCAD.Vector(0, 0, 10))
|
||||
redundant = filter(lambda f: f.Area == 0, shell.childShapes())
|
||||
if redundant:
|
||||
return shell.removeShape(redundant)
|
||||
return shell
|
||||
def isStrut(self, edge):
|
||||
p1 = PathGeom.xy(edge.valueAt(edge.FirstParameter))
|
||||
p2 = PathGeom.xy(edge.valueAt(edge.LastParameter))
|
||||
return PathGeom.pointsCoincide(p1, p2)
|
||||
|
||||
def cmdsForEdge(self, edge):
|
||||
cmds = []
|
||||
|
||||
# OCC doesn't like it very much if the shapes align with each other. So if we have a slightly
|
||||
# extended edge for the last edge in list we'll use that instead for stable results.
|
||||
if PathGeom.pointsCoincide(edge.valueAt(edge.FirstParameter), self.lastEdge.valueAt(self.lastEdge.FirstParameter)):
|
||||
shell = self.lastEdge.extrude(FreeCAD.Vector(0, 0, self.tag.height + 1))
|
||||
else:
|
||||
shell = edge.extrude(FreeCAD.Vector(0, 0, self.tag.height + 1))
|
||||
shape = shell.common(self.tag.solid)
|
||||
|
||||
if not shape.Edges:
|
||||
self.haveProblem = True
|
||||
|
||||
for e,flip in self.cleanupEdges(shape.Edges, edge):
|
||||
debugEdge(e, '++++++++ %s' % ('.' if not flip else '@'))
|
||||
cmds.extend(PathGeom.cmdsForEdge(e, flip, False))
|
||||
return cmds
|
||||
|
||||
def commandsForEdges(self):
|
||||
commands = []
|
||||
for e in self.edges:
|
||||
if self.isStrut(e):
|
||||
continue
|
||||
commands.extend(self.cmdsForEdge(e))
|
||||
return commands
|
||||
|
||||
def add(self, edge):
|
||||
self.tail = None
|
||||
self.lastEdge = edge # see cmdsForEdge
|
||||
if self.tag.solid.isInside(edge.valueAt(edge.LastParameter), 0.000001, True):
|
||||
self.addEdge(edge)
|
||||
else:
|
||||
|
@ -341,13 +384,8 @@ class MapWireToTag:
|
|||
self.addEdge(e)
|
||||
self.tail = tail
|
||||
self.exit = i
|
||||
if self.wire:
|
||||
face = self.shell().common(self.tag.solid)
|
||||
|
||||
for e,flip in self.cleanupEdges(face.Edges):
|
||||
debugEdge(e, '++++++++ %s' % ('.' if not flip else '@'))
|
||||
self.commands.extend(PathGeom.cmdsForEdge(e, flip, False))
|
||||
self.complete = True
|
||||
self.commands.extend(self.commandsForEdges())
|
||||
|
||||
def mappingComplete(self):
|
||||
return self.complete
|
||||
|
@ -399,9 +437,11 @@ class PathData:
|
|||
|
||||
def findZLimits(self, edges):
|
||||
# not considering arcs and spheres in Z direction, find the highes and lowest Z values
|
||||
minZ = edges[0].Vertexes[0].Point.z
|
||||
maxZ = minZ
|
||||
minZ = 99999999999
|
||||
maxZ = -99999999999
|
||||
for e in edges:
|
||||
if self.rapid.isRapid(e):
|
||||
continue
|
||||
for v in e.Vertexes:
|
||||
if v.Point.z < minZ:
|
||||
minZ = v.Point.z
|
||||
|
@ -476,10 +516,11 @@ class PathData:
|
|||
debugPrint(" %d: %d" % (i, count))
|
||||
#debugMarker(edge.Vertexes[0].Point, 'base', (1.0, 0.0, 0.0), 0.2)
|
||||
#debugMarker(edge.Vertexes[1].Point, 'base', (0.0, 1.0, 0.0), 0.2)
|
||||
distance = (edge.LastParameter - edge.FirstParameter) / count
|
||||
for j in range(0, count):
|
||||
tag = edge.Curve.value((j+0.5) * distance)
|
||||
tags.append(Tag(tag.x, tag.y, W, H, angle, True))
|
||||
if 0 != count:
|
||||
distance = (edge.LastParameter - edge.FirstParameter) / count
|
||||
for j in range(0, count):
|
||||
tag = edge.Curve.value((j+0.5) * distance)
|
||||
tags.append(Tag(tag.x, tag.y, W, H, angle, True))
|
||||
|
||||
return tags
|
||||
|
||||
|
@ -554,6 +595,7 @@ class ObjectDressup:
|
|||
return True
|
||||
|
||||
def createPath(self, edges, tags, rapid):
|
||||
print("createPath")
|
||||
commands = []
|
||||
lastEdge = 0
|
||||
lastTag = 0
|
||||
|
@ -562,10 +604,11 @@ class ObjectDressup:
|
|||
inters = None
|
||||
edge = None
|
||||
|
||||
self.mappers = []
|
||||
mapper = None
|
||||
|
||||
while edge or lastEdge < len(edges):
|
||||
#print("------- lastEdge = %d/%d.%d/%d" % (lastEdge, lastTag, t, len(tags)))
|
||||
debugPrint("------- lastEdge = %d/%d.%d/%d" % (lastEdge, lastTag, t, len(tags)))
|
||||
if not edge:
|
||||
edge = edges[lastEdge]
|
||||
debugEdge(edge, "======= new edge: %d/%d" % (lastEdge, len(edges)))
|
||||
|
@ -587,6 +630,7 @@ class ObjectDressup:
|
|||
i = tags[tIndex].intersects(edge, edge.FirstParameter)
|
||||
if i and self.isValidTagStartIntersection(edge, i):
|
||||
mapper = MapWireToTag(edge, tags[tIndex], i)
|
||||
self.mappers.append(mapper)
|
||||
edge = mapper.tail
|
||||
|
||||
|
||||
|
@ -606,8 +650,17 @@ class ObjectDressup:
|
|||
# print(cmd)
|
||||
return Path.Path(commands)
|
||||
|
||||
def problems(self):
|
||||
return filter(lambda m: m.haveProblem, self.mappers)
|
||||
|
||||
def execute(self, obj):
|
||||
#pr = cProfile.Profile()
|
||||
#pr.enable()
|
||||
self.doExecute(obj)
|
||||
#pr.disable()
|
||||
#pr.print_stats()
|
||||
|
||||
def doExecute(self,obj):
|
||||
if not obj.Base:
|
||||
return
|
||||
if not obj.Base.isDerivedFrom("Path::Feature"):
|
||||
|
@ -642,7 +695,7 @@ class ObjectDressup:
|
|||
tags = pathData.sortedTags(tags)
|
||||
self.setTags(obj, tags, False)
|
||||
for tag in tags:
|
||||
tag.createSolidsAt(pathData.minZ)
|
||||
tag.createSolidsAt(pathData.minZ, self.toolRadius)
|
||||
|
||||
tagID = 0
|
||||
for tag in tags:
|
||||
|
@ -653,12 +706,13 @@ class ObjectDressup:
|
|||
if tag.angle != 90:
|
||||
debugCone(tag.originAt(pathData.minZ), tag.r1, tag.r2, tag.actualHeight, "tag-%02d" % tagID)
|
||||
else:
|
||||
debugCylinder(tag.originAt(pathData.minZ), tag.width/2, tag.actualHeight, "tag-%02d" % tagID)
|
||||
debugCylinder(tag.originAt(pathData.minZ), tag.fullWidth()/2, tag.actualHeight, "tag-%02d" % tagID)
|
||||
|
||||
self.fingerprint = [tag.toString() for tag in tags]
|
||||
self.tags = tags
|
||||
|
||||
obj.Path = self.createPath(pathData.edges, tags, pathData.rapid)
|
||||
print("execute - done")
|
||||
|
||||
def setTags(self, obj, tags, update = True):
|
||||
print("setTags(%d, %d)" % (len(tags), update))
|
||||
|
@ -681,32 +735,16 @@ class ObjectDressup:
|
|||
FreeCAD.Console.PrintError(translate("PathDressup_HoldingTags", "Cannot insert holding tags for this path - please select a Profile path\n"))
|
||||
return None
|
||||
|
||||
## setup the object's properties, in case they're not set yet
|
||||
#obj.Count = self.tagCount(obj)
|
||||
#obj.Angle = self.tagAngle(obj)
|
||||
#obj.Blacklist = self.tagBlacklist(obj)
|
||||
|
||||
# if the heigt isn't set, use the height of the path
|
||||
#if not hasattr(obj, "Height") or not obj.Height:
|
||||
# obj.Height = pathData.maxZ - pathData.minZ
|
||||
# try and take an educated guess at the width
|
||||
#if not hasattr(obj, "Width") or not obj.Width:
|
||||
# width = sorted(pathData.base.Edges, key=lambda e: -e.Length)[0].Length / 10
|
||||
# while obj.Count > len([e for e in pathData.base.Edges if e.Length > 3*width]):
|
||||
# width = widht / 2
|
||||
# obj.Width = width
|
||||
|
||||
# and the tool radius, not sure yet if it's needed
|
||||
#self.toolRadius = 5
|
||||
#toolLoad = PathUtils.getLastToolLoad(obj)
|
||||
#if toolLoad is None or toolLoad.ToolNumber == 0:
|
||||
# self.toolRadius = 5
|
||||
#else:
|
||||
# tool = PathUtils.getTool(obj, toolLoad.ToolNumber)
|
||||
# if not tool or tool.Diameter == 0:
|
||||
# self.toolRadius = 5
|
||||
# else:
|
||||
# self.toolRadius = tool.Diameter / 2
|
||||
self.toolRadius = 5
|
||||
toolLoad = PathUtils.getLastToolLoad(obj)
|
||||
if toolLoad is None or toolLoad.ToolNumber == 0:
|
||||
self.toolRadius = 5
|
||||
else:
|
||||
tool = PathUtils.getTool(obj, toolLoad.ToolNumber)
|
||||
if not tool or tool.Diameter == 0:
|
||||
self.toolRadius = 5
|
||||
else:
|
||||
self.toolRadius = tool.Diameter / 2
|
||||
self.pathData = pathData
|
||||
return self.pathData
|
||||
|
||||
|
@ -723,8 +761,8 @@ class ObjectDressup:
|
|||
return self.pathData.pathLength()
|
||||
|
||||
class TaskPanel:
|
||||
DataTag = QtCore.Qt.ItemDataRole.UserRole
|
||||
DataValue = QtCore.Qt.ItemDataRole.DisplayRole
|
||||
DataX = QtCore.Qt.ItemDataRole.UserRole
|
||||
DataY = QtCore.Qt.ItemDataRole.UserRole + 1
|
||||
|
||||
def __init__(self, obj):
|
||||
self.obj = obj
|
||||
|
@ -738,6 +776,7 @@ class TaskPanel:
|
|||
FreeCADGui.Selection.removeObserver(self.s)
|
||||
|
||||
def accept(self):
|
||||
self.getFields()
|
||||
FreeCAD.ActiveDocument.commitTransaction()
|
||||
FreeCADGui.ActiveDocument.resetEdit()
|
||||
FreeCADGui.Control.closeDialog()
|
||||
|
@ -750,40 +789,38 @@ class TaskPanel:
|
|||
# install the function mode resident
|
||||
FreeCADGui.Selection.addObserver(self.s)
|
||||
|
||||
def tableWidgetItem(self, tag, val):
|
||||
item = QtGui.QTableWidgetItem()
|
||||
item.setTextAlignment(QtCore.Qt.AlignRight)
|
||||
item.setData(self.DataTag, tag)
|
||||
item.setData(self.DataValue, val)
|
||||
return item
|
||||
|
||||
def getFields(self):
|
||||
width = self.form.dsbWidth.value()
|
||||
height = self.form.dsbHeight.value()
|
||||
angle = self.form.dsbAngle.value()
|
||||
tags = []
|
||||
for row in range(0, self.form.twTags.rowCount()):
|
||||
x = self.form.twTags.item(row, 0).data(self.DataValue)
|
||||
y = self.form.twTags.item(row, 1).data(self.DataValue)
|
||||
w = self.form.twTags.item(row, 2).data(self.DataValue)
|
||||
h = self.form.twTags.item(row, 3).data(self.DataValue)
|
||||
a = self.form.twTags.item(row, 4).data(self.DataValue)
|
||||
tags.append(Tag(x, y, w, h, a, True))
|
||||
print("getFields: %d" % (len(tags)))
|
||||
for i in range(0, self.form.lwTags.count()):
|
||||
item = self.form.lwTags.item(i)
|
||||
enabled = item.checkState() == QtCore.Qt.CheckState.Checked
|
||||
x = item.data(self.DataX)
|
||||
y = item.data(self.DataY)
|
||||
tags.append(Tag(x, y, width, height, angle, enabled))
|
||||
self.obj.Proxy.setTags(self.obj, tags)
|
||||
|
||||
def updateTags(self):
|
||||
def updateTagsView(self):
|
||||
self.tags = self.obj.Proxy.getTags(self.obj)
|
||||
self.form.twTags.blockSignals(True)
|
||||
self.form.twTags.setSortingEnabled(False)
|
||||
self.form.twTags.clearSpans()
|
||||
print("updateTags: %d" % (len(self.tags)))
|
||||
self.form.twTags.setRowCount(len(self.tags))
|
||||
for row, tag in enumerate(self.tags):
|
||||
self.form.twTags.setItem(row, 0, self.tableWidgetItem(tag, tag.x))
|
||||
self.form.twTags.setItem(row, 1, self.tableWidgetItem(tag, tag.y))
|
||||
self.form.twTags.setItem(row, 2, self.tableWidgetItem(tag, tag.width))
|
||||
self.form.twTags.setItem(row, 3, self.tableWidgetItem(tag, tag.height))
|
||||
self.form.twTags.setItem(row, 4, self.tableWidgetItem(tag, tag.angle))
|
||||
self.form.twTags.setSortingEnabled(True)
|
||||
self.form.twTags.blockSignals(False)
|
||||
print("updateTagsView: %d" % (len(self.tags)))
|
||||
self.form.lwTags.blockSignals(True)
|
||||
self.form.lwTags.clear()
|
||||
for tag in self.tags:
|
||||
lbl = "(%.2f, %.2f)" % (tag.x, tag.y)
|
||||
item = QtGui.QListWidgetItem(lbl)
|
||||
item.setData(self.DataX, tag.x)
|
||||
item.setData(self.DataY, tag.y)
|
||||
if tag.enabled:
|
||||
item.setCheckState(QtCore.Qt.CheckState.Checked)
|
||||
else:
|
||||
item.setCheckState(QtCore.Qt.CheckState.Unchecked)
|
||||
flags = QtCore.Qt.ItemFlag.ItemIsSelectable
|
||||
flags |= QtCore.Qt.ItemFlag.ItemIsEnabled | QtCore.Qt.ItemFlag.ItemIsUserCheckable
|
||||
item.setFlags(flags)
|
||||
self.form.lwTags.addItem(item)
|
||||
self.form.lwTags.blockSignals(False)
|
||||
|
||||
def cleanupUI(self):
|
||||
print("cleanupUI")
|
||||
|
@ -792,67 +829,50 @@ class TaskPanel:
|
|||
if obj.Name.startswith('tag'):
|
||||
FreeCAD.ActiveDocument.removeObject(obj.Name)
|
||||
|
||||
def updateUI(self):
|
||||
print("updateUI")
|
||||
self.cleanupUI()
|
||||
self.getFields()
|
||||
if debugDressup:
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
|
||||
|
||||
def whenApplyClicked(self):
|
||||
print("whenApplyClicked")
|
||||
def generateNewTags(self):
|
||||
print("generateNewTags")
|
||||
self.cleanupUI()
|
||||
|
||||
count = self.form.sbCount.value()
|
||||
spacing = self.form.dsbSpacing.value()
|
||||
width = self.form.dsbWidth.value()
|
||||
height = self.form.dsbHeight.value()
|
||||
angle = self.form.dsbAngle.value()
|
||||
|
||||
tags = self.obj.Proxy.generateTags(self.obj, count, width, height, angle, spacing * 0.99)
|
||||
tags = self.obj.Proxy.generateTags(self.obj, count, width, height, angle)
|
||||
|
||||
self.obj.Proxy.setTags(self.obj, tags)
|
||||
self.updateTags()
|
||||
if debugDressup:
|
||||
# this causes a big of an echo and a double click on the spin buttons, don't know why though
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
self.updateTagsView()
|
||||
#if debugDressup:
|
||||
# # this causes a big of an echo and a double click on the spin buttons, don't know why though
|
||||
# FreeCAD.ActiveDocument.recompute()
|
||||
|
||||
def autoApply(self):
|
||||
print("autoApply")
|
||||
if self.form.cbAutoApply.checkState() == QtCore.Qt.CheckState.Checked:
|
||||
self.whenApplyClicked()
|
||||
# def autoApply(self):
|
||||
# print("autoApply")
|
||||
# if self.form.cbAutoApply.checkState() == QtCore.Qt.CheckState.Checked:
|
||||
# self.whenApplyClicked()
|
||||
|
||||
def updateTagSpacing(self, count):
|
||||
print("updateTagSpacing")
|
||||
if count == 0:
|
||||
spacing = 0
|
||||
else:
|
||||
spacing = self.pathLength / count
|
||||
self.form.dsbSpacing.blockSignals(True)
|
||||
self.form.dsbSpacing.setValue(spacing)
|
||||
self.form.dsbSpacing.blockSignals(False)
|
||||
def updateModel(self):
|
||||
self.getFields()
|
||||
self.updateTagsView()
|
||||
#FreeCAD.ActiveDocument.recompute()
|
||||
|
||||
def whenCountChanged(self):
|
||||
print("whenCountChanged")
|
||||
self.updateTagSpacing(self.form.sbCount.value())
|
||||
self.autoApply()
|
||||
count = self.form.sbCount.value()
|
||||
self.form.pbGenerate.setEnabled(count)
|
||||
|
||||
def whenSpacingChanged(self):
|
||||
print("whenSpacingChanged")
|
||||
if self.form.dsbSpacing.value() == 0:
|
||||
count = 0
|
||||
else:
|
||||
count = int(self.pathLength / self.form.dsbSpacing.value())
|
||||
self.form.sbCount.blockSignals(True)
|
||||
self.form.sbCount.setValue(count)
|
||||
self.form.sbCount.blockSignals(False)
|
||||
self.autoApply()
|
||||
def whenTagSelectionChanged(self):
|
||||
print('whenTagSelectionChanged')
|
||||
item = self.form.lwTags.currentItem()
|
||||
self.form.pbDelete.setEnabled(not item is None)
|
||||
|
||||
def whenOkClicked(self):
|
||||
print("whenOkClicked")
|
||||
self.whenApplyClicked()
|
||||
self.form.toolBox.setCurrentWidget(self.form.tbpTags)
|
||||
def deleteSelectedTag(self):
|
||||
item = self.form.lwTags.currentItem()
|
||||
x = item.data(self.DataX)
|
||||
y = item.data(self.DataY)
|
||||
tags = filter(lambda t: t.x != x or t.y != y, self.tags)
|
||||
self.obj.Proxy.setTags(self.obj, tags)
|
||||
self.updateTagsView()
|
||||
|
||||
def setupSpinBox(self, widget, val, decimals = 2):
|
||||
widget.setMinimum(0)
|
||||
|
@ -861,29 +881,44 @@ class TaskPanel:
|
|||
widget.setValue(val)
|
||||
|
||||
def setFields(self):
|
||||
self.pathLength = self.obj.Proxy.getPathLength(self.obj)
|
||||
vHeader = self.form.twTags.verticalHeader()
|
||||
vHeader.setResizeMode(QtGui.QHeaderView.Fixed)
|
||||
vHeader.setDefaultSectionSize(20)
|
||||
self.updateTags()
|
||||
self.setupSpinBox(self.form.sbCount, self.form.twTags.rowCount(), None)
|
||||
self.setupSpinBox(self.form.dsbSpacing, 0)
|
||||
self.updateTagsView()
|
||||
self.setupSpinBox(self.form.sbCount, len(self.tags), None)
|
||||
self.setupSpinBox(self.form.dsbHeight, self.obj.Proxy.getHeight(self.obj))
|
||||
self.setupSpinBox(self.form.dsbWidth, self.obj.Proxy.getWidth(self.obj))
|
||||
self.setupSpinBox(self.form.dsbAngle, self.obj.Proxy.getAngle(self.obj))
|
||||
self.updateTagSpacing(self.form.twTags.rowCount())
|
||||
self.setupSpinBox(self.form.dsbAngle, self.obj.Proxy.getAngle(self.obj), 0)
|
||||
self.form.dsbAngle.setMaximum(90)
|
||||
self.form.dsbAngle.setSingleStep(5.)
|
||||
|
||||
def updateModelHeight(self):
|
||||
print('updateModelHeight')
|
||||
self.updateModel()
|
||||
|
||||
def updateModelWidth(self):
|
||||
print('updateModelWidth')
|
||||
self.updateModel()
|
||||
|
||||
def updateModelAngle(self):
|
||||
print('updateModelAngle')
|
||||
self.updateModel()
|
||||
|
||||
def updateModelTags(self):
|
||||
print('updateModelTags')
|
||||
self.updateModel()
|
||||
|
||||
def setupUi(self):
|
||||
self.setFields()
|
||||
self.whenCountChanged()
|
||||
|
||||
self.form.sbCount.valueChanged.connect(self.whenCountChanged)
|
||||
self.form.dsbSpacing.valueChanged.connect(self.whenSpacingChanged)
|
||||
self.form.dsbHeight.valueChanged.connect(self.autoApply)
|
||||
self.form.dsbWidth.valueChanged.connect(self.autoApply)
|
||||
self.form.dsbAngle.valueChanged.connect(self.autoApply)
|
||||
#self.form.pbAdd.clicked.connect(self.)
|
||||
self.form.buttonBox.button(QtGui.QDialogButtonBox.Apply).clicked.connect(self.whenApplyClicked)
|
||||
self.form.buttonBox.button(QtGui.QDialogButtonBox.Ok).clicked.connect(self.whenOkClicked)
|
||||
self.form.twTags.itemChanged.connect(self.updateUI)
|
||||
self.form.pbGenerate.clicked.connect(self.generateNewTags)
|
||||
|
||||
self.form.dsbHeight.editingFinished.connect(self.updateModelHeight)
|
||||
self.form.dsbWidth.editingFinished.connect(self.updateModelWidth)
|
||||
self.form.dsbAngle.editingFinished.connect(self.updateModelAngle)
|
||||
self.form.lwTags.itemChanged.connect(self.updateModelTags)
|
||||
self.form.lwTags.itemSelectionChanged.connect(self.whenTagSelectionChanged)
|
||||
|
||||
self.form.pbDelete.clicked.connect(self.deleteSelectedTag)
|
||||
|
||||
class SelObserver:
|
||||
def __init__(self):
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -52,7 +52,7 @@ class TestHoldingTags(PathTestBase):
|
|||
def test01(self):
|
||||
"""Verify solid for a 90 degree tag is a cylinder."""
|
||||
tag = Tag(100, 200, 4, 5, 90, True)
|
||||
tag.createSolidsAt(17)
|
||||
tag.createSolidsAt(17, 0)
|
||||
|
||||
self.assertIsNotNone(tag.solid)
|
||||
self.assertCylinderAt(tag.solid, Vector(100, 200, 17), 2, 5)
|
||||
|
@ -60,7 +60,7 @@ class TestHoldingTags(PathTestBase):
|
|||
def test02(self):
|
||||
"""Verify trapezoidal tag has a cone shape with a lid."""
|
||||
tag = Tag(0, 0, 18, 5, 45, True)
|
||||
tag.createSolidsAt(0)
|
||||
tag.createSolidsAt(0, 0)
|
||||
|
||||
self.assertIsNotNone(tag.solid)
|
||||
self.assertConeAt(tag.solid, Vector(0,0,0), 9, 4, 5)
|
||||
|
@ -68,14 +68,14 @@ class TestHoldingTags(PathTestBase):
|
|||
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)
|
||||
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)
|
||||
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)
|
||||
|
|
Loading…
Reference in New Issue
Block a user