From 853b4fc878d888ebcbf687ddbf9c0819958c0fe2 Mon Sep 17 00:00:00 2001 From: HamzaYslmn Date: Mon, 20 Jul 2026 22:08:57 +0300 Subject: [PATCH] Fix arc leak in shape approximation breaking native item spacing on holed shapes --- extern/CMakeLists.txt | 5 ++ extern/shape-approximation-297.patch | 74 ++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+) create mode 100644 extern/shape-approximation-297.patch diff --git a/extern/CMakeLists.txt b/extern/CMakeLists.txt index ee71b9988..325caa7ee 100644 --- a/extern/CMakeLists.txt +++ b/extern/CMakeLists.txt @@ -34,11 +34,16 @@ FetchContent_Declare( FetchContent_MakeAvailable(optimizationtools) # Fetch fontanf/shape. +# PATCH: approximate_by_line_segments seeded its union with the arc-bearing shape, +# leaking CircularArcs through compute_union -> is_polygon() throw on inflated holed +# shapes (the native item_item_minimum_spacing crash). Patch is idempotent: apply, +# or verify already-applied via --reverse --check. set(SHAPE_BUILD_TEST OFF) FetchContent_Declare( shape GIT_REPOSITORY https://github.com/fontanf/shape.git GIT_TAG 1809a9c7a1f8734fc8c7330ffe00cc3270ba7d44 + PATCH_COMMAND git apply --ignore-whitespace "${CMAKE_CURRENT_SOURCE_DIR}/shape-approximation-297.patch" || git apply --reverse --check --ignore-whitespace "${CMAKE_CURRENT_SOURCE_DIR}/shape-approximation-297.patch" #SOURCE_DIR "${PROJECT_SOURCE_DIR}/../shape/" EXCLUDE_FROM_ALL) FetchContent_MakeAvailable(shape) diff --git a/extern/shape-approximation-297.patch b/extern/shape-approximation-297.patch new file mode 100644 index 000000000..71b05f0d4 --- /dev/null +++ b/extern/shape-approximation-297.patch @@ -0,0 +1,74 @@ +diff --git a/src/approximation.cpp b/src/approximation.cpp +index 99a797d..6bb6f4f 100644 +--- a/src/approximation.cpp ++++ b/src/approximation.cpp +@@ -359,7 +359,9 @@ ShapeWithHoles shape::approximate_shape_by_line_segments( + return shape_new; + } + +- std::vector union_input = {{shape}}; ++ // Seed with the FLATTENED shape — seeding with `shape` leaks CircularArc ++ // elements through compute_union into the result (packingsolver #297). ++ std::vector union_input = {shape_new}; + + for (const ShapeElement& element: shape.elements) { + switch (element.type) { +@@ -384,7 +386,11 @@ ShapeWithHoles shape::approximate_shape_by_line_segments( + + //Writer().add_shapes_with_holes(union_input).write_json("union_input.json"); + std::vector union_output = compute_union(union_input); +- return union_output.front(); ++ const ShapeWithHoles* best = &union_output.front(); ++ for (const ShapeWithHoles& component: union_output) ++ if (component.shape.compute_area() > best->shape.compute_area()) ++ best = &component; ++ return *best; + } + + Shape shape::approximate_path_by_line_segments( +@@ -501,7 +507,10 @@ ShapeWithHoles shape::approximate_by_line_segments( + return shape_new; + } + +- std::vector union_input = {shape}; ++ // Union the FLATTENED shape with the arc extras. Seeding with `shape` leaks ++ // CircularArc elements through compute_union into the result, whose holes then ++ // fail is_polygon() — crashes on inflated holed shapes (packingsolver #297). ++ std::vector union_input = {shape_new}; + + for (const ShapeElement& element: shape.shape.elements) { + switch (element.type) { +@@ -519,7 +528,6 @@ ShapeWithHoles shape::approximate_by_line_segments( + } + + for (const Shape& hole: shape.holes) { +- Shape hole_new; + for (const ShapeElement& element: hole.elements) { + switch (element.type) { + case ShapeElementType::LineSegment: { +@@ -534,7 +542,6 @@ ShapeWithHoles shape::approximate_by_line_segments( + } + } + } +- shape_new.holes.push_back(hole_new); + } + + std::vector union_output = compute_union(union_input); +@@ -545,10 +552,15 @@ ShapeWithHoles shape::approximate_by_line_segments( + .add_shapes_with_holes(union_output, "Union output") + .write_json("union_input.json"); + #endif +- if (!union_output.front().is_polygon()) { ++ // Extras always touch the shape, but be defensive: keep the largest component. ++ const ShapeWithHoles* best = &union_output.front(); ++ for (const ShapeWithHoles& component: union_output) ++ if (component.shape.compute_area() > best->shape.compute_area()) ++ best = &component; ++ if (!best->is_polygon()) { + throw std::logic_error(FUNC_SIGNATURE); + } +- return union_output.front(); ++ return *best; + } + + void shape::approximate_shape_by_line_segments_export_inputs(