openscad-cpp-evaluator 0.33.0__tar.gz → 0.34.0__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 (203) hide show
  1. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/CLAUDE.md +41 -2
  2. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/PKG-INFO +1 -1
  3. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/include/openscad_cpp_evaluator/evaluator.hpp +6 -0
  4. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/pyproject.toml +1 -1
  5. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/src/CMakeLists.txt +1 -0
  6. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/src/builtins/builtins.hpp +9 -0
  7. openscad_cpp_evaluator-0.34.0/src/builtins/dxf_dim.cpp +221 -0
  8. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/src/builtins/function_builtins.cpp +7 -2
  9. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/src/builtins/registry.cpp +3 -0
  10. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/src/builtins/topology.cpp +70 -0
  11. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/src/csg_generate.cpp +107 -1
  12. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/src/eval_context.cpp +6 -0
  13. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/tests/test_booleans.cpp +180 -0
  14. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/tests/test_dxf_svg_import.cpp +131 -0
  15. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/tests/test_scoping.cpp +12 -0
  16. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/uv.lock +1 -1
  17. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/.github/workflows/ci.yml +0 -0
  18. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/.github/workflows/wheels.yml +0 -0
  19. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/.gitignore +0 -0
  20. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/.gitmodules +0 -0
  21. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/CMakeLists.txt +0 -0
  22. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/LICENSE +0 -0
  23. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/README.md +0 -0
  24. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/bindings/CMakeLists.txt +0 -0
  25. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/bindings/ast_to_py.cpp +0 -0
  26. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/bindings/module.cpp +0 -0
  27. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/cmake/embed_font.cmake +0 -0
  28. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/examples/CMakeLists.txt +0 -0
  29. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/examples/eval_perf_benchmark.cpp +0 -0
  30. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/examples/manifold_cache_reuse.cpp +0 -0
  31. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/examples/minimal_debugger.cpp +0 -0
  32. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
  33. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/external/openscad_cpp_parser/.gitignore +0 -0
  34. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/external/openscad_cpp_parser/CLAUDE.md +0 -0
  35. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
  36. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/external/openscad_cpp_parser/README.md +0 -0
  37. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
  38. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
  39. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
  40. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
  41. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
  42. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
  43. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
  44. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
  45. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
  46. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
  47. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
  48. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
  49. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
  50. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
  51. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
  52. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
  53. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/external/openscad_cpp_parser/src/api.cpp +0 -0
  54. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
  55. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
  56. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
  57. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
  58. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
  59. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
  60. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
  61. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
  62. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/external/openscad_cpp_parser/src/comments.cpp +0 -0
  63. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
  64. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
  65. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
  66. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
  67. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
  68. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
  69. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
  70. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/external/openscad_cpp_parser/src/position.cpp +0 -0
  71. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
  72. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/external/openscad_cpp_parser/src/scope.cpp +0 -0
  73. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
  74. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
  75. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
  76. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
  77. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
  78. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
  79. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
  80. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
  81. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
  82. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
  83. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
  84. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
  85. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
  86. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
  87. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
  88. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
  89. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
  90. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
  91. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
  92. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
  93. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
  94. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
  95. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
  96. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
  97. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
  98. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
  99. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/include/openscad_cpp_evaluator/bytecode.hpp +0 -0
  100. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
  101. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
  102. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
  103. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
  104. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
  105. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
  106. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
  107. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
  108. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
  109. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
  110. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
  111. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
  112. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
  113. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/include/openscad_cpp_evaluator/export.hpp +0 -0
  114. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/include/openscad_cpp_evaluator/font_match.hpp +0 -0
  115. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
  116. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/include/openscad_cpp_evaluator/freetype_font_provider.hpp +0 -0
  117. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
  118. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
  119. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
  120. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/include/openscad_cpp_evaluator/mesh_check.hpp +0 -0
  121. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
  122. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
  123. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/include/openscad_cpp_evaluator/profile.hpp +0 -0
  124. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
  125. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/include/openscad_cpp_evaluator/segments.hpp +0 -0
  126. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
  127. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
  128. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/include/openscad_cpp_evaluator/value.hpp +0 -0
  129. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
  130. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/python/openscad_cpp_evaluator/__init__.py +0 -0
  131. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/resources/fonts/LICENSE +0 -0
  132. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/resources/fonts/LiberationSans-Regular.ttf +0 -0
  133. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/src/builtins/booleans.cpp +0 -0
  134. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/src/builtins/call_args.cpp +0 -0
  135. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/src/builtins/color.cpp +0 -0
  136. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/src/builtins/control.cpp +0 -0
  137. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/src/builtins/extrude.cpp +0 -0
  138. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/src/builtins/import.cpp +0 -0
  139. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/src/builtins/modifiers.cpp +0 -0
  140. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/src/builtins/primitives_2d.cpp +0 -0
  141. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/src/builtins/primitives_3d.cpp +0 -0
  142. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/src/builtins/roof.cpp +0 -0
  143. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/src/builtins/segments.cpp +0 -0
  144. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/src/builtins/surface.cpp +0 -0
  145. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/src/builtins/text.cpp +0 -0
  146. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/src/builtins/transforms.cpp +0 -0
  147. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/src/bytecode_compiler.cpp +0 -0
  148. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/src/bytecode_vm.cpp +0 -0
  149. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/src/colored_body.cpp +0 -0
  150. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/src/csg_resolve.cpp +0 -0
  151. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/src/css_colors.cpp +0 -0
  152. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/src/debug/debug_repl.cpp +0 -0
  153. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/src/debug_profile.cpp +0 -0
  154. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/src/eval_error.cpp +0 -0
  155. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/src/eval_use.cpp +0 -0
  156. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/src/export.cpp +0 -0
  157. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/src/expr_eval.cpp +0 -0
  158. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/src/import/dxf_import.cpp +0 -0
  159. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/src/import/mesh_import.cpp +0 -0
  160. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/src/import/surface_load.cpp +0 -0
  161. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/src/import/svg_import.cpp +0 -0
  162. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/src/manifold_cache.cpp +0 -0
  163. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/src/mesh_check.cpp +0 -0
  164. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/src/stmt_eval.cpp +0 -0
  165. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/src/text/font_match.cpp +0 -0
  166. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/src/text/freetype_font_provider.cpp +0 -0
  167. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/src/text/text_metrics.cpp +0 -0
  168. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/src/user_calls.cpp +0 -0
  169. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/src/value.cpp +0 -0
  170. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/src/zip_stored.cpp +0 -0
  171. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/tests/CMakeLists.txt +0 -0
  172. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/tests/test_bytecode_compiler.cpp +0 -0
  173. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/tests/test_cli.cpp +0 -0
  174. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/tests/test_control_flow.cpp +0 -0
  175. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/tests/test_csg_tree.cpp +0 -0
  176. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/tests/test_debug_hooks.cpp +0 -0
  177. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/tests/test_eval_context.cpp +0 -0
  178. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/tests/test_export.cpp +0 -0
  179. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/tests/test_expr_eval.cpp +0 -0
  180. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/tests/test_extrude_roof.cpp +0 -0
  181. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/tests/test_function_builtins.cpp +0 -0
  182. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/tests/test_helpers.hpp +0 -0
  183. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/tests/test_import_export.cpp +0 -0
  184. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/tests/test_manifold_cache.cpp +0 -0
  185. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/tests/test_mesh_check.cpp +0 -0
  186. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/tests/test_multi_color_merge.cpp +0 -0
  187. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/tests/test_primitives.cpp +0 -0
  188. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/tests/test_profiling.cpp +0 -0
  189. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/tests/test_python_bindings.py +0 -0
  190. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/tests/test_scope_trail.cpp +0 -0
  191. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/tests/test_surface.cpp +0 -0
  192. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/tests/test_tail_calls.cpp +0 -0
  193. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/tests/test_text.cpp +0 -0
  194. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/tests/test_transforms.cpp +0 -0
  195. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/tests/test_unexpected_args.cpp +0 -0
  196. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/tests/test_use.cpp +0 -0
  197. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/tests/test_value.cpp +0 -0
  198. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/tests/test_viewport_params.cpp +0 -0
  199. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/tests/test_zip_stored.cpp +0 -0
  200. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/tools/cli/CMakeLists.txt +0 -0
  201. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/tools/cli/cli_lib.cpp +0 -0
  202. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/tools/cli/cli_lib.hpp +0 -0
  203. {openscad_cpp_evaluator-0.33.0 → openscad_cpp_evaluator-0.34.0}/tools/cli/main.cpp +0 -0
