openscad-cpp-evaluator 1.30.0__tar.gz → 1.31.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.
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/CLAUDE.md +30 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/PKG-INFO +1 -1
- openscad_cpp_evaluator-1.31.0/include/openscad_cpp_evaluator/segments.hpp +58 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/pyproject.toml +1 -1
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/src/builtins/extrude.cpp +190 -20
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/src/builtins/function_builtins.cpp +1 -0
- openscad_cpp_evaluator-1.31.0/src/builtins/segments.cpp +256 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/src/builtins/text.cpp +4 -1
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/tests/CMakeLists.txt +1 -0
- openscad_cpp_evaluator-1.31.0/tests/test_discretizer.cpp +132 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/uv.lock +1 -1
- openscad_cpp_evaluator-1.30.0/include/openscad_cpp_evaluator/segments.hpp +0 -19
- openscad_cpp_evaluator-1.30.0/src/builtins/segments.cpp +0 -31
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/.github/workflows/ci.yml +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/.github/workflows/wheels.yml +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/.gitignore +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/.gitmodules +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/LICENSE +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/README.md +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/bindings/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/bindings/ast_to_py.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/bindings/module.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/cmake/embed_font.cmake +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/examples/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/examples/eval_perf_benchmark.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/examples/manifold_cache_reuse.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/examples/minimal_debugger.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/.gitignore +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/CLAUDE.md +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/README.md +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope_table.hpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/src/.DS_Store +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/src/api.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/src/comments.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/src/position.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/src/scope.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/tests/test_concurrent_parse.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/tests/test_render_expression.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/tests/test_strict_commas.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/include/openscad_cpp_evaluator/bytecode.hpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/include/openscad_cpp_evaluator/coverage.hpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/include/openscad_cpp_evaluator/evaluator.hpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/include/openscad_cpp_evaluator/export.hpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/include/openscad_cpp_evaluator/font_match.hpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/include/openscad_cpp_evaluator/format_closure.hpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/include/openscad_cpp_evaluator/freetype_font_provider.hpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/include/openscad_cpp_evaluator/mesh_check.hpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/include/openscad_cpp_evaluator/profile.hpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/include/openscad_cpp_evaluator/utf8.hpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/include/openscad_cpp_evaluator/value.hpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/python/openscad_cpp_evaluator/__init__.py +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/resources/fonts/LICENSE +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/resources/fonts/LiberationSans-Bold.ttf +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/resources/fonts/LiberationSans-BoldItalic.ttf +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/resources/fonts/LiberationSans-Italic.ttf +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/resources/fonts/LiberationSans-Regular.ttf +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/src/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/src/builtins/booleans.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/src/builtins/builtins.hpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/src/builtins/call_args.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/src/builtins/color.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/src/builtins/control.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/src/builtins/dxf_dim.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/src/builtins/import.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/src/builtins/modifiers.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/src/builtins/primitives_2d.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/src/builtins/primitives_3d.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/src/builtins/registry.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/src/builtins/roof.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/src/builtins/surface.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/src/builtins/topology.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/src/builtins/transforms.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/src/bytecode_compiler.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/src/bytecode_vm.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/src/colored_body.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/src/coverage.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/src/csg_generate.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/src/csg_resolve.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/src/css_colors.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/src/debug/debug_repl.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/src/debug_profile.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/src/eval_context.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/src/eval_error.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/src/eval_use.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/src/export.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/src/expr_eval.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/src/format_closure.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/src/import/dxf_import.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/src/import/mesh_import.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/src/import/surface_load.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/src/import/svg_import.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/src/manifold_cache.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/src/measure_geometry.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/src/mesh_check.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/src/stmt_eval.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/src/text/font_match.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/src/text/freetype_font_provider.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/src/text/text_metrics.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/src/user_calls.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/src/utf8.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/src/value.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/src/xkcd_colors.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/src/xkcd_colors.hpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/src/zip_stored.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/tests/test_booleans.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/tests/test_bytecode_compiler.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/tests/test_children_separate.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/tests/test_cli.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/tests/test_control_flow.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/tests/test_coverage.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/tests/test_csg_tree.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/tests/test_debug_hooks.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/tests/test_dxf_svg_import.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/tests/test_eval_context.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/tests/test_export.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/tests/test_expr_eval.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/tests/test_extrude_roof.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/tests/test_feature_detection.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/tests/test_function_builtins.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/tests/test_helpers.hpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/tests/test_import_export.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/tests/test_include_cache.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/tests/test_manifold_cache.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/tests/test_mesh_check.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/tests/test_multi_color_merge.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/tests/test_primitives.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/tests/test_profiling.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/tests/test_python_bindings.py +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/tests/test_render_expr.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/tests/test_scope_trail.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/tests/test_scoping.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/tests/test_surface.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/tests/test_tail_calls.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/tests/test_text.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/tests/test_transforms.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/tests/test_unexpected_args.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/tests/test_use.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/tests/test_value.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/tests/test_viewport_params.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/tests/test_zip_stored.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/tools/cli/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/tools/cli/cli_lib.cpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/tools/cli/cli_lib.hpp +0 -0
- {openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/tools/cli/main.cpp +0 -0
|
@@ -1569,6 +1569,36 @@ than the current pause point). Ported identically (same qualifier syntax, same e
|
|
|
1569
1569
|
- `tools/cli/main.cpp` — a 3-line wrapper: builds `args` from `argv`, calls `runCli(args)` with the
|
|
1570
1570
|
real `std::cin`/`std::cout`/`std::cerr`, returns its exit code.
|
|
1571
1571
|
|
|
1572
|
+
## Curve discretization: `$fn`/`$fe`/`$fa`/`$fs` (`Discretizer`, segments.hpp)
|
|
1573
|
+
|
|
1574
|
+
A port of upstream's `CurveDiscretizer` (src/core/CurveDiscretizer.cc): circle/arc segment counts,
|
|
1575
|
+
`linear_extrude` slice counts (helix, conical helix, diagonal) and outline splitting. Every count was
|
|
1576
|
+
checked against the 2026.02.01 binary (`--enable=discretization-by-error`), including volumes, since
|
|
1577
|
+
the side-quad diagonal choice changes a twisted solid without changing its triangle count.
|
|
1578
|
+
|
|
1579
|
+
- **`$fe`** (max radial error): `n = pi/acos(1 - fe/r)`, 5 once `fe/r >= 0.1909830056`, capped at the
|
|
1580
|
+
`$fa`/`$fs`=0.01 count; replaces `$fa`/`$fs` entirely; arcs scale the UNceiled n. Upstream gates it
|
|
1581
|
+
behind `--enable=discretization-by-error`; here it is always on (`supported_feature
|
|
1582
|
+
("discretization-by-error")`). Not predefined in the root ctx, as upstream: a bare read warns.
|
|
1583
|
+
- **Bugs this fixed**, all pre-existing and all silent:
|
|
1584
|
+
- `linear_extrude(twist=)` without `slices` was ONE slice: 180 degrees collapsed to a straight prism.
|
|
1585
|
+
- `slices=N` gave N+1 slices (Manifold's `nDivisions` is the copies between the ends).
|
|
1586
|
+
- Twisted and non-uniformly-scaled extrusions never split their outline edges.
|
|
1587
|
+
- Partial `rotate_extrude(angle=)` packed the full circle's count into the arc (Manifold's
|
|
1588
|
+
`Revolve` takes the count for the arc it is given).
|
|
1589
|
+
- Text curves used `$fn` at radius 0, halved, instead of `circular(size)/8 + 1`.
|
|
1590
|
+
- A fractional `$fn` truncated instead of rounding up.
|
|
1591
|
+
- **Twisted/non-uniform extrusions are built by hand** (`extrudeTwisted`, extrude.cpp), not by
|
|
1592
|
+
`Manifold::Extrude`: their side quads are not planar and upstream splits each along its SHORTER
|
|
1593
|
+
diagonal (`sgn_vdiff`, ties broken by twist direction and hole-ness). `Manifold::Extrude` always picks
|
|
1594
|
+
the same diagonal: same triangle count, different volume (241 vs 254 for a 180-degree twist).
|
|
1595
|
+
Planar cases (no twist, uniform scale) and a top scaled to zero stay on `Manifold::Extrude`.
|
|
1596
|
+
- **1-2 section arcs** (short `rotate_extrude` angles at coarse settings): `Revolve` substitutes its own
|
|
1597
|
+
default below 3, so these are an extrude warped round Z. The warp inverts the solid for a positive
|
|
1598
|
+
angle, hence the mirror-in-Y dance.
|
|
1599
|
+
- Kept departure: `$fa`/`$fs` <= 0 fall back to 12/2 rather than upstream's clamp-to-0.01 with a warning.
|
|
1600
|
+
- Not ported: `linear_extrude(v=)` (accepted, ignored); DXF/SVG import arcs don't read the discretizer.
|
|
1601
|
+
|
|
1572
1602
|
## Cancel and the module recursion limit (#554)
|
|
1573
1603
|
|
|
1574
1604
|
- **`Evaluator::setCancelFlag(shared_ptr<atomic<bool>>)`** -- bound as `CancelSignal` /
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include "openscad_cpp_evaluator/eval_context.hpp"
|
|
4
|
+
#include "openscad_cpp_evaluator/csg_node.hpp"
|
|
5
|
+
|
|
6
|
+
#include <manifold/common.h>
|
|
7
|
+
|
|
8
|
+
#include <optional>
|
|
9
|
+
|
|
10
|
+
namespace oscadeval {
|
|
11
|
+
|
|
12
|
+
// How curves become polygons: OpenSCAD's CurveDiscretizer (src/core/
|
|
13
|
+
// CurveDiscretizer.cc, upstream master cc19000e0), ported. Every count here
|
|
14
|
+
// matches the 2026.02.01 binary's.
|
|
15
|
+
//
|
|
16
|
+
// $fn > $fe > $fa/$fs. $fe is the most a curve's polygon may stray from the
|
|
17
|
+
// true circle, measured along a radius to an edge's midpoint; when set (>=
|
|
18
|
+
// 2^-20) it replaces $fa/$fs entirely. Upstream reads $fe only behind
|
|
19
|
+
// --enable=discretization-by-error; this evaluator always honours it, and
|
|
20
|
+
// advertises that as supported_feature("discretization-by-error").
|
|
21
|
+
//
|
|
22
|
+
// Departures from upstream, kept from before this port: $fa/$fs <= 0 fall
|
|
23
|
+
// back to their defaults (12/2) rather than clamping to 0.01 with a warning.
|
|
24
|
+
struct Discretizer {
|
|
25
|
+
double fn = 0.0;
|
|
26
|
+
double fe = 0.0;
|
|
27
|
+
double fa = 12.0;
|
|
28
|
+
double fs = 2.0;
|
|
29
|
+
|
|
30
|
+
static Discretizer fromCtx(const EvalContext& ctx);
|
|
31
|
+
// Stored in, and read back from, a node's params -- they are part of its
|
|
32
|
+
// cache key, and generate has only the params to go on.
|
|
33
|
+
static Discretizer fromParams(const CSGParams& params);
|
|
34
|
+
void store(CSGParams& params) const;
|
|
35
|
+
|
|
36
|
+
// Segments for `angle` degrees of arc at radius r. nullopt where
|
|
37
|
+
// upstream has none (r below 2^-20, a non-finite $fn/$fe/angle): every
|
|
38
|
+
// caller supplies its own fallback, as upstream's value_or() does.
|
|
39
|
+
std::optional<int> circular(double r, double angleDegrees = 360.0) const;
|
|
40
|
+
|
|
41
|
+
// linear_extrude slice counts: twist without scale, twist with uniform
|
|
42
|
+
// scale, and non-uniform scale (with or without twist).
|
|
43
|
+
std::optional<int> helixSlices(double rSqr, double height, double twistDegrees) const;
|
|
44
|
+
std::optional<int> conicalHelixSlices(double rSqr, double height, double twistDegrees, double scale) const;
|
|
45
|
+
std::optional<int> diagonalSlices(double deltaSqr, double height) const;
|
|
46
|
+
|
|
47
|
+
// Subdivide an outline's edges so a twisted or non-uniformly scaled
|
|
48
|
+
// extrusion keeps its shape between slices. `segments` is linear_extrude's
|
|
49
|
+
// own argument (0 = not given).
|
|
50
|
+
manifold::SimplePolygon splitOutline(const manifold::SimplePolygon& outline, double twistDegrees,
|
|
51
|
+
double scaleX, double scaleY, unsigned slices, unsigned segments) const;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// Full-circle segment count from ctx's $fn/$fe/$fa/$fs at radius r;
|
|
55
|
+
// upstream's primitives fall back to 3.
|
|
56
|
+
int fnSegmentsFromCtx(const EvalContext& ctx, double r = 0.0);
|
|
57
|
+
|
|
58
|
+
} // namespace oscadeval
|
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
#include "openscad_cpp_evaluator/segments.hpp"
|
|
6
6
|
|
|
7
7
|
#include <algorithm>
|
|
8
|
+
#include <manifold/polygon.h>
|
|
8
9
|
#include <cmath>
|
|
10
|
+
#include <numbers>
|
|
9
11
|
|
|
10
12
|
namespace oscadeval {
|
|
11
13
|
|
|
@@ -26,13 +28,110 @@ namespace oscadeval {
|
|
|
26
28
|
// still not covered by PushBuiltinWrap) is behaviorally unobservable --
|
|
27
29
|
// see computeRoofParams's own doc comment (roof.cpp) for the one builtin
|
|
28
30
|
// in this group where that ISN'T true and a different split was needed.
|
|
31
|
+
namespace {
|
|
32
|
+
|
|
33
|
+
// Upstream's sgn_vdiff: which of two diagonals is shorter, treating lengths
|
|
34
|
+
// within 1 part in 1e5 as a tie.
|
|
35
|
+
int sgnVdiff(const manifold::vec2& v1, const manifold::vec2& v2) {
|
|
36
|
+
const double l1 = manifold::la::length(v1), l2 = manifold::la::length(v2);
|
|
37
|
+
return 2 * std::fabs(l1 - l2) * 1e5 > l1 + l2 ? (l1 < l2 ? -1 : 1) : 0;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// A twisted or non-uniformly scaled extrusion, built the way upstream's
|
|
41
|
+
// linear_extrude.cc builds it. Its side quads are not planar, so which
|
|
42
|
+
// diagonal splits each one changes the solid, and Manifold's own Extrude
|
|
43
|
+
// always picks the same one: the result had OpenSCAD's triangle count and a
|
|
44
|
+
// different volume (241 vs 254 for a 180-degree twist of square(5)).
|
|
45
|
+
// Upstream splits along the SHORTER diagonal, breaking exact ties by twist
|
|
46
|
+
// direction and whether the outline is a hole.
|
|
47
|
+
manifold::Manifold extrudeTwisted(const manifold::Polygons& polys, double height, int slices, double twist,
|
|
48
|
+
double scaleX, double scaleY) {
|
|
49
|
+
size_t stride = 0;
|
|
50
|
+
for (const manifold::SimplePolygon& o : polys) stride += o.size();
|
|
51
|
+
const auto ringPoint = [&](const manifold::vec2& v, int j) {
|
|
52
|
+
const double t = static_cast<double>(j) / slices;
|
|
53
|
+
const double a = -twist * t * std::numbers::pi / 180.0;
|
|
54
|
+
return manifold::vec2((v.x * std::cos(a) - v.y * std::sin(a)) * (1 - (1 - scaleX) * t),
|
|
55
|
+
(v.x * std::sin(a) + v.y * std::cos(a)) * (1 - (1 - scaleY) * t));
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
manifold::MeshGL64 mesh;
|
|
59
|
+
mesh.numProp = 3;
|
|
60
|
+
for (int j = 0; j <= slices; ++j) {
|
|
61
|
+
for (const manifold::SimplePolygon& o : polys) {
|
|
62
|
+
for (const manifold::vec2& v : o) {
|
|
63
|
+
const manifold::vec2 p = ringPoint(v, j);
|
|
64
|
+
mesh.vertProperties.insert(mesh.vertProperties.end(),
|
|
65
|
+
{p.x, p.y, height * j / slices});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const auto tri = [&](size_t a, size_t b, size_t c) { mesh.triVerts.insert(mesh.triVerts.end(), {a, b, c}); };
|
|
70
|
+
|
|
71
|
+
const bool backTwist = twist <= 0; // rotation_slice_top <= rotation_slice_bottom, every slice
|
|
72
|
+
for (int j = 1; j <= slices; ++j) {
|
|
73
|
+
const size_t bot = (j - 1) * stride, top = j * stride;
|
|
74
|
+
size_t cur = 0;
|
|
75
|
+
for (const manifold::SimplePolygon& o : polys) {
|
|
76
|
+
const size_t n = o.size();
|
|
77
|
+
double area2 = 0;
|
|
78
|
+
for (size_t i = 0; i < n; ++i) area2 += o[i].x * o[(i + 1) % n].y - o[(i + 1) % n].x * o[i].y;
|
|
79
|
+
const bool flip = (area2 < 0) != backTwist; // !positive xor back_twist
|
|
80
|
+
manifold::vec2 prevBot = ringPoint(o[0], j - 1), prevTop = ringPoint(o[0], j);
|
|
81
|
+
for (size_t i = 1; i <= n; ++i) {
|
|
82
|
+
const manifold::vec2 vBot = ringPoint(o[i % n], j - 1), vTop = ringPoint(o[i % n], j);
|
|
83
|
+
const size_t idx = cur + i % n, prev = cur + i - 1;
|
|
84
|
+
const int diff = sgnVdiff(prevBot - vTop, vBot - prevTop);
|
|
85
|
+
if (diff == -1 || (diff == 0 && !flip)) {
|
|
86
|
+
tri(bot + idx, top + idx, bot + prev);
|
|
87
|
+
tri(top + prev, bot + prev, top + idx);
|
|
88
|
+
} else {
|
|
89
|
+
tri(bot + idx, top + prev, bot + prev);
|
|
90
|
+
tri(bot + idx, top + idx, top + prev);
|
|
91
|
+
}
|
|
92
|
+
prevBot = vBot;
|
|
93
|
+
prevTop = vTop;
|
|
94
|
+
}
|
|
95
|
+
cur += n;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Caps: one triangulation of the outline, reused at both ends.
|
|
100
|
+
manifold::PolygonsIdx indexed;
|
|
101
|
+
size_t cur = 0;
|
|
102
|
+
for (const manifold::SimplePolygon& o : polys) {
|
|
103
|
+
manifold::SimplePolygonIdx ring;
|
|
104
|
+
for (size_t i = 0; i < o.size(); ++i) ring.push_back({o[i], static_cast<int>(cur + i)});
|
|
105
|
+
indexed.push_back(std::move(ring));
|
|
106
|
+
cur += o.size();
|
|
107
|
+
}
|
|
108
|
+
const size_t topBase = static_cast<size_t>(slices) * stride;
|
|
109
|
+
for (const manifold::ivec3& t : manifold::TriangulateIdx(indexed)) {
|
|
110
|
+
tri(t.x, t.z, t.y); // bottom faces down
|
|
111
|
+
tri(topBase + t.x, topBase + t.y, topBase + t.z); // top faces up
|
|
112
|
+
}
|
|
113
|
+
return manifold::Manifold(mesh);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
} // namespace
|
|
117
|
+
|
|
29
118
|
BuiltinWrapParams computeLinearExtrudeParams(Evaluator& ev, const oscad::ModularCall& node, EvalContext& ctx) {
|
|
30
119
|
auto [args, effCtx] = resolveCallArgs(ev, node.arguments, ctx);
|
|
31
120
|
|
|
32
121
|
const double height = toDoubleLenient(getArg(args, 0, "height", Value{1.0}));
|
|
33
122
|
const bool center = truthy(getArg(args, std::nullopt, "center", Value{false}));
|
|
34
123
|
const double twist = toDoubleLenient(getArg(args, std::nullopt, "twist", Value{0.0}));
|
|
35
|
-
|
|
124
|
+
// Upstream's validate_integral: any finite number counts as given, and is
|
|
125
|
+
// truncated and clamped (slices >= 1, segments >= 0). Not given, the
|
|
126
|
+
// discretizer decides.
|
|
127
|
+
const auto integral = [&](const char* name, double lo) -> std::optional<double> {
|
|
128
|
+
const Value v = getArg(args, std::nullopt, name, Value{});
|
|
129
|
+
const double* d = std::get_if<double>(&v);
|
|
130
|
+
if (!d || !std::isfinite(*d)) return std::nullopt;
|
|
131
|
+
return *d < lo ? lo : std::trunc(*d);
|
|
132
|
+
};
|
|
133
|
+
const std::optional<double> slices = integral("slices", 1.0);
|
|
134
|
+
const std::optional<double> segments = integral("segments", 0.0);
|
|
36
135
|
const Value scaleArg = getArg(args, std::nullopt, "scale", Value{});
|
|
37
136
|
|
|
38
137
|
double scaleX = 1.0, scaleY = 1.0;
|
|
@@ -47,7 +146,9 @@ BuiltinWrapParams computeLinearExtrudeParams(Evaluator& ev, const oscad::Modular
|
|
|
47
146
|
params["height"] = Value{height};
|
|
48
147
|
params["center"] = Value{center};
|
|
49
148
|
params["twist"] = Value{twist};
|
|
50
|
-
params["slices"] = Value{
|
|
149
|
+
params["slices"] = slices ? Value{*slices} : Value{};
|
|
150
|
+
params["segments"] = segments ? Value{*segments} : Value{};
|
|
151
|
+
Discretizer::fromCtx(effCtx).store(params);
|
|
51
152
|
params["scale_x"] = Value{scaleX};
|
|
52
153
|
params["scale_y"] = Value{scaleY};
|
|
53
154
|
params["color"] = colorToValue(effCtx.color);
|
|
@@ -69,11 +170,60 @@ std::vector<ColoredBody> generateLinearExtrude(Evaluator& ev, const CSGParams& p
|
|
|
69
170
|
if (!cs || cs->IsEmpty()) return {};
|
|
70
171
|
|
|
71
172
|
const double height = std::get<double>(params.at("height"));
|
|
72
|
-
const int slices = static_cast<int>(std::get<double>(params.at("slices")));
|
|
73
173
|
const double twist = std::get<double>(params.at("twist"));
|
|
74
|
-
const
|
|
174
|
+
const double scaleX = std::get<double>(params.at("scale_x"));
|
|
175
|
+
const double scaleY = std::get<double>(params.at("scale_y"));
|
|
176
|
+
const double* givenSlices = std::get_if<double>(¶ms.at("slices"));
|
|
177
|
+
const double* givenSegments = std::get_if<double>(¶ms.at("segments"));
|
|
178
|
+
const Discretizer disc = Discretizer::fromParams(params);
|
|
179
|
+
manifold::Polygons polys = cs->ToPolygons();
|
|
180
|
+
|
|
181
|
+
// Upstream's calc_num_slices (linear_extrude.cc).
|
|
182
|
+
const auto maxDeltaSqr = [&] {
|
|
183
|
+
double m = 0;
|
|
184
|
+
for (const manifold::SimplePolygon& o : polys)
|
|
185
|
+
for (const manifold::vec2& v : o) m = std::max(m, manifold::la::length2(v - manifold::vec2(v.x * scaleX, v.y * scaleY)));
|
|
186
|
+
return m;
|
|
187
|
+
};
|
|
188
|
+
const int twistFallback = std::max(static_cast<int>(std::ceil(twist / 120.0)), 1);
|
|
189
|
+
int slices = 1;
|
|
190
|
+
if (givenSlices) {
|
|
191
|
+
slices = static_cast<int>(*givenSlices);
|
|
192
|
+
} else if (twist != 0.0) {
|
|
193
|
+
double maxR1Sqr = 0;
|
|
194
|
+
for (const manifold::SimplePolygon& o : polys)
|
|
195
|
+
for (const manifold::vec2& v : o) maxR1Sqr = std::max(maxR1Sqr, manifold::la::length2(v));
|
|
196
|
+
if (scaleX == 1.0 && scaleY == 1.0) {
|
|
197
|
+
slices = disc.helixSlices(maxR1Sqr, height, twist).value_or(twistFallback);
|
|
198
|
+
} else if (scaleX != scaleY) {
|
|
199
|
+
slices = std::max(disc.diagonalSlices(maxDeltaSqr(), height).value_or(1),
|
|
200
|
+
disc.helixSlices(maxR1Sqr, height, twist).value_or(twistFallback));
|
|
201
|
+
} else {
|
|
202
|
+
slices = disc.conicalHelixSlices(maxR1Sqr, height, twist, scaleX).value_or(twistFallback);
|
|
203
|
+
}
|
|
204
|
+
} else if (scaleX != scaleY) {
|
|
205
|
+
slices = disc.diagonalSlices(maxDeltaSqr(), height).value_or(1);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Split outline edges where a straight one would lose the shape between
|
|
209
|
+
// slices: twist or non-uniform scale, or `segments` asked for. segments=0
|
|
210
|
+
// turns it off.
|
|
211
|
+
const unsigned segments = givenSegments ? static_cast<unsigned>(*givenSegments) : 0;
|
|
212
|
+
const bool nonLinear = twist != 0.0 || scaleX != scaleY;
|
|
213
|
+
if (!(givenSegments && segments == 0) && (segments > 0 || nonLinear)) {
|
|
214
|
+
for (manifold::SimplePolygon& o : polys)
|
|
215
|
+
o = disc.splitOutline(o, twist, scaleX, scaleY, static_cast<unsigned>(slices), segments);
|
|
216
|
+
}
|
|
75
217
|
|
|
76
|
-
|
|
218
|
+
// Planar side quads (no twist, uniform scale) come out the same however
|
|
219
|
+
// they are split, so Manifold's own Extrude does. So does a top scaled to
|
|
220
|
+
// zero in either axis, where upstream's shorter-diagonal rule is
|
|
221
|
+
// reversed to avoid zero-thickness ears; a cone tip is Extrude's to make.
|
|
222
|
+
// Manifold's nDivisions is the copies BETWEEN the ends: slices - 1.
|
|
223
|
+
manifold::Manifold body =
|
|
224
|
+
nonLinear && scaleX > 0 && scaleY > 0 && height > 0
|
|
225
|
+
? extrudeTwisted(polys, height, slices, twist, scaleX, scaleY)
|
|
226
|
+
: manifold::Manifold::Extrude(polys, height, slices - 1, -twist, manifold::vec2(scaleX, scaleY));
|
|
77
227
|
if (std::get<bool>(params.at("center"))) body = body.Translate(manifold::vec3(0, 0, -height / 2));
|
|
78
228
|
return {ev.tagGenerated(std::move(body), node, params.at("color"))};
|
|
79
229
|
}
|
|
@@ -91,18 +241,9 @@ BuiltinWrapParams computeRotateExtrudeParams(Evaluator& ev, const oscad::Modular
|
|
|
91
241
|
|
|
92
242
|
const double angle = toDoubleLenient(getArg(args, 0, "angle", Value{360.0}));
|
|
93
243
|
|
|
94
|
-
const auto dynOr = [&](const char* name, double fallback) {
|
|
95
|
-
const Value* v = effCtx.dyn->find(name);
|
|
96
|
-
if (!v) return fallback;
|
|
97
|
-
const double* d = std::get_if<double>(v);
|
|
98
|
-
return d ? *d : fallback;
|
|
99
|
-
};
|
|
100
|
-
|
|
101
244
|
CSGParams params;
|
|
102
245
|
params["angle"] = Value{angle};
|
|
103
|
-
|
|
104
|
-
params["fa"] = Value{dynOr("$fa", 12.0)};
|
|
105
|
-
params["fs"] = Value{dynOr("$fs", 2.0)};
|
|
246
|
+
Discretizer::fromCtx(effCtx).store(params);
|
|
106
247
|
params["color"] = colorToValue(effCtx.color);
|
|
107
248
|
return BuiltinWrapParams{std::move(params), std::move(effCtx)};
|
|
108
249
|
}
|
|
@@ -121,12 +262,41 @@ std::vector<ColoredBody> generateRotateExtrude(Evaluator& ev, const CSGParams& p
|
|
|
121
262
|
const std::optional<manifold::CrossSection> cs = toCrossSection(flattenCsgTree(children));
|
|
122
263
|
if (!cs || cs->IsEmpty()) return {};
|
|
123
264
|
|
|
265
|
+
// Upstream (rotate_extrude.cc) sizes the arc by the profile's extent in X,
|
|
266
|
+
// measured from the axis: both ends start at 0.
|
|
124
267
|
const manifold::Rect bounds = cs->Bounds();
|
|
125
|
-
const double
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
268
|
+
const double width = std::max(bounds.max.x, 0.0) - std::min(bounds.min.x, 0.0);
|
|
269
|
+
const double angle = std::get<double>(params.at("angle"));
|
|
270
|
+
const int sections = Discretizer::fromParams(params).circular(width, angle).value_or(
|
|
271
|
+
std::max(1, static_cast<int>(std::fabs(angle) / 360 * 3)));
|
|
272
|
+
// Revolve takes this as the section count for the arc it is given -- not
|
|
273
|
+
// per full circle, which is what it used to be handed, packing a whole
|
|
274
|
+
// circle's worth into a partial one.
|
|
275
|
+
manifold::Manifold body;
|
|
276
|
+
if (sections >= 3 || bounds.min.x < 0) {
|
|
277
|
+
// (A profile left of the axis keeps Revolve's handling; a 1-2 section
|
|
278
|
+
// arc of one is the only case still drawn with 3.)
|
|
279
|
+
body = manifold::Manifold::Revolve(cs->ToPolygons(), std::max(sections, 3), angle);
|
|
280
|
+
} else {
|
|
281
|
+
// Revolve substitutes its own default below 3 sections, but a short
|
|
282
|
+
// arc at coarse settings is 1 or 2 (upstream draws exactly that). So
|
|
283
|
+
// extrude the profile into that many sections and wrap them round the
|
|
284
|
+
// Z axis. The wrap turns the solid inside out for a positive angle, so
|
|
285
|
+
// then the profile goes in mirrored in Y and comes back out unmirrored.
|
|
286
|
+
const double sign = angle > 0 ? -1.0 : 1.0;
|
|
287
|
+
manifold::Polygons profile = cs->ToPolygons();
|
|
288
|
+
if (sign < 0) {
|
|
289
|
+
for (manifold::SimplePolygon& o : profile) {
|
|
290
|
+
for (manifold::vec2& v : o) v.y = -v.y;
|
|
291
|
+
std::reverse(o.begin(), o.end());
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
const double rad = angle * std::numbers::pi / 180.0;
|
|
295
|
+
body = manifold::Manifold::Extrude(profile, 1.0, sections - 1).Warp([rad, sign](manifold::vec3& v) {
|
|
296
|
+
const double a = v.z * rad;
|
|
297
|
+
v = manifold::vec3(v.x * std::cos(a), v.x * std::sin(a), sign * v.y);
|
|
298
|
+
});
|
|
299
|
+
}
|
|
130
300
|
return {ev.tagGenerated(std::move(body), node, params.at("color"))};
|
|
131
301
|
}
|
|
132
302
|
|
{openscad_cpp_evaluator-1.30.0 → openscad_cpp_evaluator-1.31.0}/src/builtins/function_builtins.cpp
RENAMED
|
@@ -1017,6 +1017,7 @@ const std::unordered_map<std::string, double>& featureLevels() {
|
|
|
1017
1017
|
{"object-function", 1.0}, // object(), unconditional here
|
|
1018
1018
|
{"roof-op", 1.0}, // roof(), method="voronoi" only
|
|
1019
1019
|
{"svg-class", 1.0}, // import(svg, class=); id= is upstream's
|
|
1020
|
+
{"discretization-by-error", 1.0}, // $fe, always on (upstream: behind --enable)
|
|
1020
1021
|
};
|
|
1021
1022
|
return levels;
|
|
1022
1023
|
}
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
#include "openscad_cpp_evaluator/segments.hpp"
|
|
2
|
+
|
|
3
|
+
#include <algorithm>
|
|
4
|
+
#include <cmath>
|
|
5
|
+
#include <numbers>
|
|
6
|
+
#include <queue>
|
|
7
|
+
#include <vector>
|
|
8
|
+
|
|
9
|
+
namespace oscadeval {
|
|
10
|
+
|
|
11
|
+
namespace {
|
|
12
|
+
|
|
13
|
+
constexpr double kPi = std::numbers::pi;
|
|
14
|
+
constexpr double kDeg2Rad = kPi / 180.0;
|
|
15
|
+
constexpr double kGridFine = 0.00000095367431640625; // 2^-20, upstream's GRID_FINE
|
|
16
|
+
constexpr double kFMinimum = 0.01; // upstream's F_MINIMUM
|
|
17
|
+
|
|
18
|
+
double dynNumberOr(const EvalContext& ctx, const char* name, double fallback) {
|
|
19
|
+
const Value* v = ctx.dyn->find(name);
|
|
20
|
+
if (!v) return fallback;
|
|
21
|
+
const double* d = std::get_if<double>(v);
|
|
22
|
+
return d ? *d : fallback;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
double paramOr(const CSGParams& params, const char* key, double fallback) {
|
|
26
|
+
auto it = params.find(key);
|
|
27
|
+
if (it == params.end()) return fallback;
|
|
28
|
+
const double* d = std::get_if<double>(&it->second);
|
|
29
|
+
return d ? *d : fallback;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
double segmentsGivenFa(double fa) { return 360.0 / fa; }
|
|
33
|
+
double segmentsGivenFs(double r, double fs) { return r * 2 * kPi / fs; }
|
|
34
|
+
|
|
35
|
+
// https://mathworld.wolfram.com/Helix.html -- see upstream for the derivation.
|
|
36
|
+
double helixArcLength(double rSqr, double height, double twistDegrees) {
|
|
37
|
+
const double t = twistDegrees * kDeg2Rad;
|
|
38
|
+
const double c = height / t;
|
|
39
|
+
return t * std::sqrt(rSqr + c * c);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
double archimedesLength(double a, double theta) {
|
|
43
|
+
return 0.5 * a * (theta * std::sqrt(1 + theta * theta) + std::asinh(theta));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
manifold::vec2 transformAt(const manifold::vec2& v, double twist, double scaleX, double scaleY, double t) {
|
|
47
|
+
// Eigen::Scaling(lerp(1, s, t)) * rotate_degrees(-twist * t), as upstream.
|
|
48
|
+
const double a = -twist * t * kDeg2Rad;
|
|
49
|
+
const double x = v.x * std::cos(a) - v.y * std::sin(a);
|
|
50
|
+
const double y = v.x * std::sin(a) + v.y * std::cos(a);
|
|
51
|
+
return {x * (1 + (scaleX - 1) * t), y * (1 + (scaleY - 1) * t)};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// The longest an outline edge gets over every slice of the extrusion.
|
|
55
|
+
double maxEdgeLength(const manifold::vec2& v0, const manifold::vec2& v1, double twist, double scaleX,
|
|
56
|
+
double scaleY, unsigned slices) {
|
|
57
|
+
if (scaleX == scaleY) return manifold::la::length(v1 - v0) * std::max(scaleX, 1.0);
|
|
58
|
+
double longest = 0.0;
|
|
59
|
+
for (unsigned j = 0; j <= slices; ++j) {
|
|
60
|
+
const double t = static_cast<double>(j) / slices;
|
|
61
|
+
longest = std::max(longest, manifold::la::length(transformAt(v1, twist, scaleX, scaleY, t) -
|
|
62
|
+
transformAt(v0, twist, scaleX, scaleY, t)));
|
|
63
|
+
}
|
|
64
|
+
return longest;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
void addSegmentedEdge(manifold::SimplePolygon& out, const manifold::vec2& v0, const manifold::vec2& v1,
|
|
68
|
+
unsigned segments) {
|
|
69
|
+
for (unsigned j = 0; j < segments; ++j) {
|
|
70
|
+
const double t = static_cast<double>(j) / segments;
|
|
71
|
+
out.push_back((1 - t) * v0 + t * v1);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Upstream's splitOutlineByFn: while the outline has fewer than fn vertices,
|
|
76
|
+
// split the edge whose segments are longest, a whole group of near-equal
|
|
77
|
+
// edges at a time so the result stays symmetrical.
|
|
78
|
+
manifold::SimplePolygon splitByFn(const manifold::SimplePolygon& o, double twist, double scaleX, double scaleY,
|
|
79
|
+
double fn, unsigned slices) {
|
|
80
|
+
struct Tracker {
|
|
81
|
+
size_t edge;
|
|
82
|
+
double maxLen;
|
|
83
|
+
unsigned count = 1;
|
|
84
|
+
double metric() const { return maxLen / (count + 0.5); }
|
|
85
|
+
bool operator<(const Tracker& rhs) const { return metric() < rhs.metric(); }
|
|
86
|
+
bool closeMatch(const Tracker& other) const {
|
|
87
|
+
const double l1 = metric(), l2 = other.metric();
|
|
88
|
+
return std::min(l1, l2) / std::max(l1, l2) >= 0.999;
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
const size_t n = o.size();
|
|
92
|
+
std::vector<unsigned> counts(n, 1);
|
|
93
|
+
std::priority_queue<Tracker> q;
|
|
94
|
+
for (size_t i = 1; i <= n; ++i) {
|
|
95
|
+
q.push(Tracker{i - 1, maxEdgeLength(o[i - 1], o[i % n], twist, scaleX, scaleY, slices)});
|
|
96
|
+
}
|
|
97
|
+
std::vector<Tracker> group;
|
|
98
|
+
size_t total = n;
|
|
99
|
+
while (total < fn) {
|
|
100
|
+
while (!q.empty() && (group.empty() || q.top().closeMatch(group.front()))) {
|
|
101
|
+
group.push_back(q.top());
|
|
102
|
+
q.pop();
|
|
103
|
+
}
|
|
104
|
+
if (total + group.size() <= fn) {
|
|
105
|
+
while (!group.empty()) {
|
|
106
|
+
Tracker cur = group.back();
|
|
107
|
+
group.pop_back();
|
|
108
|
+
++cur.count;
|
|
109
|
+
++counts[cur.edge];
|
|
110
|
+
++total;
|
|
111
|
+
q.push(cur);
|
|
112
|
+
}
|
|
113
|
+
} else {
|
|
114
|
+
while (!group.empty()) {
|
|
115
|
+
q.push(group.back());
|
|
116
|
+
group.pop_back();
|
|
117
|
+
}
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
manifold::SimplePolygon out;
|
|
122
|
+
for (size_t i = 1; i <= n; ++i) addSegmentedEdge(out, o[i - 1], o[i % n], counts[i - 1]);
|
|
123
|
+
return out;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
manifold::SimplePolygon splitByFs(const manifold::SimplePolygon& o, double twist, double scaleX, double scaleY,
|
|
127
|
+
double fs, unsigned slices) {
|
|
128
|
+
const size_t n = o.size();
|
|
129
|
+
manifold::SimplePolygon out;
|
|
130
|
+
for (size_t i = 1; i <= n; ++i) {
|
|
131
|
+
const double len = maxEdgeLength(o[i - 1], o[i % n], twist, scaleX, scaleY, slices);
|
|
132
|
+
addSegmentedEdge(out, o[i - 1], o[i % n], static_cast<unsigned>(std::ceil(len / fs)));
|
|
133
|
+
}
|
|
134
|
+
return out;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
} // namespace
|
|
138
|
+
|
|
139
|
+
Discretizer Discretizer::fromCtx(const EvalContext& ctx) {
|
|
140
|
+
Discretizer d;
|
|
141
|
+
d.fn = dynNumberOr(ctx, "$fn", 0.0);
|
|
142
|
+
d.fe = dynNumberOr(ctx, "$fe", 0.0);
|
|
143
|
+
d.fa = dynNumberOr(ctx, "$fa", 12.0);
|
|
144
|
+
d.fs = dynNumberOr(ctx, "$fs", 2.0);
|
|
145
|
+
if (d.fa <= 0) d.fa = 12.0;
|
|
146
|
+
if (d.fs <= 0) d.fs = 2.0;
|
|
147
|
+
if (d.fn < 0) d.fn = 0.0;
|
|
148
|
+
if (d.fe < 0) d.fe = 0.0;
|
|
149
|
+
return d;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
Discretizer Discretizer::fromParams(const CSGParams& params) {
|
|
153
|
+
Discretizer d;
|
|
154
|
+
d.fn = paramOr(params, "fn", 0.0);
|
|
155
|
+
d.fe = paramOr(params, "fe", 0.0);
|
|
156
|
+
d.fa = paramOr(params, "fa", 12.0);
|
|
157
|
+
d.fs = paramOr(params, "fs", 2.0);
|
|
158
|
+
return d;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
void Discretizer::store(CSGParams& params) const {
|
|
162
|
+
params["fn"] = Value{fn};
|
|
163
|
+
params["fe"] = Value{fe};
|
|
164
|
+
params["fa"] = Value{fa};
|
|
165
|
+
params["fs"] = Value{fs};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
std::optional<int> Discretizer::circular(double r, double angleDegrees) const {
|
|
169
|
+
if (r < kGridFine || !std::isfinite(fn) || !std::isfinite(angleDegrees)) return std::nullopt;
|
|
170
|
+
double result;
|
|
171
|
+
if (fn > 0.0) {
|
|
172
|
+
// Ceiled before scaling to the arc, as upstream keeps for compatibility.
|
|
173
|
+
result = std::ceil(std::max(fn, 3.0)) * std::fabs(angleDegrees) / 360.0;
|
|
174
|
+
} else {
|
|
175
|
+
if (!std::isfinite(fe)) return std::nullopt;
|
|
176
|
+
if (fe >= kGridFine) {
|
|
177
|
+
// Apothem r - fe: r*cos(pi/n) = r - fe, so n = pi / acos(1 - fe/r).
|
|
178
|
+
// At the ratio giving exactly 5 (which also covers fe >= r), 5.
|
|
179
|
+
const double maxSegments =
|
|
180
|
+
std::max(std::min(segmentsGivenFa(kFMinimum), segmentsGivenFs(r, kFMinimum)), 5.0);
|
|
181
|
+
const double ratio = fe / r;
|
|
182
|
+
result = ratio >= 0.1909830056 ? 5.0 : std::min(maxSegments, kPi / std::acos(1 - ratio));
|
|
183
|
+
// NOT ceiled before scaling to the arc, unlike the $fa/$fs branch.
|
|
184
|
+
} else {
|
|
185
|
+
result = std::ceil(std::max(std::min(segmentsGivenFa(fa), segmentsGivenFs(r, fs)), 5.0));
|
|
186
|
+
}
|
|
187
|
+
result *= std::fabs(angleDegrees) / 360.0;
|
|
188
|
+
}
|
|
189
|
+
return std::max(1, static_cast<int>(std::ceil(result)));
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
std::optional<int> Discretizer::helixSlices(double rSqr, double height, double twistDegrees) const {
|
|
193
|
+
twistDegrees = std::fabs(twistDegrees);
|
|
194
|
+
// At least 3 slices per full turn: 180 degrees in one slice is never manifold.
|
|
195
|
+
const int minSlices = std::max(static_cast<int>(std::ceil(twistDegrees / 120.0)), 1);
|
|
196
|
+
if (std::sqrt(rSqr) < kGridFine || !std::isfinite(fn) || std::isnan(height) || std::isnan(twistDegrees))
|
|
197
|
+
return std::nullopt;
|
|
198
|
+
if (fn > 0.0) return std::max(static_cast<int>(std::ceil(twistDegrees / 360.0 * fn)), minSlices);
|
|
199
|
+
if (fe > 0.0) {
|
|
200
|
+
// Error of the chord between two slices' furthest vertex, from the
|
|
201
|
+
// helix it approximates; see upstream's helix_slices_given_fe.
|
|
202
|
+
const double r = std::sqrt(rSqr);
|
|
203
|
+
if (fe >= r) return minSlices;
|
|
204
|
+
const double theta = 2 * (kPi - std::acos(fe / r - 1));
|
|
205
|
+
return std::max(static_cast<int>(std::ceil(twistDegrees * kDeg2Rad / theta)), minSlices);
|
|
206
|
+
}
|
|
207
|
+
const int faSlices = static_cast<int>(std::ceil(twistDegrees / fa));
|
|
208
|
+
const int fsSlices = static_cast<int>(std::ceil(helixArcLength(rSqr, height, twistDegrees) / fs));
|
|
209
|
+
return std::max(std::min(faSlices, fsSlices), minSlices);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
std::optional<int> Discretizer::conicalHelixSlices(double rSqr, double height, double twistDegrees,
|
|
213
|
+
double scale) const {
|
|
214
|
+
twistDegrees = std::fabs(twistDegrees);
|
|
215
|
+
const double r = std::sqrt(rSqr);
|
|
216
|
+
const int minSlices = std::max(static_cast<int>(std::ceil(twistDegrees / 120.0)), 1);
|
|
217
|
+
if (r < kGridFine || !std::isfinite(fn)) return std::nullopt;
|
|
218
|
+
if (fn > 0.0) return std::max(static_cast<int>(std::ceil(twistDegrees * fn / 360)), minSlices);
|
|
219
|
+
// Upstream has no $fe case here: a scaled twist is sized by $fa/$fs.
|
|
220
|
+
// The vertex follows a section of an Archimedes spiral; see upstream.
|
|
221
|
+
const double rads = twistDegrees * kDeg2Rad;
|
|
222
|
+
const double angleEnd = scale > 1 ? rads * scale / (scale - 1) : rads / (1 - scale);
|
|
223
|
+
const double angleStart = angleEnd - rads;
|
|
224
|
+
const double a = r / angleEnd;
|
|
225
|
+
const double spiral = archimedesLength(a, angleEnd) - archimedesLength(a, angleStart);
|
|
226
|
+
const double total = std::sqrt(spiral * spiral + height * height);
|
|
227
|
+
const int fsSlices = static_cast<int>(std::ceil(total / fs));
|
|
228
|
+
const int faSlices = static_cast<int>(std::ceil(twistDegrees / fa));
|
|
229
|
+
return std::max(std::min(faSlices, fsSlices), minSlices);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
std::optional<int> Discretizer::diagonalSlices(double deltaSqr, double height) const {
|
|
233
|
+
if (std::sqrt(deltaSqr) < kGridFine || !std::isfinite(fn)) return std::nullopt;
|
|
234
|
+
if (fn > 0.0) return std::max(static_cast<int>(fn), 1);
|
|
235
|
+
return std::max(static_cast<int>(std::ceil(std::sqrt(deltaSqr + height * height) / fs)), 1);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
manifold::SimplePolygon Discretizer::splitOutline(const manifold::SimplePolygon& o, double twist, double scaleX,
|
|
239
|
+
double scaleY, unsigned slices, unsigned segments) const {
|
|
240
|
+
if (o.empty()) return o;
|
|
241
|
+
if (segments > 0 || fn > 0.0) {
|
|
242
|
+
const unsigned minVertices = segments > 0 ? segments : static_cast<unsigned>(std::max(fn, 3.0));
|
|
243
|
+
return o.size() >= minVertices ? o : splitByFn(o, twist, scaleX, scaleY, minVertices, slices);
|
|
244
|
+
}
|
|
245
|
+
// $fs, then check whether $fa gives fewer. (No $fe case upstream.)
|
|
246
|
+
const auto faSegs = static_cast<unsigned>(std::ceil(360.0 / fa));
|
|
247
|
+
if (o.size() >= faSegs) return o;
|
|
248
|
+
manifold::SimplePolygon byFs = splitByFs(o, twist, scaleX, scaleY, fs, slices);
|
|
249
|
+
return byFs.size() >= faSegs ? splitByFn(o, twist, scaleX, scaleY, faSegs, slices) : byFs;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
int fnSegmentsFromCtx(const EvalContext& ctx, double r) {
|
|
253
|
+
return Discretizer::fromCtx(ctx).circular(r).value_or(3);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
} // namespace oscadeval
|
|
@@ -58,7 +58,10 @@ CSGParams resolveText(Evaluator& ev, const oscad::ModularCall& node, EvalContext
|
|
|
58
58
|
const FontHandle handle = fp.resolveFont(fontSpec);
|
|
59
59
|
const FontMetrics fm = fp.metrics(handle);
|
|
60
60
|
const double scale = size * (100.0 / 72.0) / fm.unitsPerEm;
|
|
61
|
-
|
|
61
|
+
// Steps per glyph curve: an eighth of a circle's segments at the text's
|
|
62
|
+
// size, plus one (upstream FreetypeRenderer). This read $fn at radius 0
|
|
63
|
+
// and halved it, so default text came out at 2 steps per curve, not 4.
|
|
64
|
+
const int segs = std::max(Discretizer::fromCtx(effCtx).circular(size).value_or(3) / 8 + 1, 2);
|
|
62
65
|
const TextMeasurement m = measureText(fp, handle, text, size, spacing, shape);
|
|
63
66
|
const auto [offsetX, offsetY] = textAlignOffset(
|
|
64
67
|
halign, valign, m, [&](const std::string& w) { ev.warn(w, &node.position()); });
|