openscad-cpp-evaluator 0.29.0__tar.gz → 0.29.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (200) hide show
  1. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/.github/workflows/wheels.yml +21 -1
  2. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/PKG-INFO +1 -1
  3. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/include/openscad_cpp_evaluator/export.hpp +4 -1
  4. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/pyproject.toml +1 -1
  5. openscad_cpp_evaluator-0.29.2/src/builtins/topology.cpp +246 -0
  6. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/tests/test_booleans.cpp +123 -0
  7. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/tests/test_import_export.cpp +34 -0
  8. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/tests/test_zip_stored.cpp +84 -0
  9. openscad_cpp_evaluator-0.29.0/src/builtins/topology.cpp +0 -116
  10. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/.github/workflows/ci.yml +0 -0
  11. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/.gitignore +0 -0
  12. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/.gitmodules +0 -0
  13. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/CLAUDE.md +0 -0
  14. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/CMakeLists.txt +0 -0
  15. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/LICENSE +0 -0
  16. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/README.md +0 -0
  17. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/bindings/CMakeLists.txt +0 -0
  18. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/bindings/ast_to_py.cpp +0 -0
  19. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/bindings/module.cpp +0 -0
  20. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/cmake/embed_font.cmake +0 -0
  21. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/examples/CMakeLists.txt +0 -0
  22. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/examples/eval_perf_benchmark.cpp +0 -0
  23. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/examples/manifold_cache_reuse.cpp +0 -0
  24. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/examples/minimal_debugger.cpp +0 -0
  25. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
  26. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/external/openscad_cpp_parser/.gitignore +0 -0
  27. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/external/openscad_cpp_parser/CLAUDE.md +0 -0
  28. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
  29. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/external/openscad_cpp_parser/README.md +0 -0
  30. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
  31. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
  32. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
  33. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
  34. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
  35. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
  36. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
  37. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
  38. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
  39. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
  40. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
  41. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
  42. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
  43. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
  44. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
  45. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
  46. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/external/openscad_cpp_parser/src/api.cpp +0 -0
  47. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
  48. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
  49. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
  50. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
  51. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
  52. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
  53. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
  54. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
  55. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/external/openscad_cpp_parser/src/comments.cpp +0 -0
  56. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
  57. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
  58. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
  59. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
  60. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
  61. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
  62. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
  63. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/external/openscad_cpp_parser/src/position.cpp +0 -0
  64. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
  65. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/external/openscad_cpp_parser/src/scope.cpp +0 -0
  66. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
  67. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
  68. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
  69. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
  70. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
  71. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
  72. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
  73. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
  74. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
  75. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
  76. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
  77. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
  78. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
  79. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
  80. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
  81. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
  82. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
  83. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
  84. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
  85. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
  86. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
  87. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
  88. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
  89. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
  90. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
  91. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
  92. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/include/openscad_cpp_evaluator/bytecode.hpp +0 -0
  93. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
  94. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
  95. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
  96. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
  97. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
  98. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
  99. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
  100. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
  101. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
  102. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
  103. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
  104. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
  105. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
  106. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/include/openscad_cpp_evaluator/evaluator.hpp +0 -0
  107. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
  108. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
  109. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
  110. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
  111. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/include/openscad_cpp_evaluator/mesh_check.hpp +0 -0
  112. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
  113. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
  114. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/include/openscad_cpp_evaluator/profile.hpp +0 -0
  115. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
  116. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/include/openscad_cpp_evaluator/segments.hpp +0 -0
  117. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/include/openscad_cpp_evaluator/stb_font_provider.hpp +0 -0
  118. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
  119. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
  120. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/include/openscad_cpp_evaluator/value.hpp +0 -0
  121. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
  122. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/python/openscad_cpp_evaluator/__init__.py +0 -0
  123. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/resources/fonts/LICENSE +0 -0
  124. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/resources/fonts/LiberationSans-Regular.ttf +0 -0
  125. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/src/CMakeLists.txt +0 -0
  126. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/src/builtins/booleans.cpp +0 -0
  127. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/src/builtins/builtins.hpp +0 -0
  128. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/src/builtins/call_args.cpp +0 -0
  129. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/src/builtins/color.cpp +0 -0
  130. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/src/builtins/control.cpp +0 -0
  131. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/src/builtins/extrude.cpp +0 -0
  132. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/src/builtins/function_builtins.cpp +0 -0
  133. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/src/builtins/import.cpp +0 -0
  134. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/src/builtins/modifiers.cpp +0 -0
  135. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/src/builtins/primitives_2d.cpp +0 -0
  136. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/src/builtins/primitives_3d.cpp +0 -0
  137. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/src/builtins/registry.cpp +0 -0
  138. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/src/builtins/roof.cpp +0 -0
  139. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/src/builtins/segments.cpp +0 -0
  140. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/src/builtins/surface.cpp +0 -0
  141. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/src/builtins/text.cpp +0 -0
  142. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/src/builtins/transforms.cpp +0 -0
  143. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/src/bytecode_compiler.cpp +0 -0
  144. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/src/bytecode_vm.cpp +0 -0
  145. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/src/colored_body.cpp +0 -0
  146. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/src/csg_generate.cpp +0 -0
  147. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/src/csg_resolve.cpp +0 -0
  148. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/src/css_colors.cpp +0 -0
  149. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/src/debug/debug_repl.cpp +0 -0
  150. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/src/debug_profile.cpp +0 -0
  151. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/src/eval_context.cpp +0 -0
  152. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/src/eval_error.cpp +0 -0
  153. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/src/eval_use.cpp +0 -0
  154. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/src/export.cpp +0 -0
  155. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/src/expr_eval.cpp +0 -0
  156. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/src/import/dxf_import.cpp +0 -0
  157. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/src/import/mesh_import.cpp +0 -0
  158. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/src/import/surface_load.cpp +0 -0
  159. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/src/import/svg_import.cpp +0 -0
  160. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/src/manifold_cache.cpp +0 -0
  161. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/src/mesh_check.cpp +0 -0
  162. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/src/stmt_eval.cpp +0 -0
  163. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/src/text/stb_font_provider.cpp +0 -0
  164. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/src/text/text_metrics.cpp +0 -0
  165. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/src/user_calls.cpp +0 -0
  166. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/src/value.cpp +0 -0
  167. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/src/zip_stored.cpp +0 -0
  168. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/tests/CMakeLists.txt +0 -0
  169. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/tests/test_bytecode_compiler.cpp +0 -0
  170. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/tests/test_cli.cpp +0 -0
  171. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/tests/test_control_flow.cpp +0 -0
  172. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/tests/test_csg_tree.cpp +0 -0
  173. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/tests/test_debug_hooks.cpp +0 -0
  174. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/tests/test_dxf_svg_import.cpp +0 -0
  175. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/tests/test_eval_context.cpp +0 -0
  176. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/tests/test_export.cpp +0 -0
  177. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/tests/test_expr_eval.cpp +0 -0
  178. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/tests/test_extrude_roof.cpp +0 -0
  179. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/tests/test_function_builtins.cpp +0 -0
  180. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/tests/test_helpers.hpp +0 -0
  181. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/tests/test_manifold_cache.cpp +0 -0
  182. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/tests/test_mesh_check.cpp +0 -0
  183. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/tests/test_multi_color_merge.cpp +0 -0
  184. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/tests/test_primitives.cpp +0 -0
  185. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/tests/test_profiling.cpp +0 -0
  186. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/tests/test_python_bindings.py +0 -0
  187. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/tests/test_scope_trail.cpp +0 -0
  188. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/tests/test_scoping.cpp +0 -0
  189. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/tests/test_surface.cpp +0 -0
  190. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/tests/test_tail_calls.cpp +0 -0
  191. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/tests/test_text.cpp +0 -0
  192. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/tests/test_transforms.cpp +0 -0
  193. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/tests/test_use.cpp +0 -0
  194. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/tests/test_value.cpp +0 -0
  195. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/tests/test_viewport_params.cpp +0 -0
  196. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/tools/cli/CMakeLists.txt +0 -0
  197. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/tools/cli/cli_lib.cpp +0 -0
  198. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/tools/cli/cli_lib.hpp +0 -0
  199. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/tools/cli/main.cpp +0 -0
  200. {openscad_cpp_evaluator-0.29.0 → openscad_cpp_evaluator-0.29.2}/uv.lock +0 -0