@@ -512,6 +512,9 @@ grep for `ponytail:`.
512
512
  `Evaluator::evaluate()` also takes an optional `viewportParams` map (added after a test-suite
513
513
  parity review found it missing entirely): seeds arbitrary `$`-prefixed entries into `ctx.dyn`
514
514
  (e.g. `$vpt`/`$vpr`/`$vpd`/`$vpf` from a GUI host's current camera, or `$t` for animation time)
515
+ — note `EvalContext::makeRoot` also seeds `$preview` to **`false`**, always: there is no preview
516
+ mode here, and leaving it undef made the common `$preview ? cheap : real` idiom take the cheap
517
+ branch by accident while warning about an unknown variable
515
518
  before evaluation starts, via a direct `ctx.dyn` write that deliberately never touches
516
519
  `ctx.dynExplicit` — lets a caller tell "the script itself assigned this `$`-var"
517
520
  (`dynExplicit`) apart from "merely present because the caller seeded it" after `evaluate()`
@@ -558,13 +561,49 @@ grep for `ponytail:`.
558
561
  color instead of preserving each part's own, matching real OpenSCAD's actual behavior once fixed
559
562
  (cross-checked triangle-color-count-and-values against the Python reference directly, not just
560
563
  “does it run”).
561
- - `src/builtins/topology.cpp` — `hull()`/`minkowski()`. Both splice their children transparently in
564
+ - `src/builtins/dxf_dim.cpp` — `dxf_dim()`/`dxf_cross()`, which read a measurement out of a DXF file
565
+ rather than out of the model. Ported from the reference's `io/dxfdim.cc` plus the DIMENSION/LINE
566
+ half of `io/DxfData.cc`: `dxf_dim()` returns the first matching DIMENSION's value by its type
567
+ (`70 & 7` — 0 rotated, 1 aligned, 2 angular, 3/4 diameter/radius, 6 ordinate; type 5 is
568
+ unsupported there too), `dxf_cross()` intersects the first two LINE entities. Note the reference's
569
+ own coordinate quirk, reproduced here: groups 11/12/16 (and 21/22/26) are scaled but NOT
570
+ origin-shifted, being extents rather than positions.
571
+
572
+ Deliberately its own group-code walk rather than sharing `src/import/dxf_import.cpp`'s: that one
573
+ produces closed contours, and these need the DIMENSION entities' seven coordinate slots and the
574
+ raw LINE endpoints, neither of which a contour list keeps. One divergence: `dxf_cross()` takes the
575
+ first two LINEs, where the reference walks every 2-point path — for a real cross (two strokes
576
+ meeting in the middle, not at their endpoints) that is the same set.
577
+
578
+ - `src/csg_generate.cpp` — `Evaluator::applyDimensionRules`, run centrally just before every node's
579
+ `GenerateFn` (and once more over the top-level list, which is an implicit union). Mirrors the
580
+ reference's `isValidDim` + `collectChildren2D`/`collectChildren3D`: a node's dimension is fixed by
581
+ its first non-background, non-empty child, and every child of the other dimension is dropped with
582
+ `Mixing 2D and 3D objects is not supported` followed by `Ignoring {N}D child object for {M}D
583
+ operation`. `linear_extrude`/`rotate_extrude`/`offset` are always-2D and `projection` always-3D, so
584
+ they skip the "mixing" line and emit only the second; `roof()` emits neither, matching the
585
+ reference. Background (`%`) children are exempt.
586
+
587
+ Doing this centrally is also what keeps `generateCsg` safe: it builds ONE result and switches on
588
+ whether it holds a `body` or a `section`, so a group that changed dimension part-way dereferenced
589
+ the empty optional -- `union() { cube(1); square(4); }` took the process down with "mutex lock
590
+ failed: Invalid argument". Filtering before dispatch means no `GenerateFn` ever sees a mixed group.
591
+ All 25 module/shape combinations were diffed against OpenSCAD 2026.02.01 warning for warning, with
592
+ the surviving geometry compared by STL volume.
593
+
594
+ - `src/builtins/topology.cpp` — `hull()`/`minkowski()`/`fill()`. All three splice their children transparently in
562
595
  the CSG tree exactly like union/difference/intersection (resolve just evaluates them for the
563
596
  side effect); `hull()` picks an all-3D (`Manifold::Hull`) or all-2D (`CrossSection::Hull`) hull of
564
597
  the *foreground* (role-split) children, falling back to sections only when no child has a body;
565
598
  `minkowski()` only ever operates on 3D bodies (a 2D sibling among the foreground children is
566
599
  silently ignored, matching the reference exactly — no 2D `minkowski_sum` fallback the way hull
567
- has one). Both pass background/highlight/show_only bodies through untouched.
600
+ has one). All pass background/highlight/show_only bodies through untouched. `fill()` is 2D-only
601
+ (a 3D foreground child draws the reference's own `fill() not yet implemented for 3D` warning and
602
+ contributes nothing): it unions the foreground sections, keeps only the outlines with positive
603
+ signed area — outer boundaries, since Manifold winds a hole clockwise — and re-unions those,
604
+ which matters because two outers can overlap once the holes between them are gone. Ported from
605
+ the reference's `applyFill2D`, and checked shape-for-shape against OpenSCAD 2026.02.01 by
606
+ extruding and comparing STL volumes.
568
607
  - `src/builtins/extrude.cpp` — `linear_extrude()`/`rotate_extrude()`/`projection(cut=)`. All three
