From 41d20a62cd8227e94b843a3f3dca4a682bd60766 Mon Sep 17 00:00:00 2001 From: PhaseLoop Date: Thu, 20 Mar 2025 12:48:52 +0100 Subject: [PATCH] Replace Array operation with array dressup --- src/Mod/CAM/Path/Dressup/Array.py | 379 ++++++++++++++++++++++++++ src/Mod/CAM/Path/Dressup/Base.py | 93 +++++++ src/Mod/CAM/Path/Dressup/Gui/Array.py | 103 +++++++ src/Mod/CAM/Path/GuiInit.py | 1 + src/Mod/CAM/Path/Op/Gui/Array.py | 15 + 5 files changed, 591 insertions(+) create mode 100644 src/Mod/CAM/Path/Dressup/Array.py create mode 100644 src/Mod/CAM/Path/Dressup/Base.py create mode 100644 src/Mod/CAM/Path/Dressup/Gui/Array.py diff --git a/src/Mod/CAM/Path/Dressup/Array.py b/src/Mod/CAM/Path/Dressup/Array.py new file mode 100644 index 0000000000..7a73811232 --- /dev/null +++ b/src/Mod/CAM/Path/Dressup/Array.py @@ -0,0 +1,379 @@ +# -*- coding: utf-8 -*- +# *************************************************************************** +# * Copyright (c) 2015 Yorik van Havre * +# * Reimplemented as dressup in 2025 phaseloop 0: + ang = self.angle / self.copies * (1 + i) + + pl = FreeCAD.Placement() + pl.rotate(self.centre, FreeCAD.Vector(0, 0, 1), ang) + np = PathUtils.applyPlacementToPath(pl, PathUtils.getPathWithPlacement(base)) + output += np.toGCode() + + # return output + return Path.Path(output) + + +def Create(base, name="DressupArray"): + """Create(base, name='DressupPathBoundary') ... creates a dressup array.""" + + if not base.isDerivedFrom("Path::Feature"): + Path.Log.error( + translate("CAM_DressupArray", "The selected object is not a path") + "\n" + ) + return None + + obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name) + job = PathUtils.findParentJob(base) + obj.Proxy = DressupArray(obj, base, job) + job.Proxy.addOperation(obj, base, True) + return obj diff --git a/src/Mod/CAM/Path/Dressup/Base.py b/src/Mod/CAM/Path/Dressup/Base.py new file mode 100644 index 0000000000..abf86550ec --- /dev/null +++ b/src/Mod/CAM/Path/Dressup/Base.py @@ -0,0 +1,93 @@ +from PySide.QtCore import QT_TRANSLATE_NOOP + +from Path.Op.Base import ObjectOp + +class DressupBase: + """ + Base class for all dressups to provide common interface with the rest of CAM + One major example is making sure all dressups export base operation settings + like coolant, tool controller, etc. + """ + + def setup_coolant_property(self, obj): + if not hasattr(obj, "CoolantMode"): + obj.addProperty( + "App::PropertyEnumeration", + "CoolantMode", + "CoolantMode", + QT_TRANSLATE_NOOP("App::Property", "Default coolant mode."), + ) + + for n in ObjectOp.opPropertyEnumerations(): + if n[0] == "CoolantMode": + setattr(obj, n[0], n[1]) + + def setup_tool_controller_property(self, obj): + if not hasattr(obj, "ToolController"): + obj.addProperty( + "App::PropertyLink", + "ToolController", + "Path", + QT_TRANSLATE_NOOP( + "App::Property", + "The tool controller that will be used to calculate the path", + ), + ) + + def __init__(self, obj, base): + + obj.addProperty( + "App::PropertyLink", + "Base", + "Base", + QT_TRANSLATE_NOOP("App::Property", "The base path to modify"), + ) + + obj.addProperty( + "App::PropertyBool", + "Active", + "Path", + QT_TRANSLATE_NOOP( + "App::Property", "Make False, to prevent operation from generating code" + ), + ) + + self.setup_coolant_property(obj) + self.setup_tool_controller_property(obj) + + def onDocumentRestored(self, obj): + """ + Called then document is being restored. Often used for object migrations, + adding missing properties, etc. + Do not overwrite - child classes should use dressupOnDocumentRestored(). + """ + self.setup_coolant_property(obj) + self.setup_tool_controller_property(obj) + + def dressupOnDocumentRestored(self, obj): + """Overwrite this method for custom handling.""" + pass + + def execute(self, obj): + """ + Export common properties from base object and + run dressupExecute() + """ + + if hasattr(obj, "Base") and hasattr(obj.Base, "CoolantMode"): + obj.CoolantMode = obj.Base.CoolantMode + + if hasattr(obj, "Base") and hasattr(obj.Base, "ToolController"): + obj.ToolController = obj.Base.ToolController + + return self.dressupExecute(obj) + + def dressupExecute(self, obj): + """ + Called whenever receiver should be recalculated. + Should be overwritten by subclasses. + """ + pass + + + diff --git a/src/Mod/CAM/Path/Dressup/Gui/Array.py b/src/Mod/CAM/Path/Dressup/Gui/Array.py new file mode 100644 index 0000000000..e72a994646 --- /dev/null +++ b/src/Mod/CAM/Path/Dressup/Gui/Array.py @@ -0,0 +1,103 @@ + + +from PySide.QtCore import QT_TRANSLATE_NOOP +import FreeCAD +import Path +import Path.Base.Util as PathUtil +import Path.Dressup.Array as DressupArray +import Path.Main.Stock as PathStock +import PathScripts.PathUtils as PathUtils + +from PySide import QtGui +from PySide.QtCore import QT_TRANSLATE_NOOP +import FreeCAD +import FreeCADGui +import Path +import PathGui + + +class DressupArrayViewProvider(object): + def __init__(self, vobj): + self.attach(vobj) + + def dumps(self): + return None + + def loads(self, state): + return None + + def attach(self, vobj): + self.vobj = vobj + self.obj = vobj.Object + self.panel = None + + def claimChildren(self): + return [self.obj.Base] + + def onDelete(self, vobj, args=None): + if vobj.Object and vobj.Object.Proxy: + vobj.Object.Proxy.onDelete(vobj.Object, args) + return True + + def setEdit(self, vobj, mode=0): + return True + + def unsetEdit(self, vobj, mode=0): + pass + + def setupTaskPanel(self, panel): + pass + + def clearTaskPanel(self): + pass + + +class CommandPathDressupArray: + def GetResources(self): + return { + "Pixmap": "CAM_Dressup", + "MenuText": QT_TRANSLATE_NOOP("CAM_DressupArray", "Array"), + "ToolTip": QT_TRANSLATE_NOOP( + "CAM_DressupArray", + "Creates an array from a selected toolpath", + ), + } + + def IsActive(self): + if FreeCAD.ActiveDocument is not None: + for o in FreeCAD.ActiveDocument.Objects: + if o.Name[:3] == "Job": + return True + return False + + def Activated(self): + # check that the selection contains exactly what we want + selection = FreeCADGui.Selection.getSelection() + if len(selection) != 1: + Path.Log.error( + translate("CAM_DressupArray", "Please select one toolpath object") + "\n" + ) + return + baseObject = selection[0] + + # everything ok! + FreeCAD.ActiveDocument.openTransaction("Create Path Array Dress-up") + FreeCADGui.addModule("Path.Dressup.Gui.Array") + FreeCADGui.doCommand( + "Path.Dressup.Gui.Array.Create(App.ActiveDocument.%s)" % baseObject.Name + ) + # FreeCAD.ActiveDocument.commitTransaction() # Final `commitTransaction()` called via TaskPanel.accept() + FreeCAD.ActiveDocument.recompute() + + +def Create(base, name="DressupPathArray"): + FreeCAD.ActiveDocument.openTransaction("Create an Array dressup") + obj = DressupArray.Create(base, name) + obj.ViewObject.Proxy = DressupArrayViewProvider(obj.ViewObject) + obj.Base.ViewObject.Visibility = False + FreeCAD.ActiveDocument.commitTransaction() + return obj + +if FreeCAD.GuiUp: + # register the FreeCAD command + FreeCADGui.addCommand("CAM_DressupArray", CommandPathDressupArray()) diff --git a/src/Mod/CAM/Path/GuiInit.py b/src/Mod/CAM/Path/GuiInit.py index 65a7b798d1..94a29a5638 100644 --- a/src/Mod/CAM/Path/GuiInit.py +++ b/src/Mod/CAM/Path/GuiInit.py @@ -40,6 +40,7 @@ def Startup(): Path.Log.debug("Initializing PathGui") from Path.Base.Gui import PropertyBag from Path.Base.Gui import SetupSheet + from Path.Dressup.Gui import Array from Path.Dressup.Gui import AxisMap from Path.Dressup.Gui import Dogbone from Path.Dressup.Gui import DogboneII diff --git a/src/Mod/CAM/Path/Op/Gui/Array.py b/src/Mod/CAM/Path/Op/Gui/Array.py index 8bbd026730..93ec682081 100644 --- a/src/Mod/CAM/Path/Op/Gui/Array.py +++ b/src/Mod/CAM/Path/Op/Gui/Array.py @@ -27,6 +27,8 @@ import PathScripts import PathScripts.PathUtils as PathUtils from Path.Dressup.Utils import toolController from PySide import QtCore +from PySide import QtGui + import math import random from PySide.QtCore import QT_TRANSLATE_NOOP @@ -203,6 +205,19 @@ class ObjectArray: self.setEditorModes(obj) def execute(self, obj): + if FreeCAD.GuiUp: + + QtGui.QMessageBox.warning( + None, + QT_TRANSLATE_NOOP("CAM_ArrayOp", "Operation is depreciated"), + QT_TRANSLATE_NOOP("CAM_ArrayOp", + ("CAM -> Path Modification -> Array operation is depreciated " + "and will be removed in future FreeCAD versions.\n\n" + "Please use CAM -> Path Dressup -> Array instead.\n\n" + "DO NOT USE CURRENT ARRAY OPERATION WHEN MACHINING WITH COOLANT!\n" + "Due to a bug - collant will not be enabled for array paths." + )), + ) # backwards compatibility for PathArrays created before support for multiple bases if isinstance(obj.Base, list): base = obj.Base