@@ -57,7 +57,27 @@ jobs:
57
57
  CIBW_SKIP: "*-musllinux*"
58
58
  # cibuildwheel builds win32 in addition to win_amd64 by default;
59
59
  # this project only targets 64-bit Windows.
60
- CIBW_ARCHS_WINDOWS: AMD64
60
+ # ARM64 is cross-compiled here on the x86_64 runner rather than
61
+ # built on a windows-11-arm one, because bison and flex are host
62
+ # build tools: they generate C++ that MSVC then cross-compiles for
63
+ # ARM64. MSYS2 -- which is where this build gets a bison new enough
64
+ # for the grammar (3.8+, winflexbison3 ships 3.7.4) -- has no native
65
+ # ARM64 build at all, so building on an ARM runner would have to
66
+ # emulate it. cibuildwheel skips CIBW_TEST_COMMAND for the
67
+ # cross-compiled wheel, since an x86_64 host cannot run it.
68
+ CIBW_ARCHS_WINDOWS: "AMD64 ARM64"
69
+ # delvewheel vendors the MSVC runtime into the wheel, but on an
70
+ # x86_64 host it can only find the x86_64 copy -- repairing the
71
+ # cross-built ARM64 wheel died with "Unable to find library:
72
+ # msvcp140.dll". The ARM64 wheel therefore leaves the runtime to
73
+ # the system, which is what every Python extension on Windows
74
+ # already assumes (CPython itself needs the redistributable).
75
+ # AMD64 keeps vendoring it, unchanged.
76
+ CIBW_REPAIR_WHEEL_COMMAND_WINDOWS: >-
77
+ cmd /c "echo {wheel} | findstr /C:win_arm64 >nul &&
78
+ (delvewheel repair --exclude msvcp140.dll --exclude vcruntime140.dll
79
+ --exclude vcruntime140_1.dll -w {dest_dir} {wheel}) ||
80
+ (delvewheel repair -w {dest_dir} {wheel})"
61
81
  CIBW_MANYLINUX_X86_64_IMAGE: manylinux_2_28