569
608
  union every 2D child into one `CrossSection` via `toCrossSection` first. `rotate_extrude`'s
570
609
  segment count can't be resolved until generate time (it depends on the merged children's bounds,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openscad_cpp_evaluator
3
- Version: 0.33.0
3
+ Version: 0.34.0
4
4
  Summary: C++ OpenSCAD evaluator with Python bindings
5
5
  Author: NeuSCAD Contributors
6
6
  License-Expression: MIT
@@ -330,6 +330,12 @@ public:
330
330
  // free function, same reasoning as tagGenerated()/builtinChildren().
331
331
  void warn(const std::string& message, const oscad::Position* position);
332
332
 
333
+ // Applies OpenSCAD's mixed-2D/3D rules to a node's children just
334
+ // before its GenerateFn runs -- see csg_generate.cpp.
335
+ void applyDimensionRules(CSGNode& node);
336
+ // ruleValue is a DimRule; the enum is file-local to csg_generate.cpp.
337
+ void applyDimensionRulesTo(const std::vector<CSGNode*>& children, int ruleValue);
338
+
333
339
  // Apply `!`: replace the whole tree with the first show_only
334
340
  // subtree, discarding its ancestors and every sibling. See its
335
341
  // definition for why this cannot be done by tagging bodies.
@@ -4,7 +4,7 @@ build-backend = "scikit_build_core.build"
4
4
 
5
5
  [project]
6
6
  name = "openscad_cpp_evaluator"
