From cf23bc689294826430e98826bc6fff1c5f5dceb5 Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Thu, 7 May 2020 23:06:49 -0500 Subject: [PATCH] Path: Implement backwards compatibility Source modules are replaced with pass-through code to send pre-existing profile-based operations to new unified `Profile` operation. Path: Set line endings to Unix style --- src/Mod/Path/CMakeLists.txt | 2 - src/Mod/Path/PathScripts/PathProfileBase.py | 170 ---- .../Path/PathScripts/PathProfileBaseGui.py | 137 --- .../Path/PathScripts/PathProfileContour.py | 96 +- .../Path/PathScripts/PathProfileContourGui.py | 32 +- src/Mod/Path/PathScripts/PathProfileEdges.py | 929 +----------------- .../Path/PathScripts/PathProfileEdgesGui.py | 35 +- src/Mod/Path/PathScripts/PathProfileFaces.py | 322 +----- .../Path/PathScripts/PathProfileFacesGui.py | 107 +- 9 files changed, 128 insertions(+), 1702 deletions(-) delete mode 100644 src/Mod/Path/PathScripts/PathProfileBase.py delete mode 100644 src/Mod/Path/PathScripts/PathProfileBaseGui.py diff --git a/src/Mod/Path/CMakeLists.txt b/src/Mod/Path/CMakeLists.txt index 73958e64d0..12538fbe55 100644 --- a/src/Mod/Path/CMakeLists.txt +++ b/src/Mod/Path/CMakeLists.txt @@ -88,8 +88,6 @@ SET(PathScripts_SRCS PathScripts/PathProbe.py PathScripts/PathProbeGui.py PathScripts/PathProfile.py - PathScripts/PathProfileBase.py - PathScripts/PathProfileBaseGui.py PathScripts/PathProfileContour.py PathScripts/PathProfileContourGui.py PathScripts/PathProfileEdges.py diff --git a/src/Mod/Path/PathScripts/PathProfileBase.py b/src/Mod/Path/PathScripts/PathProfileBase.py deleted file mode 100644 index 2a685a5f92..0000000000 --- a/src/Mod/Path/PathScripts/PathProfileBase.py +++ /dev/null @@ -1,170 +0,0 @@ -# -*- coding: utf-8 -*- - -# *************************************************************************** -# * * -# * Copyright (c) 2017 sliptonic * -# * Copyright (c) 2020 Schildkroet * -# * Copyright (c) 2020 russ4262 (Russell Johnson) * -# * * -# * This program is free software; you can redistribute it and/or modify * -# * it under the terms of the GNU Lesser General Public License (LGPL) * -# * as published by the Free Software Foundation; either version 2 of * -# * the License, or (at your option) any later version. * -# * for detail see the LICENCE text file. * -# * * -# * This program is distributed in the hope that it will be useful, * -# * but WITHOUT ANY WARRANTY; without even the implied warranty of * -# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * -# * GNU Library General Public License for more details. * -# * * -# * You should have received a copy of the GNU Library General Public * -# * License along with this program; if not, write to the Free Software * -# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * -# * USA * -# * * -# *************************************************************************** - -import PathScripts.PathAreaOp as PathAreaOp -import PathScripts.PathLog as PathLog - -from PySide import QtCore - -__title__ = "Base Path Profile Operation" -__author__ = "sliptonic (Brad Collette), Schildkroet" -__url__ = "http://www.freecadweb.org" -__doc__ = "Base class and implementation for Path profile operations." - -PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) -# PathLog.trackModule(PathLog.thisModule()) - - -# Qt translation handling -def translate(context, text, disambig=None): - return QtCore.QCoreApplication.translate(context, text, disambig) - - -class ObjectProfile(PathAreaOp.ObjectOp): - '''Base class for proxy objects of all profile operations.''' - - def initAreaOp(self, obj): - '''initAreaOp(obj) ... creates all profile specific properties. - Do not overwrite.''' - # Profile Properties - obj.addProperty("App::PropertyEnumeration", "Side", "Profile", QtCore.QT_TRANSLATE_NOOP("App::Property", "Side of edge that tool should cut")) - obj.Side = ['Outside', 'Inside'] # side of profile that cutter is on in relation to direction of profile - obj.addProperty("App::PropertyEnumeration", "Direction", "Profile", QtCore.QT_TRANSLATE_NOOP("App::Property", "The direction that the toolpath should go around the part ClockWise (CW) or CounterClockWise (CCW)")) - obj.Direction = ['CW', 'CCW'] # this is the direction that the profile runs - obj.addProperty("App::PropertyBool", "UseComp", "Profile", QtCore.QT_TRANSLATE_NOOP("App::Property", "Make True, if using Cutter Radius Compensation")) - - obj.addProperty("App::PropertyDistance", "OffsetExtra", "Profile", QtCore.QT_TRANSLATE_NOOP("App::Property", "Extra value to stay away from final profile- good for roughing toolpath")) - obj.addProperty("App::PropertyEnumeration", "JoinType", "Profile", QtCore.QT_TRANSLATE_NOOP("App::Property", "Controls how tool moves around corners. Default=Round")) - obj.JoinType = ['Round', 'Square', 'Miter'] # this is the direction that the Profile runs - obj.addProperty("App::PropertyFloat", "MiterLimit", "Profile", QtCore.QT_TRANSLATE_NOOP("App::Property", "Maximum distance before a miter join is truncated")) - obj.setEditorMode('MiterLimit', 2) - - def areaOpOnChanged(self, obj, prop): - '''areaOpOnChanged(obj, prop) ... updates Side and MiterLimit visibility depending on changed properties. - Do not overwrite.''' - if prop == "UseComp": - if not obj.UseComp: - obj.setEditorMode('Side', 2) - else: - obj.setEditorMode('Side', 0) - - if prop == 'JoinType': - if obj.JoinType == 'Miter': - obj.setEditorMode('MiterLimit', 0) - else: - obj.setEditorMode('MiterLimit', 2) - - self.extraOpOnChanged(obj, prop) - - def extraOpOnChanged(self, obj, prop): - '''otherOpOnChanged(obj, porp) ... overwrite to process onChange() events. - Can safely be overwritten by subclasses.''' - pass # pylint: disable=unnecessary-pass - - def setOpEditorProperties(self, obj): - '''setOpEditorProperties(obj, porp) ... overwrite to process operation specific changes to properties. - Can safely be overwritten by subclasses.''' - pass # pylint: disable=unnecessary-pass - - def areaOpOnDocumentRestored(self, obj): - for prop in ['UseComp', 'JoinType']: - self.areaOpOnChanged(obj, prop) - - self.setOpEditorProperties(obj) - - def areaOpAreaParams(self, obj, isHole): - '''areaOpAreaParams(obj, isHole) ... returns dictionary with area parameters. - Do not overwrite.''' - params = {} - params['Fill'] = 0 - params['Coplanar'] = 0 - params['SectionCount'] = -1 - - offset = 0.0 - if obj.UseComp: - offset = self.radius + obj.OffsetExtra.Value - if obj.Side == 'Inside': - offset = 0 - offset - if isHole: - offset = 0 - offset - params['Offset'] = offset - - jointype = ['Round', 'Square', 'Miter'] - params['JoinType'] = jointype.index(obj.JoinType) - - if obj.JoinType == 'Miter': - params['MiterLimit'] = obj.MiterLimit - - return params - - def areaOpPathParams(self, obj, isHole): - '''areaOpPathParams(obj, isHole) ... returns dictionary with path parameters. - Do not overwrite.''' - params = {} - - # Reverse the direction for holes - if isHole: - direction = "CW" if obj.Direction == "CCW" else "CCW" - else: - direction = obj.Direction - - if direction == 'CCW': - params['orientation'] = 0 - else: - params['orientation'] = 1 - - if not obj.UseComp: - if direction == 'CCW': - params['orientation'] = 1 - else: - params['orientation'] = 0 - - return params - - def areaOpUseProjection(self, obj): - '''areaOpUseProjection(obj) ... returns True''' - return True - - def areaOpSetDefaultValues(self, obj, job): - '''areaOpSetDefaultValues(obj, job) ... sets default values. - Do not overwrite.''' - obj.Side = "Outside" - obj.OffsetExtra = 0.0 - obj.Direction = "CW" - obj.UseComp = True - obj.JoinType = "Round" - obj.MiterLimit = 0.1 - - -def SetupProperties(): - setup = PathAreaOp.SetupProperties() - setup.append('Side') - setup.append('OffsetExtra') - setup.append('Direction') - setup.append('UseComp') - setup.append('JoinType') - setup.append('MiterLimit') - return setup diff --git a/src/Mod/Path/PathScripts/PathProfileBaseGui.py b/src/Mod/Path/PathScripts/PathProfileBaseGui.py deleted file mode 100644 index 350bef44fc..0000000000 --- a/src/Mod/Path/PathScripts/PathProfileBaseGui.py +++ /dev/null @@ -1,137 +0,0 @@ -# -*- coding: utf-8 -*- - -# *************************************************************************** -# * * -# * Copyright (c) 2017 sliptonic * -# * * -# * This program is free software; you can redistribute it and/or modify * -# * it under the terms of the GNU Lesser General Public License (LGPL) * -# * as published by the Free Software Foundation; either version 2 of * -# * the License, or (at your option) any later version. * -# * for detail see the LICENCE text file. * -# * * -# * This program is distributed in the hope that it will be useful, * -# * but WITHOUT ANY WARRANTY; without even the implied warranty of * -# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * -# * GNU Library General Public License for more details. * -# * * -# * You should have received a copy of the GNU Library General Public * -# * License along with this program; if not, write to the Free Software * -# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * -# * USA * -# * * -# *************************************************************************** - -import FreeCAD -import FreeCADGui -import PathScripts.PathGui as PathGui -import PathScripts.PathOpGui as PathOpGui - -from PySide import QtCore - -__title__ = "Path Profile Operation Base UI" -__author__ = "sliptonic (Brad Collette)" -__url__ = "http://www.freecadweb.org" -__doc__ = "Base page controller for profile operations." - -def translate(context, text, disambig=None): - return QtCore.QCoreApplication.translate(context, text, disambig) - -FeatureSide = 0x01 -FeatureProcessing = 0x02 - -class TaskPanelOpPage(PathOpGui.TaskPanelPage): - '''Base class for profile operation page controllers. Two sub features are - support - FeatureSide ... Is the Side property exposed in the UI - FeatureProcessing ... Are the processing check boxes supported by the operation - ''' - - def profileFeatures(self): - '''profileFeatures() ... return which of the optional profile features are supported. - Currently two features are supported: - FeatureSide ... Is the Side property exposed in the UI - FeatureProcessing ... Are the processing check boxes supported by the operation - Must be overwritten by subclasses.''' - - def getForm(self): - '''getForm() ... returns UI customized according to profileFeatures()''' - form = FreeCADGui.PySideUic.loadUi(":/panels/PageOpProfileFullEdit.ui") - - if not FeatureSide & self.profileFeatures(): - form.cutSide.hide() - form.cutSideLabel.hide() - - if not FeatureProcessing & self.profileFeatures(): - form.processCircles.hide() - form.processHoles.hide() - form.processPerimeter.hide() - - return form - - def getFields(self, obj): - '''getFields(obj) ... transfers values from UI to obj's proprties''' - PathGui.updateInputField(obj, 'OffsetExtra', self.form.extraOffset) - if obj.UseComp != self.form.useCompensation.isChecked(): - obj.UseComp = self.form.useCompensation.isChecked() - if obj.UseStartPoint != self.form.useStartPoint.isChecked(): - obj.UseStartPoint = self.form.useStartPoint.isChecked() - if obj.Direction != str(self.form.direction.currentText()): - obj.Direction = str(self.form.direction.currentText()) - if obj.EnableRotation != str(self.form.enableRotation.currentText()): - obj.EnableRotation = str(self.form.enableRotation.currentText()) - - self.updateToolController(obj, self.form.toolController) - self.updateCoolant(obj, self.form.coolantController) - - if FeatureSide & self.profileFeatures(): - if obj.Side != str(self.form.cutSide.currentText()): - obj.Side = str(self.form.cutSide.currentText()) - - if FeatureProcessing & self.profileFeatures(): - if obj.processHoles != self.form.processHoles.isChecked(): - obj.processHoles = self.form.processHoles.isChecked() - if obj.processPerimeter != self.form.processPerimeter.isChecked(): - obj.processPerimeter = self.form.processPerimeter.isChecked() - if obj.processCircles != self.form.processCircles.isChecked(): - obj.processCircles = self.form.processCircles.isChecked() - - def setFields(self, obj): - '''setFields(obj) ... transfers obj's property values to UI''' - self.form.extraOffset.setText(FreeCAD.Units.Quantity(obj.OffsetExtra.Value, FreeCAD.Units.Length).UserString) - self.form.useCompensation.setChecked(obj.UseComp) - self.form.useStartPoint.setChecked(obj.UseStartPoint) - - self.selectInComboBox(obj.Direction, self.form.direction) - self.setupToolController(obj, self.form.toolController) - self.setupCoolant(obj, self.form.coolantController) - self.selectInComboBox(obj.EnableRotation, self.form.enableRotation) - - if FeatureSide & self.profileFeatures(): - self.selectInComboBox(obj.Side, self.form.cutSide) - - if FeatureProcessing & self.profileFeatures(): - self.form.processHoles.setChecked(obj.processHoles) - self.form.processPerimeter.setChecked(obj.processPerimeter) - self.form.processCircles.setChecked(obj.processCircles) - - def getSignalsForUpdate(self, obj): - '''getSignalsForUpdate(obj) ... return list of signals for updating obj''' - signals = [] - signals.append(self.form.direction.currentIndexChanged) - signals.append(self.form.useCompensation.clicked) - signals.append(self.form.useStartPoint.clicked) - signals.append(self.form.extraOffset.editingFinished) - signals.append(self.form.toolController.currentIndexChanged) - signals.append(self.form.coolantController.currentIndexChanged) - signals.append(self.form.enableRotation.currentIndexChanged) - - if FeatureSide & self.profileFeatures(): - signals.append(self.form.cutSide.currentIndexChanged) - - if FeatureProcessing & self.profileFeatures(): - signals.append(self.form.processHoles.clicked) - signals.append(self.form.processPerimeter.clicked) - signals.append(self.form.processCircles.clicked) - - return signals diff --git a/src/Mod/Path/PathScripts/PathProfileContour.py b/src/Mod/Path/PathScripts/PathProfileContour.py index 5aaeb8c56d..dfaf43dcb2 100644 --- a/src/Mod/Path/PathScripts/PathProfileContour.py +++ b/src/Mod/Path/PathScripts/PathProfileContour.py @@ -21,100 +21,32 @@ # * USA * # * * # *************************************************************************** - -from __future__ import print_function +# * Major modifications: 2020 Russell Johnson * import FreeCAD -import Path -import PathScripts.PathProfileBase as PathProfileBase -import PathScripts.PathLog as PathLog +import PathScripts.PathProfile as PathProfile -from PathScripts import PathUtils -from PySide import QtCore -# lazily loaded modules -from lazy_loader.lazy_loader import LazyLoader -ArchPanel = LazyLoader('ArchPanel', globals(), 'ArchPanel') -Part = LazyLoader('Part', globals(), 'Part') - -FreeCAD.setLogLevel('Path.Area', 0) - -PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) -#PathLog.trackModule(PathLog.thisModule()) - -# Qt translation handling -def translate(context, text, disambig=None): - return QtCore.QCoreApplication.translate(context, text, disambig) - -__title__ = "Path Contour Operation" +__title__ = "Path Contour Operation (depreciated)" __author__ = "sliptonic (Brad Collette)" __url__ = "http://www.freecadweb.org" -__doc__ = "Implementation of the Contour operation." +__doc__ = "Implementation of the Contour operation (depreciated)." -class ObjectContour(PathProfileBase.ObjectProfile): - '''Proxy object for Contour operations.''' +class ObjectContour(PathProfile.ObjectProfile): + '''Psuedo class for Profile operation, + allowing for backward compatibility with pre-existing "Contour" operations.''' + pass +# Eclass - def baseObject(self): - '''baseObject() ... returns super of receiver - Used to call base implementation in overwritten functions.''' - return super(self.__class__, self) - - def areaOpFeatures(self, obj): - '''areaOpFeatures(obj) ... returns 0, Contour only requires the base profile features.''' - return 0 - - def initAreaOp(self, obj): - '''initAreaOp(obj) ... call super's implementation and hide Side property.''' - self.baseObject().initAreaOp(obj) - obj.setEditorMode('Side', 2) # it's always outside - - def areaOpOnDocumentRestored(self, obj): - obj.setEditorMode('Side', 2) # it's always outside - - def areaOpSetDefaultValues(self, obj, job): - '''areaOpSetDefaultValues(obj, job) ... call super's implementation and set Side="Outside".''' - self.baseObject().areaOpSetDefaultValues(obj, job) - obj.Side = 'Outside' - - def areaOpShapes(self, obj): - '''areaOpShapes(obj) ... return envelope over the job's Base.Shape or all Arch.Panel shapes.''' - if obj.UseComp: - self.commandlist.append(Path.Command("(Compensated Tool Path. Diameter: " + str(self.radius * 2) + ")")) - else: - self.commandlist.append(Path.Command("(Uncompensated Tool Path)")) - - isPanel = False - if 1 == len(self.model) and hasattr(self.model[0], "Proxy"): - if isinstance(self.model[0].Proxy, ArchPanel.PanelSheet): # process the sheet - panel = self.model[0] - isPanel = True - panel.Proxy.execute(panel) - shapes = panel.Proxy.getOutlines(panel, transform=True) - for shape in shapes: - f = Part.makeFace([shape], 'Part::FaceMakerSimple') - thickness = panel.Group[0].Source.Thickness - return [(f.extrude(FreeCAD.Vector(0, 0, thickness)), False)] - - if not isPanel: - return [(PathUtils.getEnvelope(partshape=base.Shape, subshape=None, depthparams=self.depthparams), False) for base in self.model if hasattr(base, 'Shape')] - - def areaOpAreaParams(self, obj, isHole): - params = self.baseObject().areaOpAreaParams(obj, isHole) - params['Coplanar'] = 2 - return params - - def opUpdateDepths(self, obj): - obj.OpStartDepth = obj.OpStockZMax - obj.OpFinalDepth = obj.OpStockZMin def SetupProperties(): - return [p for p in PathProfileBase.SetupProperties() if p != 'Side'] + return PathProfile.SetupProperties() -def Create(name, obj = None): - '''Create(name) ... Creates and returns a Contour operation.''' + +def Create(name, obj=None): + '''Create(name) ... Creates and returns a Profile operation.''' if obj is None: - obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name) + obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name) obj.Proxy = ObjectContour(obj, name) return obj - diff --git a/src/Mod/Path/PathScripts/PathProfileContourGui.py b/src/Mod/Path/PathScripts/PathProfileContourGui.py index 6c9321ac6c..74277be7d2 100644 --- a/src/Mod/Path/PathScripts/PathProfileContourGui.py +++ b/src/Mod/Path/PathScripts/PathProfileContourGui.py @@ -21,32 +21,34 @@ # * USA * # * * # *************************************************************************** +# * Major modifications: 2020 Russell Johnson * import FreeCAD import PathScripts.PathOpGui as PathOpGui -import PathScripts.PathProfileBaseGui as PathProfileBaseGui -import PathScripts.PathProfileContour as PathProfileContour - +import PathScripts.PathProfile as PathProfile +import PathScripts.PathProfileGui as PathProfileGui from PySide import QtCore -__title__ = "Path Contour Operation UI" + +__title__ = "Path Contour Operation UI (depreciated)" __author__ = "sliptonic (Brad Collette)" __url__ = "http://www.freecadweb.org" -__doc__ = "Contour operation page controller and command implementation." +__doc__ = "Contour operation page controller and command implementation (depreciated)." -class TaskPanelOpPage(PathProfileBaseGui.TaskPanelOpPage): - '''Page controller for the contour operation UI.''' - def profileFeatures(self): - '''profileFeatues() ... return 0 - profile doesn't support any of the optional UI features.''' - return 0 +class TaskPanelOpPage(PathProfileGui.TaskPanelOpPage): + '''Psuedo page controller class for Profile operation, + allowing for backward compatibility with pre-existing "Contour" operations.''' + pass +# Eclass -Command = PathOpGui.SetupOperation('Contour', - PathProfileContour.Create, + +Command = PathOpGui.SetupOperation('Profile', + PathProfile.Create, TaskPanelOpPage, 'Path-Contour', - QtCore.QT_TRANSLATE_NOOP("PathProfileContour", "Contour"), - QtCore.QT_TRANSLATE_NOOP("PathProfileContour", "Creates a Contour Path for the Base Object "), - PathProfileContour.SetupProperties) + QtCore.QT_TRANSLATE_NOOP("PathProfile", "Profile"), + QtCore.QT_TRANSLATE_NOOP("PathProfile", "Profile entire model, selected face(s) or selected edge(s)"), + PathProfile.SetupProperties) FreeCAD.Console.PrintLog("Loading PathProfileContourGui... done\n") diff --git a/src/Mod/Path/PathScripts/PathProfileEdges.py b/src/Mod/Path/PathScripts/PathProfileEdges.py index b266b53ea4..e23668c4ba 100644 --- a/src/Mod/Path/PathScripts/PathProfileEdges.py +++ b/src/Mod/Path/PathScripts/PathProfileEdges.py @@ -21,937 +21,32 @@ # * USA * # * * # *************************************************************************** +# * Major modifications: 2020 Russell Johnson * import FreeCAD -import Path -import PathScripts.PathLog as PathLog -import PathScripts.PathOp as PathOp -import PathScripts.PathProfileBase as PathProfileBase -import PathScripts.PathUtils as PathUtils - -import math -import PySide - -# lazily loaded modules -from lazy_loader.lazy_loader import LazyLoader -Part = LazyLoader('Part', globals(), 'Part') -DraftGeomUtils = LazyLoader('DraftGeomUtils', globals(), 'DraftGeomUtils') - -PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) -# PathLog.trackModule(PathLog.thisModule()) +import PathScripts.PathProfile as PathProfile -# Qt translation handling -def translate(context, text, disambig=None): - return PySide.QtCore.QCoreApplication.translate(context, text, disambig) - - -__title__ = "Path Profile Edges Operation" +__title__ = "Path Profile Edges Operation (depreciated)" __author__ = "sliptonic (Brad Collette)" __url__ = "http://www.freecadweb.org" -__doc__ = "Path Profile operation based on edges." +__doc__ = "Path Profile operation based on edges (depreciated)." __contributors__ = "russ4262 (Russell Johnson)" -class ObjectProfile(PathProfileBase.ObjectProfile): - '''Proxy object for Profile operations based on edges.''' - - def baseObject(self): - '''baseObject() ... returns super of receiver - Used to call base implementation in overwritten functions.''' - return super(self.__class__, self) - - def areaOpFeatures(self, obj): - '''areaOpFeatures(obj) ... add support for edge base geometry.''' - return PathOp.FeatureBaseEdges - - def areaOpShapes(self, obj): - '''areaOpShapes(obj) ... returns envelope for all wires formed by the base edges.''' - PathLog.track() - - inaccessible = translate('PathProfileEdges', 'The selected edge(s) are inaccessible. If multiple, re-ordering selection might work.') - if PathLog.getLevel(PathLog.thisModule()) == 4: - self.tmpGrp = FreeCAD.ActiveDocument.addObject('App::DocumentObjectGroup', 'tmpDebugGrp') - tmpGrpNm = self.tmpGrp.Name - self.JOB = PathUtils.findParentJob(obj) - - self.offsetExtra = abs(obj.OffsetExtra.Value) - - if obj.UseComp: - self.useComp = True - self.ofstRadius = self.radius + self.offsetExtra - self.commandlist.append(Path.Command("(Compensated Tool Path. Diameter: " + str(self.radius * 2) + ")")) - else: - self.useComp = False - self.ofstRadius = self.offsetExtra - self.commandlist.append(Path.Command("(Uncompensated Tool Path)")) - - shapes = [] - if obj.Base: - basewires = [] - - zMin = None - for b in obj.Base: - edgelist = [] - for sub in b[1]: - edgelist.append(getattr(b[0].Shape, sub)) - basewires.append((b[0], DraftGeomUtils.findWires(edgelist))) - if zMin is None or b[0].Shape.BoundBox.ZMin < zMin: - zMin = b[0].Shape.BoundBox.ZMin - - PathLog.debug('PathProfileEdges areaOpShapes():: len(basewires) is {}'.format(len(basewires))) - for base, wires in basewires: - for wire in wires: - if wire.isClosed() is True: - # f = Part.makeFace(wire, 'Part::FaceMakerSimple') - # if planar error, Comment out previous line, uncomment the next two - (origWire, flatWire) = self._flattenWire(obj, wire, obj.FinalDepth.Value) - f = origWire.Wires[0] - if f is not False: - # shift the compound to the bottom of the base object for proper sectioning - zShift = zMin - f.BoundBox.ZMin - newPlace = FreeCAD.Placement(FreeCAD.Vector(0, 0, zShift), f.Placement.Rotation) - f.Placement = newPlace - env = PathUtils.getEnvelope(base.Shape, subshape=f, depthparams=self.depthparams) - shapes.append((env, False)) - else: - PathLog.error(inaccessible) - else: - if self.JOB.GeometryTolerance.Value == 0.0: - msg = self.JOB.Label + '.GeometryTolerance = 0.0.' - msg += translate('PathProfileEdges', 'Please set to an acceptable value greater than zero.') - PathLog.error(msg) - else: - cutWireObjs = False - flattened = self._flattenWire(obj, wire, obj.FinalDepth.Value) - if flattened: - (origWire, flatWire) = flattened - if PathLog.getLevel(PathLog.thisModule()) == 4: - os = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmpFlatWire') - os.Shape = flatWire - os.purgeTouched() - self.tmpGrp.addObject(os) - cutShp = self._getCutAreaCrossSection(obj, base, origWire, flatWire) - if cutShp is not False: - cutWireObjs = self._extractPathWire(obj, base, flatWire, cutShp) - - if cutWireObjs is not False: - for cW in cutWireObjs: - shapes.append((cW, False)) - self.profileEdgesIsOpen = True - else: - PathLog.error(inaccessible) - else: - PathLog.error(inaccessible) - - # Delete the temporary objects - if PathLog.getLevel(PathLog.thisModule()) == 4: - if FreeCAD.GuiUp: - import FreeCADGui - FreeCADGui.ActiveDocument.getObject(tmpGrpNm).Visibility = False - self.tmpGrp.purgeTouched() - - return shapes - - def _flattenWire(self, obj, wire, trgtDep): - '''_flattenWire(obj, wire)... Return a flattened version of the wire''' - PathLog.debug('_flattenWire()') - wBB = wire.BoundBox - - if wBB.ZLength > 0.0: - PathLog.debug('Wire is not horizontally co-planar. Flattening it.') - - # Extrude non-horizontal wire - extFwdLen = wBB.ZLength * 2.2 - mbbEXT = wire.extrude(FreeCAD.Vector(0, 0, extFwdLen)) - - # Create cross-section of shape and translate - sliceZ = wire.BoundBox.ZMin + (extFwdLen / 2) - crsectFaceShp = self._makeCrossSection(mbbEXT, sliceZ, trgtDep) - if crsectFaceShp is not False: - return (wire, crsectFaceShp) - else: - return False - else: - srtWire = Part.Wire(Part.__sortEdges__(wire.Edges)) - srtWire.translate(FreeCAD.Vector(0, 0, trgtDep - srtWire.BoundBox.ZMin)) - - return (wire, srtWire) - - # Open-edges methods - def _getCutAreaCrossSection(self, obj, base, origWire, flatWire): - PathLog.debug('_getCutAreaCrossSection()') - FCAD = FreeCAD.ActiveDocument - tolerance = self.JOB.GeometryTolerance.Value - toolDiam = 2 * self.radius # self.radius defined in PathAreaOp or PathProfileBase modules - minBfr = toolDiam * 1.25 - bbBfr = (self.ofstRadius * 2) * 1.25 - if bbBfr < minBfr: - bbBfr = minBfr - fwBB = flatWire.BoundBox - wBB = origWire.BoundBox - minArea = (self.ofstRadius - tolerance)**2 * math.pi - - useWire = origWire.Wires[0] - numOrigEdges = len(useWire.Edges) - sdv = wBB.ZMax - fdv = obj.FinalDepth.Value - extLenFwd = sdv - fdv - if extLenFwd <= 0.0: - msg = translate('PathProfile', - 'For open edges, select top edge and set Final Depth manually.') - FreeCAD.Console.PrintError(msg + '\n') - return False - WIRE = flatWire.Wires[0] - numEdges = len(WIRE.Edges) - - # Identify first/last edges and first/last vertex on wire - begE = WIRE.Edges[0] # beginning edge - endE = WIRE.Edges[numEdges - 1] # ending edge - blen = begE.Length - elen = endE.Length - Vb = begE.Vertexes[0] # first vertex of wire - Ve = endE.Vertexes[1] # last vertex of wire - pb = FreeCAD.Vector(Vb.X, Vb.Y, fdv) - pe = FreeCAD.Vector(Ve.X, Ve.Y, fdv) - - # Identify endpoints connecting circle center and diameter - vectDist = pe.sub(pb) - diam = vectDist.Length - cntr = vectDist.multiply(0.5).add(pb) - R = diam / 2 - - pl = FreeCAD.Placement() - pl.Rotation = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), 0) - pl.Base = FreeCAD.Vector(0, 0, 0) - - # Obtain beginning point perpendicular points - if blen > 0.1: - bcp = begE.valueAt(begE.getParameterByLength(0.1)) # point returned 0.1 mm along edge - else: - bcp = FreeCAD.Vector(begE.Vertexes[1].X, begE.Vertexes[1].Y, fdv) - if elen > 0.1: - ecp = endE.valueAt(endE.getParameterByLength(elen - 0.1)) # point returned 0.1 mm along edge - else: - ecp = FreeCAD.Vector(endE.Vertexes[1].X, endE.Vertexes[1].Y, fdv) - - # Create intersection tags for determining which side of wire to cut - (begInt, begExt, iTAG, eTAG) = self._makeIntersectionTags(useWire, numOrigEdges, fdv) - if not begInt or not begExt: - return False - self.iTAG = iTAG - self.eTAG = eTAG - - # Create extended wire boundbox, and extrude - extBndbox = self._makeExtendedBoundBox(wBB, bbBfr, fdv) - extBndboxEXT = extBndbox.extrude(FreeCAD.Vector(0, 0, extLenFwd)) - - # Cut model(selected edges) from extended edges boundbox - cutArea = extBndboxEXT.cut(base.Shape) - if PathLog.getLevel(PathLog.thisModule()) == 4: - CA = FCAD.addObject('Part::Feature', 'tmpCutArea') - CA.Shape = cutArea - CA.recompute() - CA.purgeTouched() - self.tmpGrp.addObject(CA) - - - # Get top and bottom faces of cut area (CA), and combine faces when necessary - topFc = list() - botFc = list() - bbZMax = cutArea.BoundBox.ZMax - bbZMin = cutArea.BoundBox.ZMin - for f in range(0, len(cutArea.Faces)): - FcBB = cutArea.Faces[f].BoundBox - if abs(FcBB.ZMax - bbZMax) < tolerance and abs(FcBB.ZMin - bbZMax) < tolerance: - topFc.append(f) - if abs(FcBB.ZMax - bbZMin) < tolerance and abs(FcBB.ZMin - bbZMin) < tolerance: - botFc.append(f) - if len(topFc) == 0: - PathLog.error('Failed to identify top faces of cut area.') - return False - topComp = Part.makeCompound([cutArea.Faces[f] for f in topFc]) - topComp.translate(FreeCAD.Vector(0, 0, fdv - topComp.BoundBox.ZMin)) # Translate face to final depth - if len(botFc) > 1: - PathLog.debug('len(botFc) > 1') - bndboxFace = Part.Face(extBndbox.Wires[0]) - tmpFace = Part.Face(extBndbox.Wires[0]) - for f in botFc: - Q = tmpFace.cut(cutArea.Faces[f]) - tmpFace = Q - botComp = bndboxFace.cut(tmpFace) - else: - botComp = Part.makeCompound([cutArea.Faces[f] for f in botFc]) # Part.makeCompound([CA.Shape.Faces[f] for f in botFc]) - botComp.translate(FreeCAD.Vector(0, 0, fdv - botComp.BoundBox.ZMin)) # Translate face to final depth - - # Make common of the two - comFC = topComp.common(botComp) - - # Determine with which set of intersection tags the model intersects - (cmnIntArea, cmnExtArea) = self._checkTagIntersection(iTAG, eTAG, 'QRY', comFC) - if cmnExtArea > cmnIntArea: - PathLog.debug('Cutting on Ext side.') - self.cutSide = 'E' - self.cutSideTags = eTAG - tagCOM = begExt.CenterOfMass - else: - PathLog.debug('Cutting on Int side.') - self.cutSide = 'I' - self.cutSideTags = iTAG - tagCOM = begInt.CenterOfMass - - # Make two beginning style(oriented) 'L' shape stops - begStop = self._makeStop('BEG', bcp, pb, 'BegStop') - altBegStop = self._makeStop('END', bcp, pb, 'BegStop') - - # Identify to which style 'L' stop the beginning intersection tag is closest, - # and create partner end 'L' stop geometry, and save for application later - lenBS_extETag = begStop.CenterOfMass.sub(tagCOM).Length - lenABS_extETag = altBegStop.CenterOfMass.sub(tagCOM).Length - if lenBS_extETag < lenABS_extETag: - endStop = self._makeStop('END', ecp, pe, 'EndStop') - pathStops = Part.makeCompound([begStop, endStop]) - else: - altEndStop = self._makeStop('BEG', ecp, pe, 'EndStop') - pathStops = Part.makeCompound([altBegStop, altEndStop]) - pathStops.translate(FreeCAD.Vector(0, 0, fdv - pathStops.BoundBox.ZMin)) - - # Identify closed wire in cross-section that corresponds to user-selected edge(s) - workShp = comFC - fcShp = workShp - wire = origWire - WS = workShp.Wires - lenWS = len(WS) - if lenWS < 3: - wi = 0 - else: - wi = None - for wvt in wire.Vertexes: - for w in range(0, lenWS): - twr = WS[w] - for v in range(0, len(twr.Vertexes)): - V = twr.Vertexes[v] - if abs(V.X - wvt.X) < tolerance: - if abs(V.Y - wvt.Y) < tolerance: - # Same vertex found. This wire to be used for offset - wi = w - break - # Efor - - if wi is None: - PathLog.error('The cut area cross-section wire does not coincide with selected edge. Wires[] index is None.') - return False - else: - PathLog.debug('Cross-section Wires[] index is {}.'.format(wi)) - - nWire = Part.Wire(Part.__sortEdges__(workShp.Wires[wi].Edges)) - fcShp = Part.Face(nWire) - fcShp.translate(FreeCAD.Vector(0, 0, fdv - workShp.BoundBox.ZMin)) - # Eif - - # verify that wire chosen is not inside the physical model - if wi > 0: # and isInterior is False: - PathLog.debug('Multiple wires in cut area. First choice is not 0. Testing.') - testArea = fcShp.cut(base.Shape) - - isReady = self._checkTagIntersection(iTAG, eTAG, self.cutSide, testArea) - PathLog.debug('isReady {}.'.format(isReady)) - - if isReady is False: - PathLog.debug('Using wire index {}.'.format(wi - 1)) - pWire = Part.Wire(Part.__sortEdges__(workShp.Wires[wi - 1].Edges)) - pfcShp = Part.Face(pWire) - pfcShp.translate(FreeCAD.Vector(0, 0, fdv - workShp.BoundBox.ZMin)) - workShp = pfcShp.cut(fcShp) - - if testArea.Area < minArea: - PathLog.debug('offset area is less than minArea of {}.'.format(minArea)) - PathLog.debug('Using wire index {}.'.format(wi - 1)) - pWire = Part.Wire(Part.__sortEdges__(workShp.Wires[wi - 1].Edges)) - pfcShp = Part.Face(pWire) - pfcShp.translate(FreeCAD.Vector(0, 0, fdv - workShp.BoundBox.ZMin)) - workShp = pfcShp.cut(fcShp) - # Eif - - # Add path stops at ends of wire - cutShp = workShp.cut(pathStops) - return cutShp - - def _checkTagIntersection(self, iTAG, eTAG, cutSide, tstObj): - # Identify intersection of Common area and Interior Tags - intCmn = tstObj.common(iTAG) - - # Identify intersection of Common area and Exterior Tags - extCmn = tstObj.common(eTAG) - - # Calculate common intersection (solid model side, or the non-cut side) area with tags, to determine physical cut side - cmnIntArea = intCmn.Area - cmnExtArea = extCmn.Area - if cutSide == 'QRY': - return (cmnIntArea, cmnExtArea) - - if cmnExtArea > cmnIntArea: - PathLog.debug('Cutting on Ext side.') - if cutSide == 'E': - return True - else: - PathLog.debug('Cutting on Int side.') - if cutSide == 'I': - return True - return False - - def _extractPathWire(self, obj, base, flatWire, cutShp): - PathLog.debug('_extractPathWire()') - - subLoops = list() - rtnWIRES = list() - osWrIdxs = list() - subDistFactor = 1.0 # Raise to include sub wires at greater distance from original - fdv = obj.FinalDepth.Value - wire = flatWire - lstVrtIdx = len(wire.Vertexes) - 1 - lstVrt = wire.Vertexes[lstVrtIdx] - frstVrt = wire.Vertexes[0] - cent0 = FreeCAD.Vector(frstVrt.X, frstVrt.Y, fdv) - cent1 = FreeCAD.Vector(lstVrt.X, lstVrt.Y, fdv) - - pl = FreeCAD.Placement() - pl.Rotation = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), 0) - pl.Base = FreeCAD.Vector(0, 0, 0) - - # Calculate offset shape, containing cut region - ofstShp = self._extractFaceOffset(obj, cutShp, False) - - # CHECK for ZERO area of offset shape - try: - osArea = ofstShp.Area - except Exception as ee: - PathLog.error('No area to offset shape returned.') - return False - - if PathLog.getLevel(PathLog.thisModule()) == 4: - os = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmpOffsetShape') - os.Shape = ofstShp - os.recompute() - os.purgeTouched() - self.tmpGrp.addObject(os) - - numOSWires = len(ofstShp.Wires) - for w in range(0, numOSWires): - osWrIdxs.append(w) - - # Identify two vertexes for dividing offset loop - NEAR0 = self._findNearestVertex(ofstShp, cent0) - min0i = 0 - min0 = NEAR0[0][4] - for n in range(0, len(NEAR0)): - N = NEAR0[n] - if N[4] < min0: - min0 = N[4] - min0i = n - (w0, vi0, pnt0, vrt0, d0) = NEAR0[0] # min0i - if PathLog.getLevel(PathLog.thisModule()) == 4: - near0 = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmpNear0') - near0.Shape = Part.makeLine(cent0, pnt0) - near0.recompute() - near0.purgeTouched() - self.tmpGrp.addObject(near0) - - NEAR1 = self._findNearestVertex(ofstShp, cent1) - min1i = 0 - min1 = NEAR1[0][4] - for n in range(0, len(NEAR1)): - N = NEAR1[n] - if N[4] < min1: - min1 = N[4] - min1i = n - (w1, vi1, pnt1, vrt1, d1) = NEAR1[0] # min1i - if PathLog.getLevel(PathLog.thisModule()) == 4: - near1 = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmpNear1') - near1.Shape = Part.makeLine(cent1, pnt1) - near1.recompute() - near1.purgeTouched() - self.tmpGrp.addObject(near1) - - if w0 != w1: - PathLog.warning('Offset wire endpoint indexes are not equal - w0, w1: {}, {}'.format(w0, w1)) - - if PathLog.getLevel(PathLog.thisModule()) == 4: - PathLog.debug('min0i is {}.'.format(min0i)) - PathLog.debug('min1i is {}.'.format(min1i)) - PathLog.debug('NEAR0[{}] is {}.'.format(w0, NEAR0[w0])) - PathLog.debug('NEAR1[{}] is {}.'.format(w1, NEAR1[w1])) - PathLog.debug('NEAR0 is {}.'.format(NEAR0)) - PathLog.debug('NEAR1 is {}.'.format(NEAR1)) - - mainWire = ofstShp.Wires[w0] - - # Check for additional closed loops in offset wire by checking distance to iTAG or eTAG elements - if numOSWires > 1: - # check all wires for proximity(children) to intersection tags - tagsComList = list() - for T in self.cutSideTags.Faces: - tcom = T.CenterOfMass - tv = FreeCAD.Vector(tcom.x, tcom.y, 0.0) - tagsComList.append(tv) - subDist = self.ofstRadius * subDistFactor - for w in osWrIdxs: - if w != w0: - cutSub = False - VTXS = ofstShp.Wires[w].Vertexes - for V in VTXS: - v = FreeCAD.Vector(V.X, V.Y, 0.0) - for t in tagsComList: - if t.sub(v).Length < subDist: - cutSub = True - break - if cutSub is True: - break - if cutSub is True: - sub = Part.Wire(Part.__sortEdges__(ofstShp.Wires[w].Edges)) - subLoops.append(sub) - # Eif - - # Break offset loop into two wires - one of which is the desired profile path wire. - (edgeIdxs0, edgeIdxs1) = self._separateWireAtVertexes(mainWire, mainWire.Vertexes[vi0], mainWire.Vertexes[vi1]) - edgs0 = list() - edgs1 = list() - for e in edgeIdxs0: - edgs0.append(mainWire.Edges[e]) - for e in edgeIdxs1: - edgs1.append(mainWire.Edges[e]) - part0 = Part.Wire(Part.__sortEdges__(edgs0)) - part1 = Part.Wire(Part.__sortEdges__(edgs1)) - - # Determine which part is nearest original edge(s) - distToPart0 = self._distMidToMid(wire.Wires[0], part0.Wires[0]) - distToPart1 = self._distMidToMid(wire.Wires[0], part1.Wires[0]) - if distToPart0 < distToPart1: - rtnWIRES.append(part0) - else: - rtnWIRES.append(part1) - rtnWIRES.extend(subLoops) - - return rtnWIRES - - def _extractFaceOffset(self, obj, fcShape, isHole): - '''_extractFaceOffset(obj, fcShape, isHole) ... internal function. - Original _buildPathArea() version copied from PathAreaOp.py module. This version is modified. - Adjustments made based on notes by @sliptonic - https://github.com/sliptonic/FreeCAD/wiki/PathArea-notes.''' - PathLog.debug('_extractFaceOffset()') - - areaParams = {} - JOB = PathUtils.findParentJob(obj) - tolrnc = JOB.GeometryTolerance.Value - if self.useComp is True: - offset = self.ofstRadius # + tolrnc - else: - offset = self.offsetExtra # + tolrnc - - if isHole is False: - offset = 0 - offset - - areaParams['Offset'] = offset - areaParams['Fill'] = 1 - areaParams['Coplanar'] = 0 - areaParams['SectionCount'] = 1 # -1 = full(all per depthparams??) sections - areaParams['Reorient'] = True - areaParams['OpenMode'] = 0 - areaParams['MaxArcPoints'] = 400 # 400 - areaParams['Project'] = True - # areaParams['JoinType'] = 1 - - area = Path.Area() # Create instance of Area() class object - area.setPlane(PathUtils.makeWorkplane(fcShape)) # Set working plane - area.add(fcShape) # obj.Shape to use for extracting offset - area.setParams(**areaParams) # set parameters - - return area.getShape() - - def _findNearestVertex(self, shape, point): - PathLog.debug('_findNearestVertex()') - PT = FreeCAD.Vector(point.x, point.y, 0.0) - - def sortDist(tup): - return tup[4] - - PNTS = list() - for w in range(0, len(shape.Wires)): - WR = shape.Wires[w] - V = WR.Vertexes[0] - P = FreeCAD.Vector(V.X, V.Y, 0.0) - dist = P.sub(PT).Length - vi = 0 - pnt = P - vrt = V - for v in range(0, len(WR.Vertexes)): - V = WR.Vertexes[v] - P = FreeCAD.Vector(V.X, V.Y, 0.0) - d = P.sub(PT).Length - if d < dist: - dist = d - vi = v - pnt = P - vrt = V - PNTS.append((w, vi, pnt, vrt, dist)) - PNTS.sort(key=sortDist) - return PNTS - - def _separateWireAtVertexes(self, wire, VV1, VV2): - PathLog.debug('_separateWireAtVertexes()') - tolerance = self.JOB.GeometryTolerance.Value - grps = [[], []] - wireIdxs = [[], []] - V1 = FreeCAD.Vector(VV1.X, VV1.Y, VV1.Z) - V2 = FreeCAD.Vector(VV2.X, VV2.Y, VV2.Z) - - lenE = len(wire.Edges) - FLGS = list() - for e in range(0, lenE): - FLGS.append(0) - - chk4 = False - for e in range(0, lenE): - v = 0 - E = wire.Edges[e] - fv0 = FreeCAD.Vector(E.Vertexes[0].X, E.Vertexes[0].Y, E.Vertexes[0].Z) - fv1 = FreeCAD.Vector(E.Vertexes[1].X, E.Vertexes[1].Y, E.Vertexes[1].Z) - - if fv0.sub(V1).Length < tolerance: - v = 1 - if fv1.sub(V2).Length < tolerance: - v += 3 - chk4 = True - elif fv1.sub(V1).Length < tolerance: - v = 1 - if fv0.sub(V2).Length < tolerance: - v += 3 - chk4 = True - - if fv0.sub(V2).Length < tolerance: - v = 3 - if fv1.sub(V1).Length < tolerance: - v += 1 - chk4 = True - elif fv1.sub(V2).Length < tolerance: - v = 3 - if fv0.sub(V1).Length < tolerance: - v += 1 - chk4 = True - FLGS[e] += v - # Efor - PathLog.debug('_separateWireAtVertexes() FLGS: \n{}'.format(FLGS)) - - PRE = list() - POST = list() - IDXS = list() - IDX1 = list() - IDX2 = list() - for e in range(0, lenE): - f = FLGS[e] - PRE.append(f) - POST.append(f) - IDXS.append(e) - IDX1.append(e) - IDX2.append(e) - - PRE.extend(FLGS) - PRE.extend(POST) - lenFULL = len(PRE) - IDXS.extend(IDX1) - IDXS.extend(IDX2) - - if chk4 is True: - # find beginning 1 edge - begIdx = None - begFlg = False - for e in range(0, lenFULL): - f = PRE[e] - i = IDXS[e] - if f == 4: - begIdx = e - grps[0].append(f) - wireIdxs[0].append(i) - break - # find first 3 edge - endIdx = None - for e in range(begIdx + 1, lenE + begIdx): - f = PRE[e] - i = IDXS[e] - grps[1].append(f) - wireIdxs[1].append(i) - else: - # find beginning 1 edge - begIdx = None - begFlg = False - for e in range(0, lenFULL): - f = PRE[e] - if f == 1: - if begFlg is False: - begFlg = True - else: - begIdx = e - break - # find first 3 edge and group all first wire edges - endIdx = None - for e in range(begIdx, lenE + begIdx): - f = PRE[e] - i = IDXS[e] - if f == 3: - grps[0].append(f) - wireIdxs[0].append(i) - endIdx = e - break - else: - grps[0].append(f) - wireIdxs[0].append(i) - # Collect remaining edges - for e in range(endIdx + 1, lenFULL): - f = PRE[e] - i = IDXS[e] - if f == 1: - grps[1].append(f) - wireIdxs[1].append(i) - break - else: - wireIdxs[1].append(i) - grps[1].append(f) - # Efor - # Eif - - if PathLog.getLevel(PathLog.thisModule()) != 4: - PathLog.debug('grps[0]: {}'.format(grps[0])) - PathLog.debug('grps[1]: {}'.format(grps[1])) - PathLog.debug('wireIdxs[0]: {}'.format(wireIdxs[0])) - PathLog.debug('wireIdxs[1]: {}'.format(wireIdxs[1])) - PathLog.debug('PRE: {}'.format(PRE)) - PathLog.debug('IDXS: {}'.format(IDXS)) - - return (wireIdxs[0], wireIdxs[1]) - - def _makeCrossSection(self, shape, sliceZ, zHghtTrgt=False): - '''_makeCrossSection(shape, sliceZ, zHghtTrgt=None)... - Creates cross-section objectc from shape. Translates cross-section to zHghtTrgt if available. - Makes face shape from cross-section object. Returns face shape at zHghtTrgt.''' - # Create cross-section of shape and translate - wires = list() - slcs = shape.slice(FreeCAD.Vector(0, 0, 1), sliceZ) - if len(slcs) > 0: - for i in slcs: - wires.append(i) - comp = Part.Compound(wires) - if zHghtTrgt is not False: - comp.translate(FreeCAD.Vector(0, 0, zHghtTrgt - comp.BoundBox.ZMin)) - return comp - - return False - - def _makeExtendedBoundBox(self, wBB, bbBfr, zDep): - p1 = FreeCAD.Vector(wBB.XMin - bbBfr, wBB.YMin - bbBfr, zDep) - p2 = FreeCAD.Vector(wBB.XMax + bbBfr, wBB.YMin - bbBfr, zDep) - p3 = FreeCAD.Vector(wBB.XMax + bbBfr, wBB.YMax + bbBfr, zDep) - p4 = FreeCAD.Vector(wBB.XMin - bbBfr, wBB.YMax + bbBfr, zDep) - - L1 = Part.makeLine(p1, p2) - L2 = Part.makeLine(p2, p3) - L3 = Part.makeLine(p3, p4) - L4 = Part.makeLine(p4, p1) - - return Part.Face(Part.Wire([L1, L2, L3, L4])) - - def _makeIntersectionTags(self, useWire, numOrigEdges, fdv): - # Create circular probe tags around perimiter of wire - extTags = list() - intTags = list() - tagRad = (self.radius / 2) - tagCnt = 0 - begInt = False - begExt = False - for e in range(0, numOrigEdges): - E = useWire.Edges[e] - LE = E.Length - if LE > (self.radius * 2): - nt = math.ceil(LE / (tagRad * math.pi)) # (tagRad * 2 * math.pi) is circumference - else: - nt = 4 # desired + 1 - mid = LE / nt - spc = self.radius / 10 - for i in range(0, nt): - if i == 0: - if e == 0: - if LE > 0.2: - aspc = 0.1 - else: - aspc = LE * 0.75 - cp1 = E.valueAt(E.getParameterByLength(0)) - cp2 = E.valueAt(E.getParameterByLength(aspc)) - (intTObj, extTObj) = self._makeOffsetCircleTag(cp1, cp2, tagRad, fdv, 'BeginEdge[{}]_'.format(e)) - if intTObj and extTObj: - begInt = intTObj - begExt = extTObj - else: - d = i * mid - cp1 = E.valueAt(E.getParameterByLength(d - spc)) - cp2 = E.valueAt(E.getParameterByLength(d + spc)) - (intTObj, extTObj) = self._makeOffsetCircleTag(cp1, cp2, tagRad, fdv, 'Edge[{}]_'.format(e)) - if intTObj and extTObj: - tagCnt += nt - intTags.append(intTObj) - extTags.append(extTObj) - tagArea = math.pi * tagRad**2 * tagCnt - iTAG = Part.makeCompound(intTags) - eTAG = Part.makeCompound(extTags) - - return (begInt, begExt, iTAG, eTAG) - - def _makeOffsetCircleTag(self, p1, p2, cutterRad, depth, lbl, reverse=False): - pb = FreeCAD.Vector(p1.x, p1.y, 0.0) - pe = FreeCAD.Vector(p2.x, p2.y, 0.0) - - toMid = pe.sub(pb).multiply(0.5) - lenToMid = toMid.Length - if lenToMid == 0.0: - # Probably a vertical line segment - return (False, False) - - cutFactor = (cutterRad / 2.1) / lenToMid # = 2 is tangent to wire; > 2 allows tag to overlap wire; < 2 pulls tag away from wire - perpE = FreeCAD.Vector(-1 * toMid.y, toMid.x, 0.0).multiply(-1 * cutFactor) # exterior tag - extPnt = pb.add(toMid.add(perpE)) - - pl = FreeCAD.Placement() - pl.Rotation = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), 0) - # make exterior tag - eCntr = extPnt.add(FreeCAD.Vector(0, 0, depth)) - ecw = Part.Wire(Part.makeCircle((cutterRad / 2), eCntr).Edges[0]) - extTag = Part.Face(ecw) - - # make interior tag - perpI = FreeCAD.Vector(-1 * toMid.y, toMid.x, 0.0).multiply(cutFactor) # interior tag - intPnt = pb.add(toMid.add(perpI)) - iCntr = intPnt.add(FreeCAD.Vector(0, 0, depth)) - icw = Part.Wire(Part.makeCircle((cutterRad / 2), iCntr).Edges[0]) - intTag = Part.Face(icw) - - return (intTag, extTag) - - def _makeStop(self, sType, pA, pB, lbl): - rad = self.radius - ofstRad = self.ofstRadius - extra = self.radius / 10 - - pl = FreeCAD.Placement() - pl.Rotation = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), 0) - pl.Base = FreeCAD.Vector(0, 0, 0) - - E = FreeCAD.Vector(pB.x, pB.y, 0) # endpoint - C = FreeCAD.Vector(pA.x, pA.y, 0) # checkpoint - lenEC = E.sub(C).Length - - if self.useComp is True or (self.useComp is False and self.offsetExtra != 0): - # 'L' stop shape and edge legend - # --1-- - # | | - # 2 6 - # | | - # | ----5----| - # | 4 - # -----3-------| - # positive dist in _makePerp2DVector() is CCW rotation - p1 = E - if sType == 'BEG': - p2 = self._makePerp2DVector(C, E, -0.25) # E1 - p3 = self._makePerp2DVector(p1, p2, ofstRad + 1 + extra) # E2 - p4 = self._makePerp2DVector(p2, p3, 0.25 + ofstRad + extra) # E3 - p5 = self._makePerp2DVector(p3, p4, 1 + extra) # E4 - p6 = self._makePerp2DVector(p4, p5, ofstRad + extra) # E5 - elif sType == 'END': - p2 = self._makePerp2DVector(C, E, 0.25) # E1 - p3 = self._makePerp2DVector(p1, p2, -1 * (ofstRad + 1 + extra)) # E2 - p4 = self._makePerp2DVector(p2, p3, -1 * (0.25 + ofstRad + extra)) # E3 - p5 = self._makePerp2DVector(p3, p4, -1 * (1 + extra)) # E4 - p6 = self._makePerp2DVector(p4, p5, -1 * (ofstRad + extra)) # E5 - p7 = E # E6 - L1 = Part.makeLine(p1, p2) - L2 = Part.makeLine(p2, p3) - L3 = Part.makeLine(p3, p4) - L4 = Part.makeLine(p4, p5) - L5 = Part.makeLine(p5, p6) - L6 = Part.makeLine(p6, p7) - wire = Part.Wire([L1, L2, L3, L4, L5, L6]) - else: - # 'L' stop shape and edge legend - # : - # |----2-------| - # 3 1 - # |-----4------| - # positive dist in _makePerp2DVector() is CCW rotation - p1 = E - if sType == 'BEG': - p2 = self._makePerp2DVector(C, E, -1 * (0.25 + abs(self.offsetExtra))) # left, 0.25 - p3 = self._makePerp2DVector(p1, p2, 0.25 + abs(self.offsetExtra)) - p4 = self._makePerp2DVector(p2, p3, (0.5 + abs(self.offsetExtra))) # FIRST POINT - p5 = self._makePerp2DVector(p3, p4, 0.25 + abs(self.offsetExtra)) # E1 SECOND - elif sType == 'END': - p2 = self._makePerp2DVector(C, E, (0.25 + abs(self.offsetExtra))) # left, 0.25 - p3 = self._makePerp2DVector(p1, p2, -1 * (0.25 + abs(self.offsetExtra))) - p4 = self._makePerp2DVector(p2, p3, -1 * (0.5 + abs(self.offsetExtra))) # FIRST POINT - p5 = self._makePerp2DVector(p3, p4, -1 * (0.25 + abs(self.offsetExtra))) # E1 SECOND - p6 = p1 # E4 - L1 = Part.makeLine(p1, p2) - L2 = Part.makeLine(p2, p3) - L3 = Part.makeLine(p3, p4) - L4 = Part.makeLine(p4, p5) - L5 = Part.makeLine(p5, p6) - wire = Part.Wire([L1, L2, L3, L4, L5]) - # Eif - face = Part.Face(wire) - if PathLog.getLevel(PathLog.thisModule()) == 4: - os = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmp' + lbl) - os.Shape = face - os.recompute() - os.purgeTouched() - self.tmpGrp.addObject(os) - - return face - - def _makePerp2DVector(self, v1, v2, dist): - p1 = FreeCAD.Vector(v1.x, v1.y, 0.0) - p2 = FreeCAD.Vector(v2.x, v2.y, 0.0) - toEnd = p2.sub(p1) - factor = dist / toEnd.Length - perp = FreeCAD.Vector(-1 * toEnd.y, toEnd.x, 0.0).multiply(factor) - return p1.add(toEnd.add(perp)) - - def _distMidToMid(self, wireA, wireB): - mpA = self._findWireMidpoint(wireA) - mpB = self._findWireMidpoint(wireB) - return mpA.sub(mpB).Length - - def _findWireMidpoint(self, wire): - midPnt = None - dist = 0.0 - wL = wire.Length - midW = wL / 2 - - for e in range(0, len(wire.Edges)): - E = wire.Edges[e] - elen = E.Length - d_ = dist + elen - if dist < midW and midW <= d_: - dtm = midW - dist - midPnt = E.valueAt(E.getParameterByLength(dtm)) - break - else: - dist += elen - return midPnt +class ObjectProfile(PathProfile.ObjectProfile): + '''Psuedo class for Profile operation, + allowing for backward compatibility with pre-existing "Profile Edges" operations.''' + pass +# Eclass def SetupProperties(): - return PathProfileBase.SetupProperties() + return PathProfile.SetupProperties() -def Create(name, obj = None): - '''Create(name) ... Creates and returns a Profile based on edges operation.''' +def Create(name, obj=None): + '''Create(name) ... Creates and returns a Profile operation.''' if obj is None: obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name) obj.Proxy = ObjectProfile(obj, name) diff --git a/src/Mod/Path/PathScripts/PathProfileEdgesGui.py b/src/Mod/Path/PathScripts/PathProfileEdgesGui.py index 205af01bed..9f156d5d71 100644 --- a/src/Mod/Path/PathScripts/PathProfileEdgesGui.py +++ b/src/Mod/Path/PathScripts/PathProfileEdgesGui.py @@ -21,33 +21,34 @@ # * USA * # * * # *************************************************************************** +# * Major modifications: 2020 Russell Johnson * import FreeCAD import PathScripts.PathOpGui as PathOpGui -import PathScripts.PathProfileBaseGui as PathProfileBaseGui -import PathScripts.PathProfileEdges as PathProfileEdges - +import PathScripts.PathProfile as PathProfile +import PathScripts.PathProfileGui as PathProfileGui from PySide import QtCore -__title__ = "Path Profile based on edges Operation UI" + +__title__ = "Path Profile Edges Operation UI (depreciated)" __author__ = "sliptonic (Brad Collette)" __url__ = "http://www.freecadweb.org" -__doc__ = "Profile based on edges operation page controller and command implementation." +__doc__ = "Profile Edges operation page controller and command implementation (depreciated)." -class TaskPanelOpPage(PathProfileBaseGui.TaskPanelOpPage): - '''Page controller for profile based on edges operation.''' - def profileFeatures(self): - '''profileFeatures() ... return FeatureSide - See PathProfileBaseGui.py for details.''' - return PathProfileBaseGui.FeatureSide +class TaskPanelOpPage(PathProfileGui.TaskPanelOpPage): + '''Psuedo page controller class for Profile operation, + allowing for backward compatibility with pre-existing "Profile Edges" operations.''' + pass +# Eclass -Command = PathOpGui.SetupOperation('Profile Edges', - PathProfileEdges.Create, + +Command = PathOpGui.SetupOperation('Profile', + PathProfile.Create, TaskPanelOpPage, - 'Path-Profile-Edges', - QtCore.QT_TRANSLATE_NOOP("PathProfile", "Edge Profile"), - QtCore.QT_TRANSLATE_NOOP("PathProfile", "Profile based on edges"), - PathProfileEdges.SetupProperties) + 'Path-Contour', + QtCore.QT_TRANSLATE_NOOP("PathProfile", "Profile"), + QtCore.QT_TRANSLATE_NOOP("PathProfile", "Profile entire model, selected face(s) or selected edge(s)"), + PathProfile.SetupProperties) FreeCAD.Console.PrintLog("Loading PathProfileEdgesGui... done\n") diff --git a/src/Mod/Path/PathScripts/PathProfileFaces.py b/src/Mod/Path/PathScripts/PathProfileFaces.py index 281d848699..51845ca329 100644 --- a/src/Mod/Path/PathScripts/PathProfileFaces.py +++ b/src/Mod/Path/PathScripts/PathProfileFaces.py @@ -22,328 +22,32 @@ # * USA * # * * # *************************************************************************** +# * Major modifications: 2020 Russell Johnson * import FreeCAD -import Path -import PathScripts.PathLog as PathLog -import PathScripts.PathOp as PathOp -import PathScripts.PathProfileBase as PathProfileBase -import PathScripts.PathUtils as PathUtils -import numpy +import PathScripts.PathProfile as PathProfile -from PySide import QtCore -# lazily loaded modules -from lazy_loader.lazy_loader import LazyLoader -ArchPanel = LazyLoader('ArchPanel', globals(), 'ArchPanel') -Part = LazyLoader('Part', globals(), 'Part') - -__title__ = "Path Profile Faces Operation" -__author__ = "sliptonic (Brad Collette), Schildkroet" +__title__ = "Path Profile Faces Operation (depreciated)" +__author__ = "sliptonic (Brad Collette)" __url__ = "http://www.freecadweb.org" -__doc__ = "Path Profile operation based on faces." - -PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) +__doc__ = "Path Profile operation based on faces (depreciated)." +__contributors__ = "Schildkroet" -# Qt translation handling -def translate(context, text, disambig=None): - return QtCore.QCoreApplication.translate(context, text, disambig) - - -class ObjectProfile(PathProfileBase.ObjectProfile): - '''Proxy object for Profile operations based on faces.''' - - def baseObject(self): - '''baseObject() ... returns super of receiver - Used to call base implementation in overwritten functions.''' - return super(self.__class__, self) - - def areaOpFeatures(self, obj): - '''baseObject() ... returns super of receiver - Used to call base implementation in overwritten functions.''' - # return PathOp.FeatureBaseFaces | PathOp.FeatureBasePanels | PathOp.FeatureRotation - return PathOp.FeatureBaseFaces | PathOp.FeatureBasePanels - - def initAreaOp(self, obj): - '''initAreaOp(obj) ... adds properties for hole, circle and perimeter processing.''' - # Face specific Properties - obj.addProperty("App::PropertyBool", "processHoles", "Profile", QtCore.QT_TRANSLATE_NOOP("App::Property", "Profile holes as well as the outline")) - obj.addProperty("App::PropertyBool", "processPerimeter", "Profile", QtCore.QT_TRANSLATE_NOOP("App::Property", "Profile the outline")) - obj.addProperty("App::PropertyBool", "processCircles", "Profile", QtCore.QT_TRANSLATE_NOOP("App::Property", "Profile round holes")) - - if not hasattr(obj, 'HandleMultipleFeatures'): - obj.addProperty('App::PropertyEnumeration', 'HandleMultipleFeatures', 'Profile', QtCore.QT_TRANSLATE_NOOP('PathPocket', 'Choose how to process multiple Base Geometry features.')) - - obj.HandleMultipleFeatures = ['Collectively', 'Individually'] - - self.initRotationOp(obj) - self.baseObject().initAreaOp(obj) - - def initRotationOp(self, obj): - '''initRotationOp(obj) ... setup receiver for rotation''' - if not hasattr(obj, 'ReverseDirection'): - obj.addProperty('App::PropertyBool', 'ReverseDirection', 'Rotation', QtCore.QT_TRANSLATE_NOOP('App::Property', 'Reverse direction of pocket operation.')) - if not hasattr(obj, 'InverseAngle'): - obj.addProperty('App::PropertyBool', 'InverseAngle', 'Rotation', QtCore.QT_TRANSLATE_NOOP('App::Property', 'Inverse the angle. Example: -22.5 -> 22.5 degrees.')) - if not hasattr(obj, 'AttemptInverseAngle'): - obj.addProperty('App::PropertyBool', 'AttemptInverseAngle', 'Rotation', QtCore.QT_TRANSLATE_NOOP('App::Property', 'Attempt the inverse angle for face access if original rotation fails.')) - if not hasattr(obj, 'LimitDepthToFace'): - obj.addProperty('App::PropertyBool', 'LimitDepthToFace', 'Rotation', QtCore.QT_TRANSLATE_NOOP('App::Property', 'Enforce the Z-depth of the selected face as the lowest value for final depth. Higher user values will be observed.')) - - def extraOpOnChanged(self, obj, prop): - '''extraOpOnChanged(obj, porp) ... process operation specific changes to properties.''' - if prop == 'EnableRotation': - self.setOpEditorProperties(obj) - - def setOpEditorProperties(self, obj): - if obj.EnableRotation == 'Off': - obj.setEditorMode('ReverseDirection', 2) - obj.setEditorMode('InverseAngle', 2) - obj.setEditorMode('AttemptInverseAngle', 2) - obj.setEditorMode('LimitDepthToFace', 2) - else: - obj.setEditorMode('ReverseDirection', 0) - obj.setEditorMode('InverseAngle', 0) - obj.setEditorMode('AttemptInverseAngle', 0) - obj.setEditorMode('LimitDepthToFace', 0) - - def areaOpShapes(self, obj): - '''areaOpShapes(obj) ... returns envelope for all base shapes or wires for Arch.Panels.''' - PathLog.track() - - if obj.UseComp: - self.commandlist.append(Path.Command("(Compensated Tool Path. Diameter: " + str(self.radius * 2) + ")")) - else: - self.commandlist.append(Path.Command("(Uncompensated Tool Path)")) - - shapes = [] - self.profileshape = [] # pylint: disable=attribute-defined-outside-init - - baseSubsTuples = [] - subCount = 0 - allTuples = [] - - if obj.Base: # The user has selected subobjects from the base. Process each. - if obj.EnableRotation != 'Off': - for p in range(0, len(obj.Base)): - (base, subsList) = obj.Base[p] - for sub in subsList: - subCount += 1 - shape = getattr(base.Shape, sub) - if isinstance(shape, Part.Face): - rtn = False - (norm, surf) = self.getFaceNormAndSurf(shape) - (rtn, angle, axis, praInfo) = self.faceRotationAnalysis(obj, norm, surf) # pylint: disable=unused-variable - PathLog.debug("initial faceRotationAnalysis: {}".format(praInfo)) - if rtn is True: - (clnBase, angle, clnStock, tag) = self.applyRotationalAnalysis(obj, base, angle, axis, subCount) - # Verify faces are correctly oriented - InverseAngle might be necessary - faceIA = getattr(clnBase.Shape, sub) - (norm, surf) = self.getFaceNormAndSurf(faceIA) - (rtn, praAngle, praAxis, praInfo2) = self.faceRotationAnalysis(obj, norm, surf) # pylint: disable=unused-variable - PathLog.debug("follow-up faceRotationAnalysis: {}".format(praInfo2)) - - if abs(praAngle) == 180.0: - rtn = False - if self.isFaceUp(clnBase, faceIA) is False: - PathLog.debug('isFaceUp 1 is False') - angle -= 180.0 - - if rtn is True: - PathLog.debug(translate("Path", "Face appears misaligned after initial rotation.")) - if obj.InverseAngle is False: - if obj.AttemptInverseAngle is True: - (clnBase, clnStock, angle) = self.applyInverseAngle(obj, clnBase, clnStock, axis, angle) - else: - msg = translate("Path", "Consider toggling the 'InverseAngle' property and recomputing.") - PathLog.warning(msg) - - if self.isFaceUp(clnBase, faceIA) is False: - PathLog.debug('isFaceUp 2 is False') - angle += 180.0 - else: - PathLog.debug(' isFaceUp') - - else: - PathLog.debug("Face appears to be oriented correctly.") - - if angle < 0.0: - angle += 360.0 - - tup = clnBase, sub, tag, angle, axis, clnStock - else: - if self.warnDisabledAxis(obj, axis) is False: - PathLog.debug(str(sub) + ": No rotation used") - axis = 'X' - angle = 0.0 - tag = base.Name + '_' + axis + str(angle).replace('.', '_') - stock = PathUtils.findParentJob(obj).Stock - tup = base, sub, tag, angle, axis, stock - - allTuples.append(tup) - - if subCount > 1: - msg = translate('Path', "Multiple faces in Base Geometry.") + " " - msg += translate('Path', "Depth settings will be applied to all faces.") - PathLog.warning(msg) - - (Tags, Grps) = self.sortTuplesByIndex(allTuples, 2) # return (TagList, GroupList) - subList = [] - for o in range(0, len(Tags)): - subList = [] - for (base, sub, tag, angle, axis, stock) in Grps[o]: - subList.append(sub) - - pair = base, subList, angle, axis, stock - baseSubsTuples.append(pair) - # Efor - else: - PathLog.debug(translate("Path", "EnableRotation property is 'Off'.")) - stock = PathUtils.findParentJob(obj).Stock - for (base, subList) in obj.Base: - baseSubsTuples.append((base, subList, 0.0, 'X', stock)) - - # for base in obj.Base: - finish_step = obj.FinishDepth.Value if hasattr(obj, "FinishDepth") else 0.0 - for (base, subsList, angle, axis, stock) in baseSubsTuples: - holes = [] - faces = [] - faceDepths = [] - startDepths = [] - - for sub in subsList: - shape = getattr(base.Shape, sub) - if isinstance(shape, Part.Face): - faces.append(shape) - if numpy.isclose(abs(shape.normalAt(0, 0).z), 1): # horizontal face - for wire in shape.Wires[1:]: - holes.append((base.Shape, wire)) - - # Add face depth to list - faceDepths.append(shape.BoundBox.ZMin) - else: - ignoreSub = base.Name + '.' + sub - msg = translate('Path', "Found a selected object which is not a face. Ignoring: {}".format(ignoreSub)) - PathLog.error(msg) - FreeCAD.Console.PrintWarning(msg) - - # Set initial Start and Final Depths and recalculate depthparams - finDep = obj.FinalDepth.Value - strDep = obj.StartDepth.Value - if strDep > stock.Shape.BoundBox.ZMax: - strDep = stock.Shape.BoundBox.ZMax - - startDepths.append(strDep) - self.depthparams = self._customDepthParams(obj, strDep, finDep) - - for shape, wire in holes: - f = Part.makeFace(wire, 'Part::FaceMakerSimple') - drillable = PathUtils.isDrillable(shape, wire) - if (drillable and obj.processCircles) or (not drillable and obj.processHoles): - env = PathUtils.getEnvelope(shape, subshape=f, depthparams=self.depthparams) - tup = env, True, 'pathProfileFaces', angle, axis, strDep, finDep - shapes.append(tup) - - if len(faces) > 0: - profileshape = Part.makeCompound(faces) - self.profileshape.append(profileshape) - - if obj.processPerimeter: - if obj.HandleMultipleFeatures == 'Collectively': - custDepthparams = self.depthparams - - if obj.LimitDepthToFace is True and obj.EnableRotation != 'Off': - if profileshape.BoundBox.ZMin > obj.FinalDepth.Value: - finDep = profileshape.BoundBox.ZMin - envDepthparams = self._customDepthParams(obj, strDep + 0.5, finDep) # only an envelope - try: - # env = PathUtils.getEnvelope(base.Shape, subshape=profileshape, depthparams=envDepthparams) - env = PathUtils.getEnvelope(profileshape, depthparams=envDepthparams) - except Exception: # pylint: disable=broad-except - # PathUtils.getEnvelope() failed to return an object. - PathLog.error(translate('Path', 'Unable to create path for face(s).')) - else: - tup = env, False, 'pathProfileFaces', angle, axis, strDep, finDep - shapes.append(tup) - - elif obj.HandleMultipleFeatures == 'Individually': - for shape in faces: - # profShape = Part.makeCompound([shape]) - finalDep = obj.FinalDepth.Value - custDepthparams = self.depthparams - if obj.Side == 'Inside': - if finalDep < shape.BoundBox.ZMin: - # Recalculate depthparams - finalDep = shape.BoundBox.ZMin - custDepthparams = self._customDepthParams(obj, strDep + 0.5, finalDep) - - # env = PathUtils.getEnvelope(base.Shape, subshape=profShape, depthparams=custDepthparams) - env = PathUtils.getEnvelope(shape, depthparams=custDepthparams) - tup = env, False, 'pathProfileFaces', angle, axis, strDep, finalDep - shapes.append(tup) - - # Lower high Start Depth to top of Stock - startDepth = max(startDepths) - if obj.StartDepth.Value > startDepth: - obj.StartDepth.Value = startDepth - - else: # Try to build targets from the job base - if 1 == len(self.model): - if hasattr(self.model[0], "Proxy"): - PathLog.debug("hasattr() Proxy") - if isinstance(self.model[0].Proxy, ArchPanel.PanelSheet): # process the sheet - if obj.processCircles or obj.processHoles: - for shape in self.model[0].Proxy.getHoles(self.model[0], transform=True): - for wire in shape.Wires: - drillable = PathUtils.isDrillable(self.model[0].Proxy, wire) - if (drillable and obj.processCircles) or (not drillable and obj.processHoles): - f = Part.makeFace(wire, 'Part::FaceMakerSimple') - env = PathUtils.getEnvelope(self.model[0].Shape, subshape=f, depthparams=self.depthparams) - tup = env, True, 'pathProfileFaces', 0.0, 'X', obj.StartDepth.Value, obj.FinalDepth.Value - shapes.append(tup) - - if obj.processPerimeter: - for shape in self.model[0].Proxy.getOutlines(self.model[0], transform=True): - for wire in shape.Wires: - f = Part.makeFace(wire, 'Part::FaceMakerSimple') - env = PathUtils.getEnvelope(self.model[0].Shape, subshape=f, depthparams=self.depthparams) - tup = env, False, 'pathProfileFaces', 0.0, 'X', obj.StartDepth.Value, obj.FinalDepth.Value - shapes.append(tup) - - self.removalshapes = shapes # pylint: disable=attribute-defined-outside-init - PathLog.debug("%d shapes" % len(shapes)) - - return shapes - - def areaOpSetDefaultValues(self, obj, job): - '''areaOpSetDefaultValues(obj, job) ... sets default values for hole, circle and perimeter processing.''' - self.baseObject().areaOpSetDefaultValues(obj, job) - - obj.processHoles = False - obj.processCircles = False - obj.processPerimeter = True - obj.ReverseDirection = False - obj.InverseAngle = False - obj.AttemptInverseAngle = True - obj.LimitDepthToFace = True - obj.HandleMultipleFeatures = 'Individually' +class ObjectProfile(PathProfile.ObjectProfile): + '''Psuedo class for Profile operation, + allowing for backward compatibility with pre-existing "Profile Faces" operations.''' + pass +# Eclass def SetupProperties(): - setup = PathProfileBase.SetupProperties() - setup.append("processHoles") - setup.append("processPerimeter") - setup.append("processCircles") - setup.append("ReverseDirection") - setup.append("InverseAngle") - setup.append("AttemptInverseAngle") - setup.append("HandleMultipleFeatures") - return setup + return PathProfile.SetupProperties() def Create(name, obj=None): - '''Create(name) ... Creates and returns a Profile based on faces operation.''' + '''Create(name) ... Creates and returns a Profile operation.''' if obj is None: obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name) obj.Proxy = ObjectProfile(obj, name) diff --git a/src/Mod/Path/PathScripts/PathProfileFacesGui.py b/src/Mod/Path/PathScripts/PathProfileFacesGui.py index e56c35e0c8..b080a22eb1 100644 --- a/src/Mod/Path/PathScripts/PathProfileFacesGui.py +++ b/src/Mod/Path/PathScripts/PathProfileFacesGui.py @@ -1,53 +1,54 @@ -# -*- coding: utf-8 -*- - -# *************************************************************************** -# * * -# * Copyright (c) 2017 sliptonic * -# * * -# * This program is free software; you can redistribute it and/or modify * -# * it under the terms of the GNU Lesser General Public License (LGPL) * -# * as published by the Free Software Foundation; either version 2 of * -# * the License, or (at your option) any later version. * -# * for detail see the LICENCE text file. * -# * * -# * This program is distributed in the hope that it will be useful, * -# * but WITHOUT ANY WARRANTY; without even the implied warranty of * -# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * -# * GNU Library General Public License for more details. * -# * * -# * You should have received a copy of the GNU Library General Public * -# * License along with this program; if not, write to the Free Software * -# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * -# * USA * -# * * -# *************************************************************************** - -import FreeCAD -import PathScripts.PathOpGui as PathOpGui -import PathScripts.PathProfileBaseGui as PathProfileBaseGui -import PathScripts.PathProfileFaces as PathProfileFaces - -from PySide import QtCore - -__title__ = "Path Profile based on faces Operation UI" -__author__ = "sliptonic (Brad Collette)" -__url__ = "http://www.freecadweb.org" -__doc__ = "Profile based on faces operation page controller and command implementation." - -class TaskPanelOpPage(PathProfileBaseGui.TaskPanelOpPage): - '''Page controller for profile based on faces operation.''' - - def profileFeatures(self): - '''profileFeatures() ... return FeatureSide | FeatureProcessing. - See PathProfileBaseGui.py for details.''' - return PathProfileBaseGui.FeatureSide | PathProfileBaseGui.FeatureProcessing - -Command = PathOpGui.SetupOperation('Profile Faces', - PathProfileFaces.Create, - TaskPanelOpPage, - 'Path-Profile-Face', - QtCore.QT_TRANSLATE_NOOP("PathProfile", "Face Profile"), - QtCore.QT_TRANSLATE_NOOP("PathProfile", "Profile based on face or faces"), - PathProfileFaces.SetupProperties) - -FreeCAD.Console.PrintLog("Loading PathProfileFacesGui... done\n") +# -*- coding: utf-8 -*- + +# *************************************************************************** +# * * +# * Copyright (c) 2017 sliptonic * +# * * +# * This program is free software; you can redistribute it and/or modify * +# * it under the terms of the GNU Lesser General Public License (LGPL) * +# * as published by the Free Software Foundation; either version 2 of * +# * the License, or (at your option) any later version. * +# * for detail see the LICENCE text file. * +# * * +# * This program is distributed in the hope that it will be useful, * +# * but WITHOUT ANY WARRANTY; without even the implied warranty of * +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +# * GNU Library General Public License for more details. * +# * * +# * You should have received a copy of the GNU Library General Public * +# * License along with this program; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** +# * Major modifications: 2020 Russell Johnson * + +import FreeCAD +import PathScripts.PathOpGui as PathOpGui +import PathScripts.PathProfile as PathProfile +import PathScripts.PathProfileGui as PathProfileGui +from PySide import QtCore + + +__title__ = "Path Profile Faces Operation UI (depreciated)" +__author__ = "sliptonic (Brad Collette)" +__url__ = "http://www.freecadweb.org" +__doc__ = "Profile Faces operation page controller and command implementation (depreciated)." + + +class TaskPanelOpPage(PathProfileGui.TaskPanelOpPage): + '''Psuedo page controller class for Profile operation, + allowing for backward compatibility with pre-existing "Profile Faces" operations.''' + pass +# Eclass + + +Command = PathOpGui.SetupOperation('Profile', + PathProfile.Create, + TaskPanelOpPage, + 'Path-Contour', + QtCore.QT_TRANSLATE_NOOP("PathProfile", "Profile"), + QtCore.QT_TRANSLATE_NOOP("PathProfile", "Profile entire model, selected face(s) or selected edge(s)"), + PathProfile.SetupProperties) + +FreeCAD.Console.PrintLog("Loading PathProfileFacesGui... done\n")