From 17c5a8ada079dfa6a0b63c83aea158db84eb8fe2 Mon Sep 17 00:00:00 2001 From: Florian Fontan Date: Mon, 27 Apr 2026 07:57:26 +0200 Subject: [PATCH 1/3] Implement difference for multi-shapes --- .../boolean_operations/difference/000.json | 12 +- .../boolean_operations/difference/001.json | 184 +- .../boolean_operations/difference/002.json | 4265 ++++++++--------- .../boolean_operations/difference/003.json | 52 +- .../boolean_operations/difference/004.json | 72 +- .../boolean_operations/difference/005.json | 126 +- include/shape/boolean_operations.hpp | 6 +- src/boolean_operations.cpp | 23 +- src/offset.cpp | 2 +- test/boolean_operations_test.cpp | 16 +- 10 files changed, 2426 insertions(+), 2332 deletions(-) diff --git a/data/tests/boolean_operations/difference/000.json b/data/tests/boolean_operations/difference/000.json index 6b09966..759c6b5 100644 --- a/data/tests/boolean_operations/difference/000.json +++ b/data/tests/boolean_operations/difference/000.json @@ -1,9 +1,11 @@ { - "shape": { - "type": "rectangle", - "width": 200, - "height": 200 - }, + "shapes_1": [ + { + "type": "rectangle", + "width": 200, + "height": 200 + } + ], "shapes": [ { "type": "rectangle", diff --git a/data/tests/boolean_operations/difference/001.json b/data/tests/boolean_operations/difference/001.json index 3a79d2f..1db01da 100644 --- a/data/tests/boolean_operations/difference/001.json +++ b/data/tests/boolean_operations/difference/001.json @@ -1,92 +1,94 @@ { - "shape": { - "type": "polygon", - "vertices": [ - { - "x": 500.0, - "y": 500.0 - }, - { - "x": 0.0, - "y": 500.0 - }, - { - "x": 0.0, - "y": 0.0 - }, - { - "x": 500.0, - "y": 0.0 - } - ], - "holes": [ - { - "type": "polygon", - "vertices": [ - { - "x": 50.0, - "y": 50.0 - }, - { - "x": 450.0, - "y": 50.0 - }, - { - "x": 450.0, - "y": 450.0 - }, - { - "x": 400.0, - "y": 450.0 - }, - { - "x": 400.0, - "y": 150.0 - }, - { - "x": 350.0, - "y": 150.0 - }, - { - "x": 350.0, - "y": 450.0 - }, - { - "x": 300.0, - "y": 450.0 - }, - { - "x": 250.0, - "y": 150.0 - }, - { - "x": 200.0, - "y": 450.0 - }, - { - "x": 150.0, - "y": 450.0 - }, - { - "x": 150.0, - "y": 150.0 - }, - { - "x": 100.0, - "y": 150.0 - }, - { - "x": 100.0, - "y": 450.0 - }, - { - "x": 50.0, - "y": 450.0 - } - ] - } - ] - }, + "shapes_1": [ + { + "type": "polygon", + "vertices": [ + { + "x": 500.0, + "y": 500.0 + }, + { + "x": 0.0, + "y": 500.0 + }, + { + "x": 0.0, + "y": 0.0 + }, + { + "x": 500.0, + "y": 0.0 + } + ], + "holes": [ + { + "type": "polygon", + "vertices": [ + { + "x": 50.0, + "y": 50.0 + }, + { + "x": 450.0, + "y": 50.0 + }, + { + "x": 450.0, + "y": 450.0 + }, + { + "x": 400.0, + "y": 450.0 + }, + { + "x": 400.0, + "y": 150.0 + }, + { + "x": 350.0, + "y": 150.0 + }, + { + "x": 350.0, + "y": 450.0 + }, + { + "x": 300.0, + "y": 450.0 + }, + { + "x": 250.0, + "y": 150.0 + }, + { + "x": 200.0, + "y": 450.0 + }, + { + "x": 150.0, + "y": 450.0 + }, + { + "x": 150.0, + "y": 150.0 + }, + { + "x": 100.0, + "y": 150.0 + }, + { + "x": 100.0, + "y": 450.0 + }, + { + "x": 50.0, + "y": 450.0 + } + ] + } + ] + } + ], "shapes": [ { "type": "polygon", @@ -108,8 +110,7 @@ "y": 0.0 } ], - "holes": [ - ] + "holes": [] } ], "expected_output": [ @@ -193,8 +194,7 @@ "y": 0.0 } ], - "holes": [ - ] + "holes": [] } ], "write_json": false, diff --git a/data/tests/boolean_operations/difference/002.json b/data/tests/boolean_operations/difference/002.json index 1728302..fdfe1f9 100644 --- a/data/tests/boolean_operations/difference/002.json +++ b/data/tests/boolean_operations/difference/002.json @@ -1,27 +1,28 @@ { - "shape": { - "type": "polygon", - "vertices": [ - { - "x": 450.0, - "y": 450.0 - }, - { - "x": 0.0, - "y": 450.0 - }, - { - "x": 0.0, - "y": 0.0 - }, - { - "x": 450.0, - "y": 0.0 - } - ], - "holes": [ - ] - }, + "shapes_1": [ + { + "type": "polygon", + "vertices": [ + { + "x": 450.0, + "y": 450.0 + }, + { + "x": 0.0, + "y": 450.0 + }, + { + "x": 0.0, + "y": 0.0 + }, + { + "x": 450.0, + "y": 0.0 + } + ], + "holes": [] + } + ], "shapes": [ { "type": "polygon", @@ -43,8 +44,7 @@ "y": 0.0 } ], - "holes": [ - ] + "holes": [] }, { "type": "polygon", @@ -66,8 +66,7 @@ "y": 0.0 } ], - "holes": [ - ] + "holes": [] }, { "type": "polygon", @@ -89,8 +88,7 @@ "y": 0.0 } ], - "holes": [ - ] + "holes": [] }, { "type": "polygon", @@ -112,8 +110,7 @@ "y": 0.0 } ], - "holes": [ - ] + "holes": [] }, { "type": "polygon", @@ -135,8 +132,7 @@ "y": 50.0 } ], - "holes": [ - ] + "holes": [] }, { "type": "polygon", @@ -158,8 +154,7 @@ "y": 50.0 } ], - "holes": [ - ] + "holes": [] }, { "type": "polygon", @@ -181,8 +176,7 @@ "y": 50.0 } ], - "holes": [ - ] + "holes": [] }, { "type": "polygon", @@ -204,8 +198,7 @@ "y": 50.0 } ], - "holes": [ - ] + "holes": [] }, { "type": "polygon", @@ -227,8 +220,7 @@ "y": 50.0 } ], - "holes": [ - ] + "holes": [] }, { "type": "polygon", @@ -250,8 +242,7 @@ "y": 100.0 } ], - "holes": [ - ] + "holes": [] }, { "type": "polygon", @@ -273,8 +264,7 @@ "y": 100.0 } ], - "holes": [ - ] + "holes": [] }, { "type": "polygon", @@ -296,8 +286,7 @@ "y": 100.0 } ], - "holes": [ - ] + "holes": [] }, { "type": "polygon", @@ -319,8 +308,7 @@ "y": 100.0 } ], - "holes": [ - ] + "holes": [] }, { "type": "polygon", @@ -342,8 +330,7 @@ "y": 150.0 } ], - "holes": [ - ] + "holes": [] }, { "type": "polygon", @@ -365,8 +352,7 @@ "y": 150.0 } ], - "holes": [ - ] + "holes": [] }, { "type": "polygon", @@ -388,8 +374,7 @@ "y": 150.0 } ], - "holes": [ - ] + "holes": [] }, { "type": "polygon", @@ -411,8 +396,7 @@ "y": 150.0 } ], - "holes": [ - ] + "holes": [] }, { "type": "polygon", @@ -434,8 +418,7 @@ "y": 150.0 } ], - "holes": [ - ] + "holes": [] }, { "type": "polygon", @@ -457,8 +440,7 @@ "y": 200.0 } ], - "holes": [ - ] + "holes": [] }, { "type": "polygon", @@ -480,8 +462,7 @@ "y": 200.0 } ], - "holes": [ - ] + "holes": [] }, { "type": "polygon", @@ -503,8 +484,7 @@ "y": 200.0 } ], - "holes": [ - ] + "holes": [] }, { "type": "polygon", @@ -526,8 +506,7 @@ "y": 200.0 } ], - "holes": [ - ] + "holes": [] }, { "type": "polygon", @@ -549,8 +528,7 @@ "y": 250.0 } ], - "holes": [ - ] + "holes": [] }, { "type": "polygon", @@ -572,8 +550,7 @@ "y": 250.0 } ], - "holes": [ - ] + "holes": [] }, { "type": "polygon", @@ -595,8 +572,7 @@ "y": 250.0 } ], - "holes": [ - ] + "holes": [] }, { "type": "polygon", @@ -618,8 +594,7 @@ "y": 250.0 } ], - "holes": [ - ] + "holes": [] }, { "type": "polygon", @@ -641,8 +616,7 @@ "y": 250.0 } ], - "holes": [ - ] + "holes": [] }, { "type": "polygon", @@ -664,8 +638,7 @@ "y": 300.0 } ], - "holes": [ - ] + "holes": [] }, { "type": "polygon", @@ -687,8 +660,7 @@ "y": 300.0 } ], - "holes": [ - ] + "holes": [] }, { "type": "polygon", @@ -710,8 +682,7 @@ "y": 300.0 } ], - "holes": [ - ] + "holes": [] }, { "type": "polygon", @@ -733,8 +704,7 @@ "y": 300.0 } ], - "holes": [ - ] + "holes": [] }, { "type": "polygon", @@ -756,8 +726,7 @@ "y": 350.0 } ], - "holes": [ - ] + "holes": [] }, { "type": "polygon", @@ -779,8 +748,7 @@ "y": 350.0 } ], - "holes": [ - ] + "holes": [] }, { "type": "polygon", @@ -802,8 +770,7 @@ "y": 350.0 } ], - "holes": [ - ] + "holes": [] }, { "type": "polygon", @@ -825,8 +792,7 @@ "y": 350.0 } ], - "holes": [ - ] + "holes": [] }, { "type": "polygon", @@ -848,8 +814,7 @@ "y": 350.0 } ], - "holes": [ - ] + "holes": [] }, { "type": "polygon", @@ -871,8 +836,7 @@ "y": 400.0 } ], - "holes": [ - ] + "holes": [] }, { "type": "polygon", @@ -894,8 +858,7 @@ "y": 400.0 } ], - "holes": [ - ] + "holes": [] }, { "type": "polygon", @@ -917,8 +880,7 @@ "y": 400.0 } ], - "holes": [ - ] + "holes": [] }, { "type": "polygon", @@ -940,2061 +902,2060 @@ "y": 400.0 } ], - "holes": [ - ] + "holes": [] } ], "expected_output": [ - { - "elements": [ - { - "end": { - "x": 50.0, - "y": 0.0 - }, - "start": { - "x": 0.0, - "y": 0.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 50.0, - "y": 50.0 - }, - "start": { - "x": 50.0, - "y": 0.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 0.0, - "y": 50.0 - }, - "start": { - "x": 50.0, - "y": 50.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 0.0, - "y": 0.0 - }, - "start": { - "x": 0.0, - "y": 50.0 - }, - "type": "LineSegment" - } - ], - "is_path": false, - "type": "general" - }, - { - "elements": [ - { - "end": { - "x": 50.0, - "y": 100.0 - }, - "start": { - "x": 0.0, - "y": 100.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 50.0, - "y": 150.0 - }, - "start": { - "x": 50.0, - "y": 100.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 0.0, - "y": 150.0 - }, - "start": { - "x": 50.0, - "y": 150.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 0.0, - "y": 100.0 - }, - "start": { - "x": 0.0, - "y": 150.0 - }, - "type": "LineSegment" - } - ], - "is_path": false, - "type": "general" - }, - { - "elements": [ - { - "end": { - "x": 50.0, - "y": 200.0 - }, - "start": { - "x": 0.0, - "y": 200.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 50.0, - "y": 250.0 - }, - "start": { - "x": 50.0, - "y": 200.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 0.0, - "y": 250.0 - }, - "start": { - "x": 50.0, - "y": 250.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 0.0, - "y": 200.0 - }, - "start": { - "x": 0.0, - "y": 250.0 - }, - "type": "LineSegment" - } - ], - "is_path": false, - "type": "general" - }, - { - "elements": [ - { - "end": { - "x": 50.0, - "y": 300.0 - }, - "start": { - "x": 0.0, - "y": 300.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 50.0, - "y": 350.0 - }, - "start": { - "x": 50.0, - "y": 300.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 0.0, - "y": 350.0 - }, - "start": { - "x": 50.0, - "y": 350.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 0.0, - "y": 300.0 - }, - "start": { - "x": 0.0, - "y": 350.0 - }, - "type": "LineSegment" - } - ], - "is_path": false, - "type": "general" - }, - { - "elements": [ - { - "end": { - "x": 50.0, - "y": 400.0 - }, - "start": { - "x": 0.0, - "y": 400.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 50.0, - "y": 450.0 - }, - "start": { - "x": 50.0, - "y": 400.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 0.0, - "y": 450.0 - }, - "start": { - "x": 50.0, - "y": 450.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 0.0, - "y": 400.0 - }, - "start": { - "x": 0.0, - "y": 450.0 - }, - "type": "LineSegment" - } - ], - "is_path": false, - "type": "general" - }, - { - "elements": [ - { - "end": { - "x": 100.0, - "y": 50.0 - }, - "start": { - "x": 50.0, - "y": 50.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 100.0, - "y": 100.0 - }, - "start": { - "x": 100.0, - "y": 50.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 50.0, - "y": 100.0 - }, - "start": { - "x": 100.0, - "y": 100.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 50.0, - "y": 50.0 - }, - "start": { - "x": 50.0, - "y": 100.0 - }, - "type": "LineSegment" - } - ], - "is_path": false, - "type": "general" - }, - { - "elements": [ - { - "end": { - "x": 100.0, - "y": 150.0 - }, - "start": { - "x": 50.0, - "y": 150.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 100.0, - "y": 200.0 - }, - "start": { - "x": 100.0, - "y": 150.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 50.0, - "y": 200.0 - }, - "start": { - "x": 100.0, - "y": 200.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 50.0, - "y": 150.0 - }, - "start": { - "x": 50.0, - "y": 200.0 - }, - "type": "LineSegment" - } - ], - "is_path": false, - "type": "general" - }, - { - "elements": [ - { - "end": { - "x": 100.0, - "y": 250.0 - }, - "start": { - "x": 50.0, - "y": 250.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 100.0, - "y": 300.0 - }, - "start": { - "x": 100.0, - "y": 250.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 50.0, - "y": 300.0 - }, - "start": { - "x": 100.0, - "y": 300.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 50.0, - "y": 250.0 - }, - "start": { - "x": 50.0, - "y": 300.0 - }, - "type": "LineSegment" - } - ], - "is_path": false, - "type": "general" - }, - { - "elements": [ - { - "end": { - "x": 100.0, - "y": 350.0 - }, - "start": { - "x": 50.0, - "y": 350.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 100.0, - "y": 400.0 - }, - "start": { - "x": 100.0, - "y": 350.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 50.0, - "y": 400.0 - }, - "start": { - "x": 100.0, - "y": 400.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 50.0, - "y": 350.0 - }, - "start": { - "x": 50.0, - "y": 400.0 - }, - "type": "LineSegment" - } - ], - "is_path": false, - "type": "general" - }, - { - "elements": [ - { - "end": { - "x": 150.0, - "y": 0.0 - }, - "start": { - "x": 100.0, - "y": 0.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 150.0, - "y": 50.0 - }, - "start": { - "x": 150.0, - "y": 0.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 100.0, - "y": 50.0 - }, - "start": { - "x": 150.0, - "y": 50.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 100.0, - "y": 0.0 - }, - "start": { - "x": 100.0, - "y": 50.0 - }, - "type": "LineSegment" - } - ], - "is_path": false, - "type": "general" - }, - { - "elements": [ - { - "end": { - "x": 150.0, - "y": 100.0 - }, - "start": { - "x": 100.0, - "y": 100.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 150.0, - "y": 150.0 - }, - "start": { - "x": 150.0, - "y": 100.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 100.0, - "y": 150.0 - }, - "start": { - "x": 150.0, - "y": 150.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 100.0, - "y": 100.0 - }, - "start": { - "x": 100.0, - "y": 150.0 - }, - "type": "LineSegment" - } - ], - "is_path": false, - "type": "general" - }, - { - "elements": [ - { - "end": { - "x": 150.0, - "y": 200.0 - }, - "start": { - "x": 100.0, - "y": 200.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 150.0, - "y": 250.0 - }, - "start": { - "x": 150.0, - "y": 200.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 100.0, - "y": 250.0 - }, - "start": { - "x": 150.0, - "y": 250.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 100.0, - "y": 200.0 - }, - "start": { - "x": 100.0, - "y": 250.0 - }, - "type": "LineSegment" - } - ], - "is_path": false, - "type": "general" - }, - { - "elements": [ - { - "end": { - "x": 150.0, - "y": 300.0 - }, - "start": { - "x": 100.0, - "y": 300.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 150.0, - "y": 350.0 - }, - "start": { - "x": 150.0, - "y": 300.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 100.0, - "y": 350.0 - }, - "start": { - "x": 150.0, - "y": 350.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 100.0, - "y": 300.0 - }, - "start": { - "x": 100.0, - "y": 350.0 - }, - "type": "LineSegment" - } - ], - "is_path": false, - "type": "general" - }, - { - "elements": [ - { - "end": { - "x": 150.0, - "y": 400.0 - }, - "start": { - "x": 100.0, - "y": 400.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 150.0, - "y": 450.0 - }, - "start": { - "x": 150.0, - "y": 400.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 100.0, - "y": 450.0 - }, - "start": { - "x": 150.0, - "y": 450.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 100.0, - "y": 400.0 - }, - "start": { - "x": 100.0, - "y": 450.0 - }, - "type": "LineSegment" - } - ], - "is_path": false, - "type": "general" - }, - { - "elements": [ - { - "end": { - "x": 200.0, - "y": 50.0 - }, - "start": { - "x": 150.0, - "y": 50.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 200.0, - "y": 100.0 - }, - "start": { - "x": 200.0, - "y": 50.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 150.0, - "y": 100.0 - }, - "start": { - "x": 200.0, - "y": 100.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 150.0, - "y": 50.0 - }, - "start": { - "x": 150.0, - "y": 100.0 - }, - "type": "LineSegment" - } - ], - "is_path": false, - "type": "general" - }, - { - "elements": [ - { - "end": { - "x": 200.0, - "y": 150.0 - }, - "start": { - "x": 150.0, - "y": 150.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 200.0, - "y": 200.0 - }, - "start": { - "x": 200.0, - "y": 150.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 150.0, - "y": 200.0 - }, - "start": { - "x": 200.0, - "y": 200.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 150.0, - "y": 150.0 - }, - "start": { - "x": 150.0, - "y": 200.0 - }, - "type": "LineSegment" - } - ], - "is_path": false, - "type": "general" - }, - { - "elements": [ - { - "end": { - "x": 200.0, - "y": 250.0 - }, - "start": { - "x": 150.0, - "y": 250.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 200.0, - "y": 300.0 - }, - "start": { - "x": 200.0, - "y": 250.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 150.0, - "y": 300.0 - }, - "start": { - "x": 200.0, - "y": 300.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 150.0, - "y": 250.0 - }, - "start": { - "x": 150.0, - "y": 300.0 - }, - "type": "LineSegment" - } - ], - "is_path": false, - "type": "general" - }, - { - "elements": [ - { - "end": { - "x": 200.0, - "y": 350.0 - }, - "start": { - "x": 150.0, - "y": 350.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 200.0, - "y": 400.0 - }, - "start": { - "x": 200.0, - "y": 350.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 150.0, - "y": 400.0 - }, - "start": { - "x": 200.0, - "y": 400.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 150.0, - "y": 350.0 - }, - "start": { - "x": 150.0, - "y": 400.0 - }, - "type": "LineSegment" - } - ], - "is_path": false, - "type": "general" - }, - { - "elements": [ - { - "end": { - "x": 250.0, - "y": 0.0 - }, - "start": { - "x": 200.0, - "y": 0.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 250.0, - "y": 50.0 - }, - "start": { - "x": 250.0, - "y": 0.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 200.0, - "y": 50.0 - }, - "start": { - "x": 250.0, - "y": 50.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 200.0, - "y": 0.0 - }, - "start": { - "x": 200.0, - "y": 50.0 - }, - "type": "LineSegment" - } - ], - "is_path": false, - "type": "general" - }, - { - "elements": [ - { - "end": { - "x": 250.0, - "y": 100.0 - }, - "start": { - "x": 200.0, - "y": 100.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 250.0, - "y": 150.0 - }, - "start": { - "x": 250.0, - "y": 100.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 200.0, - "y": 150.0 - }, - "start": { - "x": 250.0, - "y": 150.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 200.0, - "y": 100.0 - }, - "start": { - "x": 200.0, - "y": 150.0 - }, - "type": "LineSegment" - } - ], - "is_path": false, - "type": "general" - }, - { - "elements": [ - { - "end": { - "x": 250.0, - "y": 200.0 - }, - "start": { - "x": 200.0, - "y": 200.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 250.0, - "y": 250.0 - }, - "start": { - "x": 250.0, - "y": 200.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 200.0, - "y": 250.0 - }, - "start": { - "x": 250.0, - "y": 250.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 200.0, - "y": 200.0 - }, - "start": { - "x": 200.0, - "y": 250.0 - }, - "type": "LineSegment" - } - ], - "is_path": false, - "type": "general" - }, - { - "elements": [ - { - "end": { - "x": 250.0, - "y": 300.0 - }, - "start": { - "x": 200.0, - "y": 300.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 250.0, - "y": 350.0 - }, - "start": { - "x": 250.0, - "y": 300.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 200.0, - "y": 350.0 - }, - "start": { - "x": 250.0, - "y": 350.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 200.0, - "y": 300.0 - }, - "start": { - "x": 200.0, - "y": 350.0 - }, - "type": "LineSegment" - } - ], - "is_path": false, - "type": "general" - }, - { - "elements": [ - { - "end": { - "x": 250.0, - "y": 400.0 - }, - "start": { - "x": 200.0, - "y": 400.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 250.0, - "y": 450.0 - }, - "start": { - "x": 250.0, - "y": 400.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 200.0, - "y": 450.0 - }, - "start": { - "x": 250.0, - "y": 450.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 200.0, - "y": 400.0 - }, - "start": { - "x": 200.0, - "y": 450.0 - }, - "type": "LineSegment" - } - ], - "is_path": false, - "type": "general" - }, - { - "elements": [ - { - "end": { - "x": 300.0, - "y": 50.0 - }, - "start": { - "x": 250.0, - "y": 50.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 300.0, - "y": 100.0 - }, - "start": { - "x": 300.0, - "y": 50.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 250.0, - "y": 100.0 - }, - "start": { - "x": 300.0, - "y": 100.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 250.0, - "y": 50.0 - }, - "start": { - "x": 250.0, - "y": 100.0 - }, - "type": "LineSegment" - } - ], - "is_path": false, - "type": "general" - }, - { - "elements": [ - { - "end": { - "x": 300.0, - "y": 150.0 - }, - "start": { - "x": 250.0, - "y": 150.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 300.0, - "y": 200.0 - }, - "start": { - "x": 300.0, - "y": 150.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 250.0, - "y": 200.0 - }, - "start": { - "x": 300.0, - "y": 200.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 250.0, - "y": 150.0 - }, - "start": { - "x": 250.0, - "y": 200.0 - }, - "type": "LineSegment" - } - ], - "is_path": false, - "type": "general" - }, - { - "elements": [ - { - "end": { - "x": 300.0, - "y": 250.0 - }, - "start": { - "x": 250.0, - "y": 250.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 300.0, - "y": 300.0 - }, - "start": { - "x": 300.0, - "y": 250.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 250.0, - "y": 300.0 - }, - "start": { - "x": 300.0, - "y": 300.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 250.0, - "y": 250.0 - }, - "start": { - "x": 250.0, - "y": 300.0 - }, - "type": "LineSegment" - } - ], - "is_path": false, - "type": "general" - }, - { - "elements": [ - { - "end": { - "x": 300.0, - "y": 350.0 - }, - "start": { - "x": 250.0, - "y": 350.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 300.0, - "y": 400.0 - }, - "start": { - "x": 300.0, - "y": 350.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 250.0, - "y": 400.0 - }, - "start": { - "x": 300.0, - "y": 400.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 250.0, - "y": 350.0 - }, - "start": { - "x": 250.0, - "y": 400.0 - }, - "type": "LineSegment" - } - ], - "is_path": false, - "type": "general" - }, - { - "elements": [ - { - "end": { - "x": 350.0, - "y": 0.0 - }, - "start": { - "x": 300.0, - "y": 0.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 350.0, - "y": 50.0 - }, - "start": { - "x": 350.0, - "y": 0.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 300.0, - "y": 50.0 - }, - "start": { - "x": 350.0, - "y": 50.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 300.0, - "y": 0.0 - }, - "start": { - "x": 300.0, - "y": 50.0 - }, - "type": "LineSegment" - } - ], - "is_path": false, - "type": "general" - }, - { - "elements": [ - { - "end": { - "x": 350.0, - "y": 100.0 - }, - "start": { - "x": 300.0, - "y": 100.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 350.0, - "y": 150.0 - }, - "start": { - "x": 350.0, - "y": 100.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 300.0, - "y": 150.0 - }, - "start": { - "x": 350.0, - "y": 150.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 300.0, - "y": 100.0 - }, - "start": { - "x": 300.0, - "y": 150.0 - }, - "type": "LineSegment" - } - ], - "is_path": false, - "type": "general" - }, - { - "elements": [ - { - "end": { - "x": 350.0, - "y": 200.0 - }, - "start": { - "x": 300.0, - "y": 200.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 350.0, - "y": 250.0 - }, - "start": { - "x": 350.0, - "y": 200.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 300.0, - "y": 250.0 - }, - "start": { - "x": 350.0, - "y": 250.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 300.0, - "y": 200.0 - }, - "start": { - "x": 300.0, - "y": 250.0 - }, - "type": "LineSegment" - } - ], - "is_path": false, - "type": "general" - }, - { - "elements": [ - { - "end": { - "x": 350.0, - "y": 300.0 - }, - "start": { - "x": 300.0, - "y": 300.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 350.0, - "y": 350.0 - }, - "start": { - "x": 350.0, - "y": 300.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 300.0, - "y": 350.0 - }, - "start": { - "x": 350.0, - "y": 350.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 300.0, - "y": 300.0 - }, - "start": { - "x": 300.0, - "y": 350.0 - }, - "type": "LineSegment" - } - ], - "is_path": false, - "type": "general" - }, - { - "elements": [ - { - "end": { - "x": 350.0, - "y": 400.0 - }, - "start": { - "x": 300.0, - "y": 400.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 350.0, - "y": 450.0 - }, - "start": { - "x": 350.0, - "y": 400.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 300.0, - "y": 450.0 - }, - "start": { - "x": 350.0, - "y": 450.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 300.0, - "y": 400.0 - }, - "start": { - "x": 300.0, - "y": 450.0 - }, - "type": "LineSegment" - } - ], - "is_path": false, - "type": "general" - }, - { - "elements": [ - { - "end": { - "x": 400.0, - "y": 50.0 - }, - "start": { - "x": 350.0, - "y": 50.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 400.0, - "y": 100.0 - }, - "start": { - "x": 400.0, - "y": 50.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 350.0, - "y": 100.0 - }, - "start": { - "x": 400.0, - "y": 100.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 350.0, - "y": 50.0 - }, - "start": { - "x": 350.0, - "y": 100.0 - }, - "type": "LineSegment" - } - ], - "is_path": false, - "type": "general" - }, - { - "elements": [ - { - "end": { - "x": 400.0, - "y": 150.0 - }, - "start": { - "x": 350.0, - "y": 150.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 400.0, - "y": 200.0 - }, - "start": { - "x": 400.0, - "y": 150.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 350.0, - "y": 200.0 - }, - "start": { - "x": 400.0, - "y": 200.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 350.0, - "y": 150.0 - }, - "start": { - "x": 350.0, - "y": 200.0 - }, - "type": "LineSegment" - } - ], - "is_path": false, - "type": "general" - }, - { - "elements": [ - { - "end": { - "x": 400.0, - "y": 250.0 - }, - "start": { - "x": 350.0, - "y": 250.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 400.0, - "y": 300.0 - }, - "start": { - "x": 400.0, - "y": 250.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 350.0, - "y": 300.0 - }, - "start": { - "x": 400.0, - "y": 300.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 350.0, - "y": 250.0 - }, - "start": { - "x": 350.0, - "y": 300.0 - }, - "type": "LineSegment" - } - ], - "is_path": false, - "type": "general" - }, - { - "elements": [ - { - "end": { - "x": 400.0, - "y": 350.0 - }, - "start": { - "x": 350.0, - "y": 350.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 400.0, - "y": 400.0 - }, - "start": { - "x": 400.0, - "y": 350.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 350.0, - "y": 400.0 - }, - "start": { - "x": 400.0, - "y": 400.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 350.0, - "y": 350.0 - }, - "start": { - "x": 350.0, - "y": 400.0 - }, - "type": "LineSegment" - } - ], - "is_path": false, - "type": "general" - }, - { - "elements": [ - { - "end": { - "x": 450.0, - "y": 0.0 - }, - "start": { - "x": 400.0, - "y": 0.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 450.0, - "y": 50.0 - }, - "start": { - "x": 450.0, - "y": 0.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 400.0, - "y": 50.0 - }, - "start": { - "x": 450.0, - "y": 50.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 400.0, - "y": 0.0 - }, - "start": { - "x": 400.0, - "y": 50.0 - }, - "type": "LineSegment" - } - ], - "is_path": false, - "type": "general" - }, - { - "elements": [ - { - "end": { - "x": 450.0, - "y": 100.0 - }, - "start": { - "x": 400.0, - "y": 100.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 450.0, - "y": 150.0 - }, - "start": { - "x": 450.0, - "y": 100.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 400.0, - "y": 150.0 - }, - "start": { - "x": 450.0, - "y": 150.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 400.0, - "y": 100.0 - }, - "start": { - "x": 400.0, - "y": 150.0 - }, - "type": "LineSegment" - } - ], - "is_path": false, - "type": "general" - }, - { - "elements": [ - { - "end": { - "x": 450.0, - "y": 200.0 - }, - "start": { - "x": 400.0, - "y": 200.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 450.0, - "y": 250.0 - }, - "start": { - "x": 450.0, - "y": 200.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 400.0, - "y": 250.0 - }, - "start": { - "x": 450.0, - "y": 250.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 400.0, - "y": 200.0 - }, - "start": { - "x": 400.0, - "y": 250.0 - }, - "type": "LineSegment" - } - ], - "is_path": false, - "type": "general" - }, - { - "elements": [ - { - "end": { - "x": 450.0, - "y": 300.0 - }, - "start": { - "x": 400.0, - "y": 300.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 450.0, - "y": 350.0 - }, - "start": { - "x": 450.0, - "y": 300.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 400.0, - "y": 350.0 - }, - "start": { - "x": 450.0, - "y": 350.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 400.0, - "y": 300.0 - }, - "start": { - "x": 400.0, - "y": 350.0 - }, - "type": "LineSegment" - } - ], - "is_path": false, - "type": "general" - }, - { - "elements": [ - { - "end": { - "x": 450.0, - "y": 400.0 - }, - "start": { - "x": 400.0, - "y": 400.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 450.0, - "y": 450.0 - }, - "start": { - "x": 450.0, - "y": 400.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 400.0, - "y": 450.0 - }, - "start": { - "x": 450.0, - "y": 450.0 - }, - "type": "LineSegment" - }, - { - "end": { - "x": 400.0, - "y": 400.0 - }, - "start": { - "x": 400.0, - "y": 450.0 - }, - "type": "LineSegment" - } - ], - "is_path": false, - "type": "general" + { + "elements": [ + { + "end": { + "x": 50.0, + "y": 0.0 + }, + "start": { + "x": 0.0, + "y": 0.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 50.0, + "y": 50.0 + }, + "start": { + "x": 50.0, + "y": 0.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 0.0, + "y": 50.0 + }, + "start": { + "x": 50.0, + "y": 50.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 0.0, + "y": 0.0 + }, + "start": { + "x": 0.0, + "y": 50.0 + }, + "type": "LineSegment" + } + ], + "is_path": false, + "type": "general" + }, + { + "elements": [ + { + "end": { + "x": 50.0, + "y": 100.0 + }, + "start": { + "x": 0.0, + "y": 100.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 50.0, + "y": 150.0 + }, + "start": { + "x": 50.0, + "y": 100.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 0.0, + "y": 150.0 + }, + "start": { + "x": 50.0, + "y": 150.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 0.0, + "y": 100.0 + }, + "start": { + "x": 0.0, + "y": 150.0 + }, + "type": "LineSegment" + } + ], + "is_path": false, + "type": "general" + }, + { + "elements": [ + { + "end": { + "x": 50.0, + "y": 200.0 + }, + "start": { + "x": 0.0, + "y": 200.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 50.0, + "y": 250.0 + }, + "start": { + "x": 50.0, + "y": 200.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 0.0, + "y": 250.0 + }, + "start": { + "x": 50.0, + "y": 250.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 0.0, + "y": 200.0 + }, + "start": { + "x": 0.0, + "y": 250.0 + }, + "type": "LineSegment" + } + ], + "is_path": false, + "type": "general" + }, + { + "elements": [ + { + "end": { + "x": 50.0, + "y": 300.0 + }, + "start": { + "x": 0.0, + "y": 300.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 50.0, + "y": 350.0 + }, + "start": { + "x": 50.0, + "y": 300.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 0.0, + "y": 350.0 + }, + "start": { + "x": 50.0, + "y": 350.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 0.0, + "y": 300.0 + }, + "start": { + "x": 0.0, + "y": 350.0 + }, + "type": "LineSegment" + } + ], + "is_path": false, + "type": "general" + }, + { + "elements": [ + { + "end": { + "x": 50.0, + "y": 400.0 + }, + "start": { + "x": 0.0, + "y": 400.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 50.0, + "y": 450.0 + }, + "start": { + "x": 50.0, + "y": 400.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 0.0, + "y": 450.0 + }, + "start": { + "x": 50.0, + "y": 450.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 0.0, + "y": 400.0 + }, + "start": { + "x": 0.0, + "y": 450.0 + }, + "type": "LineSegment" + } + ], + "is_path": false, + "type": "general" + }, + { + "elements": [ + { + "end": { + "x": 100.0, + "y": 50.0 + }, + "start": { + "x": 50.0, + "y": 50.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 100.0, + "y": 100.0 + }, + "start": { + "x": 100.0, + "y": 50.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 50.0, + "y": 100.0 + }, + "start": { + "x": 100.0, + "y": 100.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 50.0, + "y": 50.0 + }, + "start": { + "x": 50.0, + "y": 100.0 + }, + "type": "LineSegment" + } + ], + "is_path": false, + "type": "general" + }, + { + "elements": [ + { + "end": { + "x": 100.0, + "y": 150.0 + }, + "start": { + "x": 50.0, + "y": 150.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 100.0, + "y": 200.0 + }, + "start": { + "x": 100.0, + "y": 150.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 50.0, + "y": 200.0 + }, + "start": { + "x": 100.0, + "y": 200.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 50.0, + "y": 150.0 + }, + "start": { + "x": 50.0, + "y": 200.0 + }, + "type": "LineSegment" + } + ], + "is_path": false, + "type": "general" + }, + { + "elements": [ + { + "end": { + "x": 100.0, + "y": 250.0 + }, + "start": { + "x": 50.0, + "y": 250.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 100.0, + "y": 300.0 + }, + "start": { + "x": 100.0, + "y": 250.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 50.0, + "y": 300.0 + }, + "start": { + "x": 100.0, + "y": 300.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 50.0, + "y": 250.0 + }, + "start": { + "x": 50.0, + "y": 300.0 + }, + "type": "LineSegment" + } + ], + "is_path": false, + "type": "general" + }, + { + "elements": [ + { + "end": { + "x": 100.0, + "y": 350.0 + }, + "start": { + "x": 50.0, + "y": 350.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 100.0, + "y": 400.0 + }, + "start": { + "x": 100.0, + "y": 350.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 50.0, + "y": 400.0 + }, + "start": { + "x": 100.0, + "y": 400.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 50.0, + "y": 350.0 + }, + "start": { + "x": 50.0, + "y": 400.0 + }, + "type": "LineSegment" + } + ], + "is_path": false, + "type": "general" + }, + { + "elements": [ + { + "end": { + "x": 150.0, + "y": 0.0 + }, + "start": { + "x": 100.0, + "y": 0.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 150.0, + "y": 50.0 + }, + "start": { + "x": 150.0, + "y": 0.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 100.0, + "y": 50.0 + }, + "start": { + "x": 150.0, + "y": 50.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 100.0, + "y": 0.0 + }, + "start": { + "x": 100.0, + "y": 50.0 + }, + "type": "LineSegment" + } + ], + "is_path": false, + "type": "general" + }, + { + "elements": [ + { + "end": { + "x": 150.0, + "y": 100.0 + }, + "start": { + "x": 100.0, + "y": 100.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 150.0, + "y": 150.0 + }, + "start": { + "x": 150.0, + "y": 100.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 100.0, + "y": 150.0 + }, + "start": { + "x": 150.0, + "y": 150.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 100.0, + "y": 100.0 + }, + "start": { + "x": 100.0, + "y": 150.0 + }, + "type": "LineSegment" + } + ], + "is_path": false, + "type": "general" + }, + { + "elements": [ + { + "end": { + "x": 150.0, + "y": 200.0 + }, + "start": { + "x": 100.0, + "y": 200.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 150.0, + "y": 250.0 + }, + "start": { + "x": 150.0, + "y": 200.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 100.0, + "y": 250.0 + }, + "start": { + "x": 150.0, + "y": 250.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 100.0, + "y": 200.0 + }, + "start": { + "x": 100.0, + "y": 250.0 + }, + "type": "LineSegment" + } + ], + "is_path": false, + "type": "general" + }, + { + "elements": [ + { + "end": { + "x": 150.0, + "y": 300.0 + }, + "start": { + "x": 100.0, + "y": 300.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 150.0, + "y": 350.0 + }, + "start": { + "x": 150.0, + "y": 300.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 100.0, + "y": 350.0 + }, + "start": { + "x": 150.0, + "y": 350.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 100.0, + "y": 300.0 + }, + "start": { + "x": 100.0, + "y": 350.0 + }, + "type": "LineSegment" + } + ], + "is_path": false, + "type": "general" + }, + { + "elements": [ + { + "end": { + "x": 150.0, + "y": 400.0 + }, + "start": { + "x": 100.0, + "y": 400.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 150.0, + "y": 450.0 + }, + "start": { + "x": 150.0, + "y": 400.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 100.0, + "y": 450.0 + }, + "start": { + "x": 150.0, + "y": 450.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 100.0, + "y": 400.0 + }, + "start": { + "x": 100.0, + "y": 450.0 + }, + "type": "LineSegment" + } + ], + "is_path": false, + "type": "general" + }, + { + "elements": [ + { + "end": { + "x": 200.0, + "y": 50.0 + }, + "start": { + "x": 150.0, + "y": 50.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 200.0, + "y": 100.0 + }, + "start": { + "x": 200.0, + "y": 50.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 150.0, + "y": 100.0 + }, + "start": { + "x": 200.0, + "y": 100.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 150.0, + "y": 50.0 + }, + "start": { + "x": 150.0, + "y": 100.0 + }, + "type": "LineSegment" + } + ], + "is_path": false, + "type": "general" + }, + { + "elements": [ + { + "end": { + "x": 200.0, + "y": 150.0 + }, + "start": { + "x": 150.0, + "y": 150.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 200.0, + "y": 200.0 + }, + "start": { + "x": 200.0, + "y": 150.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 150.0, + "y": 200.0 + }, + "start": { + "x": 200.0, + "y": 200.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 150.0, + "y": 150.0 + }, + "start": { + "x": 150.0, + "y": 200.0 + }, + "type": "LineSegment" + } + ], + "is_path": false, + "type": "general" + }, + { + "elements": [ + { + "end": { + "x": 200.0, + "y": 250.0 + }, + "start": { + "x": 150.0, + "y": 250.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 200.0, + "y": 300.0 + }, + "start": { + "x": 200.0, + "y": 250.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 150.0, + "y": 300.0 + }, + "start": { + "x": 200.0, + "y": 300.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 150.0, + "y": 250.0 + }, + "start": { + "x": 150.0, + "y": 300.0 + }, + "type": "LineSegment" + } + ], + "is_path": false, + "type": "general" + }, + { + "elements": [ + { + "end": { + "x": 200.0, + "y": 350.0 + }, + "start": { + "x": 150.0, + "y": 350.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 200.0, + "y": 400.0 + }, + "start": { + "x": 200.0, + "y": 350.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 150.0, + "y": 400.0 + }, + "start": { + "x": 200.0, + "y": 400.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 150.0, + "y": 350.0 + }, + "start": { + "x": 150.0, + "y": 400.0 + }, + "type": "LineSegment" + } + ], + "is_path": false, + "type": "general" + }, + { + "elements": [ + { + "end": { + "x": 250.0, + "y": 0.0 + }, + "start": { + "x": 200.0, + "y": 0.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 250.0, + "y": 50.0 + }, + "start": { + "x": 250.0, + "y": 0.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 200.0, + "y": 50.0 + }, + "start": { + "x": 250.0, + "y": 50.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 200.0, + "y": 0.0 + }, + "start": { + "x": 200.0, + "y": 50.0 + }, + "type": "LineSegment" + } + ], + "is_path": false, + "type": "general" + }, + { + "elements": [ + { + "end": { + "x": 250.0, + "y": 100.0 + }, + "start": { + "x": 200.0, + "y": 100.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 250.0, + "y": 150.0 + }, + "start": { + "x": 250.0, + "y": 100.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 200.0, + "y": 150.0 + }, + "start": { + "x": 250.0, + "y": 150.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 200.0, + "y": 100.0 + }, + "start": { + "x": 200.0, + "y": 150.0 + }, + "type": "LineSegment" + } + ], + "is_path": false, + "type": "general" + }, + { + "elements": [ + { + "end": { + "x": 250.0, + "y": 200.0 + }, + "start": { + "x": 200.0, + "y": 200.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 250.0, + "y": 250.0 + }, + "start": { + "x": 250.0, + "y": 200.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 200.0, + "y": 250.0 + }, + "start": { + "x": 250.0, + "y": 250.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 200.0, + "y": 200.0 + }, + "start": { + "x": 200.0, + "y": 250.0 + }, + "type": "LineSegment" + } + ], + "is_path": false, + "type": "general" + }, + { + "elements": [ + { + "end": { + "x": 250.0, + "y": 300.0 + }, + "start": { + "x": 200.0, + "y": 300.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 250.0, + "y": 350.0 + }, + "start": { + "x": 250.0, + "y": 300.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 200.0, + "y": 350.0 + }, + "start": { + "x": 250.0, + "y": 350.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 200.0, + "y": 300.0 + }, + "start": { + "x": 200.0, + "y": 350.0 + }, + "type": "LineSegment" + } + ], + "is_path": false, + "type": "general" + }, + { + "elements": [ + { + "end": { + "x": 250.0, + "y": 400.0 + }, + "start": { + "x": 200.0, + "y": 400.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 250.0, + "y": 450.0 + }, + "start": { + "x": 250.0, + "y": 400.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 200.0, + "y": 450.0 + }, + "start": { + "x": 250.0, + "y": 450.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 200.0, + "y": 400.0 + }, + "start": { + "x": 200.0, + "y": 450.0 + }, + "type": "LineSegment" + } + ], + "is_path": false, + "type": "general" + }, + { + "elements": [ + { + "end": { + "x": 300.0, + "y": 50.0 + }, + "start": { + "x": 250.0, + "y": 50.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 300.0, + "y": 100.0 + }, + "start": { + "x": 300.0, + "y": 50.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 250.0, + "y": 100.0 + }, + "start": { + "x": 300.0, + "y": 100.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 250.0, + "y": 50.0 + }, + "start": { + "x": 250.0, + "y": 100.0 + }, + "type": "LineSegment" + } + ], + "is_path": false, + "type": "general" + }, + { + "elements": [ + { + "end": { + "x": 300.0, + "y": 150.0 + }, + "start": { + "x": 250.0, + "y": 150.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 300.0, + "y": 200.0 + }, + "start": { + "x": 300.0, + "y": 150.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 250.0, + "y": 200.0 + }, + "start": { + "x": 300.0, + "y": 200.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 250.0, + "y": 150.0 + }, + "start": { + "x": 250.0, + "y": 200.0 + }, + "type": "LineSegment" + } + ], + "is_path": false, + "type": "general" + }, + { + "elements": [ + { + "end": { + "x": 300.0, + "y": 250.0 + }, + "start": { + "x": 250.0, + "y": 250.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 300.0, + "y": 300.0 + }, + "start": { + "x": 300.0, + "y": 250.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 250.0, + "y": 300.0 + }, + "start": { + "x": 300.0, + "y": 300.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 250.0, + "y": 250.0 + }, + "start": { + "x": 250.0, + "y": 300.0 + }, + "type": "LineSegment" + } + ], + "is_path": false, + "type": "general" + }, + { + "elements": [ + { + "end": { + "x": 300.0, + "y": 350.0 + }, + "start": { + "x": 250.0, + "y": 350.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 300.0, + "y": 400.0 + }, + "start": { + "x": 300.0, + "y": 350.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 250.0, + "y": 400.0 + }, + "start": { + "x": 300.0, + "y": 400.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 250.0, + "y": 350.0 + }, + "start": { + "x": 250.0, + "y": 400.0 + }, + "type": "LineSegment" + } + ], + "is_path": false, + "type": "general" + }, + { + "elements": [ + { + "end": { + "x": 350.0, + "y": 0.0 + }, + "start": { + "x": 300.0, + "y": 0.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 350.0, + "y": 50.0 + }, + "start": { + "x": 350.0, + "y": 0.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 300.0, + "y": 50.0 + }, + "start": { + "x": 350.0, + "y": 50.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 300.0, + "y": 0.0 + }, + "start": { + "x": 300.0, + "y": 50.0 + }, + "type": "LineSegment" + } + ], + "is_path": false, + "type": "general" + }, + { + "elements": [ + { + "end": { + "x": 350.0, + "y": 100.0 + }, + "start": { + "x": 300.0, + "y": 100.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 350.0, + "y": 150.0 + }, + "start": { + "x": 350.0, + "y": 100.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 300.0, + "y": 150.0 + }, + "start": { + "x": 350.0, + "y": 150.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 300.0, + "y": 100.0 + }, + "start": { + "x": 300.0, + "y": 150.0 + }, + "type": "LineSegment" + } + ], + "is_path": false, + "type": "general" + }, + { + "elements": [ + { + "end": { + "x": 350.0, + "y": 200.0 + }, + "start": { + "x": 300.0, + "y": 200.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 350.0, + "y": 250.0 + }, + "start": { + "x": 350.0, + "y": 200.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 300.0, + "y": 250.0 + }, + "start": { + "x": 350.0, + "y": 250.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 300.0, + "y": 200.0 + }, + "start": { + "x": 300.0, + "y": 250.0 + }, + "type": "LineSegment" } + ], + "is_path": false, + "type": "general" + }, + { + "elements": [ + { + "end": { + "x": 350.0, + "y": 300.0 + }, + "start": { + "x": 300.0, + "y": 300.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 350.0, + "y": 350.0 + }, + "start": { + "x": 350.0, + "y": 300.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 300.0, + "y": 350.0 + }, + "start": { + "x": 350.0, + "y": 350.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 300.0, + "y": 300.0 + }, + "start": { + "x": 300.0, + "y": 350.0 + }, + "type": "LineSegment" + } + ], + "is_path": false, + "type": "general" + }, + { + "elements": [ + { + "end": { + "x": 350.0, + "y": 400.0 + }, + "start": { + "x": 300.0, + "y": 400.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 350.0, + "y": 450.0 + }, + "start": { + "x": 350.0, + "y": 400.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 300.0, + "y": 450.0 + }, + "start": { + "x": 350.0, + "y": 450.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 300.0, + "y": 400.0 + }, + "start": { + "x": 300.0, + "y": 450.0 + }, + "type": "LineSegment" + } + ], + "is_path": false, + "type": "general" + }, + { + "elements": [ + { + "end": { + "x": 400.0, + "y": 50.0 + }, + "start": { + "x": 350.0, + "y": 50.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 400.0, + "y": 100.0 + }, + "start": { + "x": 400.0, + "y": 50.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 350.0, + "y": 100.0 + }, + "start": { + "x": 400.0, + "y": 100.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 350.0, + "y": 50.0 + }, + "start": { + "x": 350.0, + "y": 100.0 + }, + "type": "LineSegment" + } + ], + "is_path": false, + "type": "general" + }, + { + "elements": [ + { + "end": { + "x": 400.0, + "y": 150.0 + }, + "start": { + "x": 350.0, + "y": 150.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 400.0, + "y": 200.0 + }, + "start": { + "x": 400.0, + "y": 150.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 350.0, + "y": 200.0 + }, + "start": { + "x": 400.0, + "y": 200.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 350.0, + "y": 150.0 + }, + "start": { + "x": 350.0, + "y": 200.0 + }, + "type": "LineSegment" + } + ], + "is_path": false, + "type": "general" + }, + { + "elements": [ + { + "end": { + "x": 400.0, + "y": 250.0 + }, + "start": { + "x": 350.0, + "y": 250.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 400.0, + "y": 300.0 + }, + "start": { + "x": 400.0, + "y": 250.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 350.0, + "y": 300.0 + }, + "start": { + "x": 400.0, + "y": 300.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 350.0, + "y": 250.0 + }, + "start": { + "x": 350.0, + "y": 300.0 + }, + "type": "LineSegment" + } + ], + "is_path": false, + "type": "general" + }, + { + "elements": [ + { + "end": { + "x": 400.0, + "y": 350.0 + }, + "start": { + "x": 350.0, + "y": 350.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 400.0, + "y": 400.0 + }, + "start": { + "x": 400.0, + "y": 350.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 350.0, + "y": 400.0 + }, + "start": { + "x": 400.0, + "y": 400.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 350.0, + "y": 350.0 + }, + "start": { + "x": 350.0, + "y": 400.0 + }, + "type": "LineSegment" + } + ], + "is_path": false, + "type": "general" + }, + { + "elements": [ + { + "end": { + "x": 450.0, + "y": 0.0 + }, + "start": { + "x": 400.0, + "y": 0.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 450.0, + "y": 50.0 + }, + "start": { + "x": 450.0, + "y": 0.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 400.0, + "y": 50.0 + }, + "start": { + "x": 450.0, + "y": 50.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 400.0, + "y": 0.0 + }, + "start": { + "x": 400.0, + "y": 50.0 + }, + "type": "LineSegment" + } + ], + "is_path": false, + "type": "general" + }, + { + "elements": [ + { + "end": { + "x": 450.0, + "y": 100.0 + }, + "start": { + "x": 400.0, + "y": 100.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 450.0, + "y": 150.0 + }, + "start": { + "x": 450.0, + "y": 100.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 400.0, + "y": 150.0 + }, + "start": { + "x": 450.0, + "y": 150.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 400.0, + "y": 100.0 + }, + "start": { + "x": 400.0, + "y": 150.0 + }, + "type": "LineSegment" + } + ], + "is_path": false, + "type": "general" + }, + { + "elements": [ + { + "end": { + "x": 450.0, + "y": 200.0 + }, + "start": { + "x": 400.0, + "y": 200.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 450.0, + "y": 250.0 + }, + "start": { + "x": 450.0, + "y": 200.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 400.0, + "y": 250.0 + }, + "start": { + "x": 450.0, + "y": 250.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 400.0, + "y": 200.0 + }, + "start": { + "x": 400.0, + "y": 250.0 + }, + "type": "LineSegment" + } + ], + "is_path": false, + "type": "general" + }, + { + "elements": [ + { + "end": { + "x": 450.0, + "y": 300.0 + }, + "start": { + "x": 400.0, + "y": 300.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 450.0, + "y": 350.0 + }, + "start": { + "x": 450.0, + "y": 300.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 400.0, + "y": 350.0 + }, + "start": { + "x": 450.0, + "y": 350.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 400.0, + "y": 300.0 + }, + "start": { + "x": 400.0, + "y": 350.0 + }, + "type": "LineSegment" + } + ], + "is_path": false, + "type": "general" + }, + { + "elements": [ + { + "end": { + "x": 450.0, + "y": 400.0 + }, + "start": { + "x": 400.0, + "y": 400.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 450.0, + "y": 450.0 + }, + "start": { + "x": 450.0, + "y": 400.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 400.0, + "y": 450.0 + }, + "start": { + "x": 450.0, + "y": 450.0 + }, + "type": "LineSegment" + }, + { + "end": { + "x": 400.0, + "y": 400.0 + }, + "start": { + "x": 400.0, + "y": 450.0 + }, + "type": "LineSegment" + } + ], + "is_path": false, + "type": "general" + } ], "write_json": false, "write_svg": false diff --git a/data/tests/boolean_operations/difference/003.json b/data/tests/boolean_operations/difference/003.json index c2a10ed..a5d27bd 100644 --- a/data/tests/boolean_operations/difference/003.json +++ b/data/tests/boolean_operations/difference/003.json @@ -1,10 +1,12 @@ { + "shapes_1": [ + { + "type": "rectangle", + "width": 6, + "height": 6 + } + ], "description": "Subtractor strictly inside base (non-intersecting components): 2x2 square inside 6x6 square.", - "shape": { - "type": "rectangle", - "width": 6, - "height": 6 - }, "shapes": [ { "type": "rectangle", @@ -18,19 +20,43 @@ { "type": "polygon", "vertices": [ - {"x": 0, "y": 0}, - {"x": 6, "y": 0}, - {"x": 6, "y": 6}, - {"x": 0, "y": 6} + { + "x": 0, + "y": 0 + }, + { + "x": 6, + "y": 0 + }, + { + "x": 6, + "y": 6 + }, + { + "x": 0, + "y": 6 + } ], "holes": [ { "type": "polygon", "vertices": [ - {"x": 2, "y": 2}, - {"x": 4, "y": 2}, - {"x": 4, "y": 4}, - {"x": 2, "y": 4} + { + "x": 2, + "y": 2 + }, + { + "x": 4, + "y": 2 + }, + { + "x": 4, + "y": 4 + }, + { + "x": 2, + "y": 4 + } ] } ] diff --git a/data/tests/boolean_operations/difference/004.json b/data/tests/boolean_operations/difference/004.json index c95170a..9db16bf 100644 --- a/data/tests/boolean_operations/difference/004.json +++ b/data/tests/boolean_operations/difference/004.json @@ -1,10 +1,12 @@ { + "shapes_1": [ + { + "type": "rectangle", + "width": 10, + "height": 4 + } + ], "description": "Two subtractors strictly inside base (non-intersecting components): two 2x2 squares inside a 10x4 rectangle.", - "shape": { - "type": "rectangle", - "width": 10, - "height": 4 - }, "shapes": [ { "type": "rectangle", @@ -25,28 +27,64 @@ { "type": "polygon", "vertices": [ - {"x": 0, "y": 0}, - {"x": 10, "y": 0}, - {"x": 10, "y": 4}, - {"x": 0, "y": 4} + { + "x": 0, + "y": 0 + }, + { + "x": 10, + "y": 0 + }, + { + "x": 10, + "y": 4 + }, + { + "x": 0, + "y": 4 + } ], "holes": [ { "type": "polygon", "vertices": [ - {"x": 1, "y": 1}, - {"x": 3, "y": 1}, - {"x": 3, "y": 3}, - {"x": 1, "y": 3} + { + "x": 1, + "y": 1 + }, + { + "x": 3, + "y": 1 + }, + { + "x": 3, + "y": 3 + }, + { + "x": 1, + "y": 3 + } ] }, { "type": "polygon", "vertices": [ - {"x": 7, "y": 1}, - {"x": 9, "y": 1}, - {"x": 9, "y": 3}, - {"x": 7, "y": 3} + { + "x": 7, + "y": 1 + }, + { + "x": 9, + "y": 1 + }, + { + "x": 9, + "y": 3 + }, + { + "x": 7, + "y": 3 + } ] } ] diff --git a/data/tests/boolean_operations/difference/005.json b/data/tests/boolean_operations/difference/005.json index e726968..a79c5bd 100644 --- a/data/tests/boolean_operations/difference/005.json +++ b/data/tests/boolean_operations/difference/005.json @@ -1,25 +1,51 @@ { + "shapes_1": [ + { + "type": "polygon", + "vertices": [ + { + "x": 0, + "y": 0 + }, + { + "x": 8, + "y": 0 + }, + { + "x": 8, + "y": 8 + }, + { + "x": 0, + "y": 8 + } + ], + "holes": [ + { + "type": "polygon", + "vertices": [ + { + "x": 1, + "y": 1 + }, + { + "x": 3, + "y": 1 + }, + { + "x": 3, + "y": 3 + }, + { + "x": 1, + "y": 3 + } + ] + } + ] + } + ], "description": "Subtractor strictly inside base that itself has a hole: 2x2 square inside an 8x8 square that already has a 2x2 hole.", - "shape": { - "type": "polygon", - "vertices": [ - {"x": 0, "y": 0}, - {"x": 8, "y": 0}, - {"x": 8, "y": 8}, - {"x": 0, "y": 8} - ], - "holes": [ - { - "type": "polygon", - "vertices": [ - {"x": 1, "y": 1}, - {"x": 3, "y": 1}, - {"x": 3, "y": 3}, - {"x": 1, "y": 3} - ] - } - ] - }, "shapes": [ { "type": "rectangle", @@ -33,28 +59,64 @@ { "type": "polygon", "vertices": [ - {"x": 0, "y": 0}, - {"x": 8, "y": 0}, - {"x": 8, "y": 8}, - {"x": 0, "y": 8} + { + "x": 0, + "y": 0 + }, + { + "x": 8, + "y": 0 + }, + { + "x": 8, + "y": 8 + }, + { + "x": 0, + "y": 8 + } ], "holes": [ { "type": "polygon", "vertices": [ - {"x": 1, "y": 1}, - {"x": 3, "y": 1}, - {"x": 3, "y": 3}, - {"x": 1, "y": 3} + { + "x": 1, + "y": 1 + }, + { + "x": 3, + "y": 1 + }, + { + "x": 3, + "y": 3 + }, + { + "x": 1, + "y": 3 + } ] }, { "type": "polygon", "vertices": [ - {"x": 5, "y": 5}, - {"x": 7, "y": 5}, - {"x": 7, "y": 7}, - {"x": 5, "y": 7} + { + "x": 5, + "y": 5 + }, + { + "x": 7, + "y": 5 + }, + { + "x": 7, + "y": 7 + }, + { + "x": 5, + "y": 7 + } ] } ] diff --git a/include/shape/boolean_operations.hpp b/include/shape/boolean_operations.hpp index dbef4e6..443b4b7 100644 --- a/include/shape/boolean_operations.hpp +++ b/include/shape/boolean_operations.hpp @@ -26,11 +26,11 @@ void compute_intersection_export_inputs( const std::vector& shapes); /** - * Compute the difference between a given shape and a given set of shapes. + * Compute the difference between two multi-shapes. */ std::vector compute_difference( - const ShapeWithHoles& shape, - const std::vector& shapes); + const std::vector& shapes_1, + const std::vector& shapes_2); /** * Compute the difference between a given shape and a given set of shapes. diff --git a/src/boolean_operations.cpp b/src/boolean_operations.cpp index 97e23f0..9942333 100644 --- a/src/boolean_operations.cpp +++ b/src/boolean_operations.cpp @@ -1367,17 +1367,20 @@ void shape::compute_intersection_export_inputs( } std::vector shape::compute_difference( - const ShapeWithHoles& shape, - const std::vector& shapes) + const std::vector& shapes_1, + const std::vector& shapes_2) { - std::vector v; - v.push_back(shape); - for (const ShapeWithHoles& shape: shapes) - v.push_back(shape); - std::vector faces = compute_boolean_operation( - v, - BooleanOperation::Difference); - return compute_union(faces); + std::vector result; + for (const ShapeWithHoles& shape: compute_union(shapes_1)) { + std::vector v = {shape}; + v.insert(v.end(), shapes_2.begin(), shapes_2.end()); + std::vector faces = compute_boolean_operation( + v, + BooleanOperation::Difference); + std::vector diff = compute_union(faces); + result.insert(result.end(), diff.begin(), diff.end()); + } + return result; } std::vector shape::compute_symmetric_difference( diff --git a/src/offset.cpp b/src/offset.cpp index 08d9d8e..9168077 100644 --- a/src/offset.cpp +++ b/src/offset.cpp @@ -619,7 +619,7 @@ std::vector shape::deflate( element_prev_pos = element_pos; } - auto difference_output = compute_difference({shape}, difference_input); + auto difference_output = compute_difference(std::vector{{shape}}, difference_input); std::vector output; for (const ShapeWithHoles& shape: difference_output) output.push_back(shape.shape); diff --git a/test/boolean_operations_test.cpp b/test/boolean_operations_test.cpp index 22bb243..ccf1c34 100644 --- a/test/boolean_operations_test.cpp +++ b/test/boolean_operations_test.cpp @@ -329,7 +329,7 @@ INSTANTIATE_TEST_SUITE_P( struct ComputeBooleanDifferenceTestParams : TestParams { - ShapeWithHoles shape; + std::vector shapes_1; std::vector shapes; std::vector expected_output; @@ -338,7 +338,8 @@ struct ComputeBooleanDifferenceTestParams : TestParams& json_item) { ComputeBooleanDifferenceTestParams test_params = TestParams::from_json(json_item); - test_params.shape = ShapeWithHoles::from_json(json_item["shape"]); + for (auto& json_shape: json_item["shapes_1"].items()) + test_params.shapes_1.emplace_back(ShapeWithHoles::from_json(json_shape.value())); for (auto& json_shape: json_item["shapes"].items()) test_params.shapes.emplace_back(ShapeWithHoles::from_json(json_shape.value())); for (auto& json_shape: json_item["expected_output"].items()) @@ -350,8 +351,9 @@ struct ComputeBooleanDifferenceTestParams : TestParams Date: Mon, 27 Apr 2026 22:42:43 +0200 Subject: [PATCH 2/3] Improve boolean difference of multi-shapes --- src/boolean_operations.cpp | 70 +++++++++++++++++++++++++++----------- 1 file changed, 50 insertions(+), 20 deletions(-) diff --git a/src/boolean_operations.cpp b/src/boolean_operations.cpp index 9942333..162fc68 100644 --- a/src/boolean_operations.cpp +++ b/src/boolean_operations.cpp @@ -924,7 +924,8 @@ std::vector compute_boolean_operation_component( const std::vector& shapes, ComputeSplittedElementsOutput& cse_output, ComponentId component_id, - BooleanOperation boolean_operation) + BooleanOperation boolean_operation, + ShapePos num_shapes_1 = 1) { //std::cout << "compute_boolean_operation_component" << std::endl; //for (const SplittedElement& splitted_element: splitted_elements) @@ -1173,12 +1174,21 @@ std::vector compute_boolean_operation_component( break; } case BooleanOperation::Difference: { - // Fast check. + // Fast check: disqualify if inside any shape from shapes_2. bool ok = true; for (ShapePos shape_pos = 0; shape_pos < (ShapePos)component_shapes.size(); ++shape_pos) { - if (shape_pos == cse_output.shape_component_ids.position(0)) + bool is_in_shapes_1 = false; + for (ShapePos shape_1_pos = 0; + shape_1_pos < num_shapes_1; + ++shape_1_pos) { + if (shape_pos == cse_output.shape_component_ids.position(shape_1_pos)) { + is_in_shapes_1 = true; + break; + } + } + if (is_in_shapes_1) continue; if (is_inside[shape_pos]) { ok = false; @@ -1191,13 +1201,26 @@ std::vector compute_boolean_operation_component( //std::cout << face.find_point_strictly_inside().to_string() << std::endl; - // Real check. + // Real check: keep if inside at least one shape from shapes_1 + // and not inside any shape from shapes_2. IntersectionTree::IntersectOutput intersection_output = intersection_tree.intersect( face.find_point_strictly_inside(), false); //std::cout << "intersection_output.shape_ids.size() " << intersection_output.shape_ids.size() << std::endl; - if (intersection_output.shape_ids.size() == 1 - && intersection_output.shape_ids[0] == cse_output.shape_component_ids.position(0)) { + bool inside_shapes_1 = false; + bool inside_shapes_2 = false; + for (ShapePos shape_pos: intersection_output.shape_ids) { + bool is_shapes_1_pos = false; + for (ShapePos i = 0; i < num_shapes_1; ++i) { + if (shape_pos == cse_output.shape_component_ids.position(i)) { + is_shapes_1_pos = true; + break; + } + } + if (is_shapes_1_pos) inside_shapes_1 = true; + else inside_shapes_2 = true; + } + if (inside_shapes_1 && !inside_shapes_2) { //std::cout << "add face" << std::endl; face = remove_redundant_vertices(face).second; face = remove_aligned_vertices(face).second; @@ -1242,7 +1265,8 @@ std::vector compute_boolean_operation_component( std::vector compute_boolean_operation( const std::vector& shapes, - BooleanOperation boolean_operation) + BooleanOperation boolean_operation, + ShapePos num_shapes_1 = 1) { //std::cout << "compute_boolean_operation " << (int)boolean_operation << " shapes.size() " << shapes.size() << std::endl; //if (boolean_operation == BooleanOperation::Difference) @@ -1294,7 +1318,16 @@ std::vector compute_boolean_operation( if (cse_output.shape_component_ids.number_of_elements(component_id) == 0) continue; if (boolean_operation == BooleanOperation::Difference) { - if (cse_output.shape_component_ids[0] != component_id) + bool contains_shapes_1 = false; + for (ShapePos shape_1_pos = 0; + shape_1_pos < num_shapes_1; + ++shape_1_pos) { + if (cse_output.shape_component_ids[shape_1_pos] == component_id) { + contains_shapes_1 = true; + break; + } + } + if (!contains_shapes_1) continue; } // Compute the union of the shapes from this component. @@ -1305,7 +1338,8 @@ std::vector compute_boolean_operation( shapes, cse_output, component_id, - boolean_operation); + boolean_operation, + num_shapes_1); for (const ShapeWithHoles& new_shape: new_shapes) output.push_back(new_shape); } @@ -1370,17 +1404,13 @@ std::vector shape::compute_difference( const std::vector& shapes_1, const std::vector& shapes_2) { - std::vector result; - for (const ShapeWithHoles& shape: compute_union(shapes_1)) { - std::vector v = {shape}; - v.insert(v.end(), shapes_2.begin(), shapes_2.end()); - std::vector faces = compute_boolean_operation( - v, - BooleanOperation::Difference); - std::vector diff = compute_union(faces); - result.insert(result.end(), diff.begin(), diff.end()); - } - return result; + std::vector v = shapes_1; + v.insert(v.end(), shapes_2.begin(), shapes_2.end()); + std::vector faces = compute_boolean_operation( + v, + BooleanOperation::Difference, + shapes_1.size()); + return compute_union(faces); } std::vector shape::compute_symmetric_difference( From b6e91ea0ad952d4e3656cc65d73b80497d73da18 Mon Sep 17 00:00:00 2001 From: Florian Fontan Date: Tue, 28 Apr 2026 00:10:50 +0200 Subject: [PATCH 3/3] Implement symmetric difference for multi-shapes --- .../symmetric_difference/000.json | 24 ++++---- include/shape/boolean_operations.hpp | 6 +- src/boolean_operations.cpp | 58 +++++++++++++++---- test/boolean_operations_test.cpp | 28 +++++---- 4 files changed, 79 insertions(+), 37 deletions(-) diff --git a/data/tests/boolean_operations/symmetric_difference/000.json b/data/tests/boolean_operations/symmetric_difference/000.json index f52dcd5..37456f9 100644 --- a/data/tests/boolean_operations/symmetric_difference/000.json +++ b/data/tests/boolean_operations/symmetric_difference/000.json @@ -1,14 +1,18 @@ { - "shape_1": { - "type": "rectangle", - "width": 100, - "height": 100 - }, - "shape_2": { - "type": "rectangle", - "width": 200, - "height": 200 - }, + "shapes_1": [ + { + "type": "rectangle", + "width": 100, + "height": 100 + } + ], + "shapes_2": [ + { + "type": "rectangle", + "width": 200, + "height": 200 + } + ], "expected_output": [ { "type": "polygon", diff --git a/include/shape/boolean_operations.hpp b/include/shape/boolean_operations.hpp index 443b4b7..ef878f0 100644 --- a/include/shape/boolean_operations.hpp +++ b/include/shape/boolean_operations.hpp @@ -33,11 +33,11 @@ std::vector compute_difference( const std::vector& shapes_2); /** - * Compute the difference between a given shape and a given set of shapes. + * Compute the symmetric difference between two multi-shapes. */ std::vector compute_symmetric_difference( - const ShapeWithHoles& shape_1, - const ShapeWithHoles& shape_2); + const std::vector& shapes_1, + const std::vector& shapes_2); Shape extract_outline( const Shape& shape); diff --git a/src/boolean_operations.cpp b/src/boolean_operations.cpp index 162fc68..3292ba4 100644 --- a/src/boolean_operations.cpp +++ b/src/boolean_operations.cpp @@ -1229,15 +1229,45 @@ std::vector compute_boolean_operation_component( break; } case BooleanOperation::SymmetricDifference: { - // Fast check. - if (is_inside[0] && is_inside[1]) + // Fast check: discard if inside at least one shape from each side. + bool fast_inside_1 = false; + bool fast_inside_2 = false; + for (ShapePos shape_pos = 0; + shape_pos < (ShapePos)component_shapes.size(); + ++shape_pos) { + if (!is_inside[shape_pos]) + continue; + bool is_shapes_1_pos = false; + for (ShapePos i = 0; i < num_shapes_1; ++i) { + if (shape_pos == cse_output.shape_component_ids.position(i)) { + is_shapes_1_pos = true; + break; + } + } + if (is_shapes_1_pos) fast_inside_1 = true; + else fast_inside_2 = true; + } + if (fast_inside_1 && fast_inside_2) break; - // Real check. + // Real check: keep if inside exactly one side (XOR). IntersectionTree::IntersectOutput intersection_output = intersection_tree.intersect( face.find_point_strictly_inside(), false); - if (intersection_output.shape_ids.size() == 1) { + bool inside_shapes_1 = false; + bool inside_shapes_2 = false; + for (ShapePos shape_pos: intersection_output.shape_ids) { + bool is_shapes_1_pos = false; + for (ShapePos i = 0; i < num_shapes_1; ++i) { + if (shape_pos == cse_output.shape_component_ids.position(i)) { + is_shapes_1_pos = true; + break; + } + } + if (is_shapes_1_pos) inside_shapes_1 = true; + else inside_shapes_2 = true; + } + if (inside_shapes_1 != inside_shapes_2) { face = remove_redundant_vertices(face).second; face = remove_aligned_vertices(face).second; new_shapes.push_back({face}); @@ -1305,8 +1335,12 @@ std::vector compute_boolean_operation( // shape. ComputeSplittedElementsOutput cse_output = compute_splitted_elements(shapes, boolean_operation); - if (boolean_operation == BooleanOperation::Intersection - || boolean_operation == BooleanOperation::SymmetricDifference) { + if (boolean_operation == BooleanOperation::Intersection) { + if (cse_output.shape_component_ids.number_of_values() > 1) + return output; + } + if (boolean_operation == BooleanOperation::SymmetricDifference + && num_shapes_1 == 1) { if (cse_output.shape_component_ids.number_of_values() > 1) return output; } @@ -1414,15 +1448,15 @@ std::vector shape::compute_difference( } std::vector shape::compute_symmetric_difference( - const ShapeWithHoles& shape_1, - const ShapeWithHoles& shape_2) + const std::vector& shapes_1, + const std::vector& shapes_2) { - std::vector v; - v.push_back(shape_1); - v.push_back(shape_2); + std::vector v = shapes_1; + v.insert(v.end(), shapes_2.begin(), shapes_2.end()); std::vector faces = compute_boolean_operation( v, - BooleanOperation::SymmetricDifference); + BooleanOperation::SymmetricDifference, + shapes_1.size()); return compute_union(faces); } diff --git a/test/boolean_operations_test.cpp b/test/boolean_operations_test.cpp index ccf1c34..c8ba012 100644 --- a/test/boolean_operations_test.cpp +++ b/test/boolean_operations_test.cpp @@ -412,8 +412,8 @@ INSTANTIATE_TEST_SUITE_P( struct ComputeBooleanSymmetricDifferenceTestParams : TestParams { - ShapeWithHoles shape_1; - ShapeWithHoles shape_2; + std::vector shapes_1; + std::vector shapes_2; std::vector expected_output; @@ -421,8 +421,10 @@ struct ComputeBooleanSymmetricDifferenceTestParams : TestParams& json_item) { ComputeBooleanSymmetricDifferenceTestParams test_params = TestParams::from_json(json_item); - test_params.shape_1 = ShapeWithHoles::from_json(json_item["shape_1"]); - test_params.shape_2 = ShapeWithHoles::from_json(json_item["shape_2"]); + for (auto& json_shape: json_item["shapes_1"].items()) + test_params.shapes_1.emplace_back(ShapeWithHoles::from_json(json_shape.value())); + for (auto& json_shape: json_item["shapes_2"].items()) + test_params.shapes_2.emplace_back(ShapeWithHoles::from_json(json_shape.value())); for (auto& json_shape: json_item["expected_output"].items()) test_params.expected_output.emplace_back(ShapeWithHoles::from_json(json_shape.value())); return test_params; @@ -432,10 +434,12 @@ struct ComputeBooleanSymmetricDifferenceTestParams : TestParams