| | |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| |
|
| | import FreeCAD |
| | import Path |
| | import PathScripts.PathUtils as PathUtils |
| | import Path.Dressup.Utils as PathDressup |
| | import random |
| | from PySide.QtCore import QT_TRANSLATE_NOOP |
| |
|
| | __doc__ = """CAM Array dressup""" |
| |
|
| | translate = FreeCAD.Qt.translate |
| |
|
| |
|
| | class DressupArray: |
| | def __init__(self, obj, base, job): |
| | obj.addProperty( |
| | "App::PropertyLink", |
| | "Base", |
| | "Path", |
| | QT_TRANSLATE_NOOP("App::Property", "The base toolpath to modify"), |
| | ) |
| | 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.Type = ["Linear1D", "Linear2D", "Polar"] |
| | |
| | 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) |
| | if prop == "Path" and obj.ViewObject: |
| | obj.ViewObject.signalChangeIcon() |
| |
|
| | def onDocumentRestored(self, obj): |
| | """onDocumentRestored(obj) ... Called automatically when document is restored.""" |
| |
|
| | |
| | |
| | |
| | |
| | |
| |
|
| | if hasattr(obj, "CoolantMode"): |
| | obj.removeProperty("CoolantMode") |
| | FreeCAD.Console.PrintWarning( |
| | translate( |
| | "DressupArray", |
| | "Removing CoolantMode property from {} as base operation's CoolantMode is now used.", |
| | ).format(obj.Name) |
| | + "\n" |
| | ) |
| |
|
| | if hasattr(obj, "ToolController"): |
| | obj.removeProperty("ToolController") |
| | FreeCAD.Console.PrintWarning( |
| | translate( |
| | "DressupArray", |
| | "Removing ToolController property from {} as base operation's ToolController is now used.", |
| | ).format(obj.Name) |
| | + "\n" |
| | ) |
| |
|
| | 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 execute(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 |
| |
|
| | |
| | if not PathDressup.baseOp(obj.Base).Active: |
| | if obj.Path: |
| | obj.Path = Path.Path() |
| | return |
| |
|
| | |
| | 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 |
| | 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 |
| |
|
| | |
| | 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 |
| |
|
| | |
| | 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 |
| |
|
| | |
| | |
| | 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() |
| | |
| | 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() |
| | |
| | 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.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 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 |
| |
|