62
82
  CIBW_MANYLINUX_AARCH64_IMAGE: manylinux_2_28
63
83
  # manylinux_2_28 (AlmaLinux 8) uses dnf, not apt -- but its default
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openscad_cpp_evaluator
3
- Version: 0.29.0
3
+ Version: 0.29.2
4
4
  Summary: C++ OpenSCAD evaluator with Python bindings
5
5
  Author: NeuSCAD Contributors
6
6
  License-Expression: MIT
@@ -39,7 +39,10 @@ void writeObj(const std::string& path, const std::vector<ColoredBody>& bodies);
39
39
  void writeOff(const std::string& path, const std::vector<ColoredBody>& bodies);
40
40
 
41
41
  // 3MF: one mesh object + a base-color colorgroup per body (skipping empty
42
- // bodies), written as a stored (uncompressed) ZIP -- see zip_stored.hpp.
42
+ // bodies), written as a DEFLATE-compressed ZIP (writeDeflateZip, see
43
+ // zip_stored.hpp) -- the XML is highly compressible, so storing it would
44
+ // make the file several times larger for nothing. An entry that does not
45
+ // actually shrink is stored raw, as any ZIP writer would.
43
46
  // Mirrors export.py's write_3mf's XML shape exactly (core + material
44
47
  // namespaces, %.6g vertex formatting); throws std::runtime_error if there's
45
48
  // no geometry to export.
@@ -4,7 +4,7 @@ build-backend = "scikit_build_core.build"
4
4
 
5
5
  [project]
6
6
  name = "openscad_cpp_evaluator"
7
- version = "0.29.0"
7
+ version = "0.29.2"
8
8
  description = "C++ OpenSCAD evaluator with Python bindings"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -0,0 +1,246 @@
