Merge pull request #3556 from Russ4262/3D_Surface_Waterline_Fixes

Path: 3D Surface and Waterline fixes per forum identification
This commit is contained in:
sliptonic
2020-06-04 13:17:08 -05:00
committed by GitHub
6 changed files with 206 additions and 150 deletions

View File

@@ -156,7 +156,7 @@
</widget>
</item>
<item row="4" column="1" colspan="2">
<widget class="QComboBox" name="comboBox">
<widget class="QComboBox" name="profileEdges">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Profile the edges of the selection.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>

View File

@@ -165,16 +165,6 @@
<string>None</string>
</property>
</item>
<item>
<property name="text">
<string>Line</string>
</property>
</item>
<item>
<property name="text">
<string>ZigZag</string>
</property>
</item>
<item>
<property name="text">
<string>Circular</string>
@@ -185,6 +175,26 @@
<string>CircularZigZag</string>
</property>
</item>
<item>
<property name="text">
<string>Line</string>
</property>
</item>
<item>
<property name="text">
<string>Offset</string>
</property>
</item>
<item>
<property name="text">
<string>Spiral</string>
</property>
</item>
<item>
<property name="text">
<string>ZigZag</string>
</property>
</item>
</widget>
</item>
<item row="8" column="3">

View File

@@ -38,7 +38,8 @@ from PySide import QtCore
try:
import ocl
except ImportError:
msg = QtCore.QCoreApplication.translate("PathSurface", "This operation requires OpenCamLib to be installed.")
msg = QtCore.QCoreApplication.translate("PathSurface",
"This operation requires OpenCamLib to be installed.")
FreeCAD.Console.PrintError(msg + "\n")
raise ImportError
# import sys
@@ -206,7 +207,7 @@ class ObjectSurface(PathOp.ObjectOp):
'BoundBox': ['BaseBoundBox', 'Stock'],
'PatternCenterAt': ['CenterOfMass', 'CenterOfBoundBox', 'XminYmin', 'Custom'],
'CutMode': ['Conventional', 'Climb'],
'CutPattern': ['Line', 'Circular', 'CircularZigZag', 'Offset', 'Spiral', 'ZigZag'], # Additional goals ['Offset', 'ZigZagOffset', 'Grid', 'Triangle']
'CutPattern': ['Circular', 'CircularZigZag', 'Line', 'Offset', 'Spiral', 'ZigZag'], # Additional goals ['Offset', 'ZigZagOffset', 'Grid', 'Triangle']
'DropCutterDir': ['X', 'Y'],
'HandleMultipleFeatures': ['Collectively', 'Individually'],
'LayerMode': ['Single-pass', 'Multi-pass'],
@@ -575,18 +576,18 @@ class ObjectSurface(PathOp.ObjectOp):
PathSurfaceSupport._prepareModelSTLs(self, JOB, obj, m, ocl)
Mdl = JOB.Model.Group[m]
if FACES[m] is False:
PathLog.error('No data for model base: {}'.format(JOB.Model.Group[m].Label))
else:
if FACES[m]:
PathLog.debug('Working on Model.Group[{}]: {}'.format(m, Mdl.Label))
if m > 0:
# Raise to clearance between models
CMDS.append(Path.Command('N (Transition to base: {}.)'.format(Mdl.Label)))
CMDS.append(Path.Command('G0', {'Z': obj.ClearanceHeight.Value, 'F': self.vertRapid}))
PathLog.info('Working on Model.Group[{}]: {}'.format(m, Mdl.Label))
# make stock-model-voidShapes STL model for avoidance detection on transitions
PathSurfaceSupport._makeSafeSTL(self, JOB, obj, m, FACES[m], VOIDS[m], ocl)
# Process model/faces - OCL objects must be ready
CMDS.extend(self._processCutAreas(JOB, obj, m, FACES[m], VOIDS[m]))
else:
PathLog.debug('No data for model base: {}'.format(JOB.Model.Group[m].Label))
# Save gcode produced
self.commandlist.extend(CMDS)
@@ -659,7 +660,8 @@ class ObjectSurface(PathOp.ObjectOp):
exTime = str(tMins) + ' min. ' + str(round(tSecs, 5)) + ' sec.'
else:
exTime = str(round(execTime, 5)) + ' sec.'
FreeCAD.Console.PrintMessage('3D Surface operation time is {}\n'.format(exTime))
msg = translate('PathSurface', 'operation time is')
FreeCAD.Console.PrintMessage('3D Surface ' + msg + '{}\n'.format(exTime))
if self.cancelOperation:
FreeCAD.ActiveDocument.openTransaction(translate("PathSurface", "Canceled 3D Surface operation."))
@@ -751,27 +753,21 @@ class ObjectSurface(PathOp.ObjectOp):
if obj.ProfileEdges != 'None':
prflShp = self.profileShapes[mdlIdx][fsi]
if prflShp is False:
PathLog.error('No profile shape is False.')
msg = translate('PathSurface', 'No profile geometry shape returned.')
PathLog.error(msg)
return list()
if self.showDebugObjects:
P = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmpNewProfileShape')
P.Shape = prflShp
P.purgeTouched()
self.tempGroup.addObject(P)
self.showDebugObject(prflShp, 'NewProfileShape')
# get offset path geometry and perform OCL scan with that geometry
pathOffsetGeom = self._offsetFacesToPointData(obj, prflShp)
if pathOffsetGeom is False:
PathLog.error('No profile geometry returned.')
msg = translate('PathSurface', 'No profile path geometry returned.')
PathLog.error(msg)
return list()
profScan = [self._planarPerformOclScan(obj, pdc, pathOffsetGeom, True)]
geoScan = list()
if obj.ProfileEdges != 'Only':
if self.showDebugObjects:
F = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmpCutArea')
F.Shape = cmpdShp
F.purgeTouched()
self.tempGroup.addObject(F)
self.showDebugObject(cmpdShp, 'CutArea')
# get internal path geometry and perform OCL scan with that geometry
PGG = PathSurfaceSupport.PathGeometryGenerator(obj, cmpdShp, obj.CutPattern)
if self.showDebugObjects:
@@ -779,12 +775,14 @@ class ObjectSurface(PathOp.ObjectOp):
self.tmpCOM = PGG.getCenterOfPattern()
pathGeom = PGG.generatePathGeometry()
if pathGeom is False:
PathLog.error('No path geometry returned.')
msg = translate('PathSurface', 'No clearing shape returned.')
PathLog.error(msg)
return list()
if obj.CutPattern == 'Offset':
useGeom = self._offsetFacesToPointData(obj, pathGeom, profile=False)
if useGeom is False:
PathLog.error('No profile geometry returned.')
msg = translate('PathSurface', 'No clearing path geometry returned.')
PathLog.error(msg)
return list()
geoScan = [self._planarPerformOclScan(obj, pdc, useGeom, True)]
else:
@@ -803,7 +801,8 @@ class ObjectSurface(PathOp.ObjectOp):
SCANDATA.extend(profScan)
if len(SCANDATA) == 0:
PathLog.error('No scan data to convert to Gcode.')
msg = translate('PathSuface', 'No scan data to convert to Gcode.')
PathLog.error(msg)
return list()
# Apply depth offset
@@ -1173,7 +1172,6 @@ class ObjectSurface(PathOp.ObjectOp):
# Manage step over transition and CircularZigZag direction
if so > 0:
# PathLog.debug(' stepover index: {}'.format(so))
# Control ZigZag direction
if obj.CutPattern == 'CircularZigZag':
if odd is True:
@@ -1195,7 +1193,6 @@ class ObjectSurface(PathOp.ObjectOp):
for i in range(0, lenAdjPrts):
prt = ADJPRTS[i]
lenPrt = len(prt)
# PathLog.debug(' adj parts index - lenPrt: {} - {}'.format(i, lenPrt))
if prt == 'BRK' and prtsHasCmds is True:
nxtStart = ADJPRTS[i + 1][0]
minSTH = self._getMinSafeTravelHeight(safePDC, last, nxtStart, minDep=None) # Check safe travel height against fullSTL
@@ -2108,6 +2105,13 @@ class ObjectSurface(PathOp.ObjectOp):
zMax = minDep
return zMax
def showDebugObject(self, objShape, objName):
if self.showDebugObjects:
do = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmp_' + objName)
do.Shape = objShape
do.purgeTouched()
self.tempGroup.addObject(do)
def SetupProperties():
''' SetupProperties() ... Return list of properties required for operation.'''

