| | |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | from PySide.QtCore import QT_TRANSLATE_NOOP |
| | import FreeCAD |
| | import Path |
| | import Path.Base.Util as PathUtil |
| | import Path.Dressup.Utils as PathDressup |
| | import Path.Main.Stock as PathStock |
| | import PathScripts.PathUtils as PathUtils |
| |
|
| | if False: |
| | Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) |
| | Path.Log.trackModule(Path.Log.thisModule()) |
| | else: |
| | Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) |
| |
|
| |
|
| | translate = FreeCAD.Qt.translate |
| |
|
| |
|
| | def _vstr(v): |
| | if v: |
| | return "(%.2f, %.2f, %.2f)" % (v.x, v.y, v.z) |
| | return "-" |
| |
|
| |
|
| | class DressupPathBoundary(object): |
| | def promoteStockToBoundary(self, stock): |
| | """Ensure stock object has boundary properties set.""" |
| | if stock: |
| | if not hasattr(stock, "IsBoundary"): |
| | stock.addProperty("App::PropertyBool", "IsBoundary", "Base") |
| | stock.IsBoundary = True |
| | if hasattr(stock, "setEditorMode"): |
| | stock.setEditorMode("IsBoundary", 3) |
| | stock.Label = "Boundary" |
| |
|
| | def __init__(self, obj, base, job): |
| | obj.addProperty( |
| | "App::PropertyLink", |
| | "Base", |
| | "Base", |
| | QT_TRANSLATE_NOOP("App::Property", "The base path to modify"), |
| | ) |
| | obj.Base = base |
| | obj.addProperty( |
| | "App::PropertyLink", |
| | "Stock", |
| | "Boundary", |
| | QT_TRANSLATE_NOOP( |
| | "App::Property", |
| | "Solid object to be used to limit the generated Path.", |
| | ), |
| | ) |
| | obj.Stock = PathStock.CreateFromBase(job) |
| | self.promoteStockToBoundary(obj.Stock) |
| | obj.addProperty( |
| | "App::PropertyBool", |
| | "Inside", |
| | "Boundary", |
| | QT_TRANSLATE_NOOP( |
| | "App::Property", |
| | "Determines if Boundary describes an inclusion or exclusion mask.", |
| | ), |
| | ) |
| | obj.Inside = True |
| | obj.addProperty( |
| | "App::PropertyBool", |
| | "KeepToolDown", |
| | "Boundary", |
| | QT_TRANSLATE_NOOP( |
| | "App::Property", |
| | "Keep tool down.", |
| | ), |
| | ) |
| | obj.KeepToolDown = False |
| |
|
| | self.obj = obj |
| | self.safeHeight = None |
| | self.clearanceHeight = None |
| |
|
| | def dumps(self): |
| | return None |
| |
|
| | def loads(self, state): |
| | return None |
| |
|
| | def onChanged(self, obj, prop): |
| | if prop == "Path" and obj.ViewObject: |
| | obj.ViewObject.signalChangeIcon() |
| |
|
| | |
| | if prop == "Stock" and obj.Stock: |
| | self.promoteStockToBoundary(obj.Stock) |
| |
|
| | def onDocumentRestored(self, obj): |
| | self.obj = obj |
| | |
| | self.promoteStockToBoundary(obj.Stock) |
| | if not hasattr(obj, "KeepToolDown"): |
| | obj.addProperty( |
| | "App::PropertyBool", |
| | "KeepToolDown", |
| | "Boundary", |
| | QT_TRANSLATE_NOOP( |
| | "App::Property", |
| | "Keep tool down.", |
| | ), |
| | ) |
| |
|
| | 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 |
| | if hasattr(obj, "Stock") and obj.Stock: |
| | obj.Document.removeObject(obj.Stock.Name) |
| | obj.Stock = None |
| | return True |
| |
|
| | def execute(self, obj): |
| | if not hasattr(obj, "Stock") or obj.Stock is None: |
| | Path.Log.error("BoundaryStock (Stock) missing; cannot execute dressup.") |
| | obj.Path = Path.Path([]) |
| | return |
| | if not hasattr(obj.Stock, "Shape") or obj.Stock.Shape is None: |
| | Path.Log.error("Boundary stock has no Shape; cannot execute dressup.") |
| | obj.Path = Path.Path([]) |
| | return |
| | pb = PathBoundary(obj.Base, obj.Stock.Shape, obj.Inside, obj.KeepToolDown) |
| | obj.Path = pb.execute() |
| |
|
| |
|
| | |
| |
|
| |
|
| | class PathBoundary: |
| | """class PathBoundary... |
| | This class requires a base operation, boundary shape, and optional inside boolean (default is True). |
| | The `execute()` method returns a Path object with path commands limited to cut paths inside or outside |
| | the provided boundary shape. |
| | """ |
| |
|
| | def __init__(self, baseOp, boundaryShape, inside=True, keepToolDown=False): |
| | self.baseOp = baseOp |
| | self.boundary = boundaryShape |
| | self.inside = inside |
| | self.safeHeight = None |
| | self.clearanceHeight = None |
| | self.strG0ZsafeHeight = None |
| | self.strG0ZclearanceHeight = None |
| | self.keepToolDown = keepToolDown |
| |
|
| | def boundaryCommands( |
| | self, begin, end, verticalFeed, horizFeed=None, keepToolDown=False, isStartMovements=False |
| | ): |
| | Path.Log.track(_vstr(begin), _vstr(end)) |
| | if end and Path.Geom.pointsCoincide(begin, end): |
| | return [] |
| | cmds = [] |
| |
|
| | if isStartMovements or not keepToolDown: |
| | if begin.z < self.safeHeight: |
| | cmds.append(self.strG0ZsafeHeight) |
| | if begin.z < self.clearanceHeight: |
| | cmds.append(self.strG0ZclearanceHeight) |
| | if end: |
| | cmds.append(Path.Command("G0", {"X": end.x, "Y": end.y})) |
| | if end.z < self.clearanceHeight: |
| | cmds.append(Path.Command("G0", {"Z": max(self.safeHeight, end.z)})) |
| | if end.z < self.safeHeight: |
| | cmds.append(Path.Command("G1", {"Z": end.z, "F": verticalFeed})) |
| | else: |
| | if end: |
| | if horizFeed and Path.Geom.isRoughly(begin.z, end.z, 0.001): |
| | speed = horizFeed |
| | else: |
| | verticalFeed |
| | cmds.append(Path.Command("G1", {"X": end.x, "Y": end.y, "Z": end.z, "F": speed})) |
| |
|
| | return cmds |
| |
|
| | def execute(self): |
| | if ( |
| | not self.baseOp |
| | or not self.baseOp.isDerivedFrom("Path::Feature") |
| | or not self.baseOp.Path |
| | ): |
| | return None |
| |
|
| | path = PathUtils.getPathWithPlacement(self.baseOp) |
| | if len(path.Commands) == 0: |
| | Path.Log.warning("No Path Commands for %s" % self.baseOp.Label) |
| | return [] |
| |
|
| | tc = PathDressup.toolController(self.baseOp) |
| |
|
| | self.safeHeight = float(PathUtil.opProperty(self.baseOp, "SafeHeight")) |
| | self.clearanceHeight = float(PathUtil.opProperty(self.baseOp, "ClearanceHeight")) |
| | self.strG0ZsafeHeight = Path.Command( |
| | "G0", {"Z": self.safeHeight, "F": tc.VertRapid.Value} |
| | ) |
| | self.strG0ZclearanceHeight = Path.Command("G0", {"Z": self.clearanceHeight}) |
| |
|
| | cmd = path.Commands[0] |
| | pos = cmd.Placement.Base |
| | bogusX = True |
| | bogusY = True |
| | commands = [cmd] |
| | lastExit = None |
| | isStartMovements = True |
| | for cmd in path.Commands[1:]: |
| | if cmd.Name in Path.Geom.CmdMoveAll: |
| | if bogusX: |
| | bogusX = "X" not in cmd.Parameters |
| | if bogusY: |
| | bogusY = "Y" not in cmd.Parameters |
| | edge = Path.Geom.edgeForCmd(cmd, pos) |
| | if edge and cmd.Name in Path.Geom.CmdMoveDrill: |
| | inside = edge.common(self.boundary).Edges |
| | outside = edge.cut(self.boundary).Edges |
| | if 1 == len(inside) and 0 == len(outside): |
| | commands.append(cmd) |
| | if edge and not cmd.Name in Path.Geom.CmdMoveDrill: |
| | inside = edge.common(self.boundary).Edges |
| | outside = edge.cut(self.boundary).Edges |
| | if not self.inside: |
| | tmp = inside |
| | inside = outside |
| | outside = tmp |
| | |
| | |
| | if 1 == len(inside) and 0 == len(outside): |
| | Path.Log.track(_vstr(pos), _vstr(lastExit), " + ", cmd) |
| | |
| | if lastExit: |
| | if not ( |
| | bogusX or bogusY |
| | ): |
| | commands.extend( |
| | self.boundaryCommands(lastExit, pos, tc.VertFeed.Value) |
| | ) |
| | lastExit = None |
| | commands.append(cmd) |
| | pos = Path.Geom.commandEndPoint(cmd, pos) |
| | elif 0 == len(inside) and 1 == len(outside): |
| | Path.Log.track(_vstr(pos), _vstr(lastExit), " - ", cmd) |
| | |
| | if not lastExit: |
| | lastExit = pos |
| | pos = Path.Geom.commandEndPoint(cmd, pos) |
| | else: |
| | Path.Log.track(_vstr(pos), _vstr(lastExit), len(inside), len(outside), cmd) |
| | |
| | while inside or outside: |
| | ie = [e for e in inside if Path.Geom.edgeConnectsTo(e, pos)] |
| | Path.Log.track(ie) |
| | if ie: |
| | e = ie[0] |
| | LastPt = e.valueAt(e.LastParameter) |
| | flip = Path.Geom.pointsCoincide(pos, LastPt) |
| | newPos = e.valueAt(e.FirstParameter) if flip else LastPt |
| | |
| | |
| | if lastExit: |
| | if not (bogusX or bogusY): |
| | commands.extend( |
| | self.boundaryCommands( |
| | lastExit, |
| | pos, |
| | tc.VertFeed.Value, |
| | tc.HorizFeed.Value, |
| | self.keepToolDown, |
| | isStartMovements, |
| | ) |
| | ) |
| | isStartMovements = False |
| | lastExit = None |
| | Path.Log.track(e, flip) |
| | if not ( |
| | bogusX or bogusY |
| | ): |
| | commands.extend( |
| | Path.Geom.cmdsForEdge( |
| | e, |
| | flip, |
| | tc.HorizFeed.Value, |
| | tc.VertFeed.Value, |
| | ) |
| | ) |
| | inside.remove(e) |
| | pos = newPos |
| | lastExit = newPos |
| | else: |
| | oe = [e for e in outside if Path.Geom.edgeConnectsTo(e, pos)] |
| | Path.Log.track(oe) |
| | if oe: |
| | e = oe[0] |
| | ptL = e.valueAt(e.LastParameter) |
| | flip = Path.Geom.pointsCoincide(pos, ptL) |
| | newPos = e.valueAt(e.FirstParameter) if flip else ptL |
| | |
| | |
| | outside.remove(e) |
| | pos = newPos |
| | else: |
| | Path.Log.error("huh?") |
| | import Part |
| |
|
| | Part.show(Part.Vertex(pos), "pos") |
| | for e in inside: |
| | Part.show(e, "ei") |
| | for e in outside: |
| | Part.show(e, "eo") |
| | raise Exception("This is not supposed to happen") |
| | |
| | |
| | |
| | |
| | |
| | |
| | else: |
| | Path.Log.track("no-move", cmd) |
| | commands.append(cmd) |
| | if lastExit: |
| | commands.extend(self.boundaryCommands(lastExit, None, tc.VertFeed.Value)) |
| | lastExit = None |
| |
|
| | Path.Log.track(commands) |
| | return Path.Path(commands) |
| |
|
| |
|
| | |
| |
|
| |
|
| | def Create(base, name="DressupPathBoundary"): |
| | """Create(base, name='DressupPathBoundary') ... creates a dressup limiting base's Path to a boundary.""" |
| |
|
| | if not base.isDerivedFrom("Path::Feature"): |
| | Path.Log.error( |
| | translate("CAM_DressupPathBoundary", "The selected object is not a path") + "\n" |
| | ) |
| | return None |
| |
|
| | obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name) |
| | job = PathUtils.findParentJob(base) |
| | obj.Proxy = DressupPathBoundary(obj, base, job) |
| | job.Proxy.addOperation(obj, base, True) |
| | return obj |
| |
|