diff --git a/src/Mod/Path/Gui/Resources/panels/PageOpSurfaceEdit.ui b/src/Mod/Path/Gui/Resources/panels/PageOpSurfaceEdit.ui index 45902b44ff..e39c46b7bc 100644 --- a/src/Mod/Path/Gui/Resources/panels/PageOpSurfaceEdit.ui +++ b/src/Mod/Path/Gui/Resources/panels/PageOpSurfaceEdit.ui @@ -156,7 +156,7 @@ - + <html><head/><body><p>Profile the edges of the selection.</p></body></html> diff --git a/src/Mod/Path/Gui/Resources/panels/PageOpWaterlineEdit.ui b/src/Mod/Path/Gui/Resources/panels/PageOpWaterlineEdit.ui index a9cdcac0ea..38462d7ed5 100644 --- a/src/Mod/Path/Gui/Resources/panels/PageOpWaterlineEdit.ui +++ b/src/Mod/Path/Gui/Resources/panels/PageOpWaterlineEdit.ui @@ -165,16 +165,6 @@ None - - - Line - - - - - ZigZag - - Circular @@ -185,6 +175,26 @@ CircularZigZag + + + Line + + + + + Offset + + + + + Spiral + + + + + ZigZag + + diff --git a/src/Mod/Path/PathScripts/PathSurface.py b/src/Mod/Path/PathScripts/PathSurface.py index 433e26bb2e..3c879430c3 100644 --- a/src/Mod/Path/PathScripts/PathSurface.py +++ b/src/Mod/Path/PathScripts/PathSurface.py @@ -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.''' diff --git a/src/Mod/Path/PathScripts/PathSurfaceGui.py b/src/Mod/Path/PathScripts/PathSurfaceGui.py index a26b290827..d3bff733b7 100644 --- a/src/Mod/Path/PathScripts/PathSurfaceGui.py +++ b/src/Mod/Path/PathScripts/PathSurfaceGui.py @@ -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) diff --git a/src/Mod/Path/PathScripts/PathSurfaceSupport.py b/src/Mod/Path/PathScripts/PathSurfaceSupport.py index fcaface258..3e5856a1bc 100644 --- a/src/Mod/Path/PathScripts/PathSurfaceSupport.py +++ b/src/Mod/Path/PathScripts/PathSurfaceSupport.py @@ -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 diff --git a/src/Mod/Path/PathScripts/PathWaterline.py b/src/Mod/Path/PathScripts/PathWaterline.py index 0693a27edd..98465b0171 100644 --- a/src/Mod/Path/PathScripts/PathWaterline.py +++ b/src/Mod/Path/PathScripts/PathWaterline.py @@ -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.'''