View File

@@ -42,6 +42,8 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
def initPage(self, obj):
self.setTitle("3D Surface")
# self.updateVisibility()
# retrieve property enumerations
self.propEnums = PathSurface.ObjectSurface.opPropertyEnumerations(False)
def getForm(self):
'''getForm() ... returns UI'''
@@ -52,23 +54,37 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
self.updateToolController(obj, self.form.toolController)
self.updateCoolant(obj, self.form.coolantController)
PathGui.updateInputField(obj, 'DepthOffset', self.form.depthOffset)
PathGui.updateInputField(obj, 'SampleInterval', self.form.sampleInterval)
if obj.BoundBox != str(self.form.boundBoxSelect.currentText()):
obj.BoundBox = str(self.form.boundBoxSelect.currentText())
if obj.ScanType != str(self.form.scanType.currentText()):
obj.ScanType = str(self.form.scanType.currentText())
if obj.StepOver != self.form.stepOver.value():
obj.StepOver = self.form.stepOver.value()
if obj.LayerMode != str(self.form.layerMode.currentText()):
obj.LayerMode = str(self.form.layerMode.currentText())
if obj.CutPattern != str(self.form.cutPattern.currentText()):
obj.CutPattern = str(self.form.cutPattern.currentText())
"""
The following method of getting values from the UI form
allows for translations of combobox options in the UI.
The requirement is that the enumeration lists must
be in the same order in both the opPropertyEnumerations() method
and the UI panel QComboBox list.
Another step to ensure sychronization of the two lists is to
populate the list dynamically in this Gui module in `initPage()`
using the property enumerations list when loading the UI panel.
This type of dynamic combobox population is done for the
Tool Controller selection.
"""
val = self.propEnums['CutPattern'][self.form.cutPattern.currentIndex()]
if obj.CutPattern != val:
obj.CutPattern = val
val = self.propEnums['ProfileEdges'][self.form.profileEdges.currentIndex()]
if obj.ProfileEdges != val:
obj.ProfileEdges = val
if obj.AvoidLastX_Faces != self.form.avoidLastX_Faces.value():
obj.AvoidLastX_Faces = self.form.avoidLastX_Faces.value()
obj.DropCutterExtraOffset.x = FreeCAD.Units.Quantity(self.form.boundBoxExtraOffsetX.text()).Value
obj.DropCutterExtraOffset.y = FreeCAD.Units.Quantity(self.form.boundBoxExtraOffsetY.text()).Value
@@ -77,6 +93,10 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
obj.DropCutterDir = str(self.form.dropCutterDirSelect.currentText())
PathGui.updateInputField(obj, 'DepthOffset', self.form.depthOffset)
if obj.StepOver != self.form.stepOver.value():
obj.StepOver = self.form.stepOver.value()
PathGui.updateInputField(obj, 'SampleInterval', self.form.sampleInterval)
if obj.UseStartPoint != self.form.useStartPoint.isChecked():
@@ -95,7 +115,23 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
self.selectInComboBox(obj.BoundBox, self.form.boundBoxSelect)
self.selectInComboBox(obj.ScanType, self.form.scanType)
self.selectInComboBox(obj.LayerMode, self.form.layerMode)
self.selectInComboBox(obj.CutPattern, self.form.cutPattern)
"""
The following method of setting values in the UI form
allows for translations of combobox options in the UI.
The requirement is that the enumeration lists must
be in the same order in both the opPropertyEnumerations() method
and the UI panel QComboBox list.
The original method is commented out below.
"""
idx = self.propEnums['CutPattern'].index(obj.CutPattern)
self.form.cutPattern.setCurrentIndex(idx)
idx = self.propEnums['ProfileEdges'].index(obj.ProfileEdges)
self.form.profileEdges.setCurrentIndex(idx)
# self.selectInComboBox(obj.CutPattern, self.form.cutPattern)
# self.selectInComboBox(obj.ProfileEdges, self.form.profileEdges)
self.form.avoidLastX_Faces.setValue(obj.AvoidLastX_Faces)
self.form.boundBoxExtraOffsetX.setText(FreeCAD.Units.Quantity(obj.DropCutterExtraOffset.x, FreeCAD.Units.Length).UserString)
self.form.boundBoxExtraOffsetY.setText(FreeCAD.Units.Quantity(obj.DropCutterExtraOffset.y, FreeCAD.Units.Length).UserString)
self.selectInComboBox(obj.DropCutterDir, self.form.dropCutterDirSelect)
@@ -129,6 +165,8 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
signals.append(self.form.scanType.currentIndexChanged)
signals.append(self.form.layerMode.currentIndexChanged)
signals.append(self.form.cutPattern.currentIndexChanged)
signals.append(self.form.profileEdges.currentIndexChanged)
signals.append(self.form.avoidLastX_Faces.editingFinished)
signals.append(self.form.boundBoxExtraOffsetX.editingFinished)
signals.append(self.form.boundBoxExtraOffsetY.editingFinished)
signals.append(self.form.dropCutterDirSelect.currentIndexChanged)