7
- version = "0.33.0"
7
+ version = "0.34.0"
8
8
  description = "C++ OpenSCAD evaluator with Python bindings"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -29,6 +29,7 @@ add_library(openscad_cpp_evaluator STATIC
29
29
  builtins/call_args.cpp
30
30
  builtins/segments.cpp
31
31
  builtins/registry.cpp
32
+ builtins/dxf_dim.cpp
32
33
  builtins/primitives_2d.cpp
33
34
  builtins/primitives_3d.cpp
34
35
  builtins/transforms.cpp
@@ -160,6 +160,15 @@ CSGParams resolveHull(Evaluator& ev, const oscad::ModularCall& node, EvalContext
160
160
  std::vector<ColoredBody> generateHull(Evaluator& ev, const CSGParams& params,
161
161
  const std::vector<std::unique_ptr<CSGNode>>& children, const oscad::ASTNode& node);
162
162
 
163
+ // dxf_dim()/dxf_cross() -- read a dimension or a cross's centre out of a
164
+ // DXF file. Functions, not modules; dispatched from evalBuiltinFunction.
165
+ Value builtinDxfDim(Evaluator& ev, const CallArgs& args, const oscad::ASTNode& node);
166
+ Value builtinDxfCross(Evaluator& ev, const CallArgs& args, const oscad::ASTNode& node);
167
+
168
+ CSGParams resolveFill(Evaluator& ev, const oscad::ModularCall& node, EvalContext& ctx);
169
+ std::vector<ColoredBody> generateFill(Evaluator& ev, const CSGParams& params,
170
+ const std::vector<std::unique_ptr<CSGNode>>& children,
171
+ const oscad::ASTNode& node);
163
172
  CSGParams resolveMinkowski(Evaluator& ev, const oscad::ModularCall& node, EvalContext& ctx);
164
173
  std::vector<ColoredBody> generateMinkowski(Evaluator& ev, const CSGParams& params,
165
174
  const std::vector<std::unique_ptr<CSGNode>>& children,
@@ -0,0 +1,221 @@
1
+ #include "builtins.hpp"
2
+
3
+ #include "openscad_cpp_evaluator/call_args.hpp"
4
+ #include "openscad_cpp_evaluator/evaluator.hpp"
5
+
6
+ #include <cmath>
7
+ #include <filesystem>
8
+ #include <fstream>
9
+ #include <sstream>
10
+
11
+ namespace oscadeval {
12
+
13
+ // dxf_dim() / dxf_cross() -- read a measurement straight out of a DXF file
14
+ // rather than out of the model. Ported from the reference's io/dxfdim.cc
15
+ // plus the DIMENSION/LINE half of io/DxfData.cc.
16
+ //
17
+ // These share the group-code walk with src/import/dxf_import.cpp but not its
18
+ // output: that one only cares about closed contours, while these two need
19
+ // the DIMENSION entities' seven coordinate slots and the raw LINE endpoints,
20
+ // neither of which a contour list keeps.
21
+
22
+ namespace {
23
+
24
+ struct Entity {
25
+ std::string type; // "DIMENSION", "LINE", ...
26
+ std::string layer;
27
+ std::string name; // group 1: DIMENSION's text override
28
+ int dimType = 0; // group 70
29
+ double angle = 0.0; // group 50
30
+ double coords[7][2] = {}; // groups 10-16 / 20-26
31
+ std::vector<double> xverts, yverts; // groups 10/11 and 20/21, in order
32
+ };
33
+
34
+ std::string trimmed(const std::string& s) {
35
+ const size_t b = s.find_first_not_of(" \t\r\n");
36
+ if (b == std::string::npos) return "";
37
+ return s.substr(b, s.find_last_not_of(" \t\r\n") - b + 1);
38
+ }
39
+
40
+ // One entity per group-0 marker. Coordinates carry the origin shift and
41
+ // scale the reference applies while parsing, with its own exception: groups
42
+ // 11, 12 and 16 (and their 21/22/26 partners) are scaled but NOT shifted,
43
+ // since they are extents rather than positions.
44
+ std::vector<Entity> readEntities(const std::string& path, double xorigin, double yorigin, double scale) {
45
+ std::ifstream in(path);
46
+ std::vector<Entity> out;
47
+ if (!in) return out;
48
+
49
+ std::string codeLine, dataLine;
50
+ Entity cur;
51
+ bool started = false;
52
+ while (std::getline(in, codeLine) && std::getline(in, dataLine)) {
53
+ const std::string codeStr = trimmed(codeLine);
54
+ const std::string data = trimmed(dataLine);
55
+ if (codeStr.empty()) continue;
56
+ int code = 0;
57
+ try {
58
+ code = std::stoi(codeStr);
59
+ } catch (...) {
60
+ break;
61
+ }
62
+ const auto num = [&]() -> double {
63
+ try {
64
+ return std::stod(data);
65
+ } catch (...) {
66
+ return 0.0;
67
+ }
68
+ };
69
+
70
+ if (code == 0) {
71
+ if (started) out.push_back(cur);
72
+ cur = Entity{};
73
+ cur.type = data;
74
+ started = true;
75
+ continue;
76
+ }
77
+ if (!started) continue;
78
+
79
+ if (code >= 10 && code <= 16) {
80
+ cur.coords[code - 10][0] =
81
+ (code == 11 || code == 12 || code == 16) ? num() * scale : (num() - xorigin) * scale;
82
+ } else if (code >= 20 && code <= 26) {
83
+ cur.coords[code - 20][1] =
84
+ (code == 21 || code == 22 || code == 26) ? num() * scale : (num() - yorigin) * scale;
85
+ }
86
+ switch (code) {
87
+ case 1: cur.name = data; break;
88
+ case 8: cur.layer = data; break;
89
+ case 10:
90
+ case 11: cur.xverts.push_back((num() - xorigin) * scale); break;
91
+ case 20:
92
+ case 21: cur.yverts.push_back((num() - yorigin) * scale); break;
93
+ case 50: cur.angle = num(); break;
94
+ case 70:
95
+ try {
96
+ cur.dimType = std::stoi(data);
97
+ } catch (...) {
98
+ }
99
+ break;
100
+ default: break;
101
+ }
102
+ }
103
+ if (started) out.push_back(cur);
104
+ return out;
105
+ }
106
+
107
+ double deg(double r) { return r * 180.0 / 3.14159265358979323846; }
108
+ double rad(double d) { return d * 3.14159265358979323846 / 180.0; }
109
+
110
+ struct CommonArgs {
111
+ std::string path;
112
+ std::string rawFile;
113
+ std::string layer;
114
+ double xorigin = 0.0, yorigin = 0.0, scale = 1.0;
115
+ };
116
+
117
+ CommonArgs commonArgs(const CallArgs& args, const oscad::ASTNode& node) {
118
+ CommonArgs c;
119
+ const Value fileArg = getArg(args, 0, "file", Value{});
120
+ c.rawFile = std::holds_alternative<std::string>(fileArg) ? std::get<std::string>(fileArg) : fmtValue(fileArg);
121
+ c.path = resolveFilePath(fileArg, node);
122
+ const Value layerArg = getArg(args, std::nullopt, "layer", Value{});
123
+ if (const std::string* s = std::get_if<std::string>(&layerArg)) c.layer = *s;
124
+ const Value originArg = getArg(args, std::nullopt, "origin", Value{});
125
+ if (const ListPtr* l = std::get_if<ListPtr>(&originArg); l && *l && (*l)->items.size() >= 2) {
126
+ c.xorigin = toDoubleLenient((*l)->items[0]);
127
+ c.yorigin = toDoubleLenient((*l)->items[1]);
128
+ }
129
+ const Value scaleArg = getArg(args, std::nullopt, "scale", Value{1.0});
130
+ if (std::holds_alternative<double>(scaleArg)) c.scale = std::get<double>(scaleArg);
131
+ return c;
132
+ }
133
+
134
+ } // namespace
135
+
136
+ Value builtinDxfDim(Evaluator& ev, const CallArgs& args, const oscad::ASTNode& node) {
137
+ const CommonArgs c = commonArgs(args, node);
138
+ const Value nameArg = getArg(args, std::nullopt, "name", Value{});
139
+ const std::string name = std::holds_alternative<std::string>(nameArg) ? std::get<std::string>(nameArg) : "";
140
+
141
+ if (!std::filesystem::exists(c.path)) {
142
+ ev.warn("Can't open DXF file '" + c.rawFile + "'!", &node.position());
143
+ return Value{};
144
+ }
145
+ for (const Entity& e : readEntities(c.path, c.xorigin, c.yorigin, c.scale)) {
146
+ if (e.type != "DIMENSION") continue;
147
+ if (!c.layer.empty() && c.layer != e.layer) continue;
148
+ if (!name.empty() && e.name != name) continue;
149
+
150
+ const int type = e.dimType & 7;
151
+ if (type == 0) { // rotated, horizontal or vertical
152
+ const double x = e.coords[4][0] - e.coords[3][0];
153
+ const double y = e.coords[4][1] - e.coords[3][1];
154
+ return Value{std::fabs(x * std::cos(rad(e.angle)) + y * std::sin(rad(e.angle)))};
155
+ }
156
+ if (type == 1) { // aligned
157
+ const double x = e.coords[4][0] - e.coords[3][0];
158
+ const double y = e.coords[4][1] - e.coords[3][1];
159
+ return Value{std::sqrt(x * x + y * y)};
160
+ }
161
+ if (type == 2) { // angular
162
+ const double a1 = deg(std::atan2(e.coords[0][0] - e.coords[5][0], e.coords[0][1] - e.coords[5][1]));
163
+ const double a2 = deg(std::atan2(e.coords[4][0] - e.coords[3][0], e.coords[4][1] - e.coords[3][1]));
164
+ return Value{std::fabs(a1 - a2)};
165
+ }
166
+ if (type == 3 || type == 4) { // diameter or radius
167
+ const double x = e.coords[5][0] - e.coords[0][0];
168
+ const double y = e.coords[5][1] - e.coords[0][1];
169
+ return Value{std::sqrt(x * x + y * y)};
170
+ }
171
+ if (type == 6) { // ordinate
172
+ return Value{(e.dimType & 64) ? e.coords[3][0] : e.coords[3][1]};
173
+ }
174
+ // type 5 (angular 3-point) falls through, as in the reference.
175
+ ev.warn("Dimension '" + name + "' in '" + c.rawFile + "', layer '" + c.layer + "' has unsupported type!",
176
+ &node.position());
177
+ return Value{};
178
+ }
179
+ ev.warn("Can't find dimension '" + name + "' in '" + c.rawFile + "', layer '" + c.layer + "'!",
180
+ &node.position());
181
+ return Value{};
182
+ }
183
+
184
+ Value builtinDxfCross(Evaluator& ev, const CallArgs& args, const oscad::ASTNode& node) {
185
+ const CommonArgs c = commonArgs(args, node);
186
+ if (!std::filesystem::exists(c.path)) {
187
+ ev.warn("Can't open DXF file '" + c.rawFile + "'!", &node.position());
188
+ return Value{};
189
+ }
190
+
191
+ // The first two LINEs on the layer are taken to be the cross. The
192
+ // reference walks every 2-point path instead, which for a real cross is
193
+ // the same set: its two strokes meet in the middle rather than at their
194
+ // endpoints, so neither gets joined into a longer path.
195
+ double p[4][2];
196
+ int found = 0;
197
+ for (const Entity& e : readEntities(c.path, c.xorigin, c.yorigin, c.scale)) {
198
+ if (e.type != "LINE") continue;
199
+ if (!c.layer.empty() && c.layer != e.layer) continue;
200
+ if (e.xverts.size() < 2 || e.yverts.size() < 2) continue;
201
+ p[found][0] = e.xverts[0];
202
+ p[found][1] = e.yverts[0];
203
+ ++found;
204
+ p[found][0] = e.xverts[1];
205
+ p[found][1] = e.yverts[1];
206
+ ++found;
207
+ if (found == 4) {
208
+ const double x1 = p[0][0], y1 = p[0][1], x2 = p[1][0], y2 = p[1][1];
209
+ const double x3 = p[2][0], y3 = p[2][1], x4 = p[3][0], y4 = p[3][1];
210
+ const double dem = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1);
211
+ if (dem == 0.0) break; // parallel: no cross, same as the reference
212
+ const double ua = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / dem;
213
+ std::vector<Value> xy{Value{x1 + ua * (x2 - x1)}, Value{y1 + ua * (y2 - y1)}};
214
+ return Value{std::make_shared<const ValueList>(ValueList{std::move(xy)})};
215
+ }
216
+ }
217
+ ev.warn("Can't find cross in '" + c.rawFile + "', layer '" + c.layer + "'!", &node.position());
218
+ return Value{};
219
+ }
220
+
221
+ } // namespace oscadeval
@@ -1,5 +1,7 @@
1
1
  #include "openscad_cpp_evaluator/function_builtins.hpp"
2
2
 
3
+ #include "builtins.hpp" // builtinDxfDim/builtinDxfCross
4
+
3
5
  #include "openscad_cpp_evaluator/dispatch.hpp"
4
6
  #include "openscad_cpp_evaluator/evaluator.hpp"
5
7
  #include "openscad_cpp_evaluator/segments.hpp"
@@ -447,7 +449,7 @@ bool isBuiltinFunctionName(const std::string& name) {
447
449
  "acos", "atan", "atan2", "max", "min", "pow", "norm", "cross", "rands", "concat", "len", "str",
448
450
  "chr", "ord", "is_undef", "is_num", "is_bool", "is_string", "is_list", "is_function", "is_object",
449
451
  "search", "lookup", "has_key", "version", "version_num", "parent_module",
450
- "object", "textmetrics", "fontmetrics",
452
+ "object", "textmetrics", "fontmetrics", "dxf_dim", "dxf_cross",
451
453
  };
452
454
  return names.count(name) > 0;
453
455
  }
@@ -514,7 +516,7 @@ enum class BuiltinFnId {
514
516
  TextMetrics, FontMetrics, Abs, Sign, Ceil, Floor, Round, Sqrt, Ln, Log, Exp, Sin, Cos, Tan,
515
517
  Asin, Acos, Atan, Atan2, Max, Min, Pow, Norm, Cross, Rands, Concat, Len, Str, Chr, Ord,
516
518
  IsUndef, IsNum, IsBool, IsString, IsList, IsFunction, IsObject, Search, Lookup, HasKey,
517
- Version, VersionNum, ParentModule,
519
+ Version, VersionNum, ParentModule, DxfDim, DxfCross,
518
520
  };
519
521
 
520
522
  const std::unordered_map<std::string, BuiltinFnId>& builtinFnIds() {
@@ -536,6 +538,7 @@ const std::unordered_map<std::string, BuiltinFnId>& builtinFnIds() {
536
538
  {"lookup", BuiltinFnId::Lookup}, {"has_key", BuiltinFnId::HasKey},
537
539
  {"version", BuiltinFnId::Version}, {"version_num", BuiltinFnId::VersionNum},
538
540
  {"parent_module", BuiltinFnId::ParentModule},
541
+ {"dxf_dim", BuiltinFnId::DxfDim}, {"dxf_cross", BuiltinFnId::DxfCross},
539
542
  };
540
543
  return ids;
541
544
  }
@@ -949,6 +952,8 @@ Value evalBuiltinFunction(Evaluator& ev, const std::string& name, const CallArgs
949
952
  if (!v || (v->size() != 2 && v->size() != 3)) return Value{};
950
953
  return Value{(*v)[0] * 10000.0 + (*v)[1] * 100.0 + (v->size() == 3 ? (*v)[2] : 0.0)};
951
954
  }
955
+ case BuiltinFnId::DxfDim: return builtinDxfDim(ev, args, node);
956
+ case BuiltinFnId::DxfCross: return builtinDxfCross(ev, args, node);
952
957
  case BuiltinFnId::ParentModule: {
953
958
  // Defaults to 1, not 0, when called with no argument at all --
954
959
  // and an index past the end of the stack is a warning, not a
@@ -34,6 +34,7 @@ const std::unordered_map<std::string_view, ResolveFn>& resolveDispatch() {
34
34
  {"rotate_extrude", &resolveRotateExtrude},
35
35
  {"projection", &resolveProjection},
36
36
  {"roof", &resolveRoof},
37
+ {"fill", &resolveFill},
37
38
  {"offset", &resolveOffset},
38
39
  {"surface", &resolveSurface},
39
40
  {"text", &resolveText},
@@ -72,6 +73,7 @@ const std::unordered_map<std::string_view, GenerateFn>& generateDispatch() {
72
73
  {"rotate_extrude", &generateRotateExtrude},
73
74
  {"projection", &generateProjection},
74
75
  {"roof", &generateRoof},
76
+ {"fill", &generateFill},
75
77
  {"offset", &generateOffset},
76
78
  {"surface", &generateSurface},
77
79
  {"text", &generateText},
@@ -116,6 +118,7 @@ const std::vector<std::string>* builtinParamNames(const std::string& name) {
116
118
  {"rotate_extrude", {"angle", "start", "convexity"}},
117
119
  {"projection", {"cut", "convexity"}},
118
120
  {"roof", {"method", "convexity"}},
121
+ {"fill", {}},
119
122
  {"offset", {"r", "delta", "chamfer"}},
120
123
  {"surface", {"file", "center", "convexity", "invert"}},
121
124
  {"text", {"text", "size", "font", "direction", "language", "script", "halign", "valign", "spacing"}},
@@ -67,6 +67,76 @@ std::vector<ColoredBody> generateHull(Evaluator&, const CSGParams&, const std::v
67
67
  return result;
68
68
  }
69
69
 
70
+ // fill() -- 2D only: union the children, then discard every hole, keeping
71
+ // just the outer boundaries. Ported from the reference's applyFill2D: union
72
+ // the children, keep only the *positive* outlines, then re-union those (two
73
+ // outers can overlap once the holes between them are gone, and the result
74
+ // has to come back as one sanitized region rather than a pile of loops).
75
+ //
76
+ // A 3D child gets the reference's own "not yet implemented for 3D" warning
77
+ // and contributes nothing.
78
+
79
+ CSGParams resolveFill(Evaluator& ev, const oscad::ModularCall& node, EvalContext& ctx) {
80
+ auto [args, effCtx] = resolveCallArgs(ev, node.arguments, ctx);
81
+ (void)args;
82
+ ev.evalChildren(node.children, effCtx);
83
+ return CSGParams{};
84
+ }
85
+
86
+ namespace {
87
+
88
+ // Shoelace. Manifold orients an outer contour counter-clockwise (positive
89
+ // area) and a hole clockwise, so the sign is the whole test.
90
+ double signedArea(const manifold::SimplePolygon& poly) {
91
+ double a = 0.0;
92
+ for (size_t i = 0, n = poly.size(); i < n; ++i) {
93
+ const auto& p = poly[i];
94
+ const auto& q = poly[(i + 1) % n];
95
+ a += static_cast<double>(p.x) * q.y - static_cast<double>(q.x) * p.y;
96
+ }
97
+ return a / 2.0;
98
+ }
99
+
100
+ } // namespace
101
+
102
+ std::vector<ColoredBody> generateFill(Evaluator& ev, const CSGParams&,
103
+ const std::vector<std::unique_ptr<CSGNode>>& children,
104
+ const oscad::ASTNode& node) {
105
+ const std::vector<ColoredBody> bodies = flattenCsgTree(children);
106
+ if (bodies.empty()) return {};
107
+ const RoleSplit split = splitByRole(bodies);
108
+
109
+ std::optional<ColoredBody> filled;
110
+ if (!split.foreground.empty()) {
111
+ for (const ColoredBody& c : split.foreground) {
112
+ if (c.body) {
113
+ ev.warn("fill() not yet implemented for 3D", &node.position());
114
+ break;
115
+ }
116
+ }
117
+ if (const std::optional<manifold::CrossSection> cs = toCrossSection(split.foreground)) {
118
+ manifold::Polygons outers;
119
+ for (const manifold::SimplePolygon& loop : cs->ToPolygons()) {
120
+ if (signedArea(loop) > 0.0) outers.push_back(loop);
121
+ }
122
+ if (!outers.empty()) {
123
+ ColoredBody cb;
124
+ cb.section = manifold::CrossSection(outers, manifold::CrossSection::FillRule::Positive);
125
+ cb.color = split.foreground.front().color;
126
+ filled = std::move(cb);
127
+ }
128
+ }
129
+ }
130
+
131
+ std::vector<ColoredBody> result;
132
+ if (filled) result.push_back(std::move(*filled));
133
+ result.insert(result.end(), split.background.begin(), split.background.end());
134
+ result.insert(result.end(), split.highlight.begin(), split.highlight.end());
135
+ result.insert(result.end(), split.showOnly.begin(), split.showOnly.end());
136
+ result.insert(result.end(), split.displayOnly.begin(), split.displayOnly.end());
137
+ return result;
138
+ }
139
+
70
140
  CSGParams resolveMinkowski(Evaluator& ev, const oscad::ModularCall& node, EvalContext& ctx) {
71
141
  auto [args, effCtx] = resolveCallArgs(ev, node.arguments, ctx);
72
142
  (void)args;
@@ -62,6 +62,8 @@ std::vector<ColoredBody> Evaluator::generateTreeImpl(const std::vector<CSGNode*>
62
62
  for (const std::unique_ptr<CSGNode>& c : node.children) childPtrs.push_back(c.get());
63
63
  generateTreeImpl(childPtrs);
64
64
 
65
+ applyDimensionRules(node);
66
+
65
67
  auto it = dispatch.find(node.kind);
66
68
  if (node.isBuiltin && it != dispatch.end()) {
67
69
  // Resolve captured which user-level call reached this node;
@@ -90,11 +92,115 @@ std::vector<ColoredBody> Evaluator::generateTreeImpl(const std::vector<CSGNode*>
90
92
  return topLevelBodies;
91
93
  }
92
94
 
95
+
96
+ namespace {
97
+
98
+ // How a node treats children of the "wrong" dimension. Read off OpenSCAD
99
+ // 2026.02.01 module by module rather than inferred: essentially everything
100
+ // is Group (the reference routes it through applyToChildren, which calls
101
+ // isValidDim and then collectChildren2D/3D), with four exceptions.
102
+ enum class DimRule {
103
+ Group, // dimension comes from the first real child; mismatches warn twice
104
+ Only2D, // always 2D input; a 3D child is dropped with one warning
105
+ Only3D, // always 3D input; a 2D child is dropped with one warning
106
+ None, // no dimension diagnostics at all
107
+ };
108
+
109
+ DimRule dimRuleFor(const std::string& kind) {
110
+ if (kind == "linear_extrude" || kind == "rotate_extrude" || kind == "offset") return DimRule::Only2D;
111
+ if (kind == "projection") return DimRule::Only3D;
112
+ // roof() warns for neither in the reference, so neither do we.
113
+ if (kind == "roof") return DimRule::None;
114
+ return DimRule::Group;
115
+ }
116
+
117
+ bool isEmptyBody(const ColoredBody& b) {
118
+ if (b.body) return b.body->NumTri() == 0;
119
+ if (b.section) return b.section->IsEmpty();
120
+ return true;
121
+ }
122
+
123
+ // 3 for a solid, 2 for a section, 0 for "no geometry at all" (which never
124
+ // takes part in the dimension decision).
125
+ int dimensionOf(const ColoredBody& b) {
126
+ if (b.body) return 3;
127
+ if (b.section) return 2;
128
+ return 0;
129
+ }
130
+
131
+ } // namespace
132
+
133
+ // Drops children of the wrong dimension, with the reference's own two
134
+ // warnings. This is not only about diagnostics: generateCsg builds one
135
+ // result and switches on whether it holds a body or a section, so a group
136
+ // that changed dimension part-way dereferenced the empty optional and took
137
+ // the process down ("mutex lock failed" from the garbage read). Filtering
138
+ // here means no downstream generate ever sees a mixed group.
139
+ //
140
+ // Background (%) children are exempt, matching isValidDim's own
141
+ // isBackground() skip -- a ghost of the other dimension is not an error.
142
+ void Evaluator::applyDimensionRules(CSGNode& node) {
143
+ const DimRule rule = dimRuleFor(node.kind);
144
+ if (rule == DimRule::None) return;
145
+ std::vector<CSGNode*> kids;
146
+ kids.reserve(node.children.size());
147
+ for (const std::unique_ptr<CSGNode>& c : node.children) kids.push_back(c.get());
148
+ applyDimensionRulesTo(kids, static_cast<int>(rule));
149
+ }
150
+
151
+ // The top level is an implicit union, and the reference treats it as one for
152
+ // this too: `square(4); cube(1);` warns and keeps only the square.
153
+ void Evaluator::applyDimensionRulesTo(const std::vector<CSGNode*>& children, int ruleValue) {
154
+ const DimRule rule = static_cast<DimRule>(ruleValue);
155
+ int target = rule == DimRule::Only2D ? 2 : rule == DimRule::Only3D ? 3 : 0;
156
+ bool warnedMixing = false;
157
+
158
+ for (CSGNode* child : children) {
159
+ if (!child) continue;
160
+ std::vector<ColoredBody> kept;
161
+ kept.reserve(child->bodies.size());
162
+ for (ColoredBody& b : child->bodies) {
163
+ const int dim = dimensionOf(b);
164
+ if (b.role == BodyRole::Background || dim == 0 || isEmptyBody(b)) {
165
+ kept.push_back(std::move(b));
166
+ continue;
167
+ }
168
+ if (target == 0) {
169
+ target = dim; // first real child fixes the dimension
170
+ kept.push_back(std::move(b));
171
+ continue;
172
+ }
173
+ if (dim == target) {
174
+ kept.push_back(std::move(b));
175
+ continue;
176
+ }
177
+ const oscad::Position* pos = child->node ? &child->node->position() : nullptr;
178
+ if (rule == DimRule::Group && !warnedMixing) {
179
+ warn("Mixing 2D and 3D objects is not supported", pos);
180
+ warnedMixing = true;
181
+ }
182
+ warn(std::string("Ignoring ") + (dim == 3 ? "3D" : "2D") + " child object for " +
183
+ (target == 3 ? "3D" : "2D") + " operation",
184
+ pos);
185
+ // dropped
186
+ }
187
+ child->bodies = std::move(kept);
188
+ }
189
+ }
190
+
93
191
  std::vector<ColoredBody> Evaluator::generateTree(const std::vector<std::unique_ptr<CSGNode>>& tree) {
94
192
  std::vector<CSGNode*> ptrs;
95
193
  ptrs.reserve(tree.size());
96
194
  for (const std::unique_ptr<CSGNode>& n : tree) ptrs.push_back(n.get());
97
- return generateTreeImpl(ptrs);
195
+ std::vector<ColoredBody> bodies = generateTreeImpl(ptrs);
196
+ // The top level is an implicit union; give it the same Group treatment
197
+ // every other group node gets, then re-collect what survived.
198
+ applyDimensionRulesTo(ptrs, static_cast<int>(DimRule::Group));
199
+ bodies.clear();
200
+ for (CSGNode* n : ptrs) {
201
+ for (const ColoredBody& b : n->bodies) bodies.push_back(b);
202
+ }
203
+ return bodies;
98
204
  }
99
205
 
100
206
  std::vector<ColoredBody> Evaluator::generatePartialTree() {
@@ -15,6 +15,12 @@ EvalContext EvalContext::makeRoot(const oscad::Scope* rootScope) {
15
15
  ctx.dyn->set("$fs", Value{2.0});
16
16
  ctx.dyn->set("$t", Value{0.0});
17
17
  ctx.dyn->set("$parent_modules", Value{0.0});
18
+ // Always false: there is no preview mode here, every render is a full
19
+ // CSG render. The variable still has to EXIST, though -- a script that
20
+ // branches on it (the `$preview ? cheap : real` idiom) would otherwise
21
+ // see undef, take the falsy branch by accident rather than by rule, and
22
+ // warn about an unknown variable while doing it.
23
+ ctx.dyn->set("$preview", Value{false});
18
24
  ctx.let_ = TrailView<Value>::makeRoot();
19
25
  ctx.dynPositions = TrailView<const oscad::Position*>::makeRoot();
20
26
  ctx.childrenNodes = std::make_shared<const ChildrenNodeList>();