Path: Slot operation fixes and improvements
- Add `CutPattern` feature, defaulting to `ZigZag`. - Change inter-pass retractions to SafeHeight for multi-pass operations. - Fix division by zero instance for particular Perpendicular use case. - Synchronize property values with inputs in both the Property View and Tasks Editor windows. - Change `LayerMode` default value to `Multi-pass`. - Improve debug messaging. - Consolidate code and remove unused code. Path: fix exception thrown
This commit is contained in:
@@ -67,6 +67,9 @@
|
||||
</property>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="geo1Reference">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
@@ -77,7 +80,7 @@
|
||||
<string><html><head/><body><p>Choose what point to use on the first selected feature.</p></body></html></string>
|
||||
</property>
|
||||
<property name="editable">
|
||||
<bool>true</bool>
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
@@ -159,7 +162,7 @@
|
||||
<string><html><head/><body><p>Choose what point to use on the second selected feature.</p></body></html></string>
|
||||
</property>
|
||||
<property name="editable">
|
||||
<bool>true</bool>
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
|
||||
@@ -46,8 +46,12 @@ Part = LazyLoader('Part', globals(), 'Part')
|
||||
if FreeCAD.GuiUp:
|
||||
import FreeCADGui
|
||||
|
||||
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
|
||||
# PathLog.trackModule(PathLog.thisModule())
|
||||
DEBUG = False
|
||||
if DEBUG:
|
||||
PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
|
||||
PathLog.trackModule(PathLog.thisModule())
|
||||
else:
|
||||
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
|
||||
|
||||
|
||||
# Qt translation handling
|
||||
@@ -91,6 +95,7 @@ class ObjectSlot(PathOp.ObjectOp):
|
||||
# Set enumeration lists for enumeration properties
|
||||
if len(self.addNewProps) > 0:
|
||||
ENUMS = self.opPropertyEnumerations()
|
||||
# ENUMS = self.getActiveEnumerations(obj)
|
||||
for n in ENUMS:
|
||||
if n in self.addNewProps:
|
||||
setattr(obj, n, ENUMS[n])
|
||||
@@ -114,6 +119,8 @@ class ObjectSlot(PathOp.ObjectOp):
|
||||
QtCore.QT_TRANSLATE_NOOP("App::Property", "Enter custom start point for slot path.")),
|
||||
("App::PropertyVectorDistance", "CustomPoint2", "Slot",
|
||||
QtCore.QT_TRANSLATE_NOOP("App::Property", "Enter custom end point for slot path.")),
|
||||
("App::PropertyEnumeration", "CutPattern", "Slot",
|
||||
QtCore.QT_TRANSLATE_NOOP("App::Property", "Set the geometric clearing pattern to use for the operation.")),
|
||||
("App::PropertyDistance", "ExtendPathStart", "Slot",
|
||||
QtCore.QT_TRANSLATE_NOOP("App::Property", "Positive extends the beginning of the path, negative shortens.")),
|
||||
("App::PropertyDistance", "ExtendPathEnd", "Slot",
|
||||
@@ -138,6 +145,7 @@ class ObjectSlot(PathOp.ObjectOp):
|
||||
def opPropertyEnumerations(self):
|
||||
# Enumeration lists for App::PropertyEnumeration properties
|
||||
return {
|
||||
'CutPattern': ['Line', 'ZigZag'],
|
||||
'LayerMode': ['Single-pass', 'Multi-pass'],
|
||||
'PathOrientation': ['Start to End', 'Perpendicular'],
|
||||
'Reference1': ['Center of Mass', 'Center of BoundBox',
|
||||
@@ -157,7 +165,8 @@ class ObjectSlot(PathOp.ObjectOp):
|
||||
'CustomPoint2': FreeCAD.Vector(10.0, 10.0, 0.0),
|
||||
'ExtendPathEnd': 0.0,
|
||||
'Reference2': 'Center of Mass',
|
||||
'LayerMode': 'Single-pass',
|
||||
'LayerMode': 'Multi-pass',
|
||||
'CutPattern': 'ZigZag',
|
||||
'PathOrientation': 'Start to End',
|
||||
'ReverseDirection': False,
|
||||
|
||||
@@ -167,27 +176,64 @@ class ObjectSlot(PathOp.ObjectOp):
|
||||
|
||||
return defaults
|
||||
|
||||
def setEditorProperties(self, obj):
|
||||
# Used to hide inputs in properties list
|
||||
A = B = 2
|
||||
def getActiveEnumerations(self, obj):
|
||||
"""getActiveEnumerations(obj) ...
|
||||
Method returns dictionary of property enumerations based on
|
||||
active conditions in the operation."""
|
||||
ENUMS = self.opPropertyEnumerations()
|
||||
if hasattr(obj, 'Base'):
|
||||
enums2 = self.opPropertyEnumerations()['Reference2']
|
||||
if obj.Base:
|
||||
(base, subsList) = obj.Base[0]
|
||||
subCnt = len(subsList)
|
||||
if subCnt == 1:
|
||||
# Adjust available enumerations
|
||||
obj.Reference1 = self._getReference1Enums(subsList[0], True)
|
||||
A = 0
|
||||
ENUMS['Reference1'] = self._makeReference1Enumerations(subsList[0], True)
|
||||
elif subCnt == 2:
|
||||
# Adjust available enumerations
|
||||
obj.Reference1 = self._getReference1Enums(subsList[0])
|
||||
obj.Reference2 = self._getReference2Enums(subsList[1])
|
||||
ENUMS['Reference1'] = self._makeReference1Enumerations(subsList[0])
|
||||
ENUMS['Reference2'] = self._makeReference2Enumerations(subsList[1])
|
||||
return ENUMS
|
||||
|
||||
def updateEnumerations(self, obj):
|
||||
"""updateEnumerations(obj) ...
|
||||
Method updates property enumerations based on active conditions
|
||||
in the operation. Returns the updated enumerations dictionary.
|
||||
Existing property values must be stored, and then restored after
|
||||
the assignment of updated enumerations."""
|
||||
PathLog.debug('updateEnumerations()')
|
||||
# Save existing values
|
||||
pre_Ref1 = obj.Reference1
|
||||
pre_Ref2 = obj.Reference2
|
||||
|
||||
# Update enumerations
|
||||
ENUMS = self.getActiveEnumerations(obj)
|
||||
obj.Reference1 = ENUMS['Reference1']
|
||||
obj.Reference2 = ENUMS['Reference2']
|
||||
|
||||
# Restore pre-existing values if available with active enumerations.
|
||||
# If not, set to first element in active enumeration list.
|
||||
if pre_Ref1 in ENUMS['Reference1']:
|
||||
obj.Reference1 = pre_Ref1
|
||||
else:
|
||||
obj.Reference1 = ENUMS['Reference1'][0]
|
||||
if pre_Ref2 in ENUMS['Reference2']:
|
||||
obj.Reference2 = pre_Ref2
|
||||
else:
|
||||
obj.Reference2 = ENUMS['Reference2'][0]
|
||||
|
||||
return ENUMS
|
||||
|
||||
def setEditorProperties(self, obj):
|
||||
# Used to hide inputs in properties list
|
||||
A = B = 2
|
||||
if hasattr(obj, 'Base'):
|
||||
if obj.Base:
|
||||
(base, subsList) = obj.Base[0]
|
||||
subCnt = len(subsList)
|
||||
if subCnt == 1:
|
||||
A = 0
|
||||
elif subCnt == 2:
|
||||
A = B = 0
|
||||
else:
|
||||
ENUMS = self.opPropertyEnumerations()
|
||||
obj.Reference1 = ENUMS['Reference1']
|
||||
obj.Reference2 = ENUMS['Reference2']
|
||||
|
||||
obj.setEditorMode('Reference1', A)
|
||||
obj.setEditorMode('Reference2', B)
|
||||
@@ -196,6 +242,7 @@ class ObjectSlot(PathOp.ObjectOp):
|
||||
if hasattr(self, 'propertiesReady'):
|
||||
if self.propertiesReady:
|
||||
if prop in ['Base']:
|
||||
self.updateEnumerations(obj)
|
||||
self.setEditorProperties(obj)
|
||||
|
||||
def opOnDocumentRestored(self, obj):
|
||||
@@ -209,15 +256,15 @@ class ObjectSlot(PathOp.ObjectOp):
|
||||
obj.setEditorMode('ShowTempObjects', mode)
|
||||
|
||||
# Repopulate enumerations in case of changes
|
||||
ENUMS = self.opPropertyEnumerations()
|
||||
ENUMS = self.updateEnumerations(obj)
|
||||
for n in ENUMS:
|
||||
restore = False
|
||||
if hasattr(obj, n):
|
||||
val = obj.getPropertyByName(n)
|
||||
restore = True
|
||||
setattr(obj, n, ENUMS[n])
|
||||
setattr(obj, n, ENUMS[n]) # set the enumerations list
|
||||
if restore:
|
||||
setattr(obj, n, val)
|
||||
setattr(obj, n, val) # restore the value
|
||||
|
||||
self.setEditorProperties(obj)
|
||||
|
||||
@@ -320,6 +367,8 @@ class ObjectSlot(PathOp.ObjectOp):
|
||||
self.tmpGrp = FreeCAD.ActiveDocument.addObject('App::DocumentObjectGroup', 'tmpDebugGrp')
|
||||
tmpGrpNm = self.tmpGrp.Name
|
||||
|
||||
# self.updateEnumerations(obj)
|
||||
|
||||
# Identify parent Job
|
||||
JOB = PathUtils.findParentJob(obj)
|
||||
self.JOB = JOB
|
||||
@@ -418,11 +467,14 @@ class ObjectSlot(PathOp.ObjectOp):
|
||||
lenSL = len(subsList)
|
||||
featureCnt = lenSL
|
||||
if lenSL == 1:
|
||||
PathLog.debug('Reference 1: {}'.format(obj.Reference1))
|
||||
sub1 = subsList[0]
|
||||
shape_1 = getattr(base.Shape, sub1)
|
||||
self.shape1 = shape_1
|
||||
pnts = self._processSingle(obj, shape_1, sub1)
|
||||
else:
|
||||
PathLog.debug('Reference 1: {}'.format(obj.Reference1))
|
||||
PathLog.debug('Reference 2: {}'.format(obj.Reference2))
|
||||
sub1 = subsList[0]
|
||||
sub2 = subsList[1]
|
||||
shape_1 = getattr(base.Shape, sub1)
|
||||
@@ -467,7 +519,7 @@ class ObjectSlot(PathOp.ObjectOp):
|
||||
|
||||
(p1, p2) = pnts
|
||||
if self.isDebug:
|
||||
PathLog.debug('p1, p2: {}, {}'.format(p1, p2))
|
||||
PathLog.debug('Path Points are:\np1 = {}\np2 = {}'.format(p1, p2))
|
||||
if p1.sub(p2).Length != 0 and self.showTempObjects:
|
||||
O = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmp_Path')
|
||||
O.Shape = Part.makeLine(p1, p2)
|
||||
@@ -493,7 +545,7 @@ class ObjectSlot(PathOp.ObjectOp):
|
||||
It returns the slot gcode for the operation."""
|
||||
CMDS = list()
|
||||
|
||||
def layerPass(p1, p2, depth):
|
||||
def linePass(p1, p2, depth):
|
||||
cmds = list()
|
||||
# cmds.append(Path.Command('N (Tool type: {})'.format(toolType), {}))
|
||||
cmds.append(Path.Command('G0', {'X': p1.x, 'Y': p1.y, 'F': self.horizRapid}))
|
||||
@@ -502,16 +554,25 @@ class ObjectSlot(PathOp.ObjectOp):
|
||||
return cmds
|
||||
|
||||
# CMDS.append(Path.Command('N (Tool type: {})'.format(toolType), {}))
|
||||
# CMDS.append(Path.Command('G0', {'Z': obj.ClearanceHeight.Value, 'F': self.vertRapid}))
|
||||
if obj.LayerMode == 'Single-pass':
|
||||
CMDS.extend(layerPass(p1, p2, obj.FinalDepth.Value))
|
||||
CMDS.extend(linePass(p1, p2, obj.FinalDepth.Value))
|
||||
CMDS.append(Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid}))
|
||||
else:
|
||||
prvDep = obj.StartDepth.Value
|
||||
for dep in self.depthParams:
|
||||
CMDS.extend(layerPass(p1, p2, dep))
|
||||
CMDS.append(Path.Command('G0', {'Z': prvDep, 'F': self.vertRapid}))
|
||||
prvDep = dep
|
||||
if obj.CutPattern == 'Line':
|
||||
for dep in self.depthParams:
|
||||
CMDS.extend(linePass(p1, p2, dep))
|
||||
CMDS.append(Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid}))
|
||||
elif obj.CutPattern == 'ZigZag':
|
||||
CMDS.append(Path.Command('G0', {'X': p1.x, 'Y': p1.y, 'F': self.horizRapid}))
|
||||
i = 0
|
||||
for dep in self.depthParams:
|
||||
if i % 2.0 == 0: # even
|
||||
CMDS.append(Path.Command('G1', {'Z': dep, 'F': self.vertFeed}))
|
||||
CMDS.append(Path.Command('G1', {'X': p2.x, 'Y': p2.y, 'F': self.horizFeed}))
|
||||
else: # odd
|
||||
CMDS.append(Path.Command('G1', {'Z': dep, 'F': self.vertFeed}))
|
||||
CMDS.append(Path.Command('G1', {'X': p1.x, 'Y': p1.y, 'F': self.horizFeed}))
|
||||
i += 1
|
||||
CMDS.append(Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid}))
|
||||
|
||||
return CMDS
|
||||
@@ -528,7 +589,7 @@ class ObjectSlot(PathOp.ObjectOp):
|
||||
pnts = False
|
||||
|
||||
norm = shape_1.normalAt(0.0, 0.0)
|
||||
PathLog.debug('Face.normalAt(): {}'.format(norm))
|
||||
PathLog.debug('{}.normalAt(): {}'.format(sub1, norm))
|
||||
if norm.z == 1 or norm.z == -1:
|
||||
pnts = self._processSingleHorizFace(obj, shape_1)
|
||||
elif norm.z == 0:
|
||||
@@ -571,6 +632,7 @@ class ObjectSlot(PathOp.ObjectOp):
|
||||
|
||||
def _processSingleHorizFace(self, obj, shape):
|
||||
"""Determine slot path endpoints from a single horizontally oriented face."""
|
||||
PathLog.debug('_processSingleHorizFace()')
|
||||
lineTypes = ['Part::GeomLine']
|
||||
|
||||
def getRadians(self, E):
|
||||
@@ -603,8 +665,6 @@ class ObjectSlot(PathOp.ObjectOp):
|
||||
pairs = list()
|
||||
eCnt = len(shape.Edges)
|
||||
lstE = eCnt - 1
|
||||
I = [i for i in range(0, eCnt)]
|
||||
I.append(0)
|
||||
for i in range(0, eCnt):
|
||||
if i < lstE:
|
||||
ni = i + 1
|
||||
@@ -629,6 +689,11 @@ class ObjectSlot(PathOp.ObjectOp):
|
||||
if pairCnt > 1:
|
||||
pairs.sort(key=lambda tup: tup[0].Length, reverse=True)
|
||||
|
||||
if self.isDebug:
|
||||
PathLog.debug(' -pairCnt: {}'.format(pairCnt))
|
||||
for (a, b) in pairs:
|
||||
PathLog.debug(' -pair: {}, {}'.format(round(a.Length, 4), round(b.Length,4)))
|
||||
|
||||
if pairCnt == 0:
|
||||
msg = translate('PathSlot',
|
||||
'No parallel edges identified.')
|
||||
@@ -638,9 +703,9 @@ class ObjectSlot(PathOp.ObjectOp):
|
||||
same = pairs[0]
|
||||
else:
|
||||
if obj.Reference1 == 'Long Edge':
|
||||
same = pairs[0]
|
||||
elif obj.Reference1 == 'Short Edge':
|
||||
same = pairs[1]
|
||||
elif obj.Reference1 == 'Short Edge':
|
||||
same = pairs[0]
|
||||
else:
|
||||
msg = 'Reference1 '
|
||||
msg += translate('PathSlot',
|
||||
@@ -653,6 +718,7 @@ class ObjectSlot(PathOp.ObjectOp):
|
||||
|
||||
def _processSingleComplexFace(self, obj, shape):
|
||||
"""Determine slot path endpoints from a single complex face."""
|
||||
PathLog.debug('_processSingleComplexFace()')
|
||||
PNTS = list()
|
||||
|
||||
def zVal(V):
|
||||
@@ -667,6 +733,7 @@ class ObjectSlot(PathOp.ObjectOp):
|
||||
def _processSingleVertFace(self, obj, shape):
|
||||
"""Determine slot path endpoints from a single vertically oriented face
|
||||
with no single bottom edge."""
|
||||
PathLog.debug('_processSingleVertFace()')
|
||||
eCnt = len(shape.Edges)
|
||||
V0 = shape.Edges[0].Vertexes[0]
|
||||
V1 = shape.Edges[eCnt - 1].Vertexes[1]
|
||||
@@ -691,6 +758,7 @@ class ObjectSlot(PathOp.ObjectOp):
|
||||
|
||||
# Methods for processing double geometry
|
||||
def _processDouble(self, obj, shape_1, sub1, shape_2, sub2):
|
||||
PathLog.debug('_processDouble()')
|
||||
"""This is the control method for slots based on a
|
||||
two Base Geometry features."""
|
||||
cmds = False
|
||||
@@ -898,47 +966,12 @@ class ObjectSlot(PathOp.ObjectOp):
|
||||
n2 = p2
|
||||
return (n1, n2)
|
||||
|
||||
def _getEndMidPoints(self, same):
|
||||
# Find mid-points between ends of equal, oppossing edges
|
||||
e0va = same[0].Vertexes[0]
|
||||
e0vb = same[0].Vertexes[1]
|
||||
e1va = same[1].Vertexes[0]
|
||||
e1vb = same[1].Vertexes[1]
|
||||
|
||||
if False:
|
||||
midX1 = (e0va.X + e0vb.X) / 2.0
|
||||
midY1 = (e0va.Y + e0vb.Y) / 2.0
|
||||
midX2 = (e1va.X + e1vb.X) / 2.0
|
||||
midY2 = (e1va.Y + e1vb.Y) / 2.0
|
||||
m1 = FreeCAD.Vector(midX1, midY1, e0va.Z)
|
||||
m2 = FreeCAD.Vector(midX2, midY2, e0va.Z)
|
||||
|
||||
p1 = FreeCAD.Vector(e0va.X, e0va.Y, e0va.Z)
|
||||
p2 = FreeCAD.Vector(e0vb.X, e0vb.Y, e0vb.Z)
|
||||
p3 = FreeCAD.Vector(e1va.X, e1va.Y, e1va.Z)
|
||||
p4 = FreeCAD.Vector(e1vb.X, e1vb.Y, e1vb.Z)
|
||||
|
||||
L0 = Part.makeLine(p1, p2)
|
||||
L1 = Part.makeLine(p3, p4)
|
||||
comL0 = L0.CenterOfMass
|
||||
comL1 = L1.CenterOfMass
|
||||
m1 = FreeCAD.Vector(comL0.x, comL0.y, 0.0)
|
||||
m2 = FreeCAD.Vector(comL1.x, comL1.y, 0.0)
|
||||
|
||||
return (m1, m2)
|
||||
|
||||
def _getOppMidPoints(self, same):
|
||||
# Find mid-points between ends of equal, oppossing edges
|
||||
v1 = same[0].Vertexes[0]
|
||||
v2 = same[0].Vertexes[1]
|
||||
a1 = same[1].Vertexes[0]
|
||||
a2 = same[1].Vertexes[1]
|
||||
midX1 = (v1.X + a2.X) / 2.0
|
||||
midY1 = (v1.Y + a2.Y) / 2.0
|
||||
midX2 = (v2.X + a1.X) / 2.0
|
||||
midY2 = (v2.Y + a1.Y) / 2.0
|
||||
p1 = FreeCAD.Vector(midX1, midY1, v1.Z)
|
||||
p2 = FreeCAD.Vector(midX2, midY2, v1.Z)
|
||||
com1 = same[0].CenterOfMass
|
||||
com2 = same[1].CenterOfMass
|
||||
p1 = FreeCAD.Vector(com1.x, com1.y, 0.0)
|
||||
p2 = FreeCAD.Vector(com2.x, com2.y, 0.0)
|
||||
return (p1, p2)
|
||||
|
||||
def _isParallel(self, dYdX1, dYdX2):
|
||||
@@ -1152,19 +1185,31 @@ class ObjectSlot(PathOp.ObjectOp):
|
||||
return ('Wire', wires[0])
|
||||
return False
|
||||
|
||||
def _getReference1Enums(self, sub, single=False):
|
||||
# Adjust available enumerations
|
||||
enums1 = self.opPropertyEnumerations()['Reference1']
|
||||
for ri in removeIndexesFromReference_1(sub, single):
|
||||
enums1.pop(ri)
|
||||
return enums1
|
||||
def _makeReference1Enumerations(self, sub, single=False):
|
||||
"""Customize Reference1 enumerations based on feature type."""
|
||||
PathLog.debug('_makeReference1Enumerations()')
|
||||
cat = sub[:4]
|
||||
if single:
|
||||
if cat == 'Face':
|
||||
return ['Long Edge', 'Short Edge']
|
||||
elif cat == 'Edge':
|
||||
return ['Long Edge']
|
||||
elif cat == 'Vert':
|
||||
return ['Vertex']
|
||||
elif cat == 'Vert':
|
||||
return ['Vertex']
|
||||
|
||||
def _getReference2Enums(self, sub):
|
||||
# Adjust available enumerations
|
||||
enums2 = self.opPropertyEnumerations()['Reference2']
|
||||
for ri in removeIndexesFromReference_2(sub):
|
||||
enums2.pop(ri)
|
||||
return enums2
|
||||
return ['Center of Mass', 'Center of BoundBox',
|
||||
'Lowest Point', 'Highest Point']
|
||||
|
||||
def _makeReference2Enumerations(self, sub):
|
||||
"""Customize Reference2 enumerations based on feature type."""
|
||||
PathLog.debug('_makeReference2Enumerations()')
|
||||
cat = sub[:4]
|
||||
if cat == 'Vert':
|
||||
return ['Vertex']
|
||||
return ['Center of Mass', 'Center of BoundBox',
|
||||
'Lowest Point', 'Highest Point']
|
||||
|
||||
def _lineCollisionCheck(self, obj, p1, p2):
|
||||
"""Make simple circle with diameter of tool, at start point.
|
||||
@@ -1176,44 +1221,51 @@ class ObjectSlot(PathOp.ObjectOp):
|
||||
|
||||
def getPerp(p1, p2, dist):
|
||||
toEnd = p2.sub(p1)
|
||||
factor = dist / toEnd.Length
|
||||
perp = FreeCAD.Vector(-1 * toEnd.y, toEnd.x, 0.0)
|
||||
if perp.x == 0 and perp.y == 0:
|
||||
return perp
|
||||
perp.normalize()
|
||||
perp.multiply(dist)
|
||||
return perp
|
||||
|
||||
# Make first cylinder
|
||||
ce1 = Part.Wire(Part.makeCircle(rad, p1).Edges)
|
||||
ce2 = Part.Wire(Part.makeCircle(rad, p2).Edges)
|
||||
C1 = Part.Face(ce1)
|
||||
C2 = Part.Face(ce2)
|
||||
|
||||
zTrans = obj.FinalDepth.Value - C1.BoundBox.ZMin
|
||||
C1.translate(FreeCAD.Vector(0.0, 0.0, zTrans))
|
||||
zTrans = obj.FinalDepth.Value - C2.BoundBox.ZMin
|
||||
C2.translate(FreeCAD.Vector(0.0, 0.0, zTrans))
|
||||
|
||||
extFwd = obj.StartDepth.Value - obj.FinalDepth.Value
|
||||
extVect = FreeCAD.Vector(0.0, 0.0, extFwd)
|
||||
startShp = C1.extrude(extVect)
|
||||
endShp = C2.extrude(extVect)
|
||||
|
||||
perp = getPerp(p1, p2, rad)
|
||||
v1 = p1.add(perp)
|
||||
v2 = p1.sub(perp)
|
||||
v3 = p2.sub(perp)
|
||||
v4 = p2.add(perp)
|
||||
e1 = Part.makeLine(v1, v2)
|
||||
e2 = Part.makeLine(v2, v3)
|
||||
e3 = Part.makeLine(v3, v4)
|
||||
e4 = Part.makeLine(v4, v1)
|
||||
edges = Part.__sortEdges__([e1, e2, e3, e4])
|
||||
rectFace = Part.Face(Part.Wire(edges))
|
||||
zTrans = obj.FinalDepth.Value - rectFace.BoundBox.ZMin
|
||||
rectFace.translate(FreeCAD.Vector(0.0, 0.0, zTrans))
|
||||
boxShp = rectFace.extrude(extVect)
|
||||
if p2.sub(p1).Length > 0:
|
||||
# Make second cylinder
|
||||
ce2 = Part.Wire(Part.makeCircle(rad, p2).Edges)
|
||||
C2 = Part.Face(ce2)
|
||||
zTrans = obj.FinalDepth.Value - C2.BoundBox.ZMin
|
||||
C2.translate(FreeCAD.Vector(0.0, 0.0, zTrans))
|
||||
endShp = C2.extrude(extVect)
|
||||
|
||||
part1 = startShp.fuse(boxShp)
|
||||
pathTravel = part1.fuse(endShp)
|
||||
# Make extruded rectangle to connect cylinders
|
||||
perp = getPerp(p1, p2, rad)
|
||||
v1 = p1.add(perp)
|
||||
v2 = p1.sub(perp)
|
||||
v3 = p2.sub(perp)
|
||||
v4 = p2.add(perp)
|
||||
e1 = Part.makeLine(v1, v2)
|
||||
e2 = Part.makeLine(v2, v3)
|
||||
e3 = Part.makeLine(v3, v4)
|
||||
e4 = Part.makeLine(v4, v1)
|
||||
edges = Part.__sortEdges__([e1, e2, e3, e4])
|
||||
rectFace = Part.Face(Part.Wire(edges))
|
||||
zTrans = obj.FinalDepth.Value - rectFace.BoundBox.ZMin
|
||||
rectFace.translate(FreeCAD.Vector(0.0, 0.0, zTrans))
|
||||
boxShp = rectFace.extrude(extVect)
|
||||
|
||||
# Fuse two cylinders and box together
|
||||
part1 = startShp.fuse(boxShp)
|
||||
pathTravel = part1.fuse(endShp)
|
||||
else:
|
||||
pathTravel = startShp
|
||||
|
||||
if self.showTempObjects:
|
||||
O = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmp_PathTravel')
|
||||
@@ -1233,35 +1285,6 @@ class ObjectSlot(PathOp.ObjectOp):
|
||||
# Eclass
|
||||
|
||||
|
||||
# Determine applicable enumerations
|
||||
def removeIndexesFromReference_1(sub, single=False):
|
||||
"""Determine which enumerations to remove for Reference1 input
|
||||
based upon the feature type(category)."""
|
||||
cat = sub[:4]
|
||||
remIdxs = [6, 5, 4]
|
||||
if cat == 'Face':
|
||||
if single:
|
||||
remIdxs = [6, 3, 2, 1, 0]
|
||||
elif cat == 'Edge':
|
||||
if single:
|
||||
remIdxs = [6, 5, 3, 2, 1, 0]
|
||||
elif cat == 'Vert':
|
||||
remIdxs = [5, 4, 3, 2, 1, 0]
|
||||
return remIdxs
|
||||
|
||||
|
||||
def removeIndexesFromReference_2(sub):
|
||||
"""Determine which enumerations to remove for Reference2 input
|
||||
based upon the feature type(category)."""
|
||||
cat = sub[:4]
|
||||
remIdxs = [4]
|
||||
# Customize Reference combobox options
|
||||
if cat == 'Vert':
|
||||
remIdxs = [3, 2, 1, 0]
|
||||
return remIdxs
|
||||
|
||||
|
||||
|
||||
def SetupProperties():
|
||||
''' SetupProperties() ... Return list of properties required for operation.'''
|
||||
return [tup[1] for tup in ObjectSlot.opPropertyDefinitions(False)]
|
||||
|
||||
@@ -37,33 +37,75 @@ __doc__ = "Slot operation page controller and command implementation."
|
||||
__contributors__ = ""
|
||||
|
||||
|
||||
DEBUG = False
|
||||
|
||||
def debugMsg(msg):
|
||||
global DEBUG
|
||||
if DEBUG:
|
||||
FreeCAD.Console.PrintMessage('PathSlotGui:: ' + msg + '\n')
|
||||
|
||||
|
||||
class TaskPanelOpPage(PathOpGui.TaskPanelPage):
|
||||
'''Page controller class for the Slot operation.'''
|
||||
|
||||
def getForm(self):
|
||||
'''getForm() ... returns UI'''
|
||||
debugMsg('getForm()')
|
||||
return FreeCADGui.PySideUic.loadUi(":/panels/PageOpSlotEdit.ui")
|
||||
|
||||
def initPage(self, obj):
|
||||
'''initPage(obj) ... Is called after getForm() to initiate the task panel.'''
|
||||
debugMsg('initPage()')
|
||||
# pylint: disable=attribute-defined-outside-init
|
||||
self.CATS = [None, None]
|
||||
self.propEnums = PathSlot.ObjectSlot.opPropertyEnumerations(False)
|
||||
self.ENUMS = dict()
|
||||
self.setTitle("Slot - " + obj.Label)
|
||||
# retrieve property enumerations
|
||||
self.propEnums = PathSlot.ObjectSlot.opPropertyEnumerations(False)
|
||||
# Requirements due to Gui::QuantitySpinBox class use in UI panel
|
||||
self.geo1Extension = PathGui.QuantitySpinBox(self.form.geo1Extension, obj, 'ExtendPathStart')
|
||||
self.geo2Extension = PathGui.QuantitySpinBox(self.form.geo2Extension, obj, 'ExtendPathEnd')
|
||||
# self.updateVisibility()
|
||||
|
||||
def getForm(self):
|
||||
'''getForm() ... returns UI'''
|
||||
# FreeCAD.Console.PrintMessage('getForm()\n')
|
||||
return FreeCADGui.PySideUic.loadUi(":/panels/PageOpSlotEdit.ui")
|
||||
def setFields(self, obj):
|
||||
'''setFields(obj) ... transfers obj's property values to UI'''
|
||||
debugMsg('setFields()')
|
||||
debugMsg('... calling updateVisibility()')
|
||||
self.updateVisibility()
|
||||
|
||||
self.updateQuantitySpinBoxes()
|
||||
|
||||
self.setupToolController(obj, self.form.toolController)
|
||||
self.setupCoolant(obj, self.form.coolantController)
|
||||
|
||||
enums = self.propEnums['Reference1']
|
||||
if 'Reference1' in self.ENUMS:
|
||||
enums = self.ENUMS['Reference1']
|
||||
debugMsg(' -enums1: {}'.format(enums))
|
||||
idx = enums.index(obj.Reference1)
|
||||
self.form.geo1Reference.setCurrentIndex(idx)
|
||||
|
||||
enums = self.propEnums['Reference2']
|
||||
if 'Reference2' in self.ENUMS:
|
||||
enums = self.ENUMS['Reference2']
|
||||
debugMsg(' -enums2: {}'.format(enums))
|
||||
idx = enums.index(obj.Reference2)
|
||||
self.form.geo2Reference.setCurrentIndex(idx)
|
||||
|
||||
self.selectInComboBox(obj.LayerMode, self.form.layerMode)
|
||||
self.selectInComboBox(obj.PathOrientation, self.form.pathOrientation)
|
||||
|
||||
if obj.ReverseDirection:
|
||||
self.form.reverseDirection.setCheckState(QtCore.Qt.Checked)
|
||||
else:
|
||||
self.form.reverseDirection.setCheckState(QtCore.Qt.Unchecked)
|
||||
|
||||
def updateQuantitySpinBoxes(self):
|
||||
# FreeCAD.Console.PrintMessage('updateQuantitySpinBoxes()\n')
|
||||
self.geo1Extension.updateSpinBox()
|
||||
self.geo2Extension.updateSpinBox()
|
||||
|
||||
def getFields(self, obj):
|
||||
'''getFields(obj) ... transfers values from UI to obj's proprties'''
|
||||
# FreeCAD.Console.PrintMessage('getFields()\n')
|
||||
debugMsg('getFields()')
|
||||
self.updateToolController(obj, self.form.toolController)
|
||||
self.updateCoolant(obj, self.form.coolantController)
|
||||
|
||||
@@ -79,36 +121,11 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
|
||||
val = self.propEnums['PathOrientation'][self.form.pathOrientation.currentIndex()]
|
||||
obj.PathOrientation = val
|
||||
|
||||
if hasattr(self.form, 'reverseDirection'):
|
||||
obj.ReverseDirection = self.form.reverseDirection.isChecked()
|
||||
|
||||
def setFields(self, obj):
|
||||
'''setFields(obj) ... transfers obj's property values to UI'''
|
||||
# FreeCAD.Console.PrintMessage('setFields()\n')
|
||||
self.updateQuantitySpinBoxes()
|
||||
|
||||
self.setupToolController(obj, self.form.toolController)
|
||||
self.setupCoolant(obj, self.form.coolantController)
|
||||
|
||||
idx = self.propEnums['Reference1'].index(obj.Reference1)
|
||||
self.form.geo1Reference.setCurrentIndex(idx)
|
||||
idx = self.propEnums['Reference2'].index(obj.Reference2)
|
||||
self.form.geo2Reference.setCurrentIndex(idx)
|
||||
|
||||
self.selectInComboBox(obj.LayerMode, self.form.layerMode)
|
||||
self.selectInComboBox(obj.PathOrientation, self.form.pathOrientation)
|
||||
|
||||
if obj.ReverseDirection:
|
||||
self.form.reverseDirection.setCheckState(QtCore.Qt.Checked)
|
||||
else:
|
||||
self.form.reverseDirection.setCheckState(QtCore.Qt.Unchecked)
|
||||
|
||||
# FreeCAD.Console.PrintMessage('... calling updateVisibility()\n')
|
||||
self.updateVisibility()
|
||||
obj.ReverseDirection = self.form.reverseDirection.isChecked()
|
||||
|
||||
def getSignalsForUpdate(self, obj):
|
||||
'''getSignalsForUpdate(obj) ... return list of signals for updating obj'''
|
||||
# FreeCAD.Console.PrintMessage('getSignalsForUpdate()\n')
|
||||
debugMsg('getSignalsForUpdate()')
|
||||
signals = []
|
||||
signals.append(self.form.toolController.currentIndexChanged)
|
||||
signals.append(self.form.coolantController.currentIndexChanged)
|
||||
@@ -123,7 +140,7 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
|
||||
|
||||
def updateVisibility(self, sentObj=None):
|
||||
'''updateVisibility(sentObj=None)... Updates visibility of Tasks panel objects.'''
|
||||
# FreeCAD.Console.PrintMessage('updateVisibility()\n')
|
||||
# debugMsg('updateVisibility()')
|
||||
hideFeatures = True
|
||||
if hasattr(self.obj, 'Base'):
|
||||
if self.obj.Base:
|
||||
@@ -136,8 +153,8 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
|
||||
subCnt = len(sublist)
|
||||
|
||||
if subCnt == 1:
|
||||
debugMsg(' -subCnt == 1')
|
||||
# Save value, then reset choices
|
||||
self.resetRef1Choices()
|
||||
n1 = sublist[0]
|
||||
s1 = getattr(base.Shape, n1)
|
||||
# Show Reference1 and cusomize options within
|
||||
@@ -152,8 +169,7 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
|
||||
if self.CATS[1]:
|
||||
self.CATS[1] = None
|
||||
elif subCnt == 2:
|
||||
self.resetRef1Choices()
|
||||
self.resetRef2Choices()
|
||||
debugMsg(' -subCnt == 2')
|
||||
n1 = sublist[0]
|
||||
n2 = sublist[1]
|
||||
s1 = getattr(base.Shape, n1)
|
||||
@@ -171,64 +187,55 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
|
||||
else:
|
||||
self.form.pathOrientation_label.hide()
|
||||
self.form.pathOrientation.hide()
|
||||
|
||||
if hideFeatures:
|
||||
# reset values
|
||||
self.CATS = [None, None]
|
||||
self.selectInComboBox('Start to End', self.form.pathOrientation)
|
||||
# hide inputs and show message
|
||||
self.form.featureReferences.hide()
|
||||
self.form.customPoints.show()
|
||||
|
||||
"""
|
||||
'Reference1': ['Center of Mass', 'Center of BoundBox',
|
||||
'Lowest Point', 'Highest Point', 'Long Edge',
|
||||
'Short Edge', 'Vertex'],
|
||||
'Reference2': ['Center of Mass', 'Center of BoundBox',
|
||||
'Lowest Point', 'Highest Point', 'Vertex']
|
||||
"""
|
||||
|
||||
def customizeReference_1(self, sub, single=False):
|
||||
debugMsg('customizeReference_1()')
|
||||
# Customize Reference1 combobox options
|
||||
# by removing unavailable choices
|
||||
cat = sub[:4]
|
||||
if cat != self.CATS[0]:
|
||||
self.CATS[0] = cat
|
||||
cBox = self.form.geo1Reference
|
||||
cBox.blockSignals(True)
|
||||
for ri in PathSlot.removeIndexesFromReference_1(sub, single):
|
||||
cBox.removeItem(ri)
|
||||
cBox.blockSignals(False)
|
||||
slot = PathSlot.ObjectSlot
|
||||
enums = slot._makeReference1Enumerations(slot, sub, single)
|
||||
self.ENUMS['Reference1'] = enums
|
||||
debugMsg('Ref1: {}'.format(enums))
|
||||
self._updateComboBox(self.form.geo1Reference, enums)
|
||||
# self.form.geo1Reference.setCurrentIndex(0)
|
||||
# self.form.geo1Reference.setCurrentText(enums[0])
|
||||
|
||||
def customizeReference_2(self, sub):
|
||||
debugMsg('customizeReference_2()')
|
||||
# Customize Reference2 combobox options
|
||||
# by removing unavailable choices
|
||||
cat = sub[:4]
|
||||
if cat != self.CATS[1]:
|
||||
self.CATS[1] = cat
|
||||
cBox = self.form.geo2Reference
|
||||
cBox.blockSignals(True)
|
||||
for ri in PathSlot.removeIndexesFromReference_2(sub):
|
||||
cBox.removeItem(ri)
|
||||
cBox.blockSignals(False)
|
||||
cBox.setCurrentIndex(0)
|
||||
|
||||
def resetRef1Choices(self):
|
||||
# Reset Reference1 choices
|
||||
ref1 = self.form.geo1Reference
|
||||
ref1.blockSignals(True)
|
||||
ref1.clear() # Empty the combobox
|
||||
ref1.addItems(self.propEnums['Reference1'])
|
||||
ref1.blockSignals(False)
|
||||
|
||||
def resetRef2Choices(self):
|
||||
# Reset Reference2 choices
|
||||
ref2 = self.form.geo2Reference
|
||||
ref2.blockSignals(True)
|
||||
ref2.clear() # Empty the combobox
|
||||
ref2.addItems(self.propEnums['Reference2'])
|
||||
ref2.blockSignals(False)
|
||||
slot = PathSlot.ObjectSlot
|
||||
enums = slot._makeReference2Enumerations(slot, sub)
|
||||
self.ENUMS['Reference2'] = enums
|
||||
debugMsg('Ref2: {}'.format(enums))
|
||||
self._updateComboBox(self.form.geo2Reference, enums)
|
||||
# self.form.geo2Reference.setCurrentIndex(0)
|
||||
# self.form.geo2Reference.setCurrentText(enums[0])
|
||||
|
||||
def registerSignalHandlers(self, obj):
|
||||
# FreeCAD.Console.PrintMessage('registerSignalHandlers()\n')
|
||||
# debugMsg('registerSignalHandlers()')
|
||||
# self.form.pathOrientation.currentIndexChanged.connect(self.updateVisibility)
|
||||
pass
|
||||
|
||||
def _updateComboBox(self, cBox, enums):
|
||||
cBox.blockSignals(True)
|
||||
cBox.clear()
|
||||
cBox.addItems(enums)
|
||||
cBox.blockSignals(False)
|
||||
|
||||
Command = PathOpGui.SetupOperation('Slot',
|
||||
PathSlot.Create,
|
||||
|
||||
Reference in New Issue
Block a user