View File

@@ -91,12 +91,14 @@ class PathGeometryGenerator:
if shape.BoundBox.ZMin != 0.0:
shape.translate(FreeCAD.Vector(0.0, 0.0, 0.0 - shape.BoundBox.ZMin))
if shape.BoundBox.ZLength == 0.0:
self.shape = shape
if shape.BoundBox.ZLength > 1.0e-8:
msg = translate('PathSurfaceSupport',
'Shape appears to not be horizontal planar.')
msg += ' ZMax == {} mm.\n'.format(shape.BoundBox.ZMax)
FreeCAD.Console.PrintWarning(msg)
else:
FreeCAD.Console.PrintWarning('Shape appears to not be horizontal planar. ZMax is {}.\n'.format(shape.BoundBox.ZMax))
self._prepareConstants()
self.shape = shape
self._prepareConstants()
def _prepareConstants(self):
# Apply drop cutter extra offset and set the max and min XY area of the operation
@@ -118,8 +120,11 @@ class PathGeometryGenerator:
fCnt += 1
zeroCOM = zeroCOM.add(FreeCAD.Vector(comF.x, comF.y, 0.0).multiply(areaF))
if fCnt == 0:
msg = translate(self.module, 'Cannot calculate the Center Of Mass. Using Center of Boundbox instead.')
FreeCAD.Console.PrintError(msg + '\n')
msg = translate('PathSurfaceSupport',
'Cannot calculate the Center Of Mass.')
msg += ' ' + translate('PathSurfaceSupport',
'Using Center of Boundbox instead.') + '\n'
FreeCAD.Console.PrintError(msg)
bbC = self.shape.BoundBox.Center
zeroCOM = FreeCAD.Vector(bbC.x, bbC.y, 0.0)
else:
@@ -156,11 +161,9 @@ class PathGeometryGenerator:
'''generatePathGeometry()...
Call this function to obtain the path geometry shape, generated by this class.'''
if self.pattern == 'None':
# FreeCAD.Console.PrintWarning('PGG: No pattern set.\n')
return False
if self.shape is None:
# FreeCAD.Console.PrintWarning('PGG: No shape set.\n')
return False
cmd = 'self._' + self.pattern + '()'
@@ -408,7 +411,6 @@ class PathGeometryGenerator:
while cont:
ofstArea = self._getFaceOffset(shape, ofst)
if not ofstArea:
# FreeCAD.Console.PrintWarning('PGG: No offset clearing area returned.\n')
cont = False
True if cont else False # cont used for LGTM
break
@@ -426,9 +428,8 @@ class PathGeometryGenerator:
'''_getFaceOffset(shape, offset) ... internal function.
Original _buildPathArea() version copied from PathAreaOp.py module. This version is modified.
Adjustments made based on notes by @sliptonic at this webpage: https://github.com/sliptonic/FreeCAD/wiki/PathArea-notes.'''
PathLog.debug('_getFaceOffset()')
areaParams = {}
areaParams['Offset'] = offset
areaParams['Fill'] = 1 # 1
areaParams['Coplanar'] = 0
@@ -439,7 +440,6 @@ class PathGeometryGenerator:
areaParams['Project'] = True
area = Path.Area() # Create instance of Area() class object
# area.setPlane(PathUtils.makeWorkplane(shape)) # Set working plane
area.setPlane(PathUtils.makeWorkplane(self.wpc)) # Set working plane to normal at Z=1
area.add(shape)
area.setParams(**areaParams) # set parameters
@@ -475,7 +475,10 @@ class ProcessSelectedFaces:
self.module = None
self.radius = None
self.depthParams = None
self.msgNoFaces = translate(self.module, 'Face selection is unavailable for Rotational scans. Ignoring selected faces.') + '\n'
self.msgNoFaces = translate('PathSurfaceSupport',
'Face selection is unavailable for Rotational scans.') + '\n'
self.msgNoFaces += ' ' + translate('PathSurfaceSupport',
'Ignoring selected faces.') + '\n'
self.JOB = JOB
self.obj = obj
self.profileEdges = 'None'
@@ -558,9 +561,8 @@ class ProcessSelectedFaces:
self.modelSTLs[m] = True
# Process each model base, as a whole, as needed
# PathLog.debug(' -Pre-processing all models in Job.')
for m in range(0, lenGRP):
if fShapes[m] is False:
if self.modelSTLs[m] and not fShapes[m]:
PathLog.debug(' -Pre-processing {} as a whole.'.format(GRP[m].Label))
if self.obj.BoundBox == 'BaseBoundBox':
base = GRP[m]
@@ -569,7 +571,9 @@ class ProcessSelectedFaces:
pPEB = self._preProcessEntireBase(base, m)
if pPEB is False:
FreeCAD.Console.PrintError(' -Failed to pre-process base as a whole.\n')
msg = translate('PathSurfaceSupport',
'Failed to pre-process base as a whole.') + '\n'
FreeCAD.Console.PrintError(msg)
else:
(fcShp, prflShp) = pPEB
if fcShp is not False:
@@ -646,11 +650,13 @@ class ProcessSelectedFaces:
if F[m] is False:
F[m] = list()
F[m].append((shape, faceIdx))
PathLog.debug('.. Cutting {}'.format(sub))
hasFace = True
else:
if V[m] is False:
V[m] = list()
V[m].append((shape, faceIdx))
PathLog.debug('.. Avoiding {}'.format(sub))
hasVoid = True
return (hasFace, hasVoid)
@@ -678,14 +684,16 @@ class ProcessSelectedFaces:
PathLog.debug('Attempting to get cross-section of collective faces.')
if len(outFCS) == 0:
msg = translate('PathSurfaceSupport', 'Cannot process selected faces. Check horizontal surface exposure.')
msg = translate('PathSurfaceSupport',
'Cannot process selected faces. Check horizontal surface exposure.')
FreeCAD.Console.PrintError(msg + '\n')
cont = False
else:
cfsL = Part.makeCompound(outFCS)
# Handle profile edges request
if cont is True and self.profileEdges != 'None':
if cont and self.profileEdges != 'None':
PathLog.debug('.. include Profile Edge')
ofstVal = self._calculateOffsetValue(isHole)
psOfst = extractFaceOffset(cfsL, ofstVal, self.wpc)
if psOfst is not False:
@@ -694,7 +702,6 @@ class ProcessSelectedFaces:
mFS = True
cont = False
else:
# FreeCAD.Console.PrintError(' -Failed to create profile geometry for selected faces.\n')
cont = False
if cont:
@@ -706,8 +713,10 @@ class ProcessSelectedFaces:
ofstVal = self._calculateOffsetValue(isHole)
faceOfstShp = extractFaceOffset(cfsL, ofstVal, self.wpc)
if faceOfstShp is False:
FreeCAD.Console.PrintError(' -Failed to create offset face.\n')
if not faceOfstShp:
msg = translate('PathSurfaceSupport',
'Failed to create offset face.') + '\n'
FreeCAD.Console.PrintError(msg)
cont = False
if cont:
@@ -764,7 +773,6 @@ class ProcessSelectedFaces:
mFS.append(True)
cont = False
else:
# PathLog.error(' -Failed to create profile geometry for Face{}.'.format(fNum))
cont = False
if cont:
@@ -825,7 +833,6 @@ class ProcessSelectedFaces:
avoid = Part.makeCompound(outFCS)
if self.showDebugObjects:
PathLog.debug('*** tmpAvoidArea')
P = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmpVoidEnvelope')
P.Shape = avoid
P.purgeTouched()
@@ -833,7 +840,6 @@ class ProcessSelectedFaces:
if cont:
if self.showDebugObjects:
PathLog.debug('*** tmpVoidCompound')
P = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmpVoidCompound')
P.Shape = avoid
P.purgeTouched()
@@ -841,13 +847,15 @@ class ProcessSelectedFaces:
ofstVal = self._calculateOffsetValue(isHole, isVoid=True)
avdOfstShp = extractFaceOffset(avoid, ofstVal, self.wpc)
if avdOfstShp is False:
FreeCAD.Console.PrintError('Failed to create collective offset avoid face.\n')
msg = translate('PathSurfaceSupport',
'Failed to create collective offset avoid face.')
FreeCAD.Console.PrintError(msg + '\n')
cont = False
if cont:
avdShp = avdOfstShp
if self.obj.AvoidLastX_InternalFeatures is False and len(intFEAT) > 0:
if not self.obj.AvoidLastX_InternalFeatures and len(intFEAT) > 0:
if len(intFEAT) > 1:
ifc = Part.makeCompound(intFEAT)
else:
@@ -855,7 +863,9 @@ class ProcessSelectedFaces:
ofstVal = self._calculateOffsetValue(isHole=True)
ifOfstShp = extractFaceOffset(ifc, ofstVal, self.wpc)
if ifOfstShp is False:
FreeCAD.Console.PrintError('Failed to create collective offset avoid internal features.\n')
msg = translate('PathSurfaceSupport',
'Failed to create collective offset avoid internal features.') + '\n'
FreeCAD.Console.PrintError(msg)
else:
avdShp = avdOfstShp.cut(ifOfstShp)
@@ -891,10 +901,10 @@ class ProcessSelectedFaces:
if csFaceShape is False:
csFaceShape = getSliceFromEnvelope(baseEnv)
if csFaceShape is False:
PathLog.error('Failed to slice baseEnv shape.')
PathLog.debug('Failed to slice baseEnv shape.')
cont = False
if cont is True and self.profileEdges != 'None':
if cont and self.profileEdges != 'None':
PathLog.debug(' -Attempting profile geometry for model base.')
ofstVal = self._calculateOffsetValue(isHole)
psOfst = extractFaceOffset(csFaceShape, ofstVal, self.wpc)
@@ -903,14 +913,13 @@ class ProcessSelectedFaces:
return (True, psOfst)
prflShp = psOfst
else:
# FreeCAD.Console.PrintError(' -Failed to create profile geometry.\n')
cont = False
if cont:
ofstVal = self._calculateOffsetValue(isHole)
faceOffsetShape = extractFaceOffset(csFaceShape, ofstVal, self.wpc)
if faceOffsetShape is False:
PathLog.error('extractFaceOffset() failed for entire base.')
PathLog.debug('extractFaceOffset() failed for entire base.')
else:
faceOffsetShape.translate(FreeCAD.Vector(0.0, 0.0, 0.0 - faceOffsetShape.BoundBox.ZMin))
return (faceOffsetShape, prflShp)
@@ -1024,7 +1033,6 @@ def getProjectedFace(tempGroup, wire):
else:
pWire = Part.Wire(prj.Shape.Edges)
if pWire.isClosed() is False:
# PathLog.debug(' -pWire.isClosed() is False')
return False
slc = Part.Face(pWire)
slc.translate(FreeCAD.Vector(0.0, 0.0, 0.0 - slc.BoundBox.ZMin))
@@ -1069,7 +1077,7 @@ def getShapeEnvelope(shape):
try:
env = PathUtils.getEnvelope(partshape=shape, depthparams=dep_par) # Produces .Shape
except Exception as ee:
FreeCAD.Console.PrintError('try: PathUtils.getEnvelope() failed.\n' + str(ee) + '\n')
FreeCAD.Console.PrintError('PathUtils.getEnvelope() failed.\n' + str(ee) + '\n')
return False
else:
return env
@@ -1196,7 +1204,9 @@ def _makeSafeSTL(self, JOB, obj, mdlIdx, faceShapes, voidShapes, ocl):
adjStckWst = stckWst
fuseShapes.append(adjStckWst)
else:
PathLog.warning('Path transitions might not avoid the model. Verify paths.')
msg = translate('PathSurfaceSupport',
'Path transitions might not avoid the model. Verify paths.')
FreeCAD.Console.PrintWarning(msg + '\n')
else:
# If boundbox is Job.Stock, add hidden pad under stock as base plate
toolDiam = self.cutter.getDiameter()
@@ -1319,7 +1329,6 @@ def pathGeomToLinesPointSet(obj, compGeoShp, cutClimb, toolDiam, closedGap, gaps
closedGap = True
True if closedGap else False # used closedGap for LGTM
else:
# PathLog.debug('---- Gap: {} mm'.format(gap))
gap = round(gap, 6)
if gap < gaps[0]:
gaps.insert(0, gap)
@@ -1348,9 +1357,9 @@ def pathGeomToLinesPointSet(obj, compGeoShp, cutClimb, toolDiam, closedGap, gaps
isEven = lnCnt % 2
if isEven == 0:
PathLog.debug('Line count is ODD.')
PathLog.debug('Line count is ODD: {}.'.format(lnCnt))
else:
PathLog.debug('Line count is even.')
PathLog.debug('Line count is even: {}.'.format(lnCnt))
return LINES
@@ -1432,9 +1441,9 @@ def pathGeomToZigzagPointSet(obj, compGeoShp, cutClimb, toolDiam, closedGap, gap
# Fix directional issue with LAST line when line count is even
isEven = lnCnt % 2
if isEven == 0: # Changed to != with 90 degree CutPatternAngle
PathLog.debug('Line count is even.')
PathLog.debug('Line count is even: {}.'.format(lnCnt))
else:
PathLog.debug('Line count is ODD.')
PathLog.debug('Line count is ODD: {}.'.format(lnCnt))
dirFlg = -1 * dirFlg
if not obj.CutPatternReversed:
if cutClimb:
@@ -1652,7 +1661,6 @@ def pathGeomToCircularPointSet(obj, compGeoShp, cutClimb, toolDiam, closedGap, g
arc = (vA, arc[1], vC)
closedGap = True
else:
# PathLog.debug('---- Gap: {} mm'.format(gap))
gap = round(gap, 6)
if gap < gaps[0]:
gaps.insert(0, gap)
@@ -1838,7 +1846,9 @@ class FindUnifiedRegions:
tfBB_Area = tfBB.XLength * tfBB.YLength
# self._showShape(topFace, 'topFaceAlt_2_{}'.format(fNum))
if tfBB_Area < (fBB_Area * 0.9):
FreeCAD.Console.PrintError('Faild to extract processing region for Face{}.\n'.format(fNum))
msg = translate('PathSurfaceSupport',
'Faild to extract processing region for Face')
FreeCAD.Console.PrintError(msg + '{}.\n'.format(fNum))
cont = False
if cont:
@@ -2104,7 +2114,7 @@ class FindUnifiedRegions:
remList.append(s)
break
else:
FreeCAD.Console.PrintWarning(' - No common area.\n')
PathLog.debug(' - No common area.\n')
remList.sort(reverse=True)
for ri in remList:
@@ -2214,29 +2224,41 @@ class FindUnifiedRegions:
of tuples (faceShape, faceIndex) received at instantiation of the class object.'''
self.INTERNALS = list()
if len(self.FACES) == 0:
FreeCAD.Console.PrintError('No (faceShp, faceIdx) tuples received at instantiation of class.')
msg = translate('PathSurfaceSupport',
'No FACE data tuples received at instantiation of class.')
FreeCAD.Console.PrintError(msg + '\n')
return []
self._extractTopFaces()
lenFaces = len(self.topFaces)
PathLog.debug('getUnifiedRegions() lenFaces: {}.'.format(lenFaces))
if lenFaces == 0:
return []
# if single topFace, return it
if lenFaces == 1:
topFace = self.topFaces[0][0]
# self._showShape(topFace, 'TopFace')
self._showShape(topFace, 'TopFace')
# prepare inner wires as faces for internal features
lenWrs = len(topFace.Wires)
if lenWrs > 1:
for w in range(1, lenWrs):
self.INTERNALS.append(Part.Face(topFace.Wires[w]))
# prepare outer wire as face for return value in list
if hasattr(topFace, 'OuterWire'):
ow = topFace.OuterWire
else:
ow = topFace.Wires[0]
face = Part.Face(ow)
# Any internal wires need to be flattened
# before appending to self.INTERNALS
# A problem exists that inner wires are not all recognized as wires.
# Some are single circular edges.
# Face.Edges.__len_() - Face.OuterWire.Edges.__len__() = edges for inner wire(s)
# extWire = getExtrudedShape(wr)
# wCS = getCrossSection(extWire)
# wCS.translate(FreeCAD.Vector(0.0, 0.0, wr.BoundBox.ZMin))
wr = topFace.Wires[w]
self.INTERNALS.append(Part.Face(wr))
# Flatten face and extract outer wire, then convert to face
extWire = getExtrudedShape(topFace)
wCS = getCrossSection(extWire)
wCS.translate(FreeCAD.Vector(0.0, 0.0, topFace.BoundBox.ZMin))
face = Part.Face(wCS)
return [face]
# process multiple top faces, unifying if possible
@@ -2254,11 +2276,8 @@ class FindUnifiedRegions:
return [topFace for (topFace, fcIdx) in self.topFaces]
else:
# Delete shared edges from edgeData list
# FreeCAD.Console.PrintWarning('self.sharedEdgeIdxs: {}\n'.format(self.sharedEdgeIdxs))
self.sharedEdgeIdxs.sort(reverse=True)
for se in self.sharedEdgeIdxs:
# seShp = self.edgeData[se][2]
# self._showShape(seShp, 'SharedEdge')
self.edgeData.pop(se)
self._extractWiresFromEdges()
@@ -2274,6 +2293,8 @@ class FindUnifiedRegions:
after calling getUnifiedRegions().'''
if self.INTERNALS:
return self.INTERNALS
FreeCAD.Console.PrintError('getUnifiedRegions() must be called before getInternalFeatures().\n')
msg = translate('PathSurfaceSupport',
'getUnifiedRegions() must be called before getInternalFeatures().')
FreeCAD.Console.PrintError(msg + '\n')
return False
# Eclass

View File

@@ -191,9 +191,9 @@ class ObjectWaterline(PathOp.ObjectOp):
'Algorithm': ['OCL Dropcutter', 'Experimental'],
'BoundBox': ['BaseBoundBox', 'Stock'],
'PatternCenterAt': ['CenterOfMass', 'CenterOfBoundBox', 'XminYmin', 'Custom'],
'ClearLastLayer': ['Off', 'Line', 'Circular', 'CircularZigZag', 'Offset', 'Spiral', 'ZigZag'],
'ClearLastLayer': ['Off', 'Circular', 'CircularZigZag', 'Line', 'Offset', 'Spiral', 'ZigZag'],
'CutMode': ['Conventional', 'Climb'],
'CutPattern': ['None', 'Line', 'Circular', 'CircularZigZag', 'Offset', 'Spiral', 'ZigZag'], # Additional goals ['Offset', 'Spiral', 'ZigZagOffset', 'Grid', 'Triangle']
'CutPattern': ['None', 'Circular', 'CircularZigZag', 'Line', 'Offset', 'Spiral', 'ZigZag'], # Additional goals ['Offset', 'Spiral', 'ZigZagOffset', 'Grid', 'Triangle']
'HandleMultipleFeatures': ['Collectively', 'Individually'],
'LayerMode': ['Single-pass', 'Multi-pass'],
}
@@ -488,7 +488,6 @@ class ObjectWaterline(PathOp.ObjectOp):
self.opApplyPropertyLimits(obj)
# Create temporary group for temporary objects, removing existing
# if self.showDebugObjects is True:
tempGroupName = 'tempPathWaterlineGroup'
if FCAD.getObject(tempGroupName):
for to in FCAD.getObject(tempGroupName).Group:
@@ -576,7 +575,6 @@ class ObjectWaterline(PathOp.ObjectOp):
self.modelSTLs = PSF.modelSTLs
self.profileShapes = PSF.profileShapes
for m in range(0, len(JOB.Model.Group)):
# Create OCL.stl model objects
if obj.Algorithm == 'OCL Dropcutter':
@@ -662,7 +660,8 @@ class ObjectWaterline(PathOp.ObjectOp):
del self.midDep
execTime = time.time() - startTime
PathLog.info('Operation time: {} sec.'.format(execTime))
msg = translate('PathWaterline', 'operation time is')
PathLog.info('Waterline ' + msg + ' {} sec.'.format(execTime))
return True
@@ -1238,11 +1237,7 @@ class ObjectWaterline(PathOp.ObjectOp):
bbFace = PathSurfaceSupport.getCrossSection(baseEnv) # returned at Z=0.0
trimFace = borderFace.cut(bbFace)
if self.showDebugObjects is True:
TF = FreeCAD.ActiveDocument.addObject('Part::Feature', 'trimFace')
TF.Shape = trimFace
TF.purgeTouched()
self.tempGroup.addObject(TF)
self.showDebugObject(trimFace, 'TrimFace')
# Cycle through layer depths
CUTAREAS = self._getCutAreas(base.Shape, depthparams, bbFace, trimFace, borderFace)
@@ -1267,11 +1262,7 @@ class ObjectWaterline(PathOp.ObjectOp):
if area.Area > 0.0:
cont = True
caWireCnt = len(area.Wires) - 1 # first wire is boundFace wire
if self.showDebugObjects:
CA = FreeCAD.ActiveDocument.addObject('Part::Feature', 'cutArea_{}'.format(caCnt))
CA.Shape = area
CA.purgeTouched()
self.tempGroup.addObject(CA)
self.showDebugObject(area, 'CutArea_{}'.format(caCnt))
else:
data = FreeCAD.Units.Quantity(csHght, FreeCAD.Units.Length).UserString
PathLog.debug('Cut area at {} is zero.'.format(data))
@@ -1281,11 +1272,7 @@ class ObjectWaterline(PathOp.ObjectOp):
area.translate(FreeCAD.Vector(0.0, 0.0, 0.0 - area.BoundBox.ZMin))
activeArea = area.cut(trimFace)
activeAreaWireCnt = len(activeArea.Wires) # first wire is boundFace wire
if self.showDebugObjects:
CA = FreeCAD.ActiveDocument.addObject('Part::Feature', 'activeArea_{}'.format(caCnt))
CA.Shape = activeArea
CA.purgeTouched()
self.tempGroup.addObject(CA)
self.showDebugObject(activeArea, 'ActiveArea_{}'.format(caCnt))
ofstArea = PathSurfaceSupport.extractFaceOffset(activeArea, ofst, self.wpc, makeComp=False)
if not ofstArea:
data = FreeCAD.Units.Quantity(csHght, FreeCAD.Units.Length).UserString
@@ -1294,20 +1281,21 @@ class ObjectWaterline(PathOp.ObjectOp):
if cont:
# Identify solid areas in the offset data
ofstSolidFacesList = self._getSolidAreasFromPlanarFaces(ofstArea)
if ofstSolidFacesList:
clearArea = Part.makeCompound(ofstSolidFacesList)
if self.showDebugObjects is True:
CA = FreeCAD.ActiveDocument.addObject('Part::Feature', 'clearArea_{}'.format(caCnt))
CA.Shape = clearArea
CA.purgeTouched()
self.tempGroup.addObject(CA)
if obj.CutPattern == 'Offset' or obj.CutPattern == 'None':
ofstSolidFacesList = self._getSolidAreasFromPlanarFaces(ofstArea)
if ofstSolidFacesList:
clearArea = Part.makeCompound(ofstSolidFacesList)
self.showDebugObject(clearArea, 'ClearArea_{}'.format(caCnt))
else:
cont = False
data = FreeCAD.Units.Quantity(csHght, FreeCAD.Units.Length).UserString
PathLog.error('Could not determine solid faces at {}.'.format(data))
else:
cont = False
data = FreeCAD.Units.Quantity(csHght, FreeCAD.Units.Length).UserString
PathLog.error('Could not determine solid faces at {}.'.format(data))
clearArea = activeArea
if cont:
data = FreeCAD.Units.Quantity(csHght, FreeCAD.Units.Length).UserString
PathLog.debug('... Clearning area at {}.'.format(data))
# Make waterline path for current CUTAREA depth (csHght)
commands.extend(self._wiresToWaterlinePath(obj, clearArea, csHght))
clearArea.translate(FreeCAD.Vector(0.0, 0.0, 0.0 - clearArea.BoundBox.ZMin))
@@ -1325,7 +1313,8 @@ class ObjectWaterline(PathOp.ObjectOp):
commands.extend(self._makeCutPatternLayerPaths(JOB, obj, clearArea, csHght, cutPattern))
# Efor
if clearLastLayer:
if clearLastLayer and obj.ClearLastLayer != 'Off':
PathLog.debug('... Clearning last layer')
(clrLyr, cLL) = self._clearLayer(obj, 1, 1, False)
lastClearArea.translate(FreeCAD.Vector(0.0, 0.0, 0.0 - lastClearArea.BoundBox.ZMin))
if clrLyr == 'Offset':
@@ -1333,7 +1322,6 @@ class ObjectWaterline(PathOp.ObjectOp):
elif clrLyr:
commands.extend(self._makeCutPatternLayerPaths(JOB, obj, lastClearArea, lastCsHght, obj.ClearLastLayer))
PathLog.info("Waterline: All layer scans combined took " + str(time.time() - t_begin) + " s")
return commands
def _getCutAreas(self, shape, depthparams, bbFace, trimFace, borderFace):
@@ -1363,13 +1351,7 @@ class ObjectWaterline(PathOp.ObjectOp):
if useFaces:
compAdjFaces = Part.makeCompound(useFaces)
if self.showDebugObjects is True:
CA = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmpSolids_{}'.format(dp + 1))
CA.Shape = compAdjFaces
CA.purgeTouched()
self.tempGroup.addObject(CA)
self.showDebugObject(compAdjFaces, 'Solids_{}'.format(dp + 1))
if isFirst:
allPrevComp = compAdjFaces
cutArea = borderFace.cut(compAdjFaces)
@@ -1395,11 +1377,7 @@ class ObjectWaterline(PathOp.ObjectOp):
# Translate path geometry to layer height
ofstPlnrShp.translate(FreeCAD.Vector(0.0, 0.0, csHght - ofstPlnrShp.BoundBox.ZMin))
if self.showDebugObjects is True:
OA = FreeCAD.ActiveDocument.addObject('Part::Feature', 'waterlinePathArea_{}'.format(round(csHght, 2)))
OA.Shape = ofstPlnrShp
OA.purgeTouched()
self.tempGroup.addObject(OA)
self.showDebugObject(ofstPlnrShp, 'WaterlinePathArea_{}'.format(round(csHght, 2)))
commands.append(Path.Command('N (Cut Area {}.)'.format(round(csHght, 2))))
start = 1
@@ -1442,11 +1420,7 @@ class ObjectWaterline(PathOp.ObjectOp):
return commands
pathGeom.translate(FreeCAD.Vector(0.0, 0.0, csHght - pathGeom.BoundBox.ZMin))
if self.showDebugObjects is True:
OA = FreeCAD.ActiveDocument.addObject('Part::Feature', 'pathGeom_{}'.format(round(csHght, 2)))
OA.Shape = pathGeom
OA.purgeTouched()
self.tempGroup.addObject(OA)
self.showDebugObject(pathGeom, 'PathGeom_{}'.format(round(csHght, 2)))
if cutPattern == 'Line':
pntSet = PathSurfaceSupport.pathGeomToLinesPointSet(obj, pathGeom, self.CutClimb, self.toolDiam, self.closedGap, self.gaps)
@@ -1481,6 +1455,8 @@ class ObjectWaterline(PathOp.ObjectOp):
if cnt == 0:
ofst = 0.0 - self.cutOut
cnt += 1
PathLog.debug(' -Offset path count: {} at height: {}'.format(cnt, round(csHght, 2)))
return cmds
def _clearGeomToPaths(self, JOB, obj, safePDC, stpOVRS, cutPattern):
@@ -1855,6 +1831,13 @@ class ObjectWaterline(PathOp.ObjectOp):
PathLog.warning("Defaulting cutter to standard end mill.")
return ocl.CylCutter(diam_1, (CEH + lenOfst))
def showDebugObject(self, objShape, objName):
if self.showDebugObjects:
do = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmp_' + objName)
do.Shape = objShape
do.purgeTouched()
self.tempGroup.addObject(do)
def SetupProperties():
''' SetupProperties() ... Return list of properties required for operation.'''