Skip to content

Commit

Permalink
Observe precision in interpolation and clipper
Browse files Browse the repository at this point in the history
  • Loading branch information
voneiden committed May 10, 2023
1 parent def5f37 commit 3d647a5
Show file tree
Hide file tree
Showing 6 changed files with 71 additions and 23 deletions.
4 changes: 2 additions & 2 deletions src/cq_cam/fluent.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ def __init__(
plunge_feed: float = None,
rapid_height: float = None,
op_safe_height: float = None,
gcode_precision: int = 3,
precision: int = 3,
unit: Unit = Unit.METRIC,
plane: WorkPlane = WorkPlane.XY,
coordinate: WorkOffset = WorkOffset.OFFSET_1,
Expand All @@ -91,7 +91,7 @@ def __init__(
if op_safe_height is None
else op_safe_height
)
self.gcode_precision = gcode_precision
self.precision = precision
self.unit = unit
self.plane = plane
self.coordinate = coordinate
Expand Down
14 changes: 9 additions & 5 deletions src/cq_cam/operations/pocket.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,15 +187,17 @@ def build_pocket_ops(
return pocket_ops


def fill_pocket_contour_shrink(pocket: PathFace, step: float) -> list[list[PathFace]]:
def fill_pocket_contour_shrink(
pocket: PathFace, step: float, precision: int
) -> list[list[PathFace]]:
tree = Tree(PathFace(pocket.outer, [], depth=pocket.depth))
i = 0

# TODO sanify the variable names here
try:
while True:
node = tree.next_unlocked
next_outer_candidates = offset_path(node.obj.outer, -step)
next_outer_candidates = offset_path(node.obj.outer, -step, precision)
if next_outer_candidates and pocket.inners:
next_outers = [
face.outer
Expand Down Expand Up @@ -237,11 +239,13 @@ def pocket_clipper(
offset_avoid_areas: list[PathFace] = []

for face in op_areas:
offset_op_areas += offset_polyface(face, outer_offset, inner_offset)
offset_op_areas += offset_polyface(
face, outer_offset, inner_offset, job.precision
)

for face in avoid_areas:
offset_avoid_areas += offset_polyface(
face, avoid_outer_offset, avoid_inner_offset
face, avoid_outer_offset, avoid_inner_offset, job.precision
)

pocket_ops = build_pocket_ops(offset_op_areas, offset_avoid_areas)
Expand All @@ -250,7 +254,7 @@ def pocket_clipper(
sequences: list[list[PathFace]] = []
shallower_pocket_ops = []
for pocket_op in pocket_ops:
fill_sequences = fill_pocket_contour_shrink(pocket_op, stepover)
fill_sequences = fill_pocket_contour_shrink(pocket_op, stepover, job.precision)
if stepdown:
stepdown_start_depth = determine_stepdown_start_depth(
pocket_op, shallower_pocket_ops
Expand Down
16 changes: 9 additions & 7 deletions src/cq_cam/routers.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
Rapid,
)
from cq_cam.utils.geometry_op import Path, PathFace, distance_to_path
from cq_cam.utils.interpolation import edge_interpolation_count
from cq_cam.utils.utils import (
edge_end_param,
edge_end_point,
Expand Down Expand Up @@ -108,7 +109,7 @@ def shift_edges(


def route_edge(
edge: cq.Edge, start_p=None, end_p=None, arrow=False
edge: cq.Edge, precision: float, start_p=None, end_p=None, arrow=False
) -> Tuple[List[MotionCommand], cq.Vector]:
commands = []
geom_type = edge.geomType()
Expand Down Expand Up @@ -152,8 +153,7 @@ def route_edge(
commands.append(cmd(end=end_cv, center=center, mid=mid, arrow=arrow))

elif geom_type == "BSPLINE" or geom_type == "SPLINE" or geom_type == "OFFSET":
# TODO precision
n = max(int(edge.Length() / 0.1), 2)
n = edge_interpolation_count(edge, precision)

orientation = edge.wrapped.Orientation()
if orientation == TopAbs_REVERSED:
Expand Down Expand Up @@ -214,16 +214,18 @@ def route_wires(job: "Job", wires: List[Union[cq.Wire, cq.Edge]], stepover=None)
for edge_i, edge in enumerate(edges):
if edge_i == 0:
if param is not None:
new_commands, end = route_edge(edge, start_p=param, arrow=True)
new_commands, end = route_edge(
edge, job.precision, start_p=param, arrow=True
)
commands += new_commands
else:
new_commands, end = route_edge(edge, arrow=True)
new_commands, end = route_edge(edge, job.precision, arrow=True)
commands += new_commands
else:
new_commands, end = route_edge(edge)
new_commands, end = route_edge(edge, job.precision)
commands += new_commands
if param:
new_commands, end = route_edge(edges[0], end_p=param)
new_commands, end = route_edge(edges[0], job.precision, end_p=param)
commands += new_commands

previous_wire = wire
Expand Down
17 changes: 8 additions & 9 deletions src/cq_cam/utils/geometry_op.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,26 +98,25 @@ def offset_wire(
return validated_wires


def offset_path(path: ClosedPath, offset: float) -> list[Path]:
def offset_path(path: ClosedPath, offset: float, precision: int) -> list[Path]:
# noinspection PyArgumentList
scaled_path = pc.scale_to_clipper(path)

# noinspection PyArgumentList
scaled_offset = pc.scale_to_clipper(offset)
# TODO precision?

# noinspection PyArgumentList
precision = pc.scale_to_clipper(0.01)
scaled_precision = pc.scale_to_clipper(10 ** (-precision))

# Use a scaled miter limit of 2. This is the default
# noinspection PyArgumentList
pco = pc.PyclipperOffset(pc.scale_to_clipper(2.0), precision)
pco = pc.PyclipperOffset(pc.scale_to_clipper(2.0), scaled_precision)
pco.AddPath(scaled_path, pc.JT_ROUND, pc.ET_CLOSEDPOLYGON)

# TODO determine good value for the CleanPolygons
offset_paths = [
close_path(pc.scale_from_clipper(offsetted_path))
for offsetted_path in pc.CleanPolygons(
pco.Execute(scaled_offset), precision / 100
pco.Execute(scaled_offset), scaled_precision
)
if offsetted_path
]
Expand Down Expand Up @@ -187,12 +186,12 @@ def make_polyfaces(


def offset_polyface(
polyface: PathFace, outer_offset: float, inner_offset: float
polyface: PathFace, outer_offset: float, inner_offset: float, precision: int
) -> list[PathFace]:
outers = offset_path(polyface.outer, outer_offset)
outers = offset_path(polyface.outer, outer_offset, precision)
if polyface.inners:
inners = flatten_list(
[offset_path(inner, inner_offset) for inner in polyface.inners]
[offset_path(inner, inner_offset, precision) for inner in polyface.inners]
)
return make_polyfaces(outers, inners, polyface.depth)
else:
Expand Down
4 changes: 4 additions & 0 deletions src/cq_cam/utils/interpolation.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,3 +139,7 @@ def interpolate_wire_with_unstable_edges(
if not interpolated:
return wire
return cq.Wire.assembleEdges(edges)


def edge_interpolation_count(edge: cq.Edge, precision: int):
return max(int(edge.Length() * 10**precision), 2)
39 changes: 39 additions & 0 deletions src/cq_cam/utils/tests/test_clipper_precision.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import pyclipper as pc
import pytest


def round_path(path: list[list[float]], precision: int) -> list[list[float]]:
return [[round(x, precision), round(y, precision)] for x, y in path]


@pytest.mark.parametrize(
["path", "expected_path", "precision"],
[
[[[0, 0], [1, 0.01], [2, 0], [0, 1]], [[0, 0], [2, 0], [0, 1]], 1],
[
[[0, 0], [1, 0.1], [2, 0], [0, 1]],
[[0, 0], [1, 0.1], [2, 0], [0, 1]],
1,
],
[
[[0, 0], [1, 0.09], [1.1, -0.09], [2, 0], [0, 1]],
[[0, 0], [1, 0.1], [1.1, -0.1], [2, 0], [0, 1]],
1,
],
[
[[0, 0], [1, 0.05], [1.01, 0.1], [2, 0], [0, 1]],
[[0, 0], [1, 0.1], [2, 0], [0, 1]],
1,
],
],
)
def test_clean_precision(path, expected_path, precision):
"""
This is a sanity test to determine how CleanPolygon actually works
"""
scaled_path = pc.scale_to_clipper(path)
scaled_precision = pc.scale_to_clipper(10 ** (-precision))
cleaned_path = pc.CleanPolygon(scaled_path, scaled_precision)
final_path = pc.scale_from_clipper(cleaned_path)
rounded_path = round_path(final_path, precision)
assert rounded_path == expected_path

0 comments on commit 3d647a5

Please sign in to comment.