1
+ #include "builtins.hpp"
2
+
3
+ #include "openscad_cpp_evaluator/call_args.hpp"
4
+ #include "openscad_cpp_evaluator/evaluator.hpp"
5
+
6
+ #include <manifold/polygon.h>
7
+
8
+ namespace oscadeval {
9
+
10
+ // hull()/minkowski() -- like union/difference/intersection, these splice
11
+ // their children transparently in the CSG tree (resolve just evaluates
12
+ // them for the side effect); the actual hull/minkowski-sum only happens
13
+ // once every child's real geometry exists, in generate. Mirrors
14
+ // _resolve_hull/_resolve_minkowski (both bodies of the same one-line
15
+ // "evaluate children, return {}" shape as _resolve_transform).
16
+
17
+ CSGParams resolveHull(Evaluator& ev, const oscad::ModularCall& node, EvalContext& ctx) {
18
+ auto [args, effCtx] = resolveCallArgs(ev, node.arguments, ctx);
19
+ (void)args;
20
+ ev.evalChildren(node.children, effCtx);
21
+ return CSGParams{};
22
+ }
23
+
24
+ // hull() of the foreground children: an all-3D hull if any child has a
25
+ // body, else an all-2D hull over sections -- mirrors _generate_hull exactly
26
+ // (a 2D/3D mix only ever hulls the 3D bodies, dropping any 2D sibling,
27
+ // matching the reference's own `if bodies_3d: ... else: sections...`
28
+ // either/or dispatch). background/highlight/show_only bodies pass through
29
+ // untouched, same as union/difference/intersection.
30
+ std::vector<ColoredBody> generateHull(Evaluator&, const CSGParams&, const std::vector<std::unique_ptr<CSGNode>>& children,
31
+ const oscad::ASTNode&) {
32
+ const std::vector<ColoredBody> bodies = flattenCsgTree(children);
33
+ if (bodies.empty()) return {};
34
+ const RoleSplit split = splitByRole(bodies);
35
+
36
+ std::optional<ColoredBody> hullResult;
37
+ if (!split.foreground.empty()) {
38
+ std::vector<manifold::Manifold> bodies3d;
39
+ for (const ColoredBody& c : split.foreground) {
40
+ if (c.body) bodies3d.push_back(*c.body);
41
+ }
42
+ if (!bodies3d.empty()) {
43
+ ColoredBody cb;
44
+ cb.body = manifold::Manifold::Hull(bodies3d);
45
+ cb.color = split.foreground.front().color;
46
+ hullResult = std::move(cb);
47
+ } else {
48
+ std::vector<manifold::CrossSection> sections;
49
+ for (const ColoredBody& c : split.foreground) {
50
+ if (c.section) sections.push_back(*c.section);
51
+ }
52
+ if (!sections.empty()) {
53
+ ColoredBody cb;
54
+ cb.section = manifold::CrossSection::Hull(sections);
55
+ cb.color = split.foreground.front().color;
56
+ hullResult = std::move(cb);
57
+ }
58
+ }
59
+ }
60
+
61
+ std::vector<ColoredBody> result;
62
+ if (hullResult) result.push_back(std::move(*hullResult));
63
+ result.insert(result.end(), split.background.begin(), split.background.end());
64
+ result.insert(result.end(), split.highlight.begin(), split.highlight.end());
65
+ result.insert(result.end(), split.showOnly.begin(), split.showOnly.end());
66
+ result.insert(result.end(), split.displayOnly.begin(), split.displayOnly.end());
67
+ return result;
68
+ }
69
+
70
+ CSGParams resolveMinkowski(Evaluator& ev, const oscad::ModularCall& node, EvalContext& ctx) {
71
+ auto [args, effCtx] = resolveCallArgs(ev, node.arguments, ctx);
72
+ (void)args;
73
+ ev.evalChildren(node.children, effCtx);
74
+ return CSGParams{};
75
+ }
76
+
77
+
78
+ namespace {
79
+
80
+ // The convex pieces of a 2D shape, as point lists.
81
+ //
82
+ // A Minkowski sum only has a closed form for convex operands -- there it
83
+ // is the convex hull of every pairwise sum -- so a shape that is not
84
+ // convex has to be cut into pieces that are. A convex outline with no
85
+ // holes is already one piece, which is the common case (a circle being
86
+ // swept over something) and much cheaper than triangulating it.
87
+ std::vector<manifold::SimplePolygon> convexPieces(const manifold::CrossSection& section) {
88
+ const manifold::Polygons polys = section.ToPolygons();
89
+ std::vector<manifold::SimplePolygon> out;
90
+
91
+ if (polys.size() == 1) {
92
+ const manifold::SimplePolygon& ring = polys[0];
93
+ bool convex = ring.size() >= 3;
94
+ int sign = 0;
95
+ for (size_t i = 0; convex && i < ring.size(); ++i) {
96
+ const manifold::vec2& a = ring[i];
97
+ const manifold::vec2& b = ring[(i + 1) % ring.size()];
98
+ const manifold::vec2& c = ring[(i + 2) % ring.size()];
99
+ const double cross = (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x);
100
+ if (std::abs(cross) < 1e-12) continue; // collinear, no turn
101
+ const int s = cross > 0 ? 1 : -1;
102
+ if (sign == 0) sign = s;
103
+ else if (s != sign) convex = false;
104
+ }
105
+ if (convex) {
106
+ out.push_back(ring);
107
+ return out;
108
+ }
109
+ }
110
+
111
+ for (const manifold::ivec3& tri : manifold::Triangulate(polys)) {
112
+ // Triangulate indexes the contours end to end.
113
+ std::vector<manifold::vec2> flat;
114
+ for (const manifold::SimplePolygon& ring : polys)
115
+ flat.insert(flat.end(), ring.begin(), ring.end());
116
+ if (static_cast<size_t>(tri.x) >= flat.size() || static_cast<size_t>(tri.y) >= flat.size() ||
117
+ static_cast<size_t>(tri.z) >= flat.size()) {
118
+ continue;
119
+ }
120
+ out.push_back({flat[tri.x], flat[tri.y], flat[tri.z]});
121
+ }
122
+ return out;
123
+ }
124
+
125
+ // The 2D Minkowski sum of two shapes.
126
+ //
127
+ // Manifold has no 2D Minkowski, so this is the boundary-sweep identity:
128
+ // for B convex and containing the origin,
129
+ //
130
+ // A (+) B = A union (boundary of A (+) B)
131
+ //
132
+ // and the boundary is a chain of segments, each of which sums with a
133
+ // convex B to the hull of B at its two ends. So sweeping B along every
134
+ // edge of A and unioning A itself is the whole answer -- A is never cut
135
+ // up, however concave it is or however many holes it has. Only B is,
136
+ // and only because the per-segment hull needs it convex.
137
+ //
138
+ // The two conditions on B are met rather than assumed: it is decomposed
139
+ // into convex pieces (Minkowski distributes over union, so the pieces'
140
+ // sums are unioned), and each piece is shifted onto the origin with the
141
+ // shift undone afterwards, since A (+) B = ((A (+) (B - c)) + c).
142
+ //
143
+ // ponytail: one hull per edge of A per convex piece of B. A piece count
144
+ // of one is the case that turns up -- a circle swept over something --
145
+ // and then it is simply one hull per edge.
146
+ manifold::CrossSection minkowski2d(const manifold::CrossSection& a, const manifold::CrossSection& b) {
147
+ const manifold::Polygons outline = a.ToPolygons();
148
+ std::vector<manifold::CrossSection> parts;
149
+
150
+ for (const manifold::SimplePolygon& piece : convexPieces(b)) {
151
+ if (piece.size() < 3) continue;
152
+ // Bring the piece onto the origin; the sum is shifted back after.
153
+ manifold::vec2 shift = piece[0];
154
+ manifold::SimplePolygon centred;
155
+ centred.reserve(piece.size());
156
+ for (const manifold::vec2& q : piece) centred.push_back({q.x - shift.x, q.y - shift.y});
157
+
158
+ std::vector<manifold::CrossSection> swept;
159
+ // A itself: only sound because `centred` contains the origin.
160
+ swept.push_back(a);
161
+ for (const manifold::SimplePolygon& ring : outline) {
162
+ for (size_t i = 0; i < ring.size(); ++i) {
163
+ const manifold::vec2& v0 = ring[i];
164
+ const manifold::vec2& v1 = ring[(i + 1) % ring.size()];
165
+ manifold::SimplePolygon ends;
166
+ ends.reserve(centred.size() * 2);
167
+ for (const manifold::vec2& q : centred) {
168
+ ends.push_back({v0.x + q.x, v0.y + q.y});
169
+ ends.push_back({v1.x + q.x, v1.y + q.y});
170
+ }
171
+ swept.push_back(manifold::CrossSection::Hull(ends));
172
+ }
173
+ }
174
+ manifold::CrossSection sum =
175
+ manifold::CrossSection::BatchBoolean(swept, manifold::OpType::Add);
176
+ parts.push_back(sum.Translate(shift));
177
+ }
178
+
179
+ if (parts.empty()) return manifold::CrossSection();
180
+ return manifold::CrossSection::BatchBoolean(parts, manifold::OpType::Add);
181
+ }
182
+
183
+ } // namespace
184
+
185
+ // minkowski() over 3D bodies, or over 2D sections when that is what it
186
+ // was given. 2D used to be dropped on the floor -- silently, so
187
+ // `linear_extrude() minkowski() { square(); circle(); }` produced nothing
188
+ // at all where the reference produces the rounded square you asked for.
189
+ std::vector<ColoredBody> generateMinkowski(Evaluator& ev, const CSGParams&, const std::vector<std::unique_ptr<CSGNode>>& children,
190
+ const oscad::ASTNode& node) {
191
+ const std::vector<ColoredBody> bodies = flattenCsgTree(children);
192
+ const RoleSplit split = splitByRole(bodies);
193
+
194
+ std::vector<const ColoredBody*> bodies3d;
195
+ std::vector<const ColoredBody*> sections2d;
196
+ for (const ColoredBody& c : split.foreground) {
197
+ if (c.body) bodies3d.push_back(&c);
198
+ else if (c.section) sections2d.push_back(&c);
199
+ }
200
+
201
+ std::vector<ColoredBody> passthrough;
202
+ passthrough.insert(passthrough.end(), split.background.begin(), split.background.end());
203
+ passthrough.insert(passthrough.end(), split.highlight.begin(), split.highlight.end());
204
+ passthrough.insert(passthrough.end(), split.showOnly.begin(), split.showOnly.end());
205
+ passthrough.insert(passthrough.end(), split.displayOnly.begin(), split.displayOnly.end());
206
+
207
+ // 2D only: sum the sections instead. A mix of 2D and 3D is the
208
+ // reference's error case, and the 3D bodies win here as they do there.
209
+ if (bodies3d.empty() && sections2d.size() >= 2) {
210
+ manifold::CrossSection acc = *sections2d.front()->section;
211
+ for (size_t i = 1; i < sections2d.size(); ++i)
212
+ acc = minkowski2d(acc, *sections2d[i]->section);
213
+ ColoredBody cb;
214
+ cb.section = std::move(acc);
215
+ cb.color = sections2d.front()->color;
216
+ std::vector<ColoredBody> out = {std::move(cb)};
217
+ out.insert(out.end(), passthrough.begin(), passthrough.end());
218
+ return out;
219
+ }
220
+ if (bodies3d.empty() && sections2d.size() == 1) {
221
+ std::vector<ColoredBody> out = {*sections2d.front()};
222
+ out.insert(out.end(), passthrough.begin(), passthrough.end());
223
+ return out;
224
+ }
225
+ if (bodies3d.empty()) return passthrough;
226
+ if (bodies3d.size() == 1) {
227
+ std::vector<ColoredBody> result = {*bodies3d.front()};
228
+ result.insert(result.end(), passthrough.begin(), passthrough.end());
229
+ return result;
230
+ }
231
+
232
+ manifold::Manifold result = *bodies3d.front()->body;
233
+ for (size_t i = 1; i < bodies3d.size(); ++i) result = result.MinkowskiSum(*bodies3d[i]->body);
234
+ if (result.Status() != manifold::Manifold::Error::NoError) {
235
+ ev.warn("minkowski: result is not manifold", &node.position());
236
+ }
237
+
238
+ ColoredBody cb;
239
+ cb.body = std::move(result);
240
+ cb.color = bodies3d.front()->color;
241
+ std::vector<ColoredBody> out = {std::move(cb)};
242
+ out.insert(out.end(), passthrough.begin(), passthrough.end());
243
+ return out;
244
+ }
245
+
246
+ } // namespace oscadeval
@@ -317,3 +317,126 @@ TEST(PolyhedronFaces, ADegenerateFaceDoesNotThrowOrHang) {
317
317
  )");
318
318
  EXPECT_EQ(e.bodies.size(), 1u);
319
319
  }
320
+
321
+ // -- 2D minkowski ---------------------------------------------------------
322
+
323
+ // minkowski() used to drop 2D sections on the floor, silently: this
324
+ // produced no geometry at all where the reference produces the rounded
325
+ // square asked for. Manifold has no 2D Minkowski, so it is built from
326
+ // convex pieces and hulls -- see minkowski2d().
327
+ TEST(Minkowski2D, RoundsAConvexOutline) {
328
+ Evaluated e = evaluateSrc(
329
+ "linear_extrude(6) minkowski() { square([30,20], center=true); circle(4, $fn=24); }");
330
+ ASSERT_EQ(e.bodies.size(), 1u);
331
+ ASSERT_TRUE(e.bodies[0].body.has_value());
332
+ // A 30x20 rectangle grown by 4: the rectangle, four 4-wide sides, and
333
+ // the corners adding up to one circle. Times 6 high.
334
+ const double expected = (30.0 * 20.0 + 2 * 4 * (30 + 20) + M_PI * 16.0) * 6.0;
335
+ EXPECT_NEAR(e.bodies[0].body->Volume(), expected, expected * 0.01);
336
+ const manifold::Box box = e.bodies[0].body->BoundingBox();
337
+ EXPECT_NEAR(box.max.x - box.min.x, 38.0, 0.01);
338
+ EXPECT_NEAR(box.max.y - box.min.y, 28.0, 0.01);
339
+ }
340
+
341
+ // The case a hull cannot fake: a concave outline has to stay concave.
342
+ TEST(Minkowski2D, KeepsAConcaveOutlineConcave) {
343
+ Evaluated e = evaluateSrc(R"(
344
+ linear_extrude(4) minkowski() {
345
+ polygon([[0,0],[40,0],[40,25],[22,25],[22,12],[0,12]]);
346
+ circle(3, $fn=16);
347
+ }
348
+ )");
349
+ ASSERT_EQ(e.bodies.size(), 1u);
350
+ const manifold::Box box = e.bodies[0].body->BoundingBox();
351
+ EXPECT_NEAR(box.max.x - box.min.x, 46.0, 0.2);
352
+ EXPECT_NEAR(box.max.y - box.min.y, 31.0, 0.2);
353
+ // The notch has to still be a notch. A volume bound is too blunt to
354
+ // say so -- hulling the pieces rather than unioning them lands within
355
+ // 1% of the right answer here -- so probe the hole itself: the L is
356
+ // missing the region x 0..22, y 12..25, and rounding by 3 does not
357
+ // reach anywhere near the middle of it.
358
+ // The probe has to sit inside what a hull WOULD cover and outside the
359
+ // real shape, or it proves nothing: the hull runs from (0,12) to
360
+ // (22,25), and the rounded L stops at y=15 for any x below 22.
361
+ const manifold::Manifold probe =
362
+ manifold::Manifold::Cube({1.0, 1.0, 8.0}).Translate({17.0, 17.0, -2.0});
363
+ EXPECT_TRUE((*e.bodies[0].body ^ probe).IsEmpty()) << "the notch was filled in";
364
+ // ...while somewhere solid is genuinely solid.
365
+ const manifold::Manifold inside =
366
+ manifold::Manifold::Cube({2.0, 2.0, 8.0}).Translate({30.0, 5.0, -2.0});
367
+ EXPECT_FALSE((*e.bodies[0].body ^ inside).IsEmpty());
368
+ }
369
+
370
+ TEST(Minkowski2D, OneSectionIsHandedBackUnchanged) {
371
+ Evaluated e = evaluateSrc("linear_extrude(3) minkowski() { square([10,10]); }");
372
+ ASSERT_EQ(e.bodies.size(), 1u);
373
+ EXPECT_NEAR(e.bodies[0].body->Volume(), 300.0, 1e-6);
374
+ }
375
+
376
+ TEST(Minkowski2D, ThreeDStillWorksAndStillWins) {
377
+ Evaluated a = evaluateSrc("minkowski() { cube([30,20,6], center=true); sphere(3, $fn=12); }");
378
+ ASSERT_EQ(a.bodies.size(), 1u);
379
+ EXPECT_GT(a.bodies[0].body->Volume(), 30.0 * 20.0 * 6.0);
380
+ }
381
+
382
+ // The edge sweep is only valid when the shape being swept is convex and
383
+ // contains the origin, and when a third operand is folded rather than
384
+ // unioned in. Each of these fails, by a measurable amount, if the
385
+ // corresponding step is skipped -- so each is here on its own.
386
+
387
+ TEST(Minkowski2D, SweepsAConcaveSweeperByItsConvexPieces) {
388
+ // Hulling the sweeper instead of decomposing it computes A (+) hull(B)
389
+ // and comes out at 5092 against the reference's 4992.
390
+ Evaluated e = evaluateSrc(R"(
391
+ linear_extrude(4) minkowski() {
392
+ polygon([[0,0],[40,0],[40,25],[22,25],[22,12],[0,12]]);
393
+ polygon([[0,0],[8,0],[8,8],[5,8],[5,3],[0,3]]);
394
+ }
395
+ )");
396
+ ASSERT_EQ(e.bodies.size(), 1u);
397
+ EXPECT_NEAR(e.bodies[0].body->Volume(), 4992.0, 5.0);
398
+ }
399
+
400
+ TEST(Minkowski2D, ASweeperAwayFromTheOriginStillLandsInTheRightPlace) {
401
+ // The identity unions A itself, which only holds when the sweeper
402
+ // contains the origin. Without shifting it there and back, the result
403
+ // keeps an untranslated copy of A and starts at x=0 instead of x=16.
404
+ Evaluated e = evaluateSrc(R"(
405
+ linear_extrude(4) minkowski() {
406
+ polygon([[0,0],[40,0],[40,25],[22,25],[22,12],[0,12]]);
407
+ translate([20,0]) circle(4, $fn=24);
408
+ }
409
+ )");
410
+ ASSERT_EQ(e.bodies.size(), 1u);
411
+ const manifold::Box box = e.bodies[0].body->BoundingBox();
412
+ EXPECT_NEAR(box.min.x, 16.0, 0.2);
413
+ EXPECT_NEAR(e.bodies[0].body->Volume(), 5120.47, 5.0);
414
+ }
415
+
416
+ TEST(Minkowski2D, ThreeOperandsAreSummedInTurnNotUnioned) {
417
+ // A (+) B (+) C, not A (+) (B union C) -- which would give 3711.
418
+ Evaluated e = evaluateSrc(
419
+ "linear_extrude(4) minkowski() { square([30,20], center=true);"
420
+ " circle(3, $fn=16); square([6,2], center=true); }");
421
+ ASSERT_EQ(e.bodies.size(), 1u);
422
+ EXPECT_NEAR(e.bodies[0].body->Volume(), 4670.21, 5.0);
423
+ const manifold::Box box = e.bodies[0].body->BoundingBox();
424
+ EXPECT_NEAR(box.max.x - box.min.x, 42.0, 0.2);
425
+ }
426
+
427
+ TEST(Minkowski2D, AShapeWithAHoleHasBothItsContoursSwept) {
428
+ // Only the boundary of the shape being swept along is walked, so a
429
+ // hole is not a special case -- it is another contour.
430
+ Evaluated e = evaluateSrc(R"(
431
+ linear_extrude(3) minkowski() {
432
+ difference() { square([40,30], center=true); circle(8, $fn=24); }
433
+ circle(2, $fn=16);
434
+ }
435
+ )");
436
+ ASSERT_EQ(e.bodies.size(), 1u);
437
+ EXPECT_NEAR(e.bodies[0].body->Volume(), 4141.98, 5.0);
438
+ // The hole shrinks by the sweep rather than filling in.
439
+ const manifold::Manifold probe =
440
+ manifold::Manifold::Cube({1.0, 1.0, 6.0}).Translate({-0.5, -0.5, -1.0});
441
+ EXPECT_TRUE((*e.bodies[0].body ^ probe).IsEmpty()) << "the hole was filled in";
442
+ }
@@ -77,6 +77,40 @@ TEST(ImportModuleContext, ThreeMfRoundTripPreservesVolume) {
77
77
  std::filesystem::remove(path);
78
78
  }
79
79
 
80
+ // The .3mf writeThreeMf() produces is DEFLATE-compressed, not stored. The
81
+ // round-trip test above passes either way -- the reader accepts both
82
+ // methods -- so without this a regression to STORED would show up only as
83
+ // files several times bigger than they need to be.
84
+ TEST(ImportModuleContext, ThreeMfIsDeflateCompressed) {
85
+ const auto path = tempPath("cube_compressed.3mf");
86
+ writeCubeAs(path, &writeThreeMf);
87
+
88
+ std::ifstream in(path, std::ios::binary);
89
+ ASSERT_TRUE(in);
90
+ std::vector<uint8_t> buf((std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>());
91
+ in.close();
92
+
93
+ bool sawModel = false;
94
+ for (size_t i = 0; i + 46 <= buf.size(); ++i) {
95
+ if (!(buf[i] == 0x50 && buf[i + 1] == 0x4B && buf[i + 2] == 0x01 && buf[i + 3] == 0x02)) continue;
96
+ const uint16_t method = static_cast<uint16_t>(buf[i + 10] | (buf[i + 11] << 8));
97
+ const uint32_t compressedSize =
98
+ static_cast<uint32_t>(buf[i + 20] | (buf[i + 21] << 8) | (buf[i + 22] << 16) | (buf[i + 23] << 24));
99
+ const uint32_t rawSize =
100
+ static_cast<uint32_t>(buf[i + 24] | (buf[i + 25] << 8) | (buf[i + 26] << 16) | (buf[i + 27] << 24));
101
+ const uint16_t nameLen = static_cast<uint16_t>(buf[i + 28] | (buf[i + 29] << 8));
102
+ if (i + 46 + nameLen > buf.size()) continue;
103
+ const std::string name(reinterpret_cast<const char*>(&buf[i + 46]), nameLen);
104
+ if (name.find("3dmodel.model") == std::string::npos) continue;
105
+ sawModel = true;
106
+ EXPECT_EQ(method, 8) << "3dmodel.model stored uncompressed";
107
+ EXPECT_LT(compressedSize, rawSize) << "compressed " << compressedSize << " vs raw " << rawSize;
108
+ }
109
+ EXPECT_TRUE(sawModel) << "no 3dmodel.model entry in the archive";
110
+
111
+ std::filesystem::remove(path);
112
+ }
113
+
80
114
  TEST(ImportModuleContext, UnsupportedExtensionErrors) {
81
115
  Evaluator ev;
82
116
  auto ast = parseSrc("import(\"nope.xyz\");");
@@ -1,6 +1,12 @@
1
1
  // Direct tests for zip_stored.hpp -- writeStoredZip()'s own STORED-entry
2
2
  // round trip, plus reading a DEFLATE-compressed (method 8) entry, which is
3
3
  // what most third-party ZIP/3MF writers actually emit.
4
+ //
5
+ // Also what writeDeflateZip() and writeThreeMf() put in the archive. That
6
+ // went untested: the round-trip tests pass either way, since the reader
7
+ // handles both methods, so a silent regression to STORED would only show
8
+ // up as files several times larger. The method byte is read out of the
9
+ // central directory here so it cannot regress unnoticed.
4
10
 
5
11
  #include "openscad_cpp_evaluator/zip_stored.hpp"
6
12
 
@@ -169,3 +175,81 @@ TEST(ZipStored, UnsupportedCompressionMethodThrows) {
169
175
  EXPECT_THROW(readStoredZipEntry(path.string(), "hello.txt"), std::runtime_error);
170
176
  std::filesystem::remove(path);
171
177
  }
178
+
179
+
180
+ // --- what we WRITE, not just what we can read -------------------------
181
+
182
+ namespace {
183
+
184
+ // The compression method of the first central-directory entry whose name
185
+ // ends with `suffix`. Read from the central directory rather than the local
186
+ // header, since that is the authoritative copy.
187
+ uint16_t centralDirMethodBySuffix(const std::string& path, const std::string& suffix) {
188
+ std::ifstream in(path, std::ios::binary);
189
+ EXPECT_TRUE(in) << "cannot open " << path;
190
+ std::vector<uint8_t> buf((std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>());
191
+ // Walk central-directory headers by signature; these archives are small
192
+ // and have no ZIP comment, so a forward scan is enough.
193
+ for (size_t i = 0; i + 46 <= buf.size(); ++i) {
194
+ if (!(buf[i] == 0x50 && buf[i + 1] == 0x4B && buf[i + 2] == 0x01 && buf[i + 3] == 0x02)) continue;
195
+ const uint16_t method = static_cast<uint16_t>(buf[i + 10] | (buf[i + 11] << 8));
196
+ const uint16_t nameLen = static_cast<uint16_t>(buf[i + 28] | (buf[i + 29] << 8));
197
+ if (i + 46 + nameLen > buf.size()) continue;
198
+ const std::string name(reinterpret_cast<const char*>(&buf[i + 46]), nameLen);
199
+ if (name.size() >= suffix.size() && name.compare(name.size() - suffix.size(), suffix.size(), suffix) == 0)
200
+ return method;
201
+ }
202
+ ADD_FAILURE() << "no central-directory entry ending in " << suffix;
203
+ return 0xFFFF;
204
+ }
205
+
206
+ // Compressible: long runs, which is what 3MF's indented XML looks like.
207
+ std::vector<uint8_t> compressibleBytes(size_t n) {
208
+ std::vector<uint8_t> out;
209
+ out.reserve(n);
210
+ const std::string unit = "\t\t\t\t\t<vertex x=\"0\" y=\"0\" z=\"0\" />\n";
211
+ while (out.size() < n) out.insert(out.end(), unit.begin(), unit.end());
212
+ out.resize(n);
213
+ return out;
214
+ }
215
+
216
+ } // namespace
217
+
218
+ TEST(ZipStored, DeflateZipReallyWritesMethod8) {
219
+ const auto path = tempPath("deflate_method.zip");
220
+ const auto data = compressibleBytes(64 * 1024);
221
+ writeDeflateZip(path.string(), {ZipEntry{"3D/3dmodel.model", data}});
222
+ EXPECT_EQ(centralDirMethodBySuffix(path.string(), "3dmodel.model"), 8);
223
+ // And it still reads back byte for byte.
224
+ EXPECT_EQ(readStoredZipEntry(path.string(), "3D/3dmodel.model"), data);
225
+ std::filesystem::remove(path);
226
+ }
227
+
228
+ TEST(ZipStored, DeflateZipActuallyShrinksCompressibleData) {
229
+ const auto path = tempPath("deflate_size.zip");
230
+ const auto data = compressibleBytes(256 * 1024);
231
+ writeDeflateZip(path.string(), {ZipEntry{"3D/3dmodel.model", data}});
232
+ const auto onDisk = std::filesystem::file_size(path);
233
+ // Highly repetitive input; anything near its original size means the
234
+ // compressor did not run.
235
+ EXPECT_LT(onDisk, data.size() / 4) << "archive " << onDisk << " for " << data.size() << " bytes of input";
236
+ std::filesystem::remove(path);
237
+ }
238
+
239
+ TEST(ZipStored, DeflateZipStoresDataThatWouldNotShrink) {
240
+ const auto path = tempPath("deflate_tiny.zip");
241
+ const std::vector<uint8_t> tiny{'x'};
242
+ writeDeflateZip(path.string(), {ZipEntry{"a.txt", tiny}});
243
+ EXPECT_EQ(centralDirMethodBySuffix(path.string(), "a.txt"), 0);
244
+ EXPECT_EQ(readStoredZipEntry(path.string(), "a.txt"), tiny);
245
+ std::filesystem::remove(path);
246
+ }
247
+
248
+ TEST(ZipStored, StoredZipWritesMethod0) {
249
+ // The control for the tests above: same call shape, opposite method.
250
+ const auto path = tempPath("stored_method.zip");
251
+ const auto data = compressibleBytes(64 * 1024);
252
+ writeStoredZip(path.string(), {ZipEntry{"3D/3dmodel.model", data}});
253
+ EXPECT_EQ(centralDirMethodBySuffix(path.string(), "3dmodel.model"), 0);
254
+ std::filesystem::remove(path);
255
+ }