From 30ea19a923cdc7be36bd319f6c1e1f3e8e013b67 Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Sun, 9 May 2021 22:08:10 -0500 Subject: [PATCH] Path: Refactor `PathBoundary` algorithm to independent class Relocate code for PathBoundary algorithm into an independent class allowing for usage without the Dressup object creation. --- .../PathScripts/PathDressupPathBoundary.py | 228 ++++++++++-------- 1 file changed, 125 insertions(+), 103 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathDressupPathBoundary.py b/src/Mod/Path/PathScripts/PathDressupPathBoundary.py index dc6d57f2f6..1bee910851 100644 --- a/src/Mod/Path/PathScripts/PathDressupPathBoundary.py +++ b/src/Mod/Path/PathScripts/PathDressupPathBoundary.py @@ -77,7 +77,29 @@ class DressupPathBoundary(object): obj.Stock = None return True - def boundaryCommands(self, obj, begin, end, verticalFeed): + def execute(self, obj): + pb = PathBoundary(obj.Base, obj.Stock.Shape, obj.Inside) + obj.Path = pb.execute() +# Eclass + + +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): + self.baseOp = baseOp + self.boundary = boundaryShape + self.inside = inside + self.safeHeight = None + self.clearanceHeight = None + self.strG1ZsafeHeight = None + self.strG0ZclearanceHeight = None + + def boundaryCommands(self, begin, end, verticalFeed): PathLog.track(_vstr(begin), _vstr(end)) if end and PathGeom.pointsCoincide(begin, end): return [] @@ -94,117 +116,117 @@ class DressupPathBoundary(object): cmds.append(Path.Command('G1', {'Z': end.z, 'F': verticalFeed})) return cmds - def execute(self, obj): - if not obj.Base or not obj.Base.isDerivedFrom('Path::Feature') or not obj.Base.Path: - return + def execute(self): + if not self.baseOp or not self.baseOp.isDerivedFrom('Path::Feature') or not self.baseOp.Path: + return None - tc = PathDressup.toolController(obj.Base) + if len(self.baseOp.Path.Commands) == 0: + PathLog.warning("No Path Commands for %s" % self.baseOp.Label) + return [] - if len(obj.Base.Path.Commands) > 0: - self.safeHeight = float(PathUtil.opProperty(obj.Base, 'SafeHeight')) - self.clearanceHeight = float(PathUtil.opProperty(obj.Base, 'ClearanceHeight')) - self.strG1ZsafeHeight = Path.Command('G1', {'Z': self.safeHeight, 'F': tc.VertFeed.Value}) - self.strG0ZclearanceHeight = Path.Command('G0', {'Z': self.clearanceHeight}) + tc = PathDressup.toolController(self.baseOp) - boundary = obj.Stock.Shape - cmd = obj.Base.Path.Commands[0] - pos = cmd.Placement.Base # bogus m/c position to create first edge - bogusX = True - bogusY = True - commands = [cmd] - lastExit = None - for cmd in obj.Base.Path.Commands[1:]: - if cmd.Name in PathGeom.CmdMoveAll: - if bogusX == True : - bogusX = ( 'X' not in cmd.Parameters ) - if bogusY : - bogusY = ( 'Y' not in cmd.Parameters ) - edge = PathGeom.edgeForCmd(cmd, pos) - if edge: - inside = edge.common(boundary).Edges - outside = edge.cut(boundary).Edges - if not obj.Inside: # UI "inside boundary" param - tmp = inside - inside = outside - outside = tmp - # it's really a shame that one cannot trust the sequence and/or - # orientation of edges - if 1 == len(inside) and 0 == len(outside): - PathLog.track(_vstr(pos), _vstr(lastExit), ' + ', cmd) - # cmd fully included by boundary - if lastExit: + self.safeHeight = float(PathUtil.opProperty(self.baseOp, 'SafeHeight')) + self.clearanceHeight = float(PathUtil.opProperty(self.baseOp, 'ClearanceHeight')) + self.strG1ZsafeHeight = Path.Command('G1', {'Z': self.safeHeight, 'F': tc.VertFeed.Value}) + self.strG0ZclearanceHeight = Path.Command('G0', {'Z': self.clearanceHeight}) + + cmd = self.baseOp.Path.Commands[0] + pos = cmd.Placement.Base # bogus m/c position to create first edge + bogusX = True + bogusY = True + commands = [cmd] + lastExit = None + for cmd in self.baseOp.Path.Commands[1:]: + if cmd.Name in PathGeom.CmdMoveAll: + if bogusX == True : + bogusX = ( 'X' not in cmd.Parameters ) + if bogusY : + bogusY = ( 'Y' not in cmd.Parameters ) + edge = PathGeom.edgeForCmd(cmd, pos) + if edge: + inside = edge.common(self.boundary).Edges + outside = edge.cut(self.boundary).Edges + if not self.inside: # UI "inside boundary" param + tmp = inside + inside = outside + outside = tmp + # it's really a shame that one cannot trust the sequence and/or + # orientation of edges + if 1 == len(inside) and 0 == len(outside): + PathLog.track(_vstr(pos), _vstr(lastExit), ' + ', cmd) + # cmd fully included by boundary + if lastExit: + if not ( bogusX or bogusY ) : # don't insert false paths based on bogus m/c position + commands.extend(self.boundaryCommands(lastExit, pos, tc.VertFeed.Value)) + lastExit = None + commands.append(cmd) + pos = PathGeom.commandEndPoint(cmd, pos) + elif 0 == len(inside) and 1 == len(outside): + PathLog.track(_vstr(pos), _vstr(lastExit), ' - ', cmd) + # cmd fully excluded by boundary + if not lastExit: + lastExit = pos + pos = PathGeom.commandEndPoint(cmd, pos) + else: + PathLog.track(_vstr(pos), _vstr(lastExit), len(inside), len(outside), cmd) + # cmd pierces boundary + while inside or outside: + ie = [e for e in inside if PathGeom.edgeConnectsTo(e, pos)] + PathLog.track(ie) + if ie: + e = ie[0] + LastPt = e.valueAt(e.LastParameter) + flip = PathGeom.pointsCoincide(pos, LastPt) + newPos = e.valueAt(e.FirstParameter) if flip else LastPt + # inside edges are taken at this point (see swap of inside/outside + # above - so we can just connect the dots ... + if lastExit: + if not ( bogusX or bogusY ) : commands.extend(self.boundaryCommands(lastExit, pos, tc.VertFeed.Value)) + lastExit = None + PathLog.track(e, flip) if not ( bogusX or bogusY ) : # don't insert false paths based on bogus m/c position - commands.extend(self.boundaryCommands(obj, lastExit, pos, tc.VertFeed.Value)) - lastExit = None - commands.append(cmd) - pos = PathGeom.commandEndPoint(cmd, pos) - elif 0 == len(inside) and 1 == len(outside): - PathLog.track(_vstr(pos), _vstr(lastExit), ' - ', cmd) - # cmd fully excluded by boundary - if not lastExit: - lastExit = pos - pos = PathGeom.commandEndPoint(cmd, pos) - else: - PathLog.track(_vstr(pos), _vstr(lastExit), len(inside), len(outside), cmd) - # cmd pierces boundary - while inside or outside: - ie = [e for e in inside if PathGeom.edgeConnectsTo(e, pos)] - PathLog.track(ie) - if ie: - e = ie[0] - LastPt = e.valueAt(e.LastParameter) - flip = PathGeom.pointsCoincide(pos, LastPt) - newPos = e.valueAt(e.FirstParameter) if flip else LastPt - # inside edges are taken at this point (see swap of inside/outside - # above - so we can just connect the dots ... - if lastExit: - if not ( bogusX or bogusY ) : commands.extend(self.boundaryCommands(obj, lastExit, pos, tc.VertFeed.Value)) - lastExit = None - PathLog.track(e, flip) - if not ( bogusX or bogusY ) : # don't insert false paths based on bogus m/c position - commands.extend(PathGeom.cmdsForEdge(e, flip, False, 50, tc.HorizFeed.Value, tc.VertFeed.Value)) - inside.remove(e) + commands.extend(PathGeom.cmdsForEdge(e, flip, False, 50, tc.HorizFeed.Value, tc.VertFeed.Value)) + inside.remove(e) + pos = newPos + lastExit = newPos + else: + oe = [e for e in outside if PathGeom.edgeConnectsTo(e, pos)] + PathLog.track(oe) + if oe: + e = oe[0] + ptL = e.valueAt(e.LastParameter) + flip = PathGeom.pointsCoincide(pos, ptL) + newPos = e.valueAt(e.FirstParameter) if flip else ptL + # outside edges are never taken at this point (see swap of + # inside/outside above) - so just move along ... + outside.remove(e) pos = newPos - lastExit = newPos else: - oe = [e for e in outside if PathGeom.edgeConnectsTo(e, pos)] - PathLog.track(oe) - if oe: - e = oe[0] - ptL = e.valueAt(e.LastParameter) - flip = PathGeom.pointsCoincide(pos, ptL) - newPos = e.valueAt(e.FirstParameter) if flip else ptL - # outside edges are never taken at this point (see swap of - # inside/outside above) - so just move along ... - outside.remove(e) - pos = newPos - else: - PathLog.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') - # Eif + PathLog.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') # Eif - # Ewhile - # Eif - # pos = PathGeom.commandEndPoint(cmd, pos) + # Eif + # Ewhile # Eif - else: - PathLog.track('no-move', cmd) - commands.append(cmd) - if lastExit: - commands.extend(self.boundaryCommands(obj, lastExit, None, tc.VertFeed.Value)) - lastExit = None - else: - PathLog.warning("No Path Commands for %s" % obj.Base.Label) - commands = [] - PathLog.track(commands) - obj.Path = Path.Path(commands) + # pos = PathGeom.commandEndPoint(cmd, pos) + # Eif + else: + PathLog.track('no-move', cmd) + commands.append(cmd) + if lastExit: + commands.extend(self.boundaryCommands(lastExit, None, tc.VertFeed.Value)) + lastExit = None + PathLog.track(commands) + return Path.Path(commands) +# Eclass def Create(base, name='DressupPathBoundary'): '''Create(base, name='DressupPathBoundary') ... creates a dressup limiting base's Path to a boundary.'''