Merge pull request #20321 from phaseloop/array-dressup-2
[CAM] Replace Array operation with array dressup
This commit is contained in:
126
src/Mod/CAM/CAMTests/TestPathDressupArray.py
Normal file
126
src/Mod/CAM/CAMTests/TestPathDressupArray.py
Normal file
@@ -0,0 +1,126 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# ***************************************************************************
|
||||
# * Copyright (c) 2025 phaseloop <phaseloop@protonmail.com> *
|
||||
# * *
|
||||
# * 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 Path
|
||||
from Path.Dressup.Array import DressupArray
|
||||
import Path.Main.Job as PathJob
|
||||
import Path.Op.Profile as PathProfile
|
||||
|
||||
from CAMTests.PathTestUtils import PathTestBase
|
||||
|
||||
|
||||
class TestEngrave:
|
||||
def __init__(self, path):
|
||||
self.Path = Path.Path(path)
|
||||
self.ToolController = None # default tool 5mm
|
||||
self.CoolantMode = "None"
|
||||
self.Name = "Engrave"
|
||||
|
||||
def isDerivedFrom(self, type):
|
||||
if type == "Path::Feature":
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class TestFeature:
|
||||
def __init__(self):
|
||||
self.Path = Path.Path()
|
||||
self.Name = ""
|
||||
|
||||
def addProperty(self, typ, name, category, tip):
|
||||
setattr(self, name, None)
|
||||
|
||||
def setEditorMode(self, prop, mode):
|
||||
pass
|
||||
|
||||
|
||||
class TestDressupArray(PathTestBase):
|
||||
"""Unit tests for the Array dressup."""
|
||||
|
||||
def test00(self):
|
||||
"""Verify array with zero copies provides original path."""
|
||||
|
||||
source_gcode = "G0 X0 Y0 Z0\n" "G1 X10 Y10 Z0\n"
|
||||
|
||||
expected_gcode = "G0 X0.000000 Y0.000000 Z0.000000\n" "G1 X10.000000 Y10.000000 Z0.000000\n"
|
||||
|
||||
base = TestEngrave(source_gcode)
|
||||
obj = TestFeature()
|
||||
da = DressupArray(obj, base, None)
|
||||
da.execute(obj)
|
||||
self.assertTrue(obj.Path.toGCode() == expected_gcode, "Incorrect g-code generated")
|
||||
|
||||
def test01(self):
|
||||
"""Verify linear x/y/z 1D array with 1 copy."""
|
||||
|
||||
source_gcode = "G0 X0 Y0 Z0\n" "G1 X10 Y10 Z0\n"
|
||||
|
||||
expected_gcode = (
|
||||
"G0 X0.000000 Y0.000000 Z0.000000\n"
|
||||
"G1 X10.000000 Y10.000000 Z0.000000\n"
|
||||
"G0 X12.000000 Y12.000000 Z5.000000\n"
|
||||
"G1 X22.000000 Y22.000000 Z5.000000\n"
|
||||
)
|
||||
|
||||
base = TestEngrave(source_gcode)
|
||||
obj = TestFeature()
|
||||
da = DressupArray(obj, base, None)
|
||||
obj.Copies = 1
|
||||
obj.Offset = FreeCAD.Vector(12, 12, 5)
|
||||
|
||||
da.execute(obj)
|
||||
self.assertTrue(obj.Path.toGCode() == expected_gcode, "Incorrect g-code generated")
|
||||
|
||||
def test01(self):
|
||||
"""Verify linear x/y/z 2D array."""
|
||||
|
||||
source_gcode = "G0 X0 Y0 Z0\n" "G1 X10 Y10 Z0\n"
|
||||
|
||||
expected_gcode = (
|
||||
"G0 X0.000000 Y0.000000 Z0.000000\n"
|
||||
"G1 X10.000000 Y10.000000 Z0.000000\n"
|
||||
"G0 X0.000000 Y6.000000 Z0.000000\n"
|
||||
"G1 X10.000000 Y16.000000 Z0.000000\n"
|
||||
"G0 X12.000000 Y6.000000 Z0.000000\n"
|
||||
"G1 X22.000000 Y16.000000 Z0.000000\n"
|
||||
"G0 X12.000000 Y0.000000 Z0.000000\n"
|
||||
"G1 X22.000000 Y10.000000 Z0.000000\n"
|
||||
"G0 X24.000000 Y0.000000 Z0.000000\n"
|
||||
"G1 X34.000000 Y10.000000 Z0.000000\n"
|
||||
"G0 X24.000000 Y6.000000 Z0.000000\n"
|
||||
"G1 X34.000000 Y16.000000 Z0.000000\n"
|
||||
)
|
||||
|
||||
base = TestEngrave(source_gcode)
|
||||
obj = TestFeature()
|
||||
da = DressupArray(obj, base, None)
|
||||
obj.Type = "Linear2D"
|
||||
obj.Copies = 0
|
||||
obj.CopiesX = 2
|
||||
obj.CopiesY = 1
|
||||
|
||||
obj.Offset = FreeCAD.Vector(12, 6, 0)
|
||||
|
||||
da.execute(obj)
|
||||
self.assertTrue(obj.Path.toGCode() == expected_gcode, "Incorrect g-code generated")
|
||||
@@ -59,6 +59,8 @@ SET(PathPythonBaseGui_SRCS
|
||||
SET(PathPythonDressup_SRCS
|
||||
Path/Dressup/__init__.py
|
||||
Path/Dressup/Utils.py
|
||||
Path/Dressup/Array.py
|
||||
Path/Dressup/Base.py
|
||||
Path/Dressup/Boundary.py
|
||||
Path/Dressup/DogboneII.py
|
||||
Path/Dressup/Tags.py
|
||||
@@ -66,6 +68,7 @@ SET(PathPythonDressup_SRCS
|
||||
|
||||
SET(PathPythonDressupGui_SRCS
|
||||
Path/Dressup/Gui/__init__.py
|
||||
Path/Dressup/Gui/Array.py
|
||||
Path/Dressup/Gui/AxisMap.py
|
||||
Path/Dressup/Gui/Dogbone.py
|
||||
Path/Dressup/Gui/DogboneII.py
|
||||
@@ -316,6 +319,7 @@ SET(Tests_SRCS
|
||||
CAMTests/TestPathAdaptive.py
|
||||
CAMTests/TestPathCore.py
|
||||
CAMTests/TestPathDepthParams.py
|
||||
CAMTests/TestPathDressupArray.py
|
||||
CAMTests/TestPathDressupDogbone.py
|
||||
CAMTests/TestPathDressupDogboneII.py
|
||||
CAMTests/TestPathDressupHoldingTags.py
|
||||
|
||||
@@ -122,6 +122,7 @@ class CAMWorkbench(Workbench):
|
||||
drillingcmdlist = ["CAM_Drilling", "CAM_Tapping"]
|
||||
modcmdlist = ["CAM_OperationCopy", "CAM_Array", "CAM_SimpleCopy"]
|
||||
dressupcmdlist = [
|
||||
"CAM_DressupArray",
|
||||
"CAM_DressupAxisMap",
|
||||
"CAM_DressupPathBoundary",
|
||||
"CAM_DressupDogbone",
|
||||
|
||||
377
src/Mod/CAM/Path/Dressup/Array.py
Normal file
377
src/Mod/CAM/Path/Dressup/Array.py
Normal file
@@ -0,0 +1,377 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# ***************************************************************************
|
||||
# * Copyright (c) 2015 Yorik van Havre <yorik@uncreated.net> *
|
||||
# * Reimplemented as dressup in 2025 phaseloop <phaseloop@protonmail.com *
|
||||
# * 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 Path
|
||||
import PathScripts.PathUtils as PathUtils
|
||||
from Path.Dressup.Base import DressupBase
|
||||
import random
|
||||
from PySide.QtCore import QT_TRANSLATE_NOOP
|
||||
|
||||
__doc__ = """CAM Array dressup"""
|
||||
|
||||
translate = FreeCAD.Qt.translate
|
||||
|
||||
|
||||
class DressupArray(DressupBase):
|
||||
def __init__(self, obj, base, job):
|
||||
super().__init__(obj, base)
|
||||
|
||||
obj.addProperty(
|
||||
"App::PropertyEnumeration",
|
||||
"Type",
|
||||
"Path",
|
||||
QT_TRANSLATE_NOOP("App::Property", "Pattern method"),
|
||||
)
|
||||
obj.addProperty(
|
||||
"App::PropertyVectorDistance",
|
||||
"Offset",
|
||||
"Path",
|
||||
QT_TRANSLATE_NOOP(
|
||||
"App::Property",
|
||||
"The spacing between the array copies in Linear pattern",
|
||||
),
|
||||
)
|
||||
obj.addProperty(
|
||||
"App::PropertyInteger",
|
||||
"CopiesX",
|
||||
"Path",
|
||||
QT_TRANSLATE_NOOP(
|
||||
"App::Property", "The number of copies in X direction in Linear pattern"
|
||||
),
|
||||
)
|
||||
obj.addProperty(
|
||||
"App::PropertyInteger",
|
||||
"CopiesY",
|
||||
"Path",
|
||||
QT_TRANSLATE_NOOP(
|
||||
"App::Property", "The number of copies in Y direction in Linear pattern"
|
||||
),
|
||||
)
|
||||
obj.addProperty(
|
||||
"App::PropertyAngle",
|
||||
"Angle",
|
||||
"Path",
|
||||
QT_TRANSLATE_NOOP("App::Property", "Total angle in Polar pattern"),
|
||||
)
|
||||
obj.addProperty(
|
||||
"App::PropertyInteger",
|
||||
"Copies",
|
||||
"Path",
|
||||
QT_TRANSLATE_NOOP(
|
||||
"App::Property", "The number of copies in Linear 1D and Polar pattern"
|
||||
),
|
||||
)
|
||||
obj.addProperty(
|
||||
"App::PropertyVector",
|
||||
"Centre",
|
||||
"Path",
|
||||
QT_TRANSLATE_NOOP("App::Property", "The centre of rotation in Polar pattern"),
|
||||
)
|
||||
obj.addProperty(
|
||||
"App::PropertyBool",
|
||||
"SwapDirection",
|
||||
"Path",
|
||||
QT_TRANSLATE_NOOP(
|
||||
"App::Property",
|
||||
"Make copies in X direction before Y in Linear 2D pattern",
|
||||
),
|
||||
)
|
||||
obj.addProperty(
|
||||
"App::PropertyInteger",
|
||||
"JitterPercent",
|
||||
"Path",
|
||||
QT_TRANSLATE_NOOP("App::Property", "Percent of copies to randomly offset"),
|
||||
)
|
||||
obj.addProperty(
|
||||
"App::PropertyVectorDistance",
|
||||
"JitterMagnitude",
|
||||
"Path",
|
||||
QT_TRANSLATE_NOOP("App::Property", "Maximum random offset of copies"),
|
||||
)
|
||||
obj.addProperty(
|
||||
"App::PropertyInteger",
|
||||
"JitterSeed",
|
||||
"Path",
|
||||
QT_TRANSLATE_NOOP("App::Property", "Seed value for jitter randomness"),
|
||||
)
|
||||
|
||||
self.obj = obj
|
||||
obj.Base = base
|
||||
|
||||
obj.Active = True
|
||||
# assigning array tells the type of possible enum choices
|
||||
obj.Type = ["Linear1D", "Linear2D", "Polar"]
|
||||
# assign value
|
||||
obj.Type = "Linear1D"
|
||||
|
||||
obj.Copies = 0
|
||||
obj.JitterPercent = 0
|
||||
|
||||
self.setEditorModes(obj)
|
||||
obj.Proxy = self
|
||||
|
||||
def dumps(self):
|
||||
return None
|
||||
|
||||
def loads(self, state):
|
||||
return None
|
||||
|
||||
def setEditorModes(self, obj):
|
||||
if obj.Type == "Linear1D":
|
||||
angleMode = centreMode = copiesXMode = copiesYMode = swapDirectionMode = 2
|
||||
copiesMode = offsetMode = 0
|
||||
elif obj.Type == "Linear2D":
|
||||
angleMode = copiesMode = centreMode = 2
|
||||
copiesXMode = copiesYMode = offsetMode = swapDirectionMode = 0
|
||||
elif obj.Type == "Polar":
|
||||
angleMode = copiesMode = centreMode = 0
|
||||
copiesXMode = copiesYMode = offsetMode = swapDirectionMode = 2
|
||||
|
||||
obj.setEditorMode("Angle", angleMode)
|
||||
obj.setEditorMode("Copies", copiesMode)
|
||||
obj.setEditorMode("Centre", centreMode)
|
||||
obj.setEditorMode("CopiesX", copiesXMode)
|
||||
obj.setEditorMode("CopiesY", copiesYMode)
|
||||
obj.setEditorMode("Offset", offsetMode)
|
||||
obj.setEditorMode("SwapDirection", swapDirectionMode)
|
||||
obj.setEditorMode("JitterPercent", 0)
|
||||
obj.setEditorMode("JitterMagnitude", 0)
|
||||
obj.setEditorMode("JitterSeed", 0)
|
||||
|
||||
def onChanged(self, obj, prop):
|
||||
if prop == "Type":
|
||||
self.setEditorModes(obj)
|
||||
|
||||
def dresupOnDocumentRestored(self, obj):
|
||||
"""onDocumentRestored(obj) ... Called automatically when document is restored."""
|
||||
self.obj = obj
|
||||
self.setEditorModes(obj)
|
||||
|
||||
def onDelete(self, obj, args):
|
||||
if obj.Base:
|
||||
job = PathUtils.findParentJob(obj)
|
||||
if job:
|
||||
job.Proxy.addOperation(obj.Base, obj)
|
||||
if obj.Base.ViewObject:
|
||||
obj.Base.ViewObject.Visibility = True
|
||||
obj.Base = None
|
||||
return True
|
||||
|
||||
def dressupExecute(self, obj):
|
||||
|
||||
if not obj.Base or not obj.Base.isDerivedFrom("Path::Feature") or not obj.Base.Path:
|
||||
Path.Log.error(translate("PathArray", "Base is empty or an invalid object."))
|
||||
return None
|
||||
|
||||
# Do not generate paths and clear current Path data if operation not active
|
||||
if not obj.Active:
|
||||
if obj.Path:
|
||||
obj.Path = Path.Path()
|
||||
return
|
||||
|
||||
# use seed if specified, otherwise default to object name for consistency during recomputes
|
||||
seed = obj.JitterSeed or obj.Name
|
||||
|
||||
pa = PathArray(
|
||||
obj.Base,
|
||||
obj.Type,
|
||||
obj.Copies,
|
||||
obj.Offset,
|
||||
obj.CopiesX,
|
||||
obj.CopiesY,
|
||||
obj.Angle,
|
||||
obj.Centre,
|
||||
obj.SwapDirection,
|
||||
obj.JitterMagnitude,
|
||||
obj.JitterPercent,
|
||||
seed,
|
||||
)
|
||||
|
||||
obj.Path = pa.getPath()
|
||||
|
||||
|
||||
class PathArray:
|
||||
"""class PathArray ...
|
||||
This class receives one or more base operations and repeats those operations
|
||||
at set intervals based upon array type requested and the related settings for that type."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
base,
|
||||
arrayType,
|
||||
copies,
|
||||
offsetVector,
|
||||
copiesX,
|
||||
copiesY,
|
||||
angle,
|
||||
centre,
|
||||
swapDirection,
|
||||
jitterMagnitude=FreeCAD.Vector(0, 0, 0),
|
||||
jitterPercent=0,
|
||||
seed="FreeCAD",
|
||||
):
|
||||
self.base = base
|
||||
self.arrayType = arrayType # ['Linear1D', 'Linear2D', 'Polar']
|
||||
self.copies = copies
|
||||
self.offsetVector = offsetVector
|
||||
self.copiesX = copiesX
|
||||
self.copiesY = copiesY
|
||||
self.angle = angle
|
||||
self.centre = centre
|
||||
self.swapDirection = swapDirection
|
||||
self.jitterMagnitude = jitterMagnitude
|
||||
self.jitterPercent = jitterPercent
|
||||
self.seed = seed
|
||||
|
||||
# Private method
|
||||
def _calculateJitter(self, pos):
|
||||
"""_calculateJitter(pos) ...
|
||||
Returns the position argument with a random vector shift applied."""
|
||||
if self.jitterPercent == 0:
|
||||
pass
|
||||
elif random.randint(0, 100) < self.jitterPercent:
|
||||
pos.x = pos.x + random.uniform(-self.jitterMagnitude.x, self.jitterMagnitude.x)
|
||||
pos.y = pos.y + random.uniform(-self.jitterMagnitude.y, self.jitterMagnitude.y)
|
||||
pos.z = pos.z + random.uniform(-self.jitterMagnitude.z, self.jitterMagnitude.z)
|
||||
return pos
|
||||
|
||||
# Public method
|
||||
def getPath(self):
|
||||
"""getPath() ... Call this method on an instance of the class to generate and return
|
||||
path data for the requested path array."""
|
||||
|
||||
if self.base is None:
|
||||
Path.Log.error(translate("PathArray", "No base objects for PathArray."))
|
||||
return None
|
||||
|
||||
base = self.base
|
||||
|
||||
# build copies
|
||||
# initially output contains original base path, copies are added on top of that
|
||||
output = PathUtils.getPathWithPlacement(base).toGCode()
|
||||
|
||||
random.seed(self.seed)
|
||||
|
||||
if self.arrayType == "Linear1D":
|
||||
for i in range(self.copies):
|
||||
pos = FreeCAD.Vector(
|
||||
self.offsetVector.x * (i + 1),
|
||||
self.offsetVector.y * (i + 1),
|
||||
self.offsetVector.z * (i + 1),
|
||||
)
|
||||
pos = self._calculateJitter(pos)
|
||||
|
||||
pl = FreeCAD.Placement()
|
||||
pl.move(pos)
|
||||
np = Path.Path(
|
||||
[cm.transform(pl) for cm in PathUtils.getPathWithPlacement(base).Commands]
|
||||
)
|
||||
|
||||
output += np.toGCode()
|
||||
|
||||
elif self.arrayType == "Linear2D":
|
||||
if self.swapDirection:
|
||||
for i in range(self.copiesY + 1):
|
||||
for j in range(self.copiesX + 1):
|
||||
if (i % 2) == 0:
|
||||
pos = FreeCAD.Vector(
|
||||
self.offsetVector.x * j,
|
||||
self.offsetVector.y * i,
|
||||
self.offsetVector.z * i,
|
||||
)
|
||||
else:
|
||||
pos = FreeCAD.Vector(
|
||||
self.offsetVector.x * (self.copiesX - j),
|
||||
self.offsetVector.y * i,
|
||||
self.offsetVector.z * i,
|
||||
)
|
||||
pos = self._calculateJitter(pos)
|
||||
|
||||
pl = FreeCAD.Placement()
|
||||
# do not process the index 0,0. It will be processed by the base Paths themselves
|
||||
if not (i == 0 and j == 0):
|
||||
pl.move(pos)
|
||||
np = Path.Path(
|
||||
[
|
||||
cm.transform(pl)
|
||||
for cm in PathUtils.getPathWithPlacement(base).Commands
|
||||
]
|
||||
)
|
||||
output += np.toGCode()
|
||||
else:
|
||||
for i in range(self.copiesX + 1):
|
||||
for j in range(self.copiesY + 1):
|
||||
if (i % 2) == 0:
|
||||
pos = FreeCAD.Vector(
|
||||
self.offsetVector.x * i,
|
||||
self.offsetVector.y * j,
|
||||
self.offsetVector.z * i,
|
||||
)
|
||||
else:
|
||||
pos = FreeCAD.Vector(
|
||||
self.offsetVector.x * i,
|
||||
self.offsetVector.y * (self.copiesY - j),
|
||||
self.offsetVector.z * i,
|
||||
)
|
||||
pos = self._calculateJitter(pos)
|
||||
|
||||
pl = FreeCAD.Placement()
|
||||
# do not process the index 0,0. It will be processed by the base Paths themselves
|
||||
if not (i == 0 and j == 0):
|
||||
pl.move(pos)
|
||||
np = Path.Path(
|
||||
[
|
||||
cm.transform(pl)
|
||||
for cm in PathUtils.getPathWithPlacement(base).Commands
|
||||
]
|
||||
)
|
||||
output += np.toGCode()
|
||||
# Eif
|
||||
else:
|
||||
for i in range(self.copies):
|
||||
ang = 360
|
||||
if self.copies > 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
|
||||
91
src/Mod/CAM/Path/Dressup/Base.py
Normal file
91
src/Mod/CAM/Path/Dressup/Base.py
Normal file
@@ -0,0 +1,91 @@
|
||||
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
|
||||
102
src/Mod/CAM/Path/Dressup/Gui/Array.py
Normal file
102
src/Mod/CAM/Path/Dressup/Gui/Array.py
Normal file
@@ -0,0 +1,102 @@
|
||||
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())
|
||||
@@ -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
|
||||
|
||||
@@ -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,22 @@ 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
|
||||
|
||||
Reference in New Issue
Block a user