Skip to content

Commit

Permalink
Merge pull request #3629 from gwicke/line_simplification
Browse files Browse the repository at this point in the history
[path] Implement Ramer-Douglas-Peucker line simplification
  • Loading branch information
sliptonic committed Jun 19, 2020
2 parents fd1dc30 + 6b3815a commit e3a67e2
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 17 deletions.
21 changes: 4 additions & 17 deletions src/Mod/Path/PathScripts/PathSurface.py
Expand Up @@ -1094,7 +1094,8 @@ def _planarDropCutSingle(self, JOB, obj, pdc, safePDC, depthparams, SCANDATA):

def _planarSinglepassProcess(self, obj, points):
if obj.OptimizeLinearPaths:
points = self._optimizeLinearSegments(points)
points = PathUtils.simplify3dLine(points,
tolerance=obj.LinearDeflection.Value)
# Begin processing ocl points list into gcode
commands = []
for pnt in points:
Expand Down Expand Up @@ -2100,28 +2101,14 @@ def setOclCutter(self, obj, safe=False):
PathLog.warning("Defaulting cutter to standard end mill.")
return ocl.CylCutter(diam_1, (CEH + lenOfst))

def _optimizeLinearSegments(self, line):
"""Eliminate collinear interior segments"""
if len(line) > 2:
prv, pnt = line[0:2]
pts = [prv]
for nxt in line[2:]:
if not pnt.isOnLineSegment(prv, nxt):
pts.append(pnt)
prv = pnt
pnt = nxt
pts.append(line[-1])
return pts
else:
return line

def _getTransitionLine(self, pdc, p1, p2, obj):
"""Use an OCL PathDropCutter to generate a safe transition path between
two points in the x/y plane."""
p1xy, p2xy = ((p1.x, p1.y), (p2.x, p2.y))
pdcLine = self._planarDropCutScan(pdc, p1xy, p2xy)
if obj.OptimizeLinearPaths:
pdcLine = self._optimizeLinearSegments(pdcLine)
pdcLine = PathUtils.simplify3dLine(
pdcLine, tolerance=obj.LinearDeflection.Value)
zs = [obj.z for obj in pdcLine]
# PDC z values are based on the model, and do not take into account
# any remaining stock / multi layer paths. Adjust raw PDC z values to
Expand Down
38 changes: 38 additions & 0 deletions src/Mod/Path/PathScripts/PathUtils.py
Expand Up @@ -871,3 +871,41 @@ def __fixed_steps(self, start, stop, size):
return depths
else:
return [stop] + depths


def simplify3dLine(line, tolerance=1e-4):
"""Simplify a line defined by a list of App.Vectors, while keeping the
maximum deviation from the original line within the defined tolerance.
Implementation of
https://en.wikipedia.org/wiki/Ramer%E2%80%93Douglas%E2%80%93Peucker_algorithm"""
stack = [(0, len(line) - 1)]
results = []

def processRange(start, end):
"""Internal worker. Process a range of Vector indices within the
line."""
if end - start < 2:
results.extend(line[start:end])
return
# Find point with maximum distance
maxIndex, maxDistance = 0, 0.0
startPoint, endPoint = (line[start], line[end])
for i in range(start + 1, end):
v = line[i]
distance = v.distanceToLineSegment(startPoint, endPoint).Length
if distance > maxDistance:
maxDistance = distance
maxIndex = i
if maxDistance > tolerance:
# Push second branch first, to be executed last
stack.append((maxIndex, end))
stack.append((start, maxIndex))
else:
results.append(line[start])

while len(stack):
processRange(*stack.pop())
# Each segment only appended its start point to the final result, so fill in
# the last point.
results.append(line[-1])
return results

0 comments on commit e3a67e2

Please sign in to comment.