diff --git a/docs/ezycad_code_style.md b/docs/ezycad_code_style.md index 4c63d57..7d9b7c4 100644 --- a/docs/ezycad_code_style.md +++ b/docs/ezycad_code_style.md @@ -18,7 +18,7 @@ Use this style when editing or adding C/C++ code in the EzyCad project (files un - Prefer clear domain prefixes for related member groups (e.g. `m_underlay_*`) instead of mixed short forms. - **Constants** (e.g. lookup arrays for enums): `c_` prefix (e.g. `c_mode_strs`, `c_chamfer_mode_strs`). - **Functions / methods**: snake_case (e.g. `add_new_node`, `get_node_exact`, `try_get_node_idx_snap`). -- **Private methods**: snake_case with trailing underscore (e.g. `update_axis_snap_anno_`). +- **Private methods** and **file-local helpers** (anonymous-namespace free functions in a `.cpp`): snake_case with trailing underscore (e.g. `update_axis_snap_anno_`, `table_row_input_double_`). - **Type aliases**: snake_case with suffix by role, e.g. `*_ptr` for handles (`AIS_Shape_ptr`, `Shp_ptr`), `*_rslt` for result types (`Shp_rslt`). Typedefs like `ScreenCoords` are PascalCase. - **Macros**: UPPER_SNAKE_CASE (e.g. `EZY_ASSERT`, `EZY_ASSERT_MSG`, `DBG_MSG`). @@ -139,7 +139,7 @@ User-facing Markdown (now in `docs/`: `usage.md`, `usage-*.md`, `scripting.md`, ## Code organization - **Reader-first order** (`.cpp`): Put **public API and high-level workflow** at the top of the file (constructors, main entry points, orchestration). Put **lower-level details** below so the first screen shows what the module does before how it does it. -- **Helper functions** (`.cpp`): Prefer **file-local static helpers** in an anonymous namespace at the **bottom** of the implementing `.cpp` file. Forward-declare them near the top (or above their first use) when needed. Avoid large helper blocks at the top of the file; the goal is high-level code first, helpers last for readability. +- **Helper functions** (`.cpp`): Prefer **file-local helpers** in an anonymous namespace at the **bottom** of the implementing `.cpp` file (trailing `_` name; see **Naming**). Forward-declare them near the top (or above their first use) when needed. Avoid large helper blocks at the top of the file; the goal is high-level code first, helpers last for readability. - **PIMPL** (`class Foo; class Foo::Impl`): Use when hiding implementation details or when you want to swap implementations (e.g. `Sketch_nodes`, `Sketch_op_recorder`). Keep the public surface in the header; put data members, private record types, and apply/clone logic in `Impl` inside the `.cpp`. - **Templates**: Prefer putting template implementations in `.inl` files included from the header (e.g. `utl_types.inl`, `utl.inl`). - **OCCT handles**: Prefer `opencascade::handle` and project `*_ptr` aliases from `utl_types.h` (e.g. `AIS_Shape_ptr`, `Shp_ptr`). Avoid the OCCT `Handle(T)` macro in new/touched code -- clang-format mishandles it with `PointerAlignment: Left` (e.g. `Handle(Foo) &`). See [agents/conventions/occt-handles.md](../agents/conventions/occt-handles.md). @@ -155,7 +155,7 @@ User-facing Markdown (now in `docs/`: `usage.md`, `usage-*.md`, `scripting.md`, - Too much DRY can increase coupling by forcing unrelated code through one shared abstraction. - Prefer readability over clever reuse when repetition is small and explicit code is clearer. - **When duplication is acceptable**: a few lines repeated across nearby UI or glue code can beat a shared helper if call sites are likely to diverge (different tooltips, widths, or disabled logic) or if extracting would scatter one screen across many symbols. Prefer **locality**: keep a small block self-contained so a reader does not jump to understand one pane. -- **Rule of thumb**: extract when the behavior is **the same and stable** (or when a bug fix must touch N copies); wait when the pattern is still moving. If you extract, prefer a **file-local static helper at the bottom of the `.cpp`** (see **Code organization**) over a generic framework. +- **Rule of thumb**: extract when the behavior is **the same and stable** (or when a bug fix must touch N copies); wait when the pattern is still moving. If you extract, prefer a **file-local helper at the bottom of the `.cpp`** (see **Code organization**) over a generic framework. - Context matters: stronger DRY is often good in monolith/shared-library code; some duplication can be healthier in fast-changing or separated systems. - Balance DRY with KISS, YAGNI, and overall cognitive load. diff --git a/src/doc/utility.md b/src/doc/utility.md index d8b0b86..2904d21 100644 --- a/src/doc/utility.md +++ b/src/doc/utility.md @@ -50,7 +50,7 @@ CMake IDE group: `src\utl` (pattern `^utl(_|\.)`). | API | Purpose | | ---------------------------------------- | ------------------------------------------------ | -| `clear_all(...)` | Reset optional/containers/arithmetic in one call | +| `clear_all(...)` | Reset optional/containers/arithmetic/handles (`Nullify`)/enums/aggregates in one call | | `unique_sequential_name(base, existing)` | `Name`, `Name.001`, ... for sketches/shapes | | `load_texture(path)` | Toolbar icon loading | | `decode_image_bytes(bytes)` | stb_image -> RGBA for underlay import | diff --git a/src/gui.cpp b/src/gui.cpp index 72e77a0..a699670 100644 --- a/src/gui.cpp +++ b/src/gui.cpp @@ -2610,7 +2610,7 @@ void GUI::shape_list_() // pair so table rows cannot leave the ImGui tree stack unbalanced (which nested siblings // under the wrong parent and made Ungroup look like it only moved one child). std::unordered_set shape_list_ancestors; - auto draw_shape_row = [&](auto&& self, const Shp_ptr& shape) -> void + auto draw_shape_row = [&](auto&& self, const Shp_ptr& shape) -> void { EZY_ASSERT(shape); if (!shape_list_ancestors.insert(shape->get_id()).second) @@ -2972,9 +2972,7 @@ void GUI::shape_info_dialog_() if (!shape_still_exists) { - m_shape_info_open = false; - m_shape_info_shp.Nullify(); - m_shape_info_lines.clear(); + clear_all(m_shape_info_open, m_shape_info_shp, m_shape_info_lines); return; } @@ -3088,9 +3086,7 @@ void GUI::begin_step_import_(const Step_import_mode mode) void GUI::finish_step_import_(Status st, Occt_view::Step_import_geom& geom) { const bool cancelled = !m_cad_busy_progress.IsNull() && m_cad_busy_progress->cancelled(); - m_cad_busy_kind = Cad_busy_kind::Idle; - m_cad_busy_progress = {}; - m_cad_busy_modal_open = false; + clear_all(m_cad_busy_kind, m_cad_busy_progress, m_cad_busy_modal_open); if (cancelled || (!st.is_ok() && st.message().find("cancelled") != std::string::npos)) { @@ -3139,8 +3135,8 @@ void GUI::poll_cad_busy_() } Occt_view::Step_import_geom geom; - Status st = Occt_view::prepare_step_import(m_cad_busy_bytes, m_cad_busy_import_mode, m_view->step_import_model_scale(), - geom, {}); + Status st = + Occt_view::prepare_step_import(m_cad_busy_bytes, m_cad_busy_import_mode, m_view->step_import_model_scale(), geom, {}); finish_step_import_(st, geom); #else if (!m_cad_busy_import_fut.valid()) @@ -3178,8 +3174,7 @@ void GUI::cad_busy_dialog_() } ImGui::SetNextWindowSize(ImVec2(280.0f, 0.0f), ImGuiCond_Appearing); - if (!ImGui::BeginPopupModal("##EzyCadCadBusy", nullptr, - ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove)) + if (!ImGui::BeginPopupModal("##EzyCadCadBusy", nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove)) return; ImGui::TextUnformatted("Importing..."); @@ -3432,28 +3427,8 @@ void GUI::message_status_window_() // Log window implementation namespace { - -std::string format_log_line(const std::string& base, size_t repeat_count) -{ - if (repeat_count <= 1) - return base; - return base + " #" + std::to_string(repeat_count); -} - -void append_log_line(std::vector& buffer, const std::string& line) -{ - if (buffer.size() > 1) - { - buffer.pop_back(); // trailing '\0' - buffer.push_back('\n'); - } - else if (!buffer.empty()) - buffer.pop_back(); - - buffer.insert(buffer.end(), line.begin(), line.end()); - buffer.push_back('\0'); -} - +std::string format_log_line_(const std::string& base, size_t repeat_count); +void append_log_line_(std::vector& buffer, const std::string& line); } // namespace void GUI::log_message(const std::string& message) @@ -3461,7 +3436,7 @@ void GUI::log_message(const std::string& message) if (!m_log_last_line_base.empty() && message == m_log_last_line_base) { ++m_log_repeat_count; - const std::string line = format_log_line(m_log_last_line_base, m_log_repeat_count); + const std::string line = format_log_line_(m_log_last_line_base, m_log_repeat_count); m_log_buffer.resize(m_log_last_line_start); m_log_buffer.insert(m_log_buffer.end(), line.begin(), line.end()); m_log_buffer.push_back('\0'); @@ -3469,7 +3444,7 @@ void GUI::log_message(const std::string& message) return; } - append_log_line(m_log_buffer, message); + append_log_line_(m_log_buffer, message); m_log_last_line_base = message; m_log_last_line_start = m_log_buffer.size() - message.size() - 1; m_log_repeat_count = 1; @@ -4484,6 +4459,30 @@ void GUI::on_inspector_file(const std::string& file_path, const std::string& fil open_file_inspector_(file_path, file_data); } +namespace +{ +std::string format_log_line_(const std::string& base, size_t repeat_count) +{ + if (repeat_count <= 1) + return base; + return base + " #" + std::to_string(repeat_count); +} + +void append_log_line_(std::vector& buffer, const std::string& line) +{ + if (buffer.size() > 1) + { + buffer.pop_back(); // trailing '\0' + buffer.push_back('\n'); + } + else if (!buffer.empty()) + buffer.pop_back(); + + buffer.insert(buffer.end(), line.begin(), line.end()); + buffer.push_back('\0'); +} +} // namespace + #ifdef __EMSCRIPTEN__ void GUI::open_file_dialog_async() { diff --git a/src/gui.h b/src/gui.h index e40d26a..26329c8 100644 --- a/src/gui.h +++ b/src/gui.h @@ -705,20 +705,20 @@ class GUI float m_sketch_list_scroll_y{0.f}; bool m_sketch_list_scroll_restore{false}; - bool m_show_sketch_list{true}; - bool m_show_shape_list{true}; - bool m_show_options{true}; - bool m_show_settings_dialog{false}; - bool m_open_about_popup{false}; - bool m_about_popup_open{false}; - bool m_shape_info_open{false}; - Shp_ptr m_shape_info_shp; - std::vector m_shape_info_lines; - bool m_file_inspector_open{false}; - Step_import_mode m_file_inspector_step_mode{Step_import_mode::Preserve_hierarchy}; - std::string m_file_inspector_path; - std::string m_file_inspector_bytes; - utl_cad_file_info::Format m_file_inspector_fmt{utl_cad_file_info::Format::Unknown}; + bool m_show_sketch_list{true}; + bool m_show_shape_list{true}; + bool m_show_options{true}; + bool m_show_settings_dialog{false}; + bool m_open_about_popup{false}; + bool m_about_popup_open{false}; + bool m_shape_info_open{false}; + Shp_ptr m_shape_info_shp; + std::vector m_shape_info_lines; + bool m_file_inspector_open{false}; + Step_import_mode m_file_inspector_step_mode{Step_import_mode::Preserve_hierarchy}; + std::string m_file_inspector_path; + std::string m_file_inspector_bytes; + utl_cad_file_info::Format m_file_inspector_fmt{utl_cad_file_info::Format::Unknown}; enum class Cad_busy_kind : uint8_t { diff --git a/src/gui_add.cpp b/src/gui_add.cpp index 2393118..217e339 100644 --- a/src/gui_add.cpp +++ b/src/gui_add.cpp @@ -2,6 +2,11 @@ #include "utl_geom.h" #include "gui_occt_view.h" +namespace +{ +void table_row_input_double_(const char* label, const char* id, double* value); +} // namespace + void GUI::add_box_dialog_() { if (m_open_add_box_popup) @@ -18,48 +23,12 @@ void GUI::add_box_dialog_() if (ImGui::BeginTable("Add box##table", 2, ImGuiTableFlags_SizingStretchProp)) { - ImGui::TableNextRow(); - ImGui::TableSetColumnIndex(0); - ImGui::TextUnformatted("Origin X"); - ImGui::TableSetColumnIndex(1); - ImGui::SetNextItemWidth(-1); - ImGui::InputDouble("##box_origin_x", &m_add_box_origin.x, 0.0, 0.0, "%.3f"); - - ImGui::TableNextRow(); - ImGui::TableSetColumnIndex(0); - ImGui::TextUnformatted("Origin Y"); - ImGui::TableSetColumnIndex(1); - ImGui::SetNextItemWidth(-1); - ImGui::InputDouble("##box_origin_y", &m_add_box_origin.y, 0.0, 0.0, "%.3f"); - - ImGui::TableNextRow(); - ImGui::TableSetColumnIndex(0); - ImGui::TextUnformatted("Origin Z"); - ImGui::TableSetColumnIndex(1); - ImGui::SetNextItemWidth(-1); - ImGui::InputDouble("##box_origin_z", &m_add_box_origin.z, 0.0, 0.0, "%.3f"); - - ImGui::TableNextRow(); - ImGui::TableSetColumnIndex(0); - ImGui::TextUnformatted("Width (X)"); - ImGui::TableSetColumnIndex(1); - ImGui::SetNextItemWidth(-1); - ImGui::InputDouble("##box_width", &m_add_box_size.x, 0.0, 0.0, "%.3f"); - - ImGui::TableNextRow(); - ImGui::TableSetColumnIndex(0); - ImGui::TextUnformatted("Length (Y)"); - ImGui::TableSetColumnIndex(1); - ImGui::SetNextItemWidth(-1); - ImGui::InputDouble("##box_length", &m_add_box_size.y, 0.0, 0.0, "%.3f"); - - ImGui::TableNextRow(); - ImGui::TableSetColumnIndex(0); - ImGui::TextUnformatted("Height (Z)"); - ImGui::TableSetColumnIndex(1); - ImGui::SetNextItemWidth(-1); - ImGui::InputDouble("##box_height", &m_add_box_size.z, 0.0, 0.0, "%.3f"); - + table_row_input_double_("Origin X", "##box_origin_x", &m_add_box_origin.x); + table_row_input_double_("Origin Y", "##box_origin_y", &m_add_box_origin.y); + table_row_input_double_("Origin Z", "##box_origin_z", &m_add_box_origin.z); + table_row_input_double_("Width (X)", "##box_width", &m_add_box_size.x); + table_row_input_double_("Length (Y)", "##box_length", &m_add_box_size.y); + table_row_input_double_("Height (Z)", "##box_height", &m_add_box_size.z); ImGui::EndTable(); } ImGui::Spacing(); @@ -96,30 +65,10 @@ void GUI::add_pyramid_dialog_() ImGui::Spacing(); if (ImGui::BeginTable("Add pyramid##table", 2, ImGuiTableFlags_SizingStretchProp)) { - ImGui::TableNextRow(); - ImGui::TableSetColumnIndex(0); - ImGui::TextUnformatted("Origin X"); - ImGui::TableSetColumnIndex(1); - ImGui::SetNextItemWidth(-1); - ImGui::InputDouble("##pyramid_origin_x", &m_add_pyramid_origin.x, 0.0, 0.0, "%.3f"); - ImGui::TableNextRow(); - ImGui::TableSetColumnIndex(0); - ImGui::TextUnformatted("Origin Y"); - ImGui::TableSetColumnIndex(1); - ImGui::SetNextItemWidth(-1); - ImGui::InputDouble("##pyramid_origin_y", &m_add_pyramid_origin.y, 0.0, 0.0, "%.3f"); - ImGui::TableNextRow(); - ImGui::TableSetColumnIndex(0); - ImGui::TextUnformatted("Origin Z"); - ImGui::TableSetColumnIndex(1); - ImGui::SetNextItemWidth(-1); - ImGui::InputDouble("##pyramid_origin_z", &m_add_pyramid_origin.z, 0.0, 0.0, "%.3f"); - ImGui::TableNextRow(); - ImGui::TableSetColumnIndex(0); - ImGui::TextUnformatted("Side (base & height)"); - ImGui::TableSetColumnIndex(1); - ImGui::SetNextItemWidth(-1); - ImGui::InputDouble("##pyramid_side", &m_add_pyramid_side, 0.0, 0.0, "%.3f"); + table_row_input_double_("Origin X", "##pyramid_origin_x", &m_add_pyramid_origin.x); + table_row_input_double_("Origin Y", "##pyramid_origin_y", &m_add_pyramid_origin.y); + table_row_input_double_("Origin Z", "##pyramid_origin_z", &m_add_pyramid_origin.z); + table_row_input_double_("Side (base & height)", "##pyramid_side", &m_add_pyramid_side); ImGui::EndTable(); } ImGui::Spacing(); @@ -153,30 +102,10 @@ void GUI::add_sphere_dialog_() ImGui::Spacing(); if (ImGui::BeginTable("Add sphere##table", 2, ImGuiTableFlags_SizingStretchProp)) { - ImGui::TableNextRow(); - ImGui::TableSetColumnIndex(0); - ImGui::TextUnformatted("Origin X"); - ImGui::TableSetColumnIndex(1); - ImGui::SetNextItemWidth(-1); - ImGui::InputDouble("##sphere_origin_x", &m_add_sphere_origin.x, 0.0, 0.0, "%.3f"); - ImGui::TableNextRow(); - ImGui::TableSetColumnIndex(0); - ImGui::TextUnformatted("Origin Y"); - ImGui::TableSetColumnIndex(1); - ImGui::SetNextItemWidth(-1); - ImGui::InputDouble("##sphere_origin_y", &m_add_sphere_origin.y, 0.0, 0.0, "%.3f"); - ImGui::TableNextRow(); - ImGui::TableSetColumnIndex(0); - ImGui::TextUnformatted("Origin Z"); - ImGui::TableSetColumnIndex(1); - ImGui::SetNextItemWidth(-1); - ImGui::InputDouble("##sphere_origin_z", &m_add_sphere_origin.z, 0.0, 0.0, "%.3f"); - ImGui::TableNextRow(); - ImGui::TableSetColumnIndex(0); - ImGui::TextUnformatted("Radius"); - ImGui::TableSetColumnIndex(1); - ImGui::SetNextItemWidth(-1); - ImGui::InputDouble("##sphere_radius", &m_add_sphere_radius, 0.0, 0.0, "%.3f"); + table_row_input_double_("Origin X", "##sphere_origin_x", &m_add_sphere_origin.x); + table_row_input_double_("Origin Y", "##sphere_origin_y", &m_add_sphere_origin.y); + table_row_input_double_("Origin Z", "##sphere_origin_z", &m_add_sphere_origin.z); + table_row_input_double_("Radius", "##sphere_radius", &m_add_sphere_radius); ImGui::EndTable(); } @@ -212,36 +141,11 @@ void GUI::add_cylinder_dialog_() ImGui::Spacing(); if (ImGui::BeginTable("Add cylinder##table", 2, ImGuiTableFlags_SizingStretchProp)) { - ImGui::TableNextRow(); - ImGui::TableSetColumnIndex(0); - ImGui::TextUnformatted("Origin X"); - ImGui::TableSetColumnIndex(1); - ImGui::SetNextItemWidth(-1); - ImGui::InputDouble("##cyl_origin_x", &m_add_cylinder_origin.x, 0.0, 0.0, "%.3f"); - ImGui::TableNextRow(); - ImGui::TableSetColumnIndex(0); - ImGui::TextUnformatted("Origin Y"); - ImGui::TableSetColumnIndex(1); - ImGui::SetNextItemWidth(-1); - ImGui::InputDouble("##cyl_origin_y", &m_add_cylinder_origin.y, 0.0, 0.0, "%.3f"); - ImGui::TableNextRow(); - ImGui::TableSetColumnIndex(0); - ImGui::TextUnformatted("Origin Z"); - ImGui::TableSetColumnIndex(1); - ImGui::SetNextItemWidth(-1); - ImGui::InputDouble("##cyl_origin_z", &m_add_cylinder_origin.z, 0.0, 0.0, "%.3f"); - ImGui::TableNextRow(); - ImGui::TableSetColumnIndex(0); - ImGui::TextUnformatted("Radius"); - ImGui::TableSetColumnIndex(1); - ImGui::SetNextItemWidth(-1); - ImGui::InputDouble("##cyl_radius", &m_add_cylinder_radius, 0.0, 0.0, "%.3f"); - ImGui::TableNextRow(); - ImGui::TableSetColumnIndex(0); - ImGui::TextUnformatted("Height"); - ImGui::TableSetColumnIndex(1); - ImGui::SetNextItemWidth(-1); - ImGui::InputDouble("##cyl_height", &m_add_cylinder_height, 0.0, 0.0, "%.3f"); + table_row_input_double_("Origin X", "##cyl_origin_x", &m_add_cylinder_origin.x); + table_row_input_double_("Origin Y", "##cyl_origin_y", &m_add_cylinder_origin.y); + table_row_input_double_("Origin Z", "##cyl_origin_z", &m_add_cylinder_origin.z); + table_row_input_double_("Radius", "##cyl_radius", &m_add_cylinder_radius); + table_row_input_double_("Height", "##cyl_height", &m_add_cylinder_height); ImGui::EndTable(); } @@ -276,42 +180,12 @@ void GUI::add_cone_dialog_() ImGui::Spacing(); if (ImGui::BeginTable("Add cone##table", 2, ImGuiTableFlags_SizingStretchProp)) { - ImGui::TableNextRow(); - ImGui::TableSetColumnIndex(0); - ImGui::TextUnformatted("Origin X"); - ImGui::TableSetColumnIndex(1); - ImGui::SetNextItemWidth(-1); - ImGui::InputDouble("##cone_origin_x", &m_add_cone_origin.x, 0.0, 0.0, "%.3f"); - ImGui::TableNextRow(); - ImGui::TableSetColumnIndex(0); - ImGui::TextUnformatted("Origin Y"); - ImGui::TableSetColumnIndex(1); - ImGui::SetNextItemWidth(-1); - ImGui::InputDouble("##cone_origin_y", &m_add_cone_origin.y, 0.0, 0.0, "%.3f"); - ImGui::TableNextRow(); - ImGui::TableSetColumnIndex(0); - ImGui::TextUnformatted("Origin Z"); - ImGui::TableSetColumnIndex(1); - ImGui::SetNextItemWidth(-1); - ImGui::InputDouble("##cone_origin_z", &m_add_cone_origin.z, 0.0, 0.0, "%.3f"); - ImGui::TableNextRow(); - ImGui::TableSetColumnIndex(0); - ImGui::TextUnformatted("Base radius (R1)"); - ImGui::TableSetColumnIndex(1); - ImGui::SetNextItemWidth(-1); - ImGui::InputDouble("##cone_R1", &m_add_cone_R1, 0.0, 0.0, "%.3f"); - ImGui::TableNextRow(); - ImGui::TableSetColumnIndex(0); - ImGui::TextUnformatted("Top radius (R2)"); - ImGui::TableSetColumnIndex(1); - ImGui::SetNextItemWidth(-1); - ImGui::InputDouble("##cone_R2", &m_add_cone_R2, 0.0, 0.0, "%.3f"); - ImGui::TableNextRow(); - ImGui::TableSetColumnIndex(0); - ImGui::TextUnformatted("Height"); - ImGui::TableSetColumnIndex(1); - ImGui::SetNextItemWidth(-1); - ImGui::InputDouble("##cone_height", &m_add_cone_height, 0.0, 0.0, "%.3f"); + table_row_input_double_("Origin X", "##cone_origin_x", &m_add_cone_origin.x); + table_row_input_double_("Origin Y", "##cone_origin_y", &m_add_cone_origin.y); + table_row_input_double_("Origin Z", "##cone_origin_z", &m_add_cone_origin.z); + table_row_input_double_("Base radius (R1)", "##cone_R1", &m_add_cone_R1); + table_row_input_double_("Top radius (R2)", "##cone_R2", &m_add_cone_R2); + table_row_input_double_("Height", "##cone_height", &m_add_cone_height); ImGui::EndTable(); } @@ -346,36 +220,11 @@ void GUI::add_torus_dialog_() ImGui::Spacing(); if (ImGui::BeginTable("Add torus##table", 2, ImGuiTableFlags_SizingStretchProp)) { - ImGui::TableNextRow(); - ImGui::TableSetColumnIndex(0); - ImGui::TextUnformatted("Origin X"); - ImGui::TableSetColumnIndex(1); - ImGui::SetNextItemWidth(-1); - ImGui::InputDouble("##torus_origin_x", &m_add_torus_origin.x, 0.0, 0.0, "%.3f"); - ImGui::TableNextRow(); - ImGui::TableSetColumnIndex(0); - ImGui::TextUnformatted("Origin Y"); - ImGui::TableSetColumnIndex(1); - ImGui::SetNextItemWidth(-1); - ImGui::InputDouble("##torus_origin_y", &m_add_torus_origin.y, 0.0, 0.0, "%.3f"); - ImGui::TableNextRow(); - ImGui::TableSetColumnIndex(0); - ImGui::TextUnformatted("Origin Z"); - ImGui::TableSetColumnIndex(1); - ImGui::SetNextItemWidth(-1); - ImGui::InputDouble("##torus_origin_z", &m_add_torus_origin.z, 0.0, 0.0, "%.3f"); - ImGui::TableNextRow(); - ImGui::TableSetColumnIndex(0); - ImGui::TextUnformatted("Major radius (R1)"); - ImGui::TableSetColumnIndex(1); - ImGui::SetNextItemWidth(-1); - ImGui::InputDouble("##torus_R1", &m_add_torus_R1, 0.0, 0.0, "%.3f"); - ImGui::TableNextRow(); - ImGui::TableSetColumnIndex(0); - ImGui::TextUnformatted("Minor radius (R2)"); - ImGui::TableSetColumnIndex(1); - ImGui::SetNextItemWidth(-1); - ImGui::InputDouble("##torus_R2", &m_add_torus_R2, 0.0, 0.0, "%.3f"); + table_row_input_double_("Origin X", "##torus_origin_x", &m_add_torus_origin.x); + table_row_input_double_("Origin Y", "##torus_origin_y", &m_add_torus_origin.y); + table_row_input_double_("Origin Z", "##torus_origin_z", &m_add_torus_origin.z); + table_row_input_double_("Major radius (R1)", "##torus_R1", &m_add_torus_R1); + table_row_input_double_("Minor radius (R2)", "##torus_R2", &m_add_torus_R2); ImGui::EndTable(); } @@ -436,10 +285,12 @@ void GUI::add_sketch_dialog_() plane = Sketch_ref_plane::XZ; base = "Sketch_xz"; break; + case 2: plane = Sketch_ref_plane::YZ; base = "Sketch_yz"; break; + default: break; } @@ -547,4 +398,17 @@ void GUI::add_menu_items_() m_add_torus_R2 = 0.5; m_open_add_torus_popup = true; } -} \ No newline at end of file +} + +namespace +{ +void table_row_input_double_(const char* label, const char* id, double* value) +{ + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGui::TextUnformatted(label); + ImGui::TableSetColumnIndex(1); + ImGui::SetNextItemWidth(-1); + ImGui::InputDouble(id, value, 0.0, 0.0, "%.3f"); +} +} // namespace diff --git a/src/gui_occt_view.cpp b/src/gui_occt_view.cpp index 8736c2f..5731bb5 100644 --- a/src/gui_occt_view.cpp +++ b/src/gui_occt_view.cpp @@ -626,7 +626,6 @@ void Occt_view::revolve_selected(const double angle) void Occt_view::create_sketch_from_planar_face_(const ScreenCoords& screen_coords) { if (auto face = get_face_(screen_coords); face) - { if (auto pln = plane_from_face(*face); pln) { // Get the outer wire of the face @@ -643,7 +642,6 @@ void Occt_view::create_sketch_from_planar_face_(const ScreenCoords& screen_coord } else gui().show_message("Error: Selected face is not planar. Please select a planar face."); - } } void Occt_view::create_default_sketch_() @@ -702,35 +700,7 @@ void Occt_view::add_sketch_on_ref_plane(Sketch_ref_plane plane, double offset_di namespace { -[[nodiscard]] bool import_section_circle_edge_(Sketch& sketch, const TopoDS_Edge& edge, const gp_Pln& pln) -{ - const BRepAdaptor_Curve curve(edge); - const double u0 = curve.FirstParameter(); - const double u1 = curve.LastParameter(); - const double span = u1 - u0; - if (std::abs(span) <= Precision::Confusion()) - return false; - - // Full (or near-full) circles become two semicircles; sketch arcs cannot be closed loops. - const bool full_circle = curve.IsClosed() || std::abs(std::abs(span) - 2.0 * std::numbers::pi) <= 1.0e-3; - if (full_circle) - { - const gp_Pnt2d a = to_2d(pln, curve.Value(u0)); - const gp_Pnt2d mid1 = to_2d(pln, curve.Value(u0 + 0.25 * span)); - const gp_Pnt2d b = to_2d(pln, curve.Value(u0 + 0.5 * span)); - const gp_Pnt2d mid2 = to_2d(pln, curve.Value(u0 + 0.75 * span)); - sketch.add_arc_circle(a, mid1, b); - sketch.add_arc_circle(b, mid2, a); - return true; - } - - const auto [pt_a, pt_c] = get_edge_endpoints(pln, edge); - if (pt_a.Distance(pt_c) <= Precision::Confusion()) - return false; - - sketch.add_arc_circle(pt_a, arc_curve_midpoint_2d(edge, pln), pt_c); - return true; -} +[[nodiscard]] bool import_section_circle_edge_(Sketch& sketch, const TopoDS_Edge& edge, const gp_Pln& pln); struct Section_import_counts { @@ -738,39 +708,7 @@ struct Section_import_counts size_t skipped{0}; }; -Section_import_counts import_section_edges_into_sketch_(Sketch& sketch, const TopoDS_Shape& compound, const gp_Pln& pln) -{ - Section_import_counts counts; - for (TopExp_Explorer it(compound, TopAbs_EDGE); it.More(); it.Next()) - { - const TopoDS_Edge edge = TopoDS::Edge(it.Current()); - switch (BRepAdaptor_Curve(edge).GetType()) - { - case GeomAbs_Line: - { - const auto [pt_a, pt_b] = get_edge_endpoints(pln, edge); - if (pt_a.Distance(pt_b) <= Precision::Confusion()) - { - ++counts.skipped; - break; - } - sketch.add_linear_edge(pt_a, pt_b); - ++counts.imported; - break; - } - case GeomAbs_Circle: - if (import_section_circle_edge_(sketch, edge, pln)) - ++counts.imported; - else - ++counts.skipped; - break; - default: - ++counts.skipped; - break; - } - } - return counts; -} +Section_import_counts import_section_edges_into_sketch_(Sketch& sketch, const TopoDS_Shape& compound, const gp_Pln& pln); } // namespace Status Occt_view::create_sketch_from_cross_section(const std::string& base_name) @@ -2020,8 +1958,8 @@ Status Occt_view::copy_selected_shapes() } // Snapshot each root subtree (pre-order); normalize root parent_id to 0. - std::vector clip; - std::vector source_roots; + std::vector clip; + std::vector source_roots; std::unordered_set seen; for (Shape_id root_id : collapsed) { @@ -2123,8 +2061,8 @@ Status Occt_view::paste_clipboard_shapes() // Build the full insert set before mutating the document (all-or-nothing). std::vector added; added.reserve(m_shape_clipboard.size()); - Shape_id first_pasted_group = 0; - int root_order_base = next_sibling_order(paste_parent); + Shape_id first_pasted_group = 0; + int root_order_base = next_sibling_order(paste_parent); for (const Shape_rec& src : m_shape_clipboard) { @@ -2247,17 +2185,7 @@ void Occt_view::delete_(std::vector& to_delete) namespace { - -void set_grid_colors_on_viewer_(const V3d_Viewer_ptr& viewer, const glm::vec3& color1, const glm::vec3& color2) -{ - if (viewer.IsNull() || viewer->Grid().IsNull()) - return; - - Quantity_Color cc(color1.x, color1.y, color1.z, Quantity_TOC_RGB); - Quantity_Color cd(color2.x, color2.y, color2.z, Quantity_TOC_RGB); - viewer->Grid()->SetColors(cc, cd); -} - +void set_grid_colors_on_viewer_(const V3d_Viewer_ptr& viewer, const glm::vec3& color1, const glm::vec3& color2); } // namespace Occt_view::Grid_layout Occt_view::compute_grid_layout_() const @@ -3083,10 +3011,7 @@ void Occt_view::clear_sketch_list_hover_ais_state_(Sketch_list_hover_ais& hover) m_ctx->Erase(hover.ais, false); } - hover.ais.Nullify(); - hover.temp_display = false; - hover.zlayer_override = false; - hover.prev_zlayer = Graphic3d_ZLayerId_Default; + clear_all(hover.ais, hover.temp_display, hover.zlayer_override, hover.prev_zlayer); } void Occt_view::apply_sketch_list_hover_ais_state_(Sketch_list_hover_ais& hover, const Prs3d_Drawer_ptr& drawer, @@ -3682,19 +3607,7 @@ namespace { /// Move/Rotate/Scale follow the mouse while active. Restoring those modes on undo/redo would /// immediately drag whatever is selected; use the tool's parent mode instead. -Mode mode_for_history_restore_(Mode mode) -{ - switch (mode) - { - case Mode::Move: - case Mode::Rotate: - case Mode::Scale: - case Mode::Shape_cyl_align: - return GUI::parent_mode_of(mode); - default: - return mode; - } -} +Mode mode_for_history_restore_(Mode mode); } // namespace void Occt_view::push_undo_snapshot() @@ -4045,32 +3958,16 @@ void Occt_view::load(const std::string& json_str, bool restore_view) namespace { - // Project display lengths use Project_unit (inch or mm). Model space = inches * dimension_scale (default 100). -TopoDS_Shape scale_shape_about_origin_(const TopoDS_Shape& shape, double factor) -{ - if (shape.IsNull()) - return shape; - if (std::abs(factor - 1.0) <= Precision::Confusion()) - return shape; - - gp_Trsf tr; - tr.SetScale(gp_Pnt(0.0, 0.0, 0.0), factor); - return BRepBuilderAPI_Transform(shape, tr, true).Shape(); -} - +TopoDS_Shape scale_shape_about_origin_(const TopoDS_Shape& shape, double factor); // OCCT STEP reader delivers mm (xstep.cascade.unit); convert to model space. -double step_import_to_model_scale_(double dimension_scale) { return dimension_scale / k_mm_per_inch; } - +double step_import_to_model_scale_(double dimension_scale); // PLY has no unit metadata; treat file coords as inches. -double ply_import_to_model_scale_(double dimension_scale) { return dimension_scale; } - +double ply_import_to_model_scale_(double dimension_scale); // Model space -> mm for CAD/mesh export when the user picks millimeters. -double model_to_cad_mm_export_scale_(double dimension_scale) { return k_mm_per_inch / dimension_scale; } - +double model_to_cad_mm_export_scale_(double dimension_scale); // Model space -> inches for CAD/mesh export when the user picks inches. -double model_to_inch_export_scale_(double dimension_scale) { return 1.0 / dimension_scale; } - +double model_to_inch_export_scale_(double dimension_scale); } // namespace TopoDS_Shape Occt_view::shape_with_local_transform_(const AIS_Shape_ptr& ais) const @@ -4411,4 +4308,115 @@ void Occt_view::new_file() refresh_viewer_grid_(); reset_default_view(); m_gui.set_mode(Mode::Normal); -} \ No newline at end of file +} + +namespace +{ +[[nodiscard]] bool import_section_circle_edge_(Sketch& sketch, const TopoDS_Edge& edge, const gp_Pln& pln) +{ + const BRepAdaptor_Curve curve(edge); + const double u0 = curve.FirstParameter(); + const double u1 = curve.LastParameter(); + const double span = u1 - u0; + if (std::abs(span) <= Precision::Confusion()) + return false; + + // Full (or near-full) circles become two semicircles; sketch arcs cannot be closed loops. + const bool full_circle = curve.IsClosed() || std::abs(std::abs(span) - 2.0 * std::numbers::pi) <= 1.0e-3; + if (full_circle) + { + const gp_Pnt2d a = to_2d(pln, curve.Value(u0)); + const gp_Pnt2d mid1 = to_2d(pln, curve.Value(u0 + 0.25 * span)); + const gp_Pnt2d b = to_2d(pln, curve.Value(u0 + 0.5 * span)); + const gp_Pnt2d mid2 = to_2d(pln, curve.Value(u0 + 0.75 * span)); + sketch.add_arc_circle(a, mid1, b); + sketch.add_arc_circle(b, mid2, a); + return true; + } + + const auto [pt_a, pt_c] = get_edge_endpoints(pln, edge); + if (pt_a.Distance(pt_c) <= Precision::Confusion()) + return false; + + sketch.add_arc_circle(pt_a, arc_curve_midpoint_2d(edge, pln), pt_c); + return true; +} + +Section_import_counts import_section_edges_into_sketch_(Sketch& sketch, const TopoDS_Shape& compound, const gp_Pln& pln) +{ + Section_import_counts counts; + for (TopExp_Explorer it(compound, TopAbs_EDGE); it.More(); it.Next()) + { + const TopoDS_Edge edge = TopoDS::Edge(it.Current()); + switch (BRepAdaptor_Curve(edge).GetType()) + { + case GeomAbs_Line: + { + const auto [pt_a, pt_b] = get_edge_endpoints(pln, edge); + if (pt_a.Distance(pt_b) <= Precision::Confusion()) + { + ++counts.skipped; + break; + } + sketch.add_linear_edge(pt_a, pt_b); + ++counts.imported; + break; + } + case GeomAbs_Circle: + if (import_section_circle_edge_(sketch, edge, pln)) + ++counts.imported; + else + ++counts.skipped; + break; + default: + ++counts.skipped; + break; + } + } + return counts; +} + +void set_grid_colors_on_viewer_(const V3d_Viewer_ptr& viewer, const glm::vec3& color1, const glm::vec3& color2) +{ + if (viewer.IsNull() || viewer->Grid().IsNull()) + return; + + Quantity_Color cc(color1.x, color1.y, color1.z, Quantity_TOC_RGB); + Quantity_Color cd(color2.x, color2.y, color2.z, Quantity_TOC_RGB); + viewer->Grid()->SetColors(cc, cd); +} + +Mode mode_for_history_restore_(Mode mode) +{ + switch (mode) + { + case Mode::Move: + case Mode::Rotate: + case Mode::Scale: + case Mode::Shape_cyl_align: + return GUI::parent_mode_of(mode); + default: + return mode; + } +} + +TopoDS_Shape scale_shape_about_origin_(const TopoDS_Shape& shape, double factor) +{ + if (shape.IsNull()) + return shape; + if (std::abs(factor - 1.0) <= Precision::Confusion()) + return shape; + + gp_Trsf tr; + tr.SetScale(gp_Pnt(0.0, 0.0, 0.0), factor); + return BRepBuilderAPI_Transform(shape, tr, true).Shape(); +} + +double step_import_to_model_scale_(double dimension_scale) { return dimension_scale / k_mm_per_inch; } + +double ply_import_to_model_scale_(double dimension_scale) { return dimension_scale; } + +double model_to_cad_mm_export_scale_(double dimension_scale) { return k_mm_per_inch / dimension_scale; } + +double model_to_inch_export_scale_(double dimension_scale) { return 1.0 / dimension_scale; } +} // namespace \ No newline at end of file diff --git a/src/gui_occt_view.h b/src/gui_occt_view.h index 73933fd..01d6323 100644 --- a/src/gui_occt_view.h +++ b/src/gui_occt_view.h @@ -490,10 +490,10 @@ class Occt_view : protected AIS_ViewController Shape_id m_next_shape_id{1}; Shape_id m_current_group_id{0}; /// In-app clipboard: forest of Shape_rec (roots have parent_id 0; independent BREP). - std::vector m_shape_clipboard; + std::vector m_shape_clipboard; /// Live document ids of clipboard roots at copy time (for paste-as-sibling when still current). - std::vector m_shape_clipboard_source_roots; - Ezy_asset_store m_assets; + std::vector m_shape_clipboard_source_roots; + Ezy_asset_store m_assets; // -------------------------------------------------------------------- // Dimension related diff --git a/src/gui_settings.cpp b/src/gui_settings.cpp index 43a6fca..0b898f2 100644 --- a/src/gui_settings.cpp +++ b/src/gui_settings.cpp @@ -19,158 +19,12 @@ const char* const k_settings_version = "1"; const char* const k_gui_key_permanent_node_anno_scale = "permanent_node_anno_scale"; nlohmann::json build_occt_view_settings_object_(const Occt_view& view); - -nlohmann::json imgui_style_to_json(const Gui_imgui_style_settings& s) -{ - // clang-format off - return nlohmann::json{ - {"rounding_general", s.rounding_general}, - {"rounding_scroll", s.rounding_scroll}, - {"rounding_tabs", s.rounding_tabs}, - {"window_alpha", s.window_alpha}, - {"window_border", s.window_border}, - {"frame_border", s.frame_border}, - {"window_padding_x", s.window_padding_x}, - {"window_padding_y", s.window_padding_y}, - {"frame_padding_x", s.frame_padding_x}, - {"frame_padding_y", s.frame_padding_y}, - {"item_spacing_x", s.item_spacing_x}, - {"item_spacing_y", s.item_spacing_y}, - }; - // clang-format on -} - -nlohmann::json settings_headers_to_json(const Gui_settings_headers& h) -{ - // clang-format off - return nlohmann::json{ - {"view_nav", h.view_nav}, - {"new_project", h.new_project}, - {"ui", h.ui}, - {"view_presentation", h.view_presentation}, - {"grid", h.grid}, - {"sketch", h.sketch}, - {"sketch_appearance", h.sketch_appearance}, - {"sketch_dimensions", h.sketch_dimensions}, - {"sketch_nodes", h.sketch_nodes}, - {"sketch_snap", h.sketch_snap}, - {"sketch_underlay", h.sketch_underlay}, - {"startup", h.startup}, - {"hotkeys", h.hotkeys}, - }; - // clang-format on -} - -void parse_settings_headers_json(const nlohmann::json& obj, Gui_settings_headers& out) -{ - Gui_settings_headers defaults{}; - auto b = [&obj](const char* key, bool fallback) -> bool - { - if (obj.contains(key) && obj[key].is_boolean()) - return obj[key].get(); - return fallback; - }; - - // clang-format off - out.view_nav = b("view_nav", defaults.view_nav); - out.new_project = b("new_project", defaults.new_project); - out.ui = b("ui", defaults.ui); - out.view_presentation = b("view_presentation", defaults.view_presentation); - out.grid = b("grid", defaults.grid); - out.sketch = b("sketch", defaults.sketch); - out.sketch_appearance = b("sketch_appearance", defaults.sketch_appearance); - out.sketch_dimensions = b("sketch_dimensions", defaults.sketch_dimensions); - out.sketch_nodes = b("sketch_nodes", defaults.sketch_nodes); - out.sketch_snap = b("sketch_snap", defaults.sketch_snap); - out.sketch_underlay = b("sketch_underlay", defaults.sketch_underlay); - out.startup = b("startup", defaults.startup); - out.hotkeys = b("hotkeys", defaults.hotkeys); - // clang-format on -} - -void parse_imgui_style_json(const nlohmann::json& obj, const Gui_imgui_style_settings& defaults, Gui_imgui_style_settings& out) -{ - auto f = [&obj](const char* key, float fallback) -> float - { - if (obj.contains(key) && obj[key].is_number()) - { - const float v = obj[key].get(); - if (v >= 0.f && v <= 32.f) - return v; - } - - return fallback; - }; - - // clang-format off - out.rounding_general = f("rounding_general", defaults.rounding_general); - out.rounding_scroll = f("rounding_scroll", defaults.rounding_scroll); - out.rounding_tabs = f("rounding_tabs", defaults.rounding_tabs); - out.window_alpha = std::clamp(f("window_alpha", defaults.window_alpha), - k_gui_imgui_window_alpha_min, k_gui_imgui_window_alpha_max); - out.window_border = std::clamp(f("window_border", defaults.window_border), 0.f, k_gui_imgui_border_slider_max); - out.frame_border = std::clamp(f("frame_border", defaults.frame_border), 0.f, k_gui_imgui_border_slider_max); - out.window_padding_x = std::clamp(f("window_padding_x", defaults.window_padding_x), 0.f, k_gui_imgui_padding_slider_max); - out.window_padding_y = std::clamp(f("window_padding_y", defaults.window_padding_y), 0.f, k_gui_imgui_padding_slider_max); - out.frame_padding_x = std::clamp(f("frame_padding_x", defaults.frame_padding_x), 0.f, k_gui_imgui_padding_slider_max); - out.frame_padding_y = std::clamp(f("frame_padding_y", defaults.frame_padding_y), 0.f, k_gui_imgui_padding_slider_max); - out.item_spacing_x = std::clamp(f("item_spacing_x", defaults.item_spacing_x), 0.f, k_gui_imgui_spacing_slider_max); - out.item_spacing_y = std::clamp(f("item_spacing_y", defaults.item_spacing_y), 0.f, k_gui_imgui_spacing_slider_max); - // clang-format on -} - -bool settings_imgui_style_controls_(float label_col_w, Gui_imgui_style_settings& style, const char* id_prefix) -{ - bool changed = false; - if (!ImGui::BeginTable(id_prefix, 2, ImGuiTableFlags_SizingStretchProp)) - return false; - - ImGui::TableSetupColumn("label", ImGuiTableColumnFlags_WidthFixed, label_col_w); - ImGui::TableSetupColumn("control", ImGuiTableColumnFlags_WidthStretch); - - auto section = [&](const char* title) - { - ImGui::TableNextRow(); - ImGui::TableSetColumnIndex(0); - ImGui::SeparatorText(title); - }; - - auto slider = [&](const char* label, const char* id, float* v, float v_min, float v_max, const char* fmt) - { - ImGui::TableNextRow(); - ImGui::TableSetColumnIndex(0); - ImGui::AlignTextToFramePadding(); - ImGui::TextUnformatted(label); - ImGui::TableSetColumnIndex(1); - changed |= ImGui::SliderFloat(id, v, v_min, v_max, fmt); - }; - - section("Transparency"); - slider("Window transparency", "##win_alpha", &style.window_alpha, k_gui_imgui_window_alpha_min, k_gui_imgui_window_alpha_max, - "%.2f"); - - section("Rounding"); - slider("Windows, frames, popups", "##round_gen", &style.rounding_general, 0.f, k_gui_imgui_rounding_slider_max, "%.0f"); - slider("Scrollbars and sliders", "##round_scr", &style.rounding_scroll, 0.f, k_gui_imgui_rounding_slider_max, "%.0f"); - slider("Tabs", "##round_tabs", &style.rounding_tabs, 0.f, k_gui_imgui_rounding_slider_max, "%.0f"); - - section("Borders"); - slider("Window border", "##win_border", &style.window_border, 0.f, k_gui_imgui_border_slider_max, "%.1f"); - slider("Frame border", "##frame_border", &style.frame_border, 0.f, k_gui_imgui_border_slider_max, "%.1f"); - - section("Padding"); - slider("Window padding X", "##win_pad_x", &style.window_padding_x, 0.f, k_gui_imgui_padding_slider_max, "%.0f"); - slider("Window padding Y", "##win_pad_y", &style.window_padding_y, 0.f, k_gui_imgui_padding_slider_max, "%.0f"); - slider("Frame padding X", "##frame_pad_x", &style.frame_padding_x, 0.f, k_gui_imgui_padding_slider_max, "%.0f"); - slider("Frame padding Y", "##frame_pad_y", &style.frame_padding_y, 0.f, k_gui_imgui_padding_slider_max, "%.0f"); - - section("Spacing"); - slider("Item spacing X", "##item_sp_x", &style.item_spacing_x, 0.f, k_gui_imgui_spacing_slider_max, "%.0f"); - slider("Item spacing Y", "##item_sp_y", &style.item_spacing_y, 0.f, k_gui_imgui_spacing_slider_max, "%.0f"); - - ImGui::EndTable(); - return changed; -} +nlohmann::json imgui_style_to_json_(const Gui_imgui_style_settings& s); +nlohmann::json settings_headers_to_json_(const Gui_settings_headers& h); +void parse_settings_headers_json_(const nlohmann::json& obj, Gui_settings_headers& out); +void parse_imgui_style_json_(const nlohmann::json& obj, const Gui_imgui_style_settings& defaults, + Gui_imgui_style_settings& out); +bool settings_imgui_style_controls_(float label_col_w, Gui_imgui_style_settings& style, const char* id_prefix); } // namespace void GUI::set_ui_verbosity(int v) { m_ui_verbosity = std::max(k_gui_ui_verbosity_min, v); } @@ -304,9 +158,9 @@ void GUI::save_occt_view_settings() {"add_mid_pt_slot_edges", m_add_mid_pt_slot_edges}, {"load_last_opened_on_startup", m_load_last_opened_on_startup}, {"last_opened_project_path", m_last_opened_project_path}, - {"imgui_style_dark", imgui_style_to_json(m_imgui_style_dark)}, - {"imgui_style_light", imgui_style_to_json(m_imgui_style_light)}, - {"settings_headers", settings_headers_to_json(m_settings_headers)}, + {"imgui_style_dark", imgui_style_to_json_(m_imgui_style_dark)}, + {"imgui_style_light", imgui_style_to_json_(m_imgui_style_light)}, + {"settings_headers", settings_headers_to_json_(m_settings_headers)}, {"view_roll_step_deg", m_view_roll_step_deg}, {"view_zoom_scroll_scale", m_view_zoom_scroll_scale}, {"default_2d_view_width", m_default_2d_view_width}, @@ -621,14 +475,14 @@ void GUI::parse_gui_panes_settings_(const std::string& content) m_imgui_style_light = light_defaults; if (g.contains("imgui_style_dark") && g["imgui_style_dark"].is_object()) - parse_imgui_style_json(g["imgui_style_dark"], dark_defaults, m_imgui_style_dark); + parse_imgui_style_json_(g["imgui_style_dark"], dark_defaults, m_imgui_style_dark); if (g.contains("imgui_style_light") && g["imgui_style_light"].is_object()) - parse_imgui_style_json(g["imgui_style_light"], light_defaults, m_imgui_style_light); + parse_imgui_style_json_(g["imgui_style_light"], light_defaults, m_imgui_style_light); m_settings_headers = Gui_settings_headers{}; if (g.contains("settings_headers") && g["settings_headers"].is_object()) - parse_settings_headers_json(g["settings_headers"], m_settings_headers); + parse_settings_headers_json_(g["settings_headers"], m_settings_headers); m_hotkeys.reset_defaults(); if (g.contains("hotkeys") && g["hotkeys"].is_object()) @@ -2323,6 +2177,158 @@ void GUI::apply_imgui_style_from_members_() namespace { +nlohmann::json imgui_style_to_json_(const Gui_imgui_style_settings& s) +{ + // clang-format off + return nlohmann::json{ + {"rounding_general", s.rounding_general}, + {"rounding_scroll", s.rounding_scroll}, + {"rounding_tabs", s.rounding_tabs}, + {"window_alpha", s.window_alpha}, + {"window_border", s.window_border}, + {"frame_border", s.frame_border}, + {"window_padding_x", s.window_padding_x}, + {"window_padding_y", s.window_padding_y}, + {"frame_padding_x", s.frame_padding_x}, + {"frame_padding_y", s.frame_padding_y}, + {"item_spacing_x", s.item_spacing_x}, + {"item_spacing_y", s.item_spacing_y}, + }; + // clang-format on +} + +nlohmann::json settings_headers_to_json_(const Gui_settings_headers& h) +{ + // clang-format off + return nlohmann::json{ + {"view_nav", h.view_nav}, + {"new_project", h.new_project}, + {"ui", h.ui}, + {"view_presentation", h.view_presentation}, + {"grid", h.grid}, + {"sketch", h.sketch}, + {"sketch_appearance", h.sketch_appearance}, + {"sketch_dimensions", h.sketch_dimensions}, + {"sketch_nodes", h.sketch_nodes}, + {"sketch_snap", h.sketch_snap}, + {"sketch_underlay", h.sketch_underlay}, + {"startup", h.startup}, + {"hotkeys", h.hotkeys}, + }; + // clang-format on +} + +void parse_settings_headers_json_(const nlohmann::json& obj, Gui_settings_headers& out) +{ + Gui_settings_headers defaults{}; + auto b = [&obj](const char* key, bool fallback) -> bool + { + if (obj.contains(key) && obj[key].is_boolean()) + return obj[key].get(); + return fallback; + }; + + // clang-format off + out.view_nav = b("view_nav", defaults.view_nav); + out.new_project = b("new_project", defaults.new_project); + out.ui = b("ui", defaults.ui); + out.view_presentation = b("view_presentation", defaults.view_presentation); + out.grid = b("grid", defaults.grid); + out.sketch = b("sketch", defaults.sketch); + out.sketch_appearance = b("sketch_appearance", defaults.sketch_appearance); + out.sketch_dimensions = b("sketch_dimensions", defaults.sketch_dimensions); + out.sketch_nodes = b("sketch_nodes", defaults.sketch_nodes); + out.sketch_snap = b("sketch_snap", defaults.sketch_snap); + out.sketch_underlay = b("sketch_underlay", defaults.sketch_underlay); + out.startup = b("startup", defaults.startup); + out.hotkeys = b("hotkeys", defaults.hotkeys); + // clang-format on +} + +void parse_imgui_style_json_(const nlohmann::json& obj, const Gui_imgui_style_settings& defaults, Gui_imgui_style_settings& out) +{ + auto f = [&obj](const char* key, float fallback) -> float + { + if (obj.contains(key) && obj[key].is_number()) + { + const float v = obj[key].get(); + if (v >= 0.f && v <= 32.f) + return v; + } + + return fallback; + }; + + // clang-format off + out.rounding_general = f("rounding_general", defaults.rounding_general); + out.rounding_scroll = f("rounding_scroll", defaults.rounding_scroll); + out.rounding_tabs = f("rounding_tabs", defaults.rounding_tabs); + out.window_alpha = std::clamp(f("window_alpha", defaults.window_alpha), + k_gui_imgui_window_alpha_min, k_gui_imgui_window_alpha_max); + out.window_border = std::clamp(f("window_border", defaults.window_border), 0.f, k_gui_imgui_border_slider_max); + out.frame_border = std::clamp(f("frame_border", defaults.frame_border), 0.f, k_gui_imgui_border_slider_max); + out.window_padding_x = std::clamp(f("window_padding_x", defaults.window_padding_x), 0.f, k_gui_imgui_padding_slider_max); + out.window_padding_y = std::clamp(f("window_padding_y", defaults.window_padding_y), 0.f, k_gui_imgui_padding_slider_max); + out.frame_padding_x = std::clamp(f("frame_padding_x", defaults.frame_padding_x), 0.f, k_gui_imgui_padding_slider_max); + out.frame_padding_y = std::clamp(f("frame_padding_y", defaults.frame_padding_y), 0.f, k_gui_imgui_padding_slider_max); + out.item_spacing_x = std::clamp(f("item_spacing_x", defaults.item_spacing_x), 0.f, k_gui_imgui_spacing_slider_max); + out.item_spacing_y = std::clamp(f("item_spacing_y", defaults.item_spacing_y), 0.f, k_gui_imgui_spacing_slider_max); + // clang-format on +} + +bool settings_imgui_style_controls_(float label_col_w, Gui_imgui_style_settings& style, const char* id_prefix) +{ + bool changed = false; + if (!ImGui::BeginTable(id_prefix, 2, ImGuiTableFlags_SizingStretchProp)) + return false; + + ImGui::TableSetupColumn("label", ImGuiTableColumnFlags_WidthFixed, label_col_w); + ImGui::TableSetupColumn("control", ImGuiTableColumnFlags_WidthStretch); + + auto section = [&](const char* title) + { + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGui::SeparatorText(title); + }; + + auto slider = [&](const char* label, const char* id, float* v, float v_min, float v_max, const char* fmt) + { + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGui::AlignTextToFramePadding(); + ImGui::TextUnformatted(label); + ImGui::TableSetColumnIndex(1); + changed |= ImGui::SliderFloat(id, v, v_min, v_max, fmt); + }; + + section("Transparency"); + slider("Window transparency", "##win_alpha", &style.window_alpha, k_gui_imgui_window_alpha_min, k_gui_imgui_window_alpha_max, + "%.2f"); + + section("Rounding"); + slider("Windows, frames, popups", "##round_gen", &style.rounding_general, 0.f, k_gui_imgui_rounding_slider_max, "%.0f"); + slider("Scrollbars and sliders", "##round_scr", &style.rounding_scroll, 0.f, k_gui_imgui_rounding_slider_max, "%.0f"); + slider("Tabs", "##round_tabs", &style.rounding_tabs, 0.f, k_gui_imgui_rounding_slider_max, "%.0f"); + + section("Borders"); + slider("Window border", "##win_border", &style.window_border, 0.f, k_gui_imgui_border_slider_max, "%.1f"); + slider("Frame border", "##frame_border", &style.frame_border, 0.f, k_gui_imgui_border_slider_max, "%.1f"); + + section("Padding"); + slider("Window padding X", "##win_pad_x", &style.window_padding_x, 0.f, k_gui_imgui_padding_slider_max, "%.0f"); + slider("Window padding Y", "##win_pad_y", &style.window_padding_y, 0.f, k_gui_imgui_padding_slider_max, "%.0f"); + slider("Frame padding X", "##frame_pad_x", &style.frame_padding_x, 0.f, k_gui_imgui_padding_slider_max, "%.0f"); + slider("Frame padding Y", "##frame_pad_y", &style.frame_padding_y, 0.f, k_gui_imgui_padding_slider_max, "%.0f"); + + section("Spacing"); + slider("Item spacing X", "##item_sp_x", &style.item_spacing_x, 0.f, k_gui_imgui_spacing_slider_max, "%.0f"); + slider("Item spacing Y", "##item_sp_y", &style.item_spacing_y, 0.f, k_gui_imgui_spacing_slider_max, "%.0f"); + + ImGui::EndTable(); + return changed; +} + /// `occt_view` JSON object: view background gradient and grid (shared with `save_occt_view_settings` / /// `occt_view_settings_json`). nlohmann::json build_occt_view_settings_object_(const Occt_view& view) diff --git a/src/main.cpp b/src/main.cpp index 26dd02b..3d4ce1a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -97,27 +97,7 @@ using namespace glm; namespace { #if !defined(__EMSCRIPTEN__) -bool parse_cli_listen(int argc, char** argv, bool& want_listen, std::string& listen_arg, std::string& error) -{ - want_listen = false; - listen_arg.clear(); - error.clear(); - for (int i = 1; i < argc; ++i) - { - const std::string a = argv[i] ? argv[i] : ""; - if (a == "--listen") - { - if (i + 1 >= argc || argv[i + 1] == nullptr || argv[i + 1][0] == '\0') - { - error = "--listen requires [host:]port"; - return false; - } - want_listen = true; - listen_arg = argv[++i]; - } - } - return true; -} +bool parse_cli_listen_(int argc, char** argv, bool& want_listen, std::string& listen_arg, std::string& error); #endif } // namespace @@ -128,7 +108,7 @@ int main(int argc, char** argv) bool want_listen = false; std::string listen_arg; std::string listen_cli_error; - if (!parse_cli_listen(argc, argv, want_listen, listen_arg, listen_cli_error)) + if (!parse_cli_listen_(argc, argv, want_listen, listen_arg, listen_cli_error)) { std::fprintf(stderr, "EzyCad: %s\n", listen_cli_error.c_str()); return 1; @@ -496,3 +476,30 @@ int main(int argc, char** argv) return 0; } + +namespace +{ +#if !defined(__EMSCRIPTEN__) +bool parse_cli_listen_(int argc, char** argv, bool& want_listen, std::string& listen_arg, std::string& error) +{ + want_listen = false; + listen_arg.clear(); + error.clear(); + for (int i = 1; i < argc; ++i) + { + const std::string a = argv[i] ? argv[i] : ""; + if (a == "--listen") + { + if (i + 1 >= argc || argv[i + 1] == nullptr || argv[i + 1][0] == '\0') + { + error = "--listen requires [host:]port"; + return false; + } + want_listen = true; + listen_arg = argv[++i]; + } + } + return true; +} +#endif +} // namespace diff --git a/src/shp_cross_section.cpp b/src/shp_cross_section.cpp index fa58116..6fd67a1 100644 --- a/src/shp_cross_section.cpp +++ b/src/shp_cross_section.cpp @@ -2,6 +2,7 @@ #include "gui_occt_view.h" #include "shp_delta.h" +#include "utl.h" #include "utl_dbg.h" #include "utl_occt.h" @@ -271,8 +272,7 @@ std::optional Shp_cross_section::finish_section_result_(Section_result r } else { - m_last_section_compound.Nullify(); - m_have_last_section_plane = false; + clear_all(m_last_section_compound, m_have_last_section_plane); clear_section_wires_(); } @@ -453,16 +453,14 @@ void Shp_cross_section::clear_plane_annotation_() if (!m_plane_lines.IsNull()) ctx().Remove(m_plane_lines, false); - m_plane_fill.Nullify(); - m_plane_lines.Nullify(); + clear_all(m_plane_fill, m_plane_lines); } void Shp_cross_section::clear_preview_ais_() { clear_section_wires_(); clear_plane_annotation_(); - m_last_section_compound.Nullify(); - m_have_last_section_plane = false; + clear_all(m_last_section_compound, m_have_last_section_plane); } const gp_Pln& Shp_cross_section::last_section_plane() const diff --git a/src/shp_cyl_align.cpp b/src/shp_cyl_align.cpp index 116647f..a5a1ac9 100644 --- a/src/shp_cyl_align.cpp +++ b/src/shp_cyl_align.cpp @@ -16,15 +16,8 @@ Shp_cyl_align::Shp_cyl_align(Occt_view& view) void Shp_cyl_align::begin() { - m_phase = Phase::Pick_moving; - m_opts = {}; - m_axial_offset = 0; - m_moving_radius = 0; - m_fixed_radius = 0; - m_depth_override = std::nullopt; - m_moving_shp.Nullify(); - m_fixed_shp.Nullify(); - clear_all(m_moving_axis, m_fixed_axis, m_drag_pln, m_shps); + clear_all(m_phase, m_opts, m_axial_offset, m_moving_radius, m_fixed_radius, m_depth_override, m_moving_shp, m_fixed_shp, + m_moving_axis, m_fixed_axis, m_drag_pln, m_shps); } bool Shp_cyl_align::is_dragging() const { return m_phase == Phase::Drag_depth && !m_shps.empty(); } @@ -86,9 +79,7 @@ void Shp_cyl_align::enter_drag_() EZY_ASSERT(!m_moving_shp.IsNull()); EZY_ASSERT(m_moving_axis.has_value() && m_fixed_axis.has_value()); - m_axial_offset = 0; - m_depth_override = std::nullopt; - clear_all(m_drag_pln); + clear_all(m_axial_offset, m_depth_override, m_drag_pln); set_operation_shps_({m_moving_shp}); m_phase = Phase::Drag_depth; view().set_dynamic_highlight_enabled(false); @@ -100,8 +91,8 @@ void Shp_cyl_align::apply_preview_() EZY_ASSERT(m_moving_axis.has_value() && m_fixed_axis.has_value()); EZY_ASSERT(!m_shps.empty()); - const double offset = m_depth_override.value_or(m_axial_offset); - const gp_Trsf trsf = cyl_align_trsf(*m_moving_axis, *m_fixed_axis, m_opts.flip_direction, offset); + const double offset = m_depth_override.value_or(m_axial_offset); + const gp_Trsf trsf = cyl_align_trsf(*m_moving_axis, *m_fixed_axis, m_opts.flip_direction, offset); for (const Shp_ptr& shape : m_shps) shape->SetLocalTransformation(trsf); @@ -124,7 +115,7 @@ Status Shp_cyl_align::drag_depth(const ScreenCoords& screen_coords) const gp_Dir& fixed_dir = m_fixed_axis->Direction(); const gp_Vec to_moving(m_fixed_axis->Location(), m_moving_axis->Location()); - const double param0 = to_moving.Dot(gp_Vec(fixed_dir)); + const double param0 = to_moving.Dot(gp_Vec(fixed_dir)); const gp_Pnt seed_on_ax = m_fixed_axis->Location().Translated(gp_Vec(fixed_dir) * param0); if (!m_drag_pln.has_value()) @@ -197,14 +188,6 @@ void Shp_cyl_align::cancel() void Shp_cyl_align::reset() { - m_phase = Phase::Pick_moving; - m_opts = {}; - m_axial_offset = 0; - m_moving_radius = 0; - m_fixed_radius = 0; - m_depth_override = std::nullopt; - m_moving_shp.Nullify(); - m_fixed_shp.Nullify(); - clear_all(m_moving_axis, m_fixed_axis, m_drag_pln, m_shps); + begin(); gui().set_mode(Mode::Normal); } diff --git a/src/shp_delta.cpp b/src/shp_delta.cpp index 3640b50..542be01 100644 --- a/src/shp_delta.cpp +++ b/src/shp_delta.cpp @@ -2,6 +2,13 @@ #include "gui_occt_view.h" +namespace +{ +void remove_recs_(Occt_view& view, const std::vector& recs); +void insert_recs_(Occt_view& view, const std::vector& recs); +void apply_links_(Occt_view& view, const std::vector& links, bool forward); +} // namespace + Shape_rec capture_shape_rec(const Shp& shp) { Shape_rec rec; @@ -17,45 +24,6 @@ Shape_rec capture_shape_rec(const Shp& shp) return rec; } -namespace -{ - -void remove_recs_(Occt_view& view, const std::vector& recs) -{ - for (const Shape_rec& rec : recs) - view.remove_shape_by_id(rec.id); -} - -void insert_recs_(Occt_view& view, const std::vector& recs) -{ - for (const Shape_rec& rec : recs) - view.insert_shape_rec(rec); -} - -void apply_links_(Occt_view& view, const std::vector& links, bool forward) -{ - for (const Shape_tree_delta::Link_change& ch : links) - { - Shp_ptr shp = view.find_shape_by_id(ch.id); - if (shp.IsNull()) - continue; - - if (forward) - { - shp->set_parent_id(ch.new_parent); - shp->set_sibling_order(ch.new_order); - } - else - { - shp->set_parent_id(ch.old_parent); - shp->set_sibling_order(ch.old_order); - } - } - view.sync_sketch_shape_faint_style(); -} - -} // namespace - Shape_add_delta::Shape_add_delta(std::vector added) : m_added(std::move(added)) { @@ -142,3 +110,41 @@ std::unique_ptr Shape_tree_delta::clone() const { return std::make_unique(m_added, m_removed, m_links); } + +namespace +{ +void remove_recs_(Occt_view& view, const std::vector& recs) +{ + for (const Shape_rec& rec : recs) + view.remove_shape_by_id(rec.id); +} + +void insert_recs_(Occt_view& view, const std::vector& recs) +{ + for (const Shape_rec& rec : recs) + view.insert_shape_rec(rec); +} + +void apply_links_(Occt_view& view, const std::vector& links, bool forward) +{ + for (const Shape_tree_delta::Link_change& ch : links) + { + Shp_ptr shp = view.find_shape_by_id(ch.id); + if (shp.IsNull()) + continue; + + if (forward) + { + shp->set_parent_id(ch.new_parent); + shp->set_sibling_order(ch.new_order); + } + else + { + shp->set_parent_id(ch.old_parent); + shp->set_sibling_order(ch.old_order); + } + } + view.sync_sketch_shape_faint_style(); +} + +} // namespace diff --git a/src/shp_extrude.cpp b/src/shp_extrude.cpp index 745529e..e817e40 100644 --- a/src/shp_extrude.cpp +++ b/src/shp_extrude.cpp @@ -24,80 +24,13 @@ namespace { -size_t count_shape_edges_(const TopoDS_Shape& shape) -{ - size_t n = 0; - for (TopExp_Explorer ex(shape, TopAbs_EDGE); ex.More(); ex.Next()) - ++n; - - return n; -} - -gp_Pnt centroid_of_verts_(const std::vector& verts) -{ - EZY_ASSERT(!verts.empty()); - gp_XYZ sum(0.0, 0.0, 0.0); - for (const gp_Pnt& p : verts) - sum += p.XYZ(); - - sum /= static_cast(verts.size()); - - return gp_Pnt(sum); -} - -TopoDS_Wire transform_wire_(const TopoDS_Wire& wire, const gp_Trsf& trsf) -{ - return TopoDS::Wire(BRepBuilderAPI_Transform(wire, trsf, true).Shape()); -} - -gp_Trsf section_trsf_(const gp_Ax1& axis, double height_along_axis, double twist_rad) -{ - gp_Trsf rot; - rot.SetRotation(axis, twist_rad); - gp_Trsf trans; - trans.SetTranslation(gp_Vec(axis.Direction()) * height_along_axis); - - return trans * rot; -} - -/// Ruled thru-sections solid from a closed wire with height + twist along `axis`. -/// Compatibility is off so intentional twist keeps edge/vertex pairing. -TopoDS_Shape loft_twisted_wire_(const TopoDS_Wire& wire, const gp_Ax1& axis, const double h0, const double h1, - const double ang0, const double ang1, const int n_seg) -{ - EZY_ASSERT(!wire.IsNull()); - EZY_ASSERT(n_seg >= 1); - - BRepOffsetAPI_ThruSections maker(true /*isSolid*/, true /*ruled*/); - maker.CheckCompatibility(false); - for (int i = 0; i <= n_seg; ++i) - { - const double t = static_cast(i) / static_cast(n_seg); - const double height = h0 + t * (h1 - h0); - const double ang = ang0 + t * (ang1 - ang0); - maker.AddWire(transform_wire_(wire, section_trsf_(axis, height, ang))); - } - - maker.Build(); - EZY_ASSERT(maker.IsDone()); - - return try_make_solid(maker.Shape()); -} - -std::vector face_hole_wires_(const TopoDS_Face& face, const TopoDS_Wire& outer_wire) -{ - std::vector holes; - for (TopExp_Explorer ex(face, TopAbs_WIRE); ex.More(); ex.Next()) - { - const TopoDS_Wire w = TopoDS::Wire(ex.Current()); - if (w.IsNull() || w.IsSame(outer_wire)) - continue; - - holes.push_back(w); - } - - return holes; -} +size_t count_shape_edges_(const TopoDS_Shape& shape); +gp_Pnt centroid_of_verts_(const std::vector& verts); +TopoDS_Wire transform_wire_(const TopoDS_Wire& wire, const gp_Trsf& trsf); +gp_Trsf section_trsf_(const gp_Ax1& axis, double height_along_axis, double twist_rad); +TopoDS_Shape loft_twisted_wire_(const TopoDS_Wire& wire, const gp_Ax1& axis, double h0, double h1, double ang0, + double ang1, int n_seg); +std::vector face_hole_wires_(const TopoDS_Face& face, const TopoDS_Wire& outer_wire); } // namespace Shp_extrude::Shp_extrude(Occt_view& view) @@ -115,18 +48,14 @@ bool Shp_extrude::begin_face_extrude(const AIS_Shape_ptr& shp) cancel(); - m_to_extrude_pln = face->owner_sketch.get_plane(); - m_extrude_side = Plane_side::Front; - m_to_extrude_pt = closest_to_camera(view().view_handle(), face->verts_3d); - m_curr_view_pln = view().get_view_plane(*m_to_extrude_pt); - m_to_extrude = shp; - m_face_edge_count = count_shape_edges_(shp->Shape()); - m_lite_preview_active = false; - m_phase = Phase::Height; - m_twist_angle = 0.0; - m_twist_centroid = centroid_of_verts_(face->verts_3d); - m_show_angle_input = false; - m_entered_twist_deg.reset(); + m_to_extrude_pln = face->owner_sketch.get_plane(); + m_extrude_side = Plane_side::Front; + m_to_extrude_pt = closest_to_camera(view().view_handle(), face->verts_3d); + m_curr_view_pln = view().get_view_plane(*m_to_extrude_pt); + m_to_extrude = shp; + m_face_edge_count = count_shape_edges_(shp->Shape()); + m_twist_centroid = centroid_of_verts_(face->verts_3d); + clear_all(m_lite_preview_active, m_phase, m_twist_angle, m_show_angle_input, m_entered_twist_deg); const gp_Ax1& a = m_to_extrude_pln.Axis(); const gp_Ax1& b = m_curr_view_pln.Axis(); @@ -240,10 +169,7 @@ void Shp_extrude::set_twist(const bool twist) if (!twist && m_phase == Phase::Twist) { // Return to editable height preview; drop twist angle. - m_phase = Phase::Height; - m_twist_angle = 0.0; - m_show_angle_input = false; - m_entered_twist_deg.reset(); + clear_all(m_phase, m_twist_angle, m_show_angle_input, m_entered_twist_deg); clear_angle_dim_(); view().set_entered_dim(std::nullopt); view().set_show_dim_input(false); @@ -274,9 +200,7 @@ void Shp_extrude::lock_height_begin_twist_() view().set_show_dim_input(false); // Lock height to the last preview distance (typed or mouse). view().set_entered_dim(*m_last_preview_dist); - m_twist_angle = 0.0; - m_entered_twist_deg.reset(); - m_show_angle_input = false; + clear_all(m_twist_angle, m_entered_twist_deg, m_show_angle_input); clear_length_dim_(); update_extrude_preview_(*m_last_preview_dist, m_extrude_side); } @@ -669,8 +593,7 @@ void Shp_extrude::clear_session_inputs_() { view().set_show_dim_input(false); view().set_entered_dim(std::nullopt); - m_show_angle_input = false; - m_entered_twist_deg.reset(); + clear_all(m_show_angle_input, m_entered_twist_deg); gui().hide_angle_edit(false); gui().hide_dist_edit(false); } @@ -680,15 +603,8 @@ void Shp_extrude::clear_preview_() clear_lite_other_face_(); clear_length_dim_(); clear_angle_dim_(); - m_face_edge_count = 0; - m_lite_preview_active = false; - m_last_preview_dist.reset(); - m_last_preview_side = Plane_side::Front; - m_last_preview_both_sides = false; - m_last_preview_twist = 0.0; - m_last_preview_was_twist_phase = false; - m_phase = Phase::Height; - m_twist_angle = 0.0; + clear_all(m_face_edge_count, m_lite_preview_active, m_last_preview_dist, m_last_preview_side, m_last_preview_both_sides, + m_last_preview_twist, m_last_preview_was_twist_phase, m_phase, m_twist_angle); } void Shp_extrude::refresh_tmp_dimension_style(const Length_dimension_style& style) @@ -705,3 +621,81 @@ void Shp_extrude::refresh_tmp_dimension_style(const Length_dimension_style& styl ctx().Redisplay(m_tmp_angle_dim, true); } } + +namespace +{ +size_t count_shape_edges_(const TopoDS_Shape& shape) +{ + size_t n = 0; + for (TopExp_Explorer ex(shape, TopAbs_EDGE); ex.More(); ex.Next()) + ++n; + + return n; +} + +gp_Pnt centroid_of_verts_(const std::vector& verts) +{ + EZY_ASSERT(!verts.empty()); + gp_XYZ sum(0.0, 0.0, 0.0); + for (const gp_Pnt& p : verts) + sum += p.XYZ(); + + sum /= static_cast(verts.size()); + + return gp_Pnt(sum); +} + +TopoDS_Wire transform_wire_(const TopoDS_Wire& wire, const gp_Trsf& trsf) +{ + return TopoDS::Wire(BRepBuilderAPI_Transform(wire, trsf, true).Shape()); +} + +gp_Trsf section_trsf_(const gp_Ax1& axis, double height_along_axis, double twist_rad) +{ + gp_Trsf rot; + rot.SetRotation(axis, twist_rad); + gp_Trsf trans; + trans.SetTranslation(gp_Vec(axis.Direction()) * height_along_axis); + + return trans * rot; +} + +/// Ruled thru-sections solid from a closed wire with height + twist along `axis`. +/// Compatibility is off so intentional twist keeps edge/vertex pairing. +TopoDS_Shape loft_twisted_wire_(const TopoDS_Wire& wire, const gp_Ax1& axis, const double h0, const double h1, + const double ang0, const double ang1, const int n_seg) +{ + EZY_ASSERT(!wire.IsNull()); + EZY_ASSERT(n_seg >= 1); + + BRepOffsetAPI_ThruSections maker(true /*isSolid*/, true /*ruled*/); + maker.CheckCompatibility(false); + for (int i = 0; i <= n_seg; ++i) + { + const double t = static_cast(i) / static_cast(n_seg); + const double height = h0 + t * (h1 - h0); + const double ang = ang0 + t * (ang1 - ang0); + maker.AddWire(transform_wire_(wire, section_trsf_(axis, height, ang))); + } + + maker.Build(); + EZY_ASSERT(maker.IsDone()); + + return try_make_solid(maker.Shape()); +} + +std::vector face_hole_wires_(const TopoDS_Face& face, const TopoDS_Wire& outer_wire) +{ + std::vector holes; + for (TopExp_Explorer ex(face, TopAbs_WIRE); ex.More(); ex.Next()) + { + const TopoDS_Wire w = TopoDS::Wire(ex.Current()); + if (w.IsNull() || w.IsSame(outer_wire)) + continue; + + holes.push_back(w); + } + + return holes; +} +} // namespace diff --git a/src/shp_info.cpp b/src/shp_info.cpp index 04ac922..36ab6d0 100644 --- a/src/shp_info.cpp +++ b/src/shp_info.cpp @@ -17,33 +17,10 @@ namespace shp_info { namespace { -std::string fmt_double(const double v) -{ - char buf[64]; - std::snprintf(buf, sizeof(buf), "%.6g", v); - - return buf; -} - -void add_line(std::vector& out, const char* label, const std::string& value) { out.push_back({label, value}); } - -int count_subshapes(const TopoDS_Shape& shape, const TopAbs_ShapeEnum type) -{ - int n = 0; - for (TopExp_Explorer exp(shape, type); exp.More(); exp.Next()) - ++n; - - return n; -} - -std::string shape_type_name(const TopAbs_ShapeEnum type) -{ - const auto idx = static_cast(type); - if (idx < c_names_TopAbs_ShapeEnum.size()) - return std::string(c_names_TopAbs_ShapeEnum[idx]); - - return "Unknown"; -} +std::string fmt_double_(const double v); +void add_line_(std::vector& out, const char* label, const std::string& value); +int count_subshapes_(const TopoDS_Shape& shape, const TopAbs_ShapeEnum type); +std::string shape_type_name_(const TopAbs_ShapeEnum type); } // namespace std::vector collect(const TopoDS_Shape& shape, const Display_meta* display) @@ -52,49 +29,49 @@ std::vector collect(const TopoDS_Shape& shape, const Display_meta* display if (display) { - add_line(lines, "Name", display->name); - add_line(lines, "Material", display->material); - add_line(lines, "Display", display->display_mode); - add_line(lines, "Visible", display->visible ? "yes" : "no"); + add_line_(lines, "Name", display->name); + add_line_(lines, "Material", display->material); + add_line_(lines, "Display", display->display_mode); + add_line_(lines, "Visible", display->visible ? "yes" : "no"); lines.push_back({"", ""}); } if (shape.IsNull()) { - add_line(lines, "Shape", "null"); + add_line_(lines, "Shape", "null"); return lines; } const TopAbs_ShapeEnum root_type = shape.ShapeType(); - add_line(lines, "Root type", shape_type_name(root_type)); + add_line_(lines, "Root type", shape_type_name_(root_type)); BRepCheck_Analyzer analyzer(shape); - add_line(lines, "Valid", analyzer.IsValid() ? "yes" : "no"); + add_line_(lines, "Valid", analyzer.IsValid() ? "yes" : "no"); if (!shape.Location().IsIdentity()) - add_line(lines, "Located", "yes"); + add_line_(lines, "Located", "yes"); if (root_type == TopAbs_SHELL) - add_line(lines, "Closed shell", BRep_Tool::IsClosed(shape) ? "yes" : "no"); + add_line_(lines, "Closed shell", BRep_Tool::IsClosed(shape) ? "yes" : "no"); - const int n_compound = count_subshapes(shape, TopAbs_COMPOUND); - const int n_compsolid = count_subshapes(shape, TopAbs_COMPSOLID); - const int n_solid = count_subshapes(shape, TopAbs_SOLID); - const int n_shell = count_subshapes(shape, TopAbs_SHELL); - const int n_face = count_subshapes(shape, TopAbs_FACE); - const int n_wire = count_subshapes(shape, TopAbs_WIRE); - const int n_edge = count_subshapes(shape, TopAbs_EDGE); - const int n_vertex = count_subshapes(shape, TopAbs_VERTEX); + const int n_compound = count_subshapes_(shape, TopAbs_COMPOUND); + const int n_compsolid = count_subshapes_(shape, TopAbs_COMPSOLID); + const int n_solid = count_subshapes_(shape, TopAbs_SOLID); + const int n_shell = count_subshapes_(shape, TopAbs_SHELL); + const int n_face = count_subshapes_(shape, TopAbs_FACE); + const int n_wire = count_subshapes_(shape, TopAbs_WIRE); + const int n_edge = count_subshapes_(shape, TopAbs_EDGE); + const int n_vertex = count_subshapes_(shape, TopAbs_VERTEX); lines.push_back({"", ""}); - add_line(lines, "Compounds", std::to_string(n_compound)); - add_line(lines, "CompSolids", std::to_string(n_compsolid)); - add_line(lines, "Solids", std::to_string(n_solid)); - add_line(lines, "Shells", std::to_string(n_shell)); - add_line(lines, "Faces", std::to_string(n_face)); - add_line(lines, "Wires", std::to_string(n_wire)); - add_line(lines, "Edges", std::to_string(n_edge)); - add_line(lines, "Vertices", std::to_string(n_vertex)); + add_line_(lines, "Compounds", std::to_string(n_compound)); + add_line_(lines, "CompSolids", std::to_string(n_compsolid)); + add_line_(lines, "Solids", std::to_string(n_solid)); + add_line_(lines, "Shells", std::to_string(n_shell)); + add_line_(lines, "Faces", std::to_string(n_face)); + add_line_(lines, "Wires", std::to_string(n_wire)); + add_line_(lines, "Edges", std::to_string(n_edge)); + add_line_(lines, "Vertices", std::to_string(n_vertex)); Bnd_Box bbox; BRepBndLib::Add(shape, bbox); @@ -103,10 +80,10 @@ std::vector collect(const TopoDS_Shape& shape, const Display_meta* display double xmin, ymin, zmin, xmax, ymax, zmax; bbox.Get(xmin, ymin, zmin, xmax, ymax, zmax); lines.push_back({"", ""}); - add_line(lines, "BBox X", fmt_double(xmin) + " .. " + fmt_double(xmax)); - add_line(lines, "BBox Y", fmt_double(ymin) + " .. " + fmt_double(ymax)); - add_line(lines, "BBox Z", fmt_double(zmin) + " .. " + fmt_double(zmax)); - add_line(lines, "BBox size", fmt_double(xmax - xmin) + " x " + fmt_double(ymax - ymin) + " x " + fmt_double(zmax - zmin)); + add_line_(lines, "BBox X", fmt_double_(xmin) + " .. " + fmt_double_(xmax)); + add_line_(lines, "BBox Y", fmt_double_(ymin) + " .. " + fmt_double_(ymax)); + add_line_(lines, "BBox Z", fmt_double_(zmin) + " .. " + fmt_double_(zmax)); + add_line_(lines, "BBox size", fmt_double_(xmax - xmin) + " x " + fmt_double_(ymax - ymin) + " x " + fmt_double_(zmax - zmin)); } GProp_GProps vol_props; @@ -115,20 +92,51 @@ std::vector collect(const TopoDS_Shape& shape, const Display_meta* display { const gp_Pnt com = vol_props.CentreOfMass(); lines.push_back({"", ""}); - add_line(lines, "Volume", fmt_double(vol_props.Mass())); - add_line(lines, "Center of mass", fmt_double(com.X()) + ", " + fmt_double(com.Y()) + ", " + fmt_double(com.Z())); + add_line_(lines, "Volume", fmt_double_(vol_props.Mass())); + add_line_(lines, "Center of mass", fmt_double_(com.X()) + ", " + fmt_double_(com.Y()) + ", " + fmt_double_(com.Z())); } GProp_GProps surf_props; BRepGProp::SurfaceProperties(shape, surf_props); if (surf_props.Mass() > 0.0) - add_line(lines, "Surface area", fmt_double(surf_props.Mass())); + add_line_(lines, "Surface area", fmt_double_(surf_props.Mass())); GProp_GProps lin_props; BRepGProp::LinearProperties(shape, lin_props); if (lin_props.Mass() > 0.0) - add_line(lines, "Length", fmt_double(lin_props.Mass())); + add_line_(lines, "Length", fmt_double_(lin_props.Mass())); return lines; } + +namespace +{ +std::string fmt_double_(const double v) +{ + char buf[64]; + std::snprintf(buf, sizeof(buf), "%.6g", v); + + return buf; +} + +void add_line_(std::vector& out, const char* label, const std::string& value) { out.push_back({label, value}); } + +int count_subshapes_(const TopoDS_Shape& shape, const TopAbs_ShapeEnum type) +{ + int n = 0; + for (TopExp_Explorer exp(shape, type); exp.More(); exp.Next()) + ++n; + + return n; +} + +std::string shape_type_name_(const TopAbs_ShapeEnum type) +{ + const auto idx = static_cast(type); + if (idx < c_names_TopAbs_ShapeEnum.size()) + return std::string(c_names_TopAbs_ShapeEnum[idx]); + + return "Unknown"; +} +} // namespace } // namespace shp_info diff --git a/src/shp_move.cpp b/src/shp_move.cpp index dd6135b..748d782 100644 --- a/src/shp_move.cpp +++ b/src/shp_move.cpp @@ -13,8 +13,7 @@ Shp_move::Shp_move(Occt_view& view) void Shp_move::begin(std::vector shps) { - m_delta = {}; - clear_all(m_move_pln, m_center); + clear_all(m_delta, m_move_pln, m_center); set_operation_shps_(std::move(shps)); } @@ -159,10 +158,7 @@ void Shp_move::cancel() void Shp_move::reset() { - // Reset options - m_opts = {}; - m_delta = {}; - clear_all(m_move_pln, m_center, m_shps); + clear_all(m_opts, m_delta, m_move_pln, m_center, m_shps); gui().set_mode(Mode::Normal); } diff --git a/src/shp_rotate.cpp b/src/shp_rotate.cpp index 82036fb..c6a377c 100644 --- a/src/shp_rotate.cpp +++ b/src/shp_rotate.cpp @@ -19,18 +19,7 @@ Shp_rotate::Shp_rotate(Occt_view& view) void Shp_rotate::begin(std::vector shps) { clear_all(m_angle, m_initial_mouse_pos, m_rotate_pln, m_center); - if (m_rotation_axis_vis) - { - ctx().Remove(m_rotation_axis_vis, false); - m_rotation_axis_vis = nullptr; - } - - if (m_rotation_center_vis) - { - ctx().Remove(m_rotation_center_vis, false); - m_rotation_center_vis = nullptr; - } - + clear_rotation_vis_(); set_operation_shps_(std::move(shps)); } @@ -250,22 +239,18 @@ void Shp_rotate::cancel() void Shp_rotate::reset() { - // Reset state clear_all(m_angle, m_shps, m_initial_mouse_pos, m_rotate_pln, m_center); + clear_rotation_vis_(); + gui().set_mode(Mode::Normal); +} - if (m_rotation_axis_vis) - { +void Shp_rotate::clear_rotation_vis_() +{ + if (!m_rotation_axis_vis.IsNull()) ctx().Remove(m_rotation_axis_vis, false); - m_rotation_axis_vis = nullptr; - } - - if (m_rotation_center_vis) - { + if (!m_rotation_center_vis.IsNull()) ctx().Remove(m_rotation_center_vis, false); - m_rotation_center_vis = nullptr; - } - - gui().set_mode(Mode::Normal); + clear_all(m_rotation_axis_vis, m_rotation_center_vis); } void Shp_rotate::set_rotation_axis(Rotation_axis axis) diff --git a/src/shp_rotate.h b/src/shp_rotate.h index 9fe6a67..1373574 100644 --- a/src/shp_rotate.h +++ b/src/shp_rotate.h @@ -33,6 +33,7 @@ class Shp_rotate : private Shp_operation_base void reset(); void update_rotation_axis_(); void update_rotation_center_(); + void clear_rotation_vis_(); std::optional m_rotate_pln; std::optional m_initial_mouse_pos; diff --git a/src/skt_dims.cpp b/src/skt_dims.cpp index 6307817..55fe355 100644 --- a/src/skt_dims.cpp +++ b/src/skt_dims.cpp @@ -27,15 +27,7 @@ struct Symmetric_edge_span double full_len; }; -std::optional symmetric_edge_from_center(const gp_Pnt2d& center, const gp_Dir2d& dir, double full_len) -{ - if (full_len <= Precision::Confusion()) - return std::nullopt; - - const double half = full_len * 0.5; - const gp_Vec2d v(dir); - return Symmetric_edge_span{center.Translated(-v * half), center.Translated(v * half), full_len}; -} +std::optional symmetric_edge_from_center_(const gp_Pnt2d& center, const gp_Dir2d& dir, double full_len); } // namespace Sketch_dims::Sketch_dims(Sketch& sketch) @@ -69,9 +61,7 @@ void Sketch_dims::clear_tmp_dim_anno() void Sketch_dims::on_finalize_elm_start() { - m_show_dim_input = false; - m_show_angle_input = false; - m_entered_edge_angle = std::nullopt; + clear_all(m_show_dim_input, m_show_angle_input, m_entered_edge_angle); m_sketch.m_view.gui().hide_angle_edit(); clear_tmp_dim_anno(); } @@ -80,10 +70,7 @@ void Sketch_dims::on_clear_tmps() { clear_all(m_entered_edge_len, m_show_dim_inp void Sketch_dims::clear_typed_constraints() { - m_entered_edge_angle = std::nullopt; - m_entered_edge_len = std::nullopt; - m_show_angle_input = false; - m_show_dim_input = false; + clear_all(m_entered_edge_angle, m_entered_edge_len, m_show_angle_input, m_show_dim_input); } std::optional Sketch_dims::approx_sketch_interior_ref_3d_() const @@ -389,7 +376,7 @@ void Sketch_dims::check_dimension_seg_(int kind) { const gp_Pnt2d& center = m_sketch.m_nodes[edge.node_idx_a]; std::optional span = - symmetric_edge_from_center(center, m_entered_edge_len->dir, m_entered_edge_len->len); + symmetric_edge_from_center_(center, m_entered_edge_len->dir, m_entered_edge_len->len); if (span) { @@ -418,8 +405,7 @@ void Sketch_dims::check_dimension_seg_(int kind) switch (m_sketch.m_tools.tmp_edges().size()) { case 1: - m_entered_edge_angle = std::nullopt; - m_show_angle_input = false; + clear_all(m_entered_edge_angle, m_show_angle_input); m_sketch.m_view.gui().hide_angle_edit(); m_sketch.m_tools.tmp_edges().push_back({*edge.node_idx_b}); break; @@ -434,8 +420,7 @@ void Sketch_dims::check_dimension_seg_(int kind) } else { - m_entered_edge_angle = std::nullopt; - m_show_angle_input = false; + clear_all(m_entered_edge_angle, m_show_angle_input); m_sketch.m_view.gui().hide_angle_edit(); m_sketch.m_tools.tmp_edges().push_back({*edge.node_idx_b}); } @@ -626,3 +611,16 @@ void Sketch_dims::on_sketch_hidden() if (!ld.dim.IsNull()) m_sketch.m_ctx.Erase(ld.dim, false); } + +namespace +{ +std::optional symmetric_edge_from_center_(const gp_Pnt2d& center, const gp_Dir2d& dir, double full_len) +{ + if (full_len <= Precision::Confusion()) + return std::nullopt; + + const double half = full_len * 0.5; + const gp_Vec2d v(dir); + return Symmetric_edge_span{center.Translated(-v * half), center.Translated(v * half), full_len}; +} +} // namespace diff --git a/src/skt_display.cpp b/src/skt_display.cpp index 6573e67..0d860a3 100644 --- a/src/skt_display.cpp +++ b/src/skt_display.cpp @@ -25,79 +25,13 @@ constexpr float k_background_edge_rgba[4] = {0.3f, 0.3f, 0.3f, 0.3f}; constexpr float k_background_face_rgba[4] = {0.3f, 0.3f, 0.3f, 0.2f}; constexpr float k_edge_highlight_line_width = 2.0f; -Quantity_Color rgb_from_rgba_(const float* rgba) -{ - return Quantity_Color(static_cast(rgba[0]), static_cast(rgba[1]), static_cast(rgba[2]), - Quantity_TOC_RGB); -} - -float transparency_from_rgba_(const float* rgba) { return std::clamp(1.f - rgba[3], 0.f, 1.f); } - -void apply_rgba_style_(AIS_Shape& shp, const float* rgba, float line_width) -{ - shp.SetWidth(static_cast(line_width)); - shp.SetColor(rgb_from_rgba_(rgba)); - shp.SetTransparency(static_cast(transparency_from_rgba_(rgba))); -} - -Prs3d_Drawer_ptr make_edge_hilight_drawer_(const float* rgba, float line_width) -{ - Prs3d_Drawer_ptr drawer = new Prs3d_Drawer(); - const Quantity_Color qc = rgb_from_rgba_(rgba); - drawer->SetColor(qc); - drawer->SetTransparency(transparency_from_rgba_(rgba)); - Prs3d_LineAspect_ptr line = new Prs3d_LineAspect(qc, Aspect_TOL_SOLID, static_cast(line_width)); - drawer->SetLineAspect(line); - drawer->SetWireAspect(line); - drawer->SetSeenLineAspect(line); - drawer->SetFaceBoundaryAspect(line); - return drawer; -} - -Prs3d_Drawer_ptr make_face_hilight_drawer_(const float* rgba) -{ - Prs3d_Drawer_ptr drawer = new Prs3d_Drawer(); - drawer->SetupOwnDefaults(); - const Quantity_Color qc = rgb_from_rgba_(rgba); - const float transp = transparency_from_rgba_(rgba); - drawer->SetColor(qc); - drawer->SetTransparency(transp); - - Prs3d_ShadingAspect_ptr shading = new Prs3d_ShadingAspect(); - shading->SetColor(qc); - shading->SetTransparency(static_cast(transp)); - drawer->SetShadingAspect(shading); - - Graphic3d_AspectFillArea3d_ptr fill = new Graphic3d_AspectFillArea3d(); - fill->SetAlphaMode(Graphic3d_AlphaMode_Blend); - fill->SetInteriorStyle(Aspect_IS_SOLID); - fill->SetInteriorColor(qc); - fill->SetColor(qc); - drawer->SetBasicFillAreaAspect(fill); - - Prs3d_LineAspect_ptr line = new Prs3d_LineAspect(qc, Aspect_TOL_SOLID, 2.0); - drawer->SetWireAspect(line); - drawer->SetFaceBoundaryAspect(line); - return drawer; -} - -void apply_edge_hilight_(AIS_Shape& shp, const GUI& gui) -{ - Prs3d_Drawer_ptr selected = make_edge_hilight_drawer_(gui.sketch_edge_selection_color_rgba(), k_edge_highlight_line_width); - Prs3d_Drawer_ptr hover = make_edge_hilight_drawer_(gui.sketch_edge_highlight_color_rgba(), k_edge_highlight_line_width); - shp.SetHilightAttributes(selected); - shp.SetDynamicHilightAttributes(hover); -} - -void apply_face_hilight_(AIS_Shape& shp, const GUI& gui) -{ - // Use shaded hilight so selection/hover tint the face fill, not only a wire outline. - shp.SetHilightMode(AIS_Shaded); - Prs3d_Drawer_ptr selected = make_face_hilight_drawer_(gui.sketch_face_selection_color_rgba()); - Prs3d_Drawer_ptr hover = make_face_hilight_drawer_(gui.sketch_face_highlight_color_rgba()); - shp.SetHilightAttributes(selected); - shp.SetDynamicHilightAttributes(hover); -} +Quantity_Color rgb_from_rgba_(const float* rgba); +float transparency_from_rgba_(const float* rgba); +void apply_rgba_style_(AIS_Shape& shp, const float* rgba, float line_width); +Prs3d_Drawer_ptr make_edge_hilight_drawer_(const float* rgba, float line_width); +Prs3d_Drawer_ptr make_face_hilight_drawer_(const float* rgba); +void apply_edge_hilight_(AIS_Shape& shp, const GUI& gui); +void apply_face_hilight_(AIS_Shape& shp, const GUI& gui); } // namespace void Sketch::update_edge_style_(const AIS_Shape_ptr& shp) @@ -346,3 +280,80 @@ void Sketch::set_edge_style(Edge_style style) update_all_face_styles_(); update_originating_face_style(); } + +namespace +{ +Quantity_Color rgb_from_rgba_(const float* rgba) +{ + return Quantity_Color(static_cast(rgba[0]), static_cast(rgba[1]), static_cast(rgba[2]), + Quantity_TOC_RGB); +} + +float transparency_from_rgba_(const float* rgba) { return std::clamp(1.f - rgba[3], 0.f, 1.f); } + +void apply_rgba_style_(AIS_Shape& shp, const float* rgba, float line_width) +{ + shp.SetWidth(static_cast(line_width)); + shp.SetColor(rgb_from_rgba_(rgba)); + shp.SetTransparency(static_cast(transparency_from_rgba_(rgba))); +} + +Prs3d_Drawer_ptr make_edge_hilight_drawer_(const float* rgba, float line_width) +{ + Prs3d_Drawer_ptr drawer = new Prs3d_Drawer(); + const Quantity_Color qc = rgb_from_rgba_(rgba); + drawer->SetColor(qc); + drawer->SetTransparency(transparency_from_rgba_(rgba)); + Prs3d_LineAspect_ptr line = new Prs3d_LineAspect(qc, Aspect_TOL_SOLID, static_cast(line_width)); + drawer->SetLineAspect(line); + drawer->SetWireAspect(line); + drawer->SetSeenLineAspect(line); + drawer->SetFaceBoundaryAspect(line); + return drawer; +} + +Prs3d_Drawer_ptr make_face_hilight_drawer_(const float* rgba) +{ + Prs3d_Drawer_ptr drawer = new Prs3d_Drawer(); + drawer->SetupOwnDefaults(); + const Quantity_Color qc = rgb_from_rgba_(rgba); + const float transp = transparency_from_rgba_(rgba); + drawer->SetColor(qc); + drawer->SetTransparency(transp); + + Prs3d_ShadingAspect_ptr shading = new Prs3d_ShadingAspect(); + shading->SetColor(qc); + shading->SetTransparency(static_cast(transp)); + drawer->SetShadingAspect(shading); + + Graphic3d_AspectFillArea3d_ptr fill = new Graphic3d_AspectFillArea3d(); + fill->SetAlphaMode(Graphic3d_AlphaMode_Blend); + fill->SetInteriorStyle(Aspect_IS_SOLID); + fill->SetInteriorColor(qc); + fill->SetColor(qc); + drawer->SetBasicFillAreaAspect(fill); + + Prs3d_LineAspect_ptr line = new Prs3d_LineAspect(qc, Aspect_TOL_SOLID, 2.0); + drawer->SetWireAspect(line); + drawer->SetFaceBoundaryAspect(line); + return drawer; +} + +void apply_edge_hilight_(AIS_Shape& shp, const GUI& gui) +{ + Prs3d_Drawer_ptr selected = make_edge_hilight_drawer_(gui.sketch_edge_selection_color_rgba(), k_edge_highlight_line_width); + Prs3d_Drawer_ptr hover = make_edge_hilight_drawer_(gui.sketch_edge_highlight_color_rgba(), k_edge_highlight_line_width); + shp.SetHilightAttributes(selected); + shp.SetDynamicHilightAttributes(hover); +} + +void apply_face_hilight_(AIS_Shape& shp, const GUI& gui) +{ + // Use shaded hilight so selection/hover tint the face fill, not only a wire outline. + shp.SetHilightMode(AIS_Shaded); + Prs3d_Drawer_ptr selected = make_face_hilight_drawer_(gui.sketch_face_selection_color_rgba()); + Prs3d_Drawer_ptr hover = make_face_hilight_drawer_(gui.sketch_face_highlight_color_rgba()); + shp.SetHilightAttributes(selected); + shp.SetDynamicHilightAttributes(hover); +} +} // namespace diff --git a/src/skt_edge.cpp b/src/skt_edge.cpp index 041e693..91b68f3 100644 --- a/src/skt_edge.cpp +++ b/src/skt_edge.cpp @@ -11,6 +11,13 @@ #include "utl.h" #include "utl_geom.h" +namespace +{ +gp_Vec2d normalize_or_axis_(gp_Vec2d v); +gp_Vec2d curve_tangent_dir_2d_(const BRepAdaptor_Curve& curve, double u, const gp_Pln& pln, bool forward); +gp_Vec2d arc_outgoing_dir_2d_(const Sketch_edge& e, const gp_Pnt2d& from_pt, const gp_Pnt2d& to_pt, const gp_Pln& pln); +} // namespace + bool Sketch_edge::reversed(size_t idx_a, size_t idx_b) const { if (node_idx_a == idx_a && node_idx_b == idx_b) @@ -34,6 +41,20 @@ bool sketch_edge_is_arc(const Sketch_edge& e) bool sketch_edge_is_linear(const Sketch_edge& e) { return e.node_idx_b.has_value() && !sketch_edge_is_arc(e); } +gp_Vec2d sketch_edge_outgoing_dir_2d(const Sketch_edge& e, const gp_Pnt2d& from_pt, const gp_Pnt2d& to_pt, const gp_Pln& pln) +{ + if (sketch_edge_is_arc(e)) + return arc_outgoing_dir_2d_(e, from_pt, to_pt, pln); + + return normalize_or_axis_(gp_Vec2d(from_pt, to_pt)); +} + +gp_Vec2d sketch_edge_incoming_dir_2d(const Sketch_edge& e, const gp_Pnt2d& from_pt, const gp_Pnt2d& to_pt, const gp_Pln& pln) +{ + // Tangent at to_pt in the travel direction == opposite of outgoing back toward from_pt. + return -sketch_edge_outgoing_dir_2d(e, to_pt, from_pt, pln); +} + namespace { gp_Vec2d normalize_or_axis_(gp_Vec2d v) @@ -94,17 +115,3 @@ gp_Vec2d arc_outgoing_dir_2d_(const Sketch_edge& e, const gp_Pnt2d& from_pt, con return normalize_or_axis_(ret); } } // namespace - -gp_Vec2d sketch_edge_outgoing_dir_2d(const Sketch_edge& e, const gp_Pnt2d& from_pt, const gp_Pnt2d& to_pt, const gp_Pln& pln) -{ - if (sketch_edge_is_arc(e)) - return arc_outgoing_dir_2d_(e, from_pt, to_pt, pln); - - return normalize_or_axis_(gp_Vec2d(from_pt, to_pt)); -} - -gp_Vec2d sketch_edge_incoming_dir_2d(const Sketch_edge& e, const gp_Pnt2d& from_pt, const gp_Pnt2d& to_pt, const gp_Pln& pln) -{ - // Tangent at to_pt in the travel direction == opposite of outgoing back toward from_pt. - return -sketch_edge_outgoing_dir_2d(e, to_pt, from_pt, pln); -} diff --git a/src/skt_json.cpp b/src/skt_json.cpp index a3884df..2f161db 100644 --- a/src/skt_json.cpp +++ b/src/skt_json.cpp @@ -25,36 +25,8 @@ using json = nlohmann::json; namespace { -std::optional find_live_node_at_(Sketch& sketch, const gp_Pnt2d& p) -{ - const Sketch_nodes& ns = sketch.get_nodes(); - for (size_t i = 0, n = ns.size(); i < n; ++i) - if (!ns[i].deleted && ns[i].SquareDistance(p) < Precision::SquareConfusion()) - return i; - - return std::nullopt; -} - -/// Serializes one sketch node for `j["nodes"]`. Caller must only pass non-deleted nodes (compact save omits tombstones). -json node_to_json_(const Sketch_nodes::Node& nd) -{ - EZY_ASSERT(!nd.deleted); - json o = ::to_json(static_cast(nd)); - if (nd.midpoint) - o["midpoint"] = true; - - if (nd.permanent) - o["permanent"] = true; - - if (nd.origin) - o["origin"] = true; - - if (!nd.name.empty()) - o["name"] = nd.name; - - return o; -} - +std::optional find_live_node_at_(Sketch& sketch, const gp_Pnt2d& p); +json node_to_json_(const Sketch_nodes::Node& nd); } // namespace void Sketch_json::load_nodes_(Sketch& sketch, const json& nodes_json) @@ -352,3 +324,37 @@ bool Sketch_json::edges_use_node_indices_(const json& j) return e0[0].is_number(); } + +namespace +{ +std::optional find_live_node_at_(Sketch& sketch, const gp_Pnt2d& p) +{ + const Sketch_nodes& ns = sketch.get_nodes(); + for (size_t i = 0, n = ns.size(); i < n; ++i) + if (!ns[i].deleted && ns[i].SquareDistance(p) < Precision::SquareConfusion()) + return i; + + return std::nullopt; +} + +/// Serializes one sketch node for `j["nodes"]`. Caller must only pass non-deleted nodes (compact save omits tombstones). +json node_to_json_(const Sketch_nodes::Node& nd) +{ + EZY_ASSERT(!nd.deleted); + json o = ::to_json(static_cast(nd)); + if (nd.midpoint) + o["midpoint"] = true; + + if (nd.permanent) + o["permanent"] = true; + + if (nd.origin) + o["origin"] = true; + + if (!nd.name.empty()) + o["name"] = nd.name; + + return o; +} + +} // namespace diff --git a/src/skt_nodes.cpp b/src/skt_nodes.cpp index 0576fd7..4a7bd7f 100644 --- a/src/skt_nodes.cpp +++ b/src/skt_nodes.cpp @@ -24,59 +24,11 @@ glm::vec3 s_snap_guide_color_axis{0.957627f, 0.064924f, 0.54 float s_snap_guide_line_width = 1.0f; bool s_annotate_all_coaxial_nodes = true; -static Quantity_Color snap_guide_qc(const glm::vec3& c) { return Quantity_Color(c.x, c.y, c.z, Quantity_TOC_RGB); } - -static void prepare_snap_ais_(AIS_InteractiveContext& ctx, const AIS_Shape_ptr& ais) -{ - if (ais.IsNull()) - return; - - ctx.Unhilight(ais, false); - ctx.Deactivate(ais); -} - -static void update_snap_ais_shape_(AIS_InteractiveContext& ctx, AIS_Shape_ptr& ais, const TopoDS_Shape& shape, - const glm::vec3& color) -{ - if (shape.IsNull()) - { - if (!ais.IsNull()) - { - ctx.Remove(ais, false); - ais.Nullify(); - } - - return; - } - - const Quantity_Color qc = snap_guide_qc(color); - if (ais.IsNull()) - { - ais = new AIS_Shape(shape); - ais->SetWidth(s_snap_guide_line_width); - ais->SetColor(qc); - ctx.Display(ais, false); - prepare_snap_ais_(ctx, ais); - } - else - { - prepare_snap_ais_(ctx, ais); - ais->Set(shape); - ais->SetWidth(s_snap_guide_line_width); - ais->SetColor(qc); - ctx.Redisplay(ais, false); - prepare_snap_ais_(ctx, ais); - } -} - -static void clear_snap_ais_(AIS_InteractiveContext& ctx, AIS_Shape_ptr& ais) -{ - if (!ais.IsNull()) - { - ctx.Remove(ais, false); - ais.Nullify(); - } -} +Quantity_Color snap_guide_qc_(const glm::vec3& c); +void prepare_snap_ais_(AIS_InteractiveContext& ctx, const AIS_Shape_ptr& ais); +void update_snap_ais_shape_(AIS_InteractiveContext& ctx, AIS_Shape_ptr& ais, const TopoDS_Shape& shape, + const glm::vec3& color); +void clear_snap_ais_(AIS_InteractiveContext& ctx, AIS_Shape_ptr& ais); } // namespace class Sketch_nodes::Impl @@ -906,4 +858,60 @@ bool Sketch_nodes::get_annotate_all_coaxial_nodes() { return s_annotate_all_coax void Sketch_nodes::set_origin_snap_enabled(bool enabled) { m_impl->set_origin_snap_enabled(enabled); } -bool Sketch_nodes::origin_snap_enabled() const { return m_impl->origin_snap_enabled(); } \ No newline at end of file +bool Sketch_nodes::origin_snap_enabled() const { return m_impl->origin_snap_enabled(); } + +namespace +{ +Quantity_Color snap_guide_qc_(const glm::vec3& c) { return Quantity_Color(c.x, c.y, c.z, Quantity_TOC_RGB); } + +void prepare_snap_ais_(AIS_InteractiveContext& ctx, const AIS_Shape_ptr& ais) +{ + if (ais.IsNull()) + return; + + ctx.Unhilight(ais, false); + ctx.Deactivate(ais); +} + +void update_snap_ais_shape_(AIS_InteractiveContext& ctx, AIS_Shape_ptr& ais, const TopoDS_Shape& shape, const glm::vec3& color) +{ + if (shape.IsNull()) + { + if (!ais.IsNull()) + { + ctx.Remove(ais, false); + ais.Nullify(); + } + + return; + } + + const Quantity_Color qc = snap_guide_qc_(color); + if (ais.IsNull()) + { + ais = new AIS_Shape(shape); + ais->SetWidth(s_snap_guide_line_width); + ais->SetColor(qc); + ctx.Display(ais, false); + prepare_snap_ais_(ctx, ais); + } + else + { + prepare_snap_ais_(ctx, ais); + ais->Set(shape); + ais->SetWidth(s_snap_guide_line_width); + ais->SetColor(qc); + ctx.Redisplay(ais, false); + prepare_snap_ais_(ctx, ais); + } +} + +void clear_snap_ais_(AIS_InteractiveContext& ctx, AIS_Shape_ptr& ais) +{ + if (!ais.IsNull()) + { + ctx.Remove(ais, false); + ais.Nullify(); + } +} +} // namespace \ No newline at end of file diff --git a/src/skt_tools.cpp b/src/skt_tools.cpp index 40b7931..9fa2c68 100644 --- a/src/skt_tools.cpp +++ b/src/skt_tools.cpp @@ -31,25 +31,8 @@ struct Symmetric_edge_span double full_len; }; -std::optional symmetric_edge_from_center(const gp_Pnt2d& center, const gp_Dir2d& dir, double full_len) -{ - if (full_len <= Precision::Confusion()) - return std::nullopt; - - const double half = full_len * 0.5; - const gp_Vec2d v(dir); - return Symmetric_edge_span{center.Translated(-v * half), center.Translated(v * half), full_len}; -} - -std::optional symmetric_edge_from_center_and_hint(const gp_Pnt2d& center, const gp_Pnt2d& dir_hint_pt) -{ - gp_Vec2d v(center, dir_hint_pt); - const double half = v.Magnitude(); - if (half <= Precision::Confusion()) - return std::nullopt; - - return symmetric_edge_from_center(center, gp_Dir2d(v), half * 2.0); -} +std::optional symmetric_edge_from_center_(const gp_Pnt2d& center, const gp_Dir2d& dir, double full_len); +std::optional symmetric_edge_from_center_and_hint_(const gp_Pnt2d& center, const gp_Pnt2d& dir_hint_pt); } // namespace Sketch_tools::Sketch_tools(Sketch& sketch) @@ -189,7 +172,7 @@ bool Sketch_tools::complete_edge_from_center_(const ScreenCoords& screen_coords) std::optional span; if (m_sketch.m_dims.entered_edge_len().has_value()) - span = symmetric_edge_from_center(center, m_sketch.m_dims.entered_edge_len()->dir, m_sketch.m_dims.entered_edge_len()->len); + span = symmetric_edge_from_center_(center, m_sketch.m_dims.entered_edge_len()->dir, m_sketch.m_dims.entered_edge_len()->len); else if (m_sketch.m_dims.entered_edge_angle().has_value()) { @@ -201,7 +184,7 @@ bool Sketch_tools::complete_edge_from_center_(const ScreenCoords& screen_coords) gp_Dir2d dir(std::cos(angle_rad), std::sin(angle_rad)); gp_Vec2d to_click(center, *pt_opt); const double half = std::abs(to_click.Dot(gp_Vec2d(dir))); - span = symmetric_edge_from_center(center, dir, half * 2.0); + span = symmetric_edge_from_center_(center, dir, half * 2.0); } else { @@ -209,7 +192,7 @@ bool Sketch_tools::complete_edge_from_center_(const ScreenCoords& screen_coords) if (!pt_opt) return true; - span = symmetric_edge_from_center_and_hint(center, *pt_opt); + span = symmetric_edge_from_center_and_hint_(center, *pt_opt); } if (!span) @@ -217,10 +200,7 @@ bool Sketch_tools::complete_edge_from_center_(const ScreenCoords& screen_coords) edge.node_idx_a = m_sketch.m_nodes.get_node_exact(span->pt_a); m_sketch.update_edge_end_pt_(edge, m_sketch.m_nodes.get_node_exact(span->pt_b)); - m_sketch.m_dims.entered_edge_angle() = std::nullopt; - m_sketch.m_dims.entered_edge_len() = std::nullopt; - m_sketch.m_dims.set_show_angle_input(false); - m_sketch.m_dims.set_show_dim_input(false); + m_sketch.m_dims.clear_typed_constraints(); m_sketch.m_view.gui().hide_angle_edit(); finalize(); return true; @@ -259,8 +239,7 @@ void Sketch_tools::add_line_string_pt_(const ScreenCoords& screen_coords, Sketch } // Start a new edge - clear constraints for fresh start (click path for multi-line) - m_sketch.m_dims.entered_edge_angle() = std::nullopt; - m_sketch.m_dims.entered_edge_len() = std::nullopt; + clear_all(m_sketch.m_dims.entered_edge_angle(), m_sketch.m_dims.entered_edge_len()); m_sketch.m_dims.set_show_angle_input(false); m_sketch.m_view.gui().hide_angle_edit(); m_tmp_edges.push_back({node_idx}); @@ -313,19 +292,19 @@ void Sketch_tools::move_line_string_pt_(const ScreenCoords& screen_coords) const double angle_rad = to_radians(*m_sketch.m_dims.entered_edge_angle()); gp_Dir2d dir(std::cos(angle_rad), std::sin(angle_rad)); if (m_sketch.m_dims.entered_edge_len().has_value()) - span = symmetric_edge_from_center(center, dir, m_sketch.m_dims.entered_edge_len()->len); + span = symmetric_edge_from_center_(center, dir, m_sketch.m_dims.entered_edge_len()->len); else { gp_Vec2d to_mouse(center, pt_b); const double half = std::abs(to_mouse.Dot(gp_Vec2d(dir))); - span = symmetric_edge_from_center(center, dir, half * 2.0); + span = symmetric_edge_from_center_(center, dir, half * 2.0); } } else if (m_sketch.m_dims.entered_edge_len().has_value()) - span = symmetric_edge_from_center(center, m_sketch.m_dims.entered_edge_len()->dir, + span = symmetric_edge_from_center_(center, m_sketch.m_dims.entered_edge_len()->dir, m_sketch.m_dims.entered_edge_len()->len); else - span = symmetric_edge_from_center_and_hint(center, pt_b); + span = symmetric_edge_from_center_and_hint_(center, pt_b); if (!span) { @@ -645,10 +624,7 @@ void Sketch_tools::add_node_pt_(const ScreenCoords& screen_coords) { auto start_rubber_from_anchor = [this](size_t idx_a) { - m_sketch.m_dims.entered_edge_angle() = std::nullopt; - m_sketch.m_dims.entered_edge_len() = std::nullopt; - m_sketch.m_dims.set_show_angle_input(false); - m_sketch.m_dims.set_show_dim_input(false); + m_sketch.m_dims.clear_typed_constraints(); m_sketch.m_view.gui().hide_angle_edit(); m_tmp_edges.push_back({idx_a}); }; @@ -994,3 +970,26 @@ void Sketch_tools::finalize_add_node_elm_cleanup_() m_sketch.m_nodes.hide_snap_annos(); m_sketch.update_faces_(); } + +namespace +{ +std::optional symmetric_edge_from_center_(const gp_Pnt2d& center, const gp_Dir2d& dir, double full_len) +{ + if (full_len <= Precision::Confusion()) + return std::nullopt; + + const double half = full_len * 0.5; + const gp_Vec2d v(dir); + return Symmetric_edge_span{center.Translated(-v * half), center.Translated(v * half), full_len}; +} + +std::optional symmetric_edge_from_center_and_hint_(const gp_Pnt2d& center, const gp_Pnt2d& dir_hint_pt) +{ + gp_Vec2d v(center, dir_hint_pt); + const double half = v.Magnitude(); + if (half <= Precision::Confusion()) + return std::nullopt; + + return symmetric_edge_from_center_(center, gp_Dir2d(v), half * 2.0); +} +} // namespace diff --git a/src/skt_underlay.cpp b/src/skt_underlay.cpp index 87a3ee0..1a6e67e 100644 --- a/src/skt_underlay.cpp +++ b/src/skt_underlay.cpp @@ -725,10 +725,7 @@ void Sketch_underlay::Impl::ctx_erase() void Sketch_underlay::Impl::clear_() { ctx_erase(); - m_rgba.reset(); - m_asset_id.clear(); - m_w = 0; - m_h = 0; + clear_all(m_rgba, m_asset_id, m_w, m_h); } void Sketch_underlay::Impl::sync_visibility_(const gp_Pln& pln) diff --git a/src/utl.h b/src/utl.h index fff028c..6dcfe52 100644 --- a/src/utl.h +++ b/src/utl.h @@ -83,8 +83,11 @@ template class Result : public Status * * Supported types: * - Containers with `clear()` (e.g., `std::vector`, `std::string`) - * - Arithmetic types (e.g., `int`, `float`), set to 0 - * - `std::optional`, set to `std::nullopt` + * - Types with `reset()` / `std::optional` (nullopt) + * - Arithmetic types (e.g., `int`, `float`, `bool`), set to 0 / false + * - Types with `Nullify()` (OCCT handles, `TopoDS_Shape`, ...) + * - Raw pointers (nullptr) + * - Enums and aggregates (`arg = T{}`) * * Fails at compile time for unsupported types. */ diff --git a/src/utl.inl b/src/utl.inl index 44b7769..d0098aa 100644 --- a/src/utl.inl +++ b/src/utl.inl @@ -1,3 +1,5 @@ +#include + // Helper function for `for_each_flat` template void handle_arg(Lambda&& lambda, T&& arg) { @@ -57,10 +59,12 @@ inline void clear_all() {} * * Supported types: * - Containers with `clear()` (e.g., `std::vector`, `std::string`) - * - Arithmetic types (e.g., `int`, `float`), set to 0 + * - Types with `reset()` (e.g. `std::optional` via reset) + * - Arithmetic types (e.g., `int`, `float`, `bool`), set to 0 / false * - `std::optional`, set to `std::nullopt` - * - `opencascade::handle`, call Nullify() - * - Raw pointers are set to nullptr + * - Types with `Nullify()` (OCCT handles, `TopoDS_Shape`, ...), call Nullify() + * - Raw pointers, set to nullptr + * - Enums and aggregates, value-initialized (`arg = T{}`) * * Fails at compile time for unsupported types. */ @@ -82,9 +86,13 @@ template void clear_all(T& arg, Args&... args) arg = std::nullopt; else if constexpr (has_nullify::value) arg.Nullify(); + else if constexpr (std::is_enum_v || std::is_aggregate_v) + // Enums / option structs / small aggregates: value-initialize + arg = T{}; else // Fail for unsupported types - static_assert(false, "clear_all: Type must have clear(), be arithmetic, opencascade::handle, or be std::optional"); + static_assert(false, "clear_all: Type must have clear()/reset()/Nullify(), be arithmetic/optional/pointer, " + "or be an enum/aggregate"); // Recursively process remaining arguments clear_all(args...); diff --git a/src/utl_asset_store.cpp b/src/utl_asset_store.cpp index eac8e60..cbdd384 100644 --- a/src/utl_asset_store.cpp +++ b/src/utl_asset_store.cpp @@ -5,27 +5,16 @@ namespace { - -uint64_t fnv1a64_feed(uint64_t hash, const uint8_t* data, std::size_t len) -{ - for (std::size_t i = 0; i < len; ++i) - { - hash ^= static_cast(data[i]); - hash *= 1099511628211ULL; - } - - return hash; -} - +uint64_t fnv1a64_feed_(uint64_t hash, const uint8_t* data, std::size_t len); } // namespace std::string Ezy_asset_store::make_asset_id(const uint8_t* rgba, std::size_t len, int w, int h) { uint64_t hash = 14695981039346656037ULL; - hash = fnv1a64_feed(hash, reinterpret_cast(&w), sizeof(w)); - hash = fnv1a64_feed(hash, reinterpret_cast(&h), sizeof(h)); + hash = fnv1a64_feed_(hash, reinterpret_cast(&w), sizeof(w)); + hash = fnv1a64_feed_(hash, reinterpret_cast(&h), sizeof(h)); if (rgba && len > 0) - hash = fnv1a64_feed(hash, rgba, len); + hash = fnv1a64_feed_(hash, rgba, len); std::ostringstream oss; oss << std::hex << std::setfill('0') << std::setw(16) << hash; @@ -56,3 +45,17 @@ void Ezy_asset_store::import_asset(const std::string& asset_id, std::vector(data[i]); + hash *= 1099511628211ULL; + } + + return hash; +} +} // namespace diff --git a/src/utl_cad_file_info.cpp b/src/utl_cad_file_info.cpp index 58d7b23..56e0ff1 100644 --- a/src/utl_cad_file_info.cpp +++ b/src/utl_cad_file_info.cpp @@ -36,11 +36,196 @@ namespace utl_cad_file_info { namespace { -void add_line(std::vector& out, const char* label, const std::string& value) { out.push_back({label, value}); } +std::vector collect_step_(const std::string& file_path, const std::string& file_bytes, + const Atomic_progress_indicator_ptr& progress); + +void add_line_(std::vector& out, const char* label, const std::string& value); + +void add_blank_(std::vector& out); + +std::string to_lower_ext_(const std::string& path); + +std::string fmt_bytes_(const size_t n); + +std::string fmt_double_(const double v); + +bool starts_with_ci_(const std::string& s, const char* prefix); + +bool looks_like_ply_(const std::string& bytes); + +bool looks_like_step_(const std::string& bytes); + +bool looks_like_iges_(const std::string& bytes); + +bool is_binary_stl_(const std::string& bytes); + +bool looks_like_stl_(const std::string& bytes); + +int count_subshapes_(const TopoDS_Shape& shape, const TopAbs_ShapeEnum type); + +void append_shape_summary_(std::vector& lines, const TopoDS_Shape& shape); + +void append_common_(std::vector& lines, const std::string& file_path, const std::string& file_bytes, Format fmt); + +void set_progress_stage_(const Atomic_progress_indicator_ptr& progress, const char* stage); + +Message_ProgressRange start_progress_(const Atomic_progress_indicator_ptr& progress); + +std::vector collect_iges_(const std::string& file_path, const std::string& file_bytes); + +std::vector collect_stl_(const std::string& file_path, const std::string& file_bytes); + +std::vector collect_ply_(const std::string& file_path, const std::string& file_bytes); + +std::string trim_name_(std::string s); + +std::string xcaf_label_name_(const TDF_Label& lab); + +std::string xcaf_product_name_(const XCAFDoc_ShapeTool_ptr& shapes, const TDF_Label& lab); + +void append_named_from_label_(const XCAFDoc_ShapeTool_ptr& shapes, const TDF_Label& lab, std::vector& out); + +void append_tree_from_label_(const XCAFDoc_ShapeTool_ptr& shapes, const TDF_Label& lab, int parent_index, + std::vector& out); + +Status read_step_named_bodies_xcaf_(const std::string& file_bytes, std::vector& out, + const Message_ProgressRange& progress); + +Status read_step_named_bodies_plain_(const std::string& file_bytes, std::vector& out, + const Message_ProgressRange& progress); + +bool collect_step_has_status_error_(const std::vector& lines); + +std::vector collect_step_xcaf_(const std::string& file_path, const std::string& file_bytes, + const Atomic_progress_indicator_ptr& progress); + +std::vector collect_step_plain_(const std::string& file_path, const std::string& file_bytes, + const Atomic_progress_indicator_ptr& progress); + +Status read_step_named_tree_xcaf_(const std::string& file_bytes, std::vector& out, + const Message_ProgressRange& progress); +} // namespace + +Format detect(const std::string& file_path, const std::string& file_bytes) +{ + const std::string ext = to_lower_ext_(file_path); + if (ext == ".step" || ext == ".stp") + return Format::Step; + + if (ext == ".igs" || ext == ".iges") + return Format::Iges; + + if (ext == ".stl") + return Format::Stl; + + if (ext == ".ply") + return Format::Ply; + + // Content sniff when extension is missing or wrong (e.g. browser basename only). + if (looks_like_ply_(file_bytes)) + return Format::Ply; + + if (looks_like_step_(file_bytes)) + return Format::Step; + + if (looks_like_stl_(file_bytes)) + return Format::Stl; + + if (looks_like_iges_(file_bytes)) + return Format::Iges; + + return Format::Unknown; +} + +bool can_import(Format fmt) { return fmt == Format::Step || fmt == Format::Ply; } + +const char* format_label(Format fmt) +{ + switch (fmt) + { + case Format::Step: + return "STEP"; + case Format::Iges: + return "IGES"; + case Format::Stl: + return "STL"; + case Format::Ply: + return "PLY"; + default: + return "Unknown"; + } +} + +std::vector collect(const std::string& file_path, const std::string& file_bytes, + const Atomic_progress_indicator_ptr& progress) +{ + const Format fmt = detect(file_path, file_bytes); + switch (fmt) + { + case Format::Step: + return collect_step_(file_path, file_bytes, progress); + case Format::Iges: + return collect_iges_(file_path, file_bytes); + case Format::Stl: + return collect_stl_(file_path, file_bytes); + case Format::Ply: + return collect_ply_(file_path, file_bytes); + default: + { + std::vector lines; + append_common_(lines, file_path, file_bytes, Format::Unknown); + add_blank_(lines); + add_line_(lines, "Status", "unsupported or unrecognized format"); + add_line_(lines, "Hint", "Open STEP, IGES, STL, or PLY"); + return lines; + } + } +} + +Status read_step_named_bodies(const std::string& file_bytes, std::vector& out, + const Message_ProgressRange& progress) +{ + out.clear(); + const Status xcaf = read_step_named_bodies_xcaf_(file_bytes, out, progress); + if (xcaf.is_ok()) + return xcaf; + + out.clear(); + return read_step_named_bodies_plain_(file_bytes, out, Message_ProgressRange()); +} + +Status read_step_named_tree(const std::string& file_bytes, std::vector& out, const Message_ProgressRange& progress) +{ + out.clear(); + const Status xcaf = read_step_named_tree_xcaf_(file_bytes, out, progress); + if (xcaf.is_ok()) + return xcaf; + + out.clear(); + std::vector flat; + const Status plain = read_step_named_bodies_plain_(file_bytes, flat, Message_ProgressRange()); + if (!plain.is_ok()) + return plain; -void add_blank(std::vector& out) { out.push_back({"", ""}); } + for (Named_body& b : flat) + { + Named_node n; + n.shape = std::move(b.shape); + n.name = std::move(b.name); + n.is_group = false; + n.parent_index = -1; + out.push_back(std::move(n)); + } + return Status::ok(); +} -std::string to_lower_ext(const std::string& path) +namespace +{ +void add_line_(std::vector& out, const char* label, const std::string& value) { out.push_back({label, value}); } + +void add_blank_(std::vector& out) { out.push_back({"", ""}); } + +std::string to_lower_ext_(const std::string& path) { std::string ext = std::filesystem::path(path).extension().string(); for (char& c : ext) @@ -49,7 +234,7 @@ std::string to_lower_ext(const std::string& path) return ext; } -std::string fmt_bytes(const size_t n) +std::string fmt_bytes_(const size_t n) { char buf[64]; if (n < 1024) @@ -62,14 +247,14 @@ std::string fmt_bytes(const size_t n) return buf; } -std::string fmt_double(const double v) +std::string fmt_double_(const double v) { char buf[64]; std::snprintf(buf, sizeof(buf), "%.6g", v); return buf; } -bool starts_with_ci(const std::string& s, const char* prefix) +bool starts_with_ci_(const std::string& s, const char* prefix) { const size_t n = std::strlen(prefix); if (s.size() < n) @@ -82,15 +267,15 @@ bool starts_with_ci(const std::string& s, const char* prefix) return true; } -bool looks_like_ply(const std::string& bytes) { return starts_with_ci(bytes, "ply"); } +bool looks_like_ply_(const std::string& bytes) { return starts_with_ci_(bytes, "ply"); } -bool looks_like_step(const std::string& bytes) +bool looks_like_step_(const std::string& bytes) { // ISO-10303-21 exchange files typically start with "ISO-10303-21;" return bytes.find("ISO-10303-21") != std::string::npos; } -bool looks_like_iges(const std::string& bytes) +bool looks_like_iges_(const std::string& bytes) { // Start / global / directory / parameter / terminate section markers (cols 73-80). if (bytes.size() < 80) @@ -100,7 +285,7 @@ bool looks_like_iges(const std::string& bytes) return c73 == 'S' || c73 == 'G' || c73 == 'D' || c73 == 'P' || c73 == 'T'; } -bool is_binary_stl(const std::string& bytes) +bool is_binary_stl_(const std::string& bytes) { if (bytes.size() < 84) return false; @@ -111,15 +296,15 @@ bool is_binary_stl(const std::string& bytes) return expected == bytes.size(); } -bool looks_like_stl(const std::string& bytes) +bool looks_like_stl_(const std::string& bytes) { - if (is_binary_stl(bytes)) + if (is_binary_stl_(bytes)) return true; - return starts_with_ci(bytes, "solid"); + return starts_with_ci_(bytes, "solid"); } -int count_subshapes(const TopoDS_Shape& shape, const TopAbs_ShapeEnum type) +int count_subshapes_(const TopoDS_Shape& shape, const TopAbs_ShapeEnum type) { int n = 0; for (TopExp_Explorer exp(shape, type); exp.More(); exp.Next()) @@ -128,20 +313,20 @@ int count_subshapes(const TopoDS_Shape& shape, const TopAbs_ShapeEnum type) return n; } -void append_shape_summary(std::vector& lines, const TopoDS_Shape& shape) +void append_shape_summary_(std::vector& lines, const TopoDS_Shape& shape) { if (shape.IsNull()) { - add_line(lines, "Shape", "null"); + add_line_(lines, "Shape", "null"); return; } - add_line(lines, "Solids", std::to_string(count_subshapes(shape, TopAbs_SOLID))); - add_line(lines, "Shells", std::to_string(count_subshapes(shape, TopAbs_SHELL))); - add_line(lines, "Faces", std::to_string(count_subshapes(shape, TopAbs_FACE))); - add_line(lines, "Wires", std::to_string(count_subshapes(shape, TopAbs_WIRE))); - add_line(lines, "Edges", std::to_string(count_subshapes(shape, TopAbs_EDGE))); - add_line(lines, "Vertices", std::to_string(count_subshapes(shape, TopAbs_VERTEX))); + add_line_(lines, "Solids", std::to_string(count_subshapes_(shape, TopAbs_SOLID))); + add_line_(lines, "Shells", std::to_string(count_subshapes_(shape, TopAbs_SHELL))); + add_line_(lines, "Faces", std::to_string(count_subshapes_(shape, TopAbs_FACE))); + add_line_(lines, "Wires", std::to_string(count_subshapes_(shape, TopAbs_WIRE))); + add_line_(lines, "Edges", std::to_string(count_subshapes_(shape, TopAbs_EDGE))); + add_line_(lines, "Vertices", std::to_string(count_subshapes_(shape, TopAbs_VERTEX))); Bnd_Box bbox; BRepBndLib::Add(shape, bbox); @@ -150,33 +335,33 @@ void append_shape_summary(std::vector& lines, const TopoDS_Shape& shape) double xmin, ymin, zmin, xmax, ymax, zmax; bbox.Get(xmin, ymin, zmin, xmax, ymax, zmax); - add_blank(lines); - add_line(lines, "BBox X", fmt_double(xmin) + " .. " + fmt_double(xmax)); - add_line(lines, "BBox Y", fmt_double(ymin) + " .. " + fmt_double(ymax)); - add_line(lines, "BBox Z", fmt_double(zmin) + " .. " + fmt_double(zmax)); - add_line(lines, "BBox size", fmt_double(xmax - xmin) + " x " + fmt_double(ymax - ymin) + " x " + fmt_double(zmax - zmin)); + add_blank_(lines); + add_line_(lines, "BBox X", fmt_double_(xmin) + " .. " + fmt_double_(xmax)); + add_line_(lines, "BBox Y", fmt_double_(ymin) + " .. " + fmt_double_(ymax)); + add_line_(lines, "BBox Z", fmt_double_(zmin) + " .. " + fmt_double_(zmax)); + add_line_(lines, "BBox size", fmt_double_(xmax - xmin) + " x " + fmt_double_(ymax - ymin) + " x " + fmt_double_(zmax - zmin)); } -void append_common(std::vector& lines, const std::string& file_path, const std::string& file_bytes, Format fmt) +void append_common_(std::vector& lines, const std::string& file_path, const std::string& file_bytes, Format fmt) { const std::string name = std::filesystem::path(file_path).filename().string(); - add_line(lines, "File", name.empty() ? file_path : name); + add_line_(lines, "File", name.empty() ? file_path : name); if (!file_path.empty() && (file_path.find('/') != std::string::npos || file_path.find('\\') != std::string::npos)) - add_line(lines, "Path", file_path); + add_line_(lines, "Path", file_path); - add_line(lines, "Format", format_label(fmt)); - add_line(lines, "Size", fmt_bytes(file_bytes.size())); - add_line(lines, "Importable", can_import(fmt) ? "yes (File -> Import)" : "no (export-only)"); - add_line(lines, "Exportable", "yes (File -> Export)"); + add_line_(lines, "Format", format_label(fmt)); + add_line_(lines, "Size", fmt_bytes_(file_bytes.size())); + add_line_(lines, "Importable", can_import(fmt) ? "yes (File -> Import)" : "no (export-only)"); + add_line_(lines, "Exportable", "yes (File -> Export)"); } -void set_progress_stage(const Atomic_progress_indicator_ptr& progress, const char* stage) +void set_progress_stage_(const Atomic_progress_indicator_ptr& progress, const char* stage) { if (!progress.IsNull()) progress->set_stage(stage); } -Message_ProgressRange start_progress(const Atomic_progress_indicator_ptr& progress) +Message_ProgressRange start_progress_(const Atomic_progress_indicator_ptr& progress) { if (progress.IsNull()) return Message_ProgressRange(); @@ -184,43 +369,40 @@ Message_ProgressRange start_progress(const Atomic_progress_indicator_ptr& progre return progress->Start(); } -std::vector collect_step(const std::string& file_path, const std::string& file_bytes, - const Atomic_progress_indicator_ptr& progress); - -std::vector collect_iges(const std::string& file_path, const std::string& file_bytes) +std::vector collect_iges_(const std::string& file_path, const std::string& file_bytes) { std::vector lines; - append_common(lines, file_path, file_bytes, Format::Iges); - add_blank(lines); + append_common_(lines, file_path, file_bytes, Format::Iges); + add_blank_(lines); IGESControl_Reader reader; std::istringstream stream(file_bytes); const IFSelect_ReturnStatus read_st = reader.ReadStream("", stream); if (read_st != IFSelect_RetDone) { - add_line(lines, "Status", "could not read IGES data"); + add_line_(lines, "Status", "could not read IGES data"); return lines; } const int nb_roots = reader.NbRootsForTransfer(); - add_line(lines, "Roots", std::to_string(nb_roots)); + add_line_(lines, "Roots", std::to_string(nb_roots)); const int transferred = reader.TransferRoots(); - add_line(lines, "Transferred", std::to_string(transferred)); - add_line(lines, "Shapes", std::to_string(reader.NbShapes())); + add_line_(lines, "Transferred", std::to_string(transferred)); + add_line_(lines, "Shapes", std::to_string(reader.NbShapes())); - add_blank(lines); - append_shape_summary(lines, reader.OneShape()); + add_blank_(lines); + append_shape_summary_(lines, reader.OneShape()); return lines; } -std::vector collect_stl(const std::string& file_path, const std::string& file_bytes) +std::vector collect_stl_(const std::string& file_path, const std::string& file_bytes) { std::vector lines; - append_common(lines, file_path, file_bytes, Format::Stl); - add_blank(lines); + append_common_(lines, file_path, file_bytes, Format::Stl); + add_blank_(lines); - if (is_binary_stl(file_bytes)) + if (is_binary_stl_(file_bytes)) { uint32_t tri_count = 0; std::memcpy(&tri_count, file_bytes.data() + 80, sizeof(tri_count)); @@ -228,15 +410,15 @@ std::vector collect_stl(const std::string& file_path, const std::string& f while (!header.empty() && (header.back() == '\0' || std::isspace(static_cast(header.back())))) header.pop_back(); - add_line(lines, "Encoding", "binary"); + add_line_(lines, "Encoding", "binary"); if (!header.empty()) - add_line(lines, "Header", header); + add_line_(lines, "Header", header); - add_line(lines, "Triangles", std::to_string(tri_count)); + add_line_(lines, "Triangles", std::to_string(tri_count)); return lines; } - add_line(lines, "Encoding", "ASCII"); + add_line_(lines, "Encoding", "ASCII"); size_t facets = 0; size_t pos = 0; while (pos < file_bytes.size()) @@ -262,23 +444,23 @@ std::vector collect_stl(const std::string& file_path, const std::string& f name.erase(0, j); if (!name.empty()) - add_line(lines, "Solid name", name); + add_line_(lines, "Solid name", name); } } - add_line(lines, "Triangles", std::to_string(facets)); + add_line_(lines, "Triangles", std::to_string(facets)); return lines; } -std::vector collect_ply(const std::string& file_path, const std::string& file_bytes) +std::vector collect_ply_(const std::string& file_path, const std::string& file_bytes) { std::vector lines; - append_common(lines, file_path, file_bytes, Format::Ply); - add_blank(lines); + append_common_(lines, file_path, file_bytes, Format::Ply); + add_blank_(lines); - if (!looks_like_ply(file_bytes)) + if (!looks_like_ply_(file_bytes)) { - add_line(lines, "Status", "not a PLY file (missing ply magic)"); + add_line_(lines, "Status", "not a PLY file (missing ply magic)"); return lines; } @@ -286,7 +468,7 @@ std::vector collect_ply(const std::string& file_path, const std::string& f std::string line; if (!std::getline(in, line)) { - add_line(lines, "Status", "empty file"); + add_line_(lines, "Status", "empty file"); return lines; } @@ -314,20 +496,20 @@ std::vector collect_ply(const std::string& file_path, const std::string& f n_face = std::atoi(line.c_str() + 13); } - add_line(lines, "Encoding", format); + add_line_(lines, "Encoding", format); if (n_vert >= 0) - add_line(lines, "Vertices", std::to_string(n_vert)); + add_line_(lines, "Vertices", std::to_string(n_vert)); if (n_face >= 0) - add_line(lines, "Faces", std::to_string(n_face)); + add_line_(lines, "Faces", std::to_string(n_face)); if (!ended) - add_line(lines, "Status", "header incomplete (no end_header)"); + add_line_(lines, "Status", "header incomplete (no end_header)"); return lines; } -std::string trim_name(std::string s) +std::string trim_name_(std::string s) { while (!s.empty() && std::isspace(static_cast(s.back()))) s.pop_back(); @@ -340,41 +522,41 @@ std::string trim_name(std::string s) return s; } -std::string xcaf_label_name(const TDF_Label& lab) +std::string xcaf_label_name_(const TDF_Label& lab) { TDataStd_Name_ptr attr; if (!lab.FindAttribute(TDataStd_Name::GetID(), attr) || attr.IsNull()) return {}; const TCollection_AsciiString ascii(attr->Get(), '?'); - return trim_name(std::string(ascii.ToCString())); + return trim_name_(std::string(ascii.ToCString())); } -std::string xcaf_product_name(const XCAFDoc_ShapeTool_ptr& shapes, const TDF_Label& lab) +std::string xcaf_product_name_(const XCAFDoc_ShapeTool_ptr& shapes, const TDF_Label& lab) { TDF_Label ref; if (shapes->GetReferredShape(lab, ref)) { - const std::string referred = xcaf_label_name(ref); + const std::string referred = xcaf_label_name_(ref); if (!referred.empty()) return referred; } - return xcaf_label_name(lab); + return xcaf_label_name_(lab); } -void append_named_from_label(const XCAFDoc_ShapeTool_ptr& shapes, const TDF_Label& lab, std::vector& out) +void append_named_from_label_(const XCAFDoc_ShapeTool_ptr& shapes, const TDF_Label& lab, std::vector& out) { if (shapes->IsAssembly(lab)) { NCollection_Sequence comps; shapes->GetComponents(lab, comps, false); for (int i = 1; i <= comps.Length(); ++i) - append_named_from_label(shapes, comps.Value(i), out); + append_named_from_label_(shapes, comps.Value(i), out); return; } - const std::string name = xcaf_product_name(shapes, lab); + const std::string name = xcaf_product_name_(shapes, lab); TopoDS_Shape shape; if (!XCAFDoc_ShapeTool::GetShape(lab, shape) || shape.IsNull()) return; @@ -385,10 +567,10 @@ void append_named_from_label(const XCAFDoc_ShapeTool_ptr& shapes, const TDF_Labe out.push_back(Named_body{std::move(body), name}); } -void append_tree_from_label(const XCAFDoc_ShapeTool_ptr& shapes, const TDF_Label& lab, int parent_index, +void append_tree_from_label_(const XCAFDoc_ShapeTool_ptr& shapes, const TDF_Label& lab, int parent_index, std::vector& out) { - const std::string name = xcaf_product_name(shapes, lab); + const std::string name = xcaf_product_name_(shapes, lab); if (shapes->IsAssembly(lab)) { @@ -402,7 +584,7 @@ void append_tree_from_label(const XCAFDoc_ShapeTool_ptr& shapes, const TDF_Label NCollection_Sequence comps; shapes->GetComponents(lab, comps, false); for (int i = 1; i <= comps.Length(); ++i) - append_tree_from_label(shapes, comps.Value(i), grp_i, out); + append_tree_from_label_(shapes, comps.Value(i), grp_i, out); return; } @@ -440,7 +622,7 @@ void append_tree_from_label(const XCAFDoc_ShapeTool_ptr& shapes, const TDF_Label } } -Status read_step_named_bodies_xcaf(const std::string& file_bytes, std::vector& out, +Status read_step_named_bodies_xcaf_(const std::string& file_bytes, std::vector& out, const Message_ProgressRange& progress) { Interface_Static::SetCVal("xstep.cascade.unit", "MM"); @@ -477,7 +659,7 @@ Status read_step_named_bodies_xcaf(const std::string& file_bytes, std::vector& out, +Status read_step_named_bodies_plain_(const std::string& file_bytes, std::vector& out, const Message_ProgressRange& progress) { Interface_Static::SetCVal("xstep.cascade.unit", "MM"); @@ -516,7 +698,7 @@ Status read_step_named_bodies_plain(const std::string& file_bytes, std::vector& lines) +bool collect_step_has_status_error_(const std::vector& lines) { for (const Line& line : lines) if (line.label == "Status") @@ -525,16 +707,16 @@ bool collect_step_has_status_error(const std::vector& lines) return false; } -std::vector collect_step_xcaf(const std::string& file_path, const std::string& file_bytes, +std::vector collect_step_xcaf_(const std::string& file_path, const std::string& file_bytes, const Atomic_progress_indicator_ptr& progress) { std::vector lines; - append_common(lines, file_path, file_bytes, Format::Step); - add_blank(lines); + append_common_(lines, file_path, file_bytes, Format::Step); + add_blank_(lines); Interface_Static::SetCVal("xstep.cascade.unit", "MM"); - set_progress_stage(progress, "Reading STEP..."); + set_progress_stage_(progress, "Reading STEP..."); STEPCAFControl_Reader reader; reader.SetNameMode(true); reader.SetColorMode(false); @@ -544,16 +726,16 @@ std::vector collect_step_xcaf(const std::string& file_path, const std::str const IFSelect_ReturnStatus read_st = reader.ReadStream("", stream); if (read_st != IFSelect_RetDone) { - add_line(lines, "Status", "could not read STEP data"); + add_line_(lines, "Status", "could not read STEP data"); return lines; } - add_line(lines, "Roots", std::to_string(reader.NbRootsForTransfer())); + add_line_(lines, "Roots", std::to_string(reader.NbRootsForTransfer())); XCAFApp_Application_ptr app = XCAFApp_Application::GetApplication(); if (app.IsNull()) { - add_line(lines, "Status", "XCAF application unavailable"); + add_line_(lines, "Status", "XCAF application unavailable"); return lines; } @@ -561,49 +743,49 @@ std::vector collect_step_xcaf(const std::string& file_path, const std::str app->NewDocument("MDTV-XCAF", doc); if (doc.IsNull()) { - add_line(lines, "Status", "could not create XCAF document"); + add_line_(lines, "Status", "could not create XCAF document"); return lines; } - set_progress_stage(progress, "Transferring STEP..."); - if (!reader.Transfer(doc, start_progress(progress))) + set_progress_stage_(progress, "Transferring STEP..."); + if (!reader.Transfer(doc, start_progress_(progress))) { - add_line(lines, "Status", "no geometry was transferred"); + add_line_(lines, "Status", "no geometry was transferred"); return lines; } if (!progress.IsNull() && progress->cancelled()) { - add_line(lines, "Status", "cancelled"); + add_line_(lines, "Status", "cancelled"); return lines; } XCAFDoc_ShapeTool_ptr shapes = XCAFDoc_DocumentTool::ShapeTool(doc->Main()); if (shapes.IsNull()) { - add_line(lines, "Status", "missing XCAF shape tool"); + add_line_(lines, "Status", "missing XCAF shape tool"); return lines; } NCollection_Sequence free_shapes; shapes->GetFreeShapes(free_shapes); - add_line(lines, "Transferred", std::to_string(free_shapes.Length())); - add_line(lines, "Shapes", std::to_string(free_shapes.Length())); + add_line_(lines, "Transferred", std::to_string(free_shapes.Length())); + add_line_(lines, "Shapes", std::to_string(free_shapes.Length())); std::vector named; for (int i = 1; i <= free_shapes.Length(); ++i) - append_named_from_label(shapes, free_shapes.Value(i), named); + append_named_from_label_(shapes, free_shapes.Value(i), named); - add_line(lines, "Import bodies", std::to_string(named.size())); + add_line_(lines, "Import bodies", std::to_string(named.size())); int named_count = 0; for (const Named_body& b : named) if (!b.name.empty()) ++named_count; - add_line(lines, "Named bodies", std::to_string(named_count)); + add_line_(lines, "Named bodies", std::to_string(named_count)); const char* cascade = Interface_Static::CVal("xstep.cascade.unit"); if (cascade && cascade[0] != '\0') - add_line(lines, "Cascade unit", cascade); + add_line_(lines, "Cascade unit", cascade); TopoDS_Compound compound; BRep_Builder builder; @@ -612,66 +794,66 @@ std::vector collect_step_xcaf(const std::string& file_path, const std::str if (!b.shape.IsNull()) builder.Add(compound, b.shape); - add_blank(lines); - append_shape_summary(lines, compound); + add_blank_(lines); + append_shape_summary_(lines, compound); return lines; } -std::vector collect_step_plain(const std::string& file_path, const std::string& file_bytes, +std::vector collect_step_plain_(const std::string& file_path, const std::string& file_bytes, const Atomic_progress_indicator_ptr& progress) { std::vector lines; - append_common(lines, file_path, file_bytes, Format::Step); - add_blank(lines); + append_common_(lines, file_path, file_bytes, Format::Step); + add_blank_(lines); Interface_Static::SetCVal("xstep.cascade.unit", "MM"); - set_progress_stage(progress, "Reading STEP..."); + set_progress_stage_(progress, "Reading STEP..."); STEPControl_Reader reader; std::istringstream stream(file_bytes); if (reader.ReadStream("", stream) != IFSelect_RetDone) { - add_line(lines, "Status", "could not read STEP data"); + add_line_(lines, "Status", "could not read STEP data"); return lines; } - add_line(lines, "Roots", std::to_string(reader.NbRootsForTransfer())); + add_line_(lines, "Roots", std::to_string(reader.NbRootsForTransfer())); - set_progress_stage(progress, "Transferring STEP..."); - const int transferred = reader.TransferRoots(start_progress(progress)); - add_line(lines, "Transferred", std::to_string(transferred)); - add_line(lines, "Shapes", std::to_string(reader.NbShapes())); + set_progress_stage_(progress, "Transferring STEP..."); + const int transferred = reader.TransferRoots(start_progress_(progress)); + add_line_(lines, "Transferred", std::to_string(transferred)); + add_line_(lines, "Shapes", std::to_string(reader.NbShapes())); std::vector bodies; for (int i = 1; i <= reader.NbShapes(); ++i) append_cad_import_bodies(reader.Shape(i), bodies); - add_line(lines, "Import bodies", std::to_string(bodies.size())); - add_line(lines, "Named bodies", "0"); + add_line_(lines, "Import bodies", std::to_string(bodies.size())); + add_line_(lines, "Named bodies", "0"); const char* cascade = Interface_Static::CVal("xstep.cascade.unit"); if (cascade && cascade[0] != '\0') - add_line(lines, "Cascade unit", cascade); + add_line_(lines, "Cascade unit", cascade); - add_blank(lines); - append_shape_summary(lines, reader.OneShape()); + add_blank_(lines); + append_shape_summary_(lines, reader.OneShape()); return lines; } -std::vector collect_step(const std::string& file_path, const std::string& file_bytes, +std::vector collect_step_(const std::string& file_path, const std::string& file_bytes, const Atomic_progress_indicator_ptr& progress) { // Single Transfer pass (XCAF preferred). Avoids the old double-parse for Named bodies. - std::vector xcaf = collect_step_xcaf(file_path, file_bytes, progress); - if (!collect_step_has_status_error(xcaf)) + std::vector xcaf = collect_step_xcaf_(file_path, file_bytes, progress); + if (!collect_step_has_status_error_(xcaf)) return xcaf; if (!progress.IsNull() && progress->cancelled()) return xcaf; - return collect_step_plain(file_path, file_bytes, progress); + return collect_step_plain_(file_path, file_bytes, progress); } -Status read_step_named_tree_xcaf(const std::string& file_bytes, std::vector& out, +Status read_step_named_tree_xcaf_(const std::string& file_bytes, std::vector& out, const Message_ProgressRange& progress) { Interface_Static::SetCVal("xstep.cascade.unit", "MM"); @@ -708,7 +890,7 @@ Status read_step_named_tree_xcaf(const std::string& file_bytes, std::vector collect(const std::string& file_path, const std::string& file_bytes, - const Atomic_progress_indicator_ptr& progress) -{ - const Format fmt = detect(file_path, file_bytes); - switch (fmt) - { - case Format::Step: - return collect_step(file_path, file_bytes, progress); - case Format::Iges: - return collect_iges(file_path, file_bytes); - case Format::Stl: - return collect_stl(file_path, file_bytes); - case Format::Ply: - return collect_ply(file_path, file_bytes); - default: - { - std::vector lines; - append_common(lines, file_path, file_bytes, Format::Unknown); - add_blank(lines); - add_line(lines, "Status", "unsupported or unrecognized format"); - add_line(lines, "Hint", "Open STEP, IGES, STL, or PLY"); - return lines; - } - } -} - -Status read_step_named_bodies(const std::string& file_bytes, std::vector& out, - const Message_ProgressRange& progress) -{ - out.clear(); - const Status xcaf = read_step_named_bodies_xcaf(file_bytes, out, progress); - if (xcaf.is_ok()) - return xcaf; - - out.clear(); - return read_step_named_bodies_plain(file_bytes, out, Message_ProgressRange()); -} - -Status read_step_named_tree(const std::string& file_bytes, std::vector& out, const Message_ProgressRange& progress) -{ - out.clear(); - const Status xcaf = read_step_named_tree_xcaf(file_bytes, out, progress); - if (xcaf.is_ok()) - return xcaf; - - out.clear(); - std::vector flat; - const Status plain = read_step_named_bodies_plain(file_bytes, flat, Message_ProgressRange()); - if (!plain.is_ok()) - return plain; - - for (Named_body& b : flat) - { - Named_node n; - n.shape = std::move(b.shape); - n.name = std::move(b.name); - n.is_group = false; - n.parent_index = -1; - out.push_back(std::move(n)); - } - return Status::ok(); -} } // namespace utl_cad_file_info diff --git a/src/utl_geom.cpp b/src/utl_geom.cpp index 4c1d78c..e8cf2b5 100644 --- a/src/utl_geom.cpp +++ b/src/utl_geom.cpp @@ -476,8 +476,8 @@ std::optional cylinder_from_face(const TopoDS_Face& face) gp_Trsf cyl_align_trsf(const gp_Ax1& moving_axis, const gp_Ax1& fixed_axis, bool flip, double axial_offset) { - const gp_Dir from_dir = moving_axis.Direction(); - const gp_Dir to_dir = flip ? fixed_axis.Direction().Reversed() : fixed_axis.Direction(); + const gp_Dir from_dir = moving_axis.Direction(); + const gp_Dir to_dir = flip ? fixed_axis.Direction().Reversed() : fixed_axis.Direction(); const gp_Dir fixed_dir = fixed_axis.Direction(); const gp_Vec to_moving(fixed_axis.Location(), moving_axis.Location()); @@ -629,69 +629,11 @@ namespace { constexpr double k_dim_text_height_base = 16.0; -Prs3d_DimensionAspect_ptr clone_dimension_aspect(const Handle(PrsDim_Dimension) & dim) -{ - if (dim.IsNull()) - return new Prs3d_DimensionAspect(); - - const Prs3d_DimensionAspect_ptr& cur = dim->DimensionAspect(); - if (!cur.IsNull()) - return new Prs3d_DimensionAspect(*cur); - - return new Prs3d_DimensionAspect(); -} - -void arrow_style_preset(const int arrow_style, double& angle_deg, bool& arrows_3d) -{ - switch (arrow_style) - { - case 1: - angle_deg = 15.0; - arrows_3d = false; - break; - - case 2: - angle_deg = 40.0; - arrows_3d = false; - break; - - case 3: - angle_deg = 25.0; - arrows_3d = true; - break; - - default: - angle_deg = 25.0; - arrows_3d = false; - break; - } -} - -Prs3d_DimensionArrowOrientation arrow_orientation_from_index(const int idx) -{ - switch (idx) - { - // clang-format off - case 1: return Prs3d_DAO_Internal; - case 2: return Prs3d_DAO_External; - default: return Prs3d_DAO_Fit; - // clang-format on - } -} - -void apply_dimension_label_text_aspect(const Prs3d_TextAspect_ptr& text, const Quantity_Color& col, - const Length_dimension_style& style) -{ - text->SetColor(col); - text->SetHeight(k_dim_text_height_base * static_cast(style.text_height_scale)); - - Graphic3d_AspectText3d_ptr gtext = new Graphic3d_AspectText3d(); - gtext->SetColor(col); - gtext->SetDisplayType(Aspect_TODT_NORMAL); - gtext->SetStyle(Aspect_TOST_NORMAL); - gtext->SetAlphaMode(Graphic3d_AlphaMode_Opaque); - text->SetAspect(gtext); -} +Prs3d_DimensionAspect_ptr clone_dimension_aspect_(const Handle(PrsDim_Dimension) & dim); +void arrow_style_preset_(const int arrow_style, double& angle_deg, bool& arrows_3d); +Prs3d_DimensionArrowOrientation arrow_orientation_from_index_(const int idx); +void apply_dimension_label_text_aspect_(const Prs3d_TextAspect_ptr& text, const Quantity_Color& col, + const Length_dimension_style& style); } // namespace double length_dimension_auto_flyout(const double edge_len) @@ -706,7 +648,7 @@ void apply_dimension_style_(const Handle(PrsDim_Dimension) & dim, const Length_d if (dim.IsNull()) return; - Prs3d_DimensionAspect_ptr aspect = clone_dimension_aspect(dim); + Prs3d_DimensionAspect_ptr aspect = clone_dimension_aspect_(dim); const Quantity_Color col(style.color_rgb[0], style.color_rgb[1], style.color_rgb[2], Quantity_TOC_RGB); @@ -718,9 +660,9 @@ void apply_dimension_style_(const Handle(PrsDim_Dimension) & dim, const Length_d double angle_deg{}; bool arrows_3d{}; - arrow_style_preset(style.arrow_style, angle_deg, arrows_3d); + arrow_style_preset_(style.arrow_style, angle_deg, arrows_3d); aspect->MakeArrows3d(arrows_3d); - aspect->SetArrowOrientation(arrow_orientation_from_index(style.arrow_orientation)); + aspect->SetArrowOrientation(arrow_orientation_from_index_(style.arrow_orientation)); Prs3d_ArrowAspect_ptr arrow; if (const Prs3d_ArrowAspect_ptr& cur_arrow = aspect->ArrowAspect(); !cur_arrow.IsNull()) @@ -746,7 +688,7 @@ void apply_dimension_style_(const Handle(PrsDim_Dimension) & dim, const Length_d else { Prs3d_TextAspect_ptr text = new Prs3d_TextAspect(); - apply_dimension_label_text_aspect(text, col, style); + apply_dimension_label_text_aspect_(text, col, style); aspect->SetTextAspect(text); } @@ -783,7 +725,7 @@ void apply_length_dimension_list_hover_style(const PrsDim_LengthDimension_ptr& d if (dim.IsNull()) return; - Prs3d_DimensionAspect_ptr aspect = clone_dimension_aspect(dim); + Prs3d_DimensionAspect_ptr aspect = clone_dimension_aspect_(dim); const Quantity_Color qc(hover_rgb[0], hover_rgb[1], hover_rgb[2], Quantity_TOC_RGB); Aspect_TypeOfLine typ = Aspect_TOL_SOLID; @@ -1398,33 +1340,15 @@ double ezy_geom::area(const polygon_2d& poly) namespace { -std::string wkt_fmt_num(double v) -{ - if (std::abs(v - std::round(v)) < 1e-9) - return std::to_string(static_cast(std::round(v))); - - std::ostringstream os; - os << std::fixed << std::setprecision(6) << v; - return os.str(); -} - -void wkt_write_coords(std::ostringstream& ss, const std::vector& pts) -{ - for (size_t i = 0; i < pts.size(); ++i) - { - if (i > 0) - ss << ","; - - ss << wkt_fmt_num(pts[i].x()) << " " << wkt_fmt_num(pts[i].y()); - } -} +std::string wkt_fmt_num_(double v); +void wkt_write_coords_(std::ostringstream& ss, const std::vector& pts); } // namespace std::string to_wkt_string(const ezy_geom::linestring_2d& ls) { std::ostringstream ss; ss << "LINESTRING("; - wkt_write_coords(ss, ls.points); + wkt_write_coords_(ss, ls.points); ss << ")"; return ss.str(); } @@ -1433,7 +1357,7 @@ std::string to_wkt_string(const ezy_geom::ring_2d& ring) { std::ostringstream ss; ss << "LINESTRING("; - wkt_write_coords(ss, ring); + wkt_write_coords_(ss, ring); ss << ")"; return ss.str(); } @@ -1448,7 +1372,7 @@ std::string to_wkt_string(const ezy_geom::polygon_2d& poly) ss << ","; ss << "("; - wkt_write_coords(ss, r); + wkt_write_coords_(ss, r); ss << ")"; }; @@ -1586,32 +1510,10 @@ std::optional snap_foot_to_open_segment_interior_if_close(const gp_Pnt namespace { -Geom_TrimmedCurve_ptr edge_trimmed_curve_(const TopoDS_Edge& edge) -{ - double f = 0.0; - double l = 0.0; - Geom_Curve_ptr c = BRep_Tool::Curve(edge, f, l); - Geom_TrimmedCurve_ptr ret = new Geom_TrimmedCurve(c, f, l); - if (edge.Orientation() == TopAbs_REVERSED) - ret->Reverse(); - return ret; -} - -bool on_segment_for_inclusion_(const gp_Pnt2d& p, const gp_Pnt2d& a, const gp_Pnt2d& b, Segment_inclusion inclusion) -{ - const double tol = Precision::Confusion(); - if (p.Distance(a) <= tol || p.Distance(b) <= tol) - return inclusion == Segment_inclusion::Closed; - - return point_on_open_segment_2d(p, a, b); -} - -bool on_open_arc_parameter_(double u, double u_first, double u_last) -{ - const double span = u_last - u_first; - const double margin = std::max(Precision::Confusion(), std::abs(span) * 1e-9); - return u > u_first + margin && u < u_last - margin; -} +Geom_TrimmedCurve_ptr edge_trimmed_curve_(const TopoDS_Edge& edge); +bool on_segment_for_inclusion_(const gp_Pnt2d& p, const gp_Pnt2d& a, const gp_Pnt2d& b, + Segment_inclusion inclusion); +bool on_open_arc_parameter_(double u, double u_first, double u_last); } // namespace bool point_on_open_arc_interior_2d(const gp_Pnt2d& p, const TopoDS_Edge& arc_edge, const gp_Pln& pln) @@ -1702,3 +1604,118 @@ std::vector arc_arc_intersections_2d(const TopoDS_Edge& arc_a, const T return ret; } + +namespace +{ +Prs3d_DimensionAspect_ptr clone_dimension_aspect_(const Handle(PrsDim_Dimension) & dim) +{ + if (dim.IsNull()) + return new Prs3d_DimensionAspect(); + + const Prs3d_DimensionAspect_ptr& cur = dim->DimensionAspect(); + if (!cur.IsNull()) + return new Prs3d_DimensionAspect(*cur); + + return new Prs3d_DimensionAspect(); +} + +void arrow_style_preset_(const int arrow_style, double& angle_deg, bool& arrows_3d) +{ + switch (arrow_style) + { + case 1: + angle_deg = 15.0; + arrows_3d = false; + break; + + case 2: + angle_deg = 40.0; + arrows_3d = false; + break; + + case 3: + angle_deg = 25.0; + arrows_3d = true; + break; + + default: + angle_deg = 25.0; + arrows_3d = false; + break; + } +} + +Prs3d_DimensionArrowOrientation arrow_orientation_from_index_(const int idx) +{ + switch (idx) + { + // clang-format off + case 1: return Prs3d_DAO_Internal; + case 2: return Prs3d_DAO_External; + default: return Prs3d_DAO_Fit; + // clang-format on + } +} + +void apply_dimension_label_text_aspect_(const Prs3d_TextAspect_ptr& text, const Quantity_Color& col, + const Length_dimension_style& style) +{ + text->SetColor(col); + text->SetHeight(k_dim_text_height_base * static_cast(style.text_height_scale)); + + Graphic3d_AspectText3d_ptr gtext = new Graphic3d_AspectText3d(); + gtext->SetColor(col); + gtext->SetDisplayType(Aspect_TODT_NORMAL); + gtext->SetStyle(Aspect_TOST_NORMAL); + gtext->SetAlphaMode(Graphic3d_AlphaMode_Opaque); + text->SetAspect(gtext); +} + +std::string wkt_fmt_num_(double v) +{ + if (std::abs(v - std::round(v)) < 1e-9) + return std::to_string(static_cast(std::round(v))); + + std::ostringstream os; + os << std::fixed << std::setprecision(6) << v; + return os.str(); +} + +void wkt_write_coords_(std::ostringstream& ss, const std::vector& pts) +{ + for (size_t i = 0; i < pts.size(); ++i) + { + if (i > 0) + ss << ","; + + ss << wkt_fmt_num_(pts[i].x()) << " " << wkt_fmt_num_(pts[i].y()); + } +} + +Geom_TrimmedCurve_ptr edge_trimmed_curve_(const TopoDS_Edge& edge) +{ + double f = 0.0; + double l = 0.0; + Geom_Curve_ptr c = BRep_Tool::Curve(edge, f, l); + Geom_TrimmedCurve_ptr ret = new Geom_TrimmedCurve(c, f, l); + if (edge.Orientation() == TopAbs_REVERSED) + ret->Reverse(); + return ret; +} + +bool on_segment_for_inclusion_(const gp_Pnt2d& p, const gp_Pnt2d& a, const gp_Pnt2d& b, Segment_inclusion inclusion) +{ + const double tol = Precision::Confusion(); + if (p.Distance(a) <= tol || p.Distance(b) <= tol) + return inclusion == Segment_inclusion::Closed; + + return point_on_open_segment_2d(p, a, b); +} + +bool on_open_arc_parameter_(double u, double u_first, double u_last) +{ + const double span = u_last - u_first; + const double margin = std::max(Precision::Confusion(), std::abs(span) * 1e-9); + return u > u_first + margin && u < u_last - margin; +} +} // namespace diff --git a/src/utl_io.cpp b/src/utl_io.cpp index e487cfc..db8cdb7 100644 --- a/src/utl_io.cpp +++ b/src/utl_io.cpp @@ -24,7 +24,150 @@ struct Zip_entry std::string data; }; -uint32_t crc32_bytes(const uint8_t* data, std::size_t len) +uint32_t crc32_bytes_(const uint8_t* data, std::size_t len); +void write_u16_(std::vector& out, uint16_t v); +void write_u32_(std::vector& out, uint32_t v); +bool read_u16_(const uint8_t* p, std::size_t avail, std::size_t& off, uint16_t& v); +bool read_u32_(const uint8_t* p, std::size_t avail, std::size_t& off, uint32_t& v); +std::vector zip_write_stored_(const std::vector& entries); +bool zip_read_stored_(const std::string& bytes, std::vector& out_entries); +void collect_underlay_asset_ids_(const nlohmann::json& j, std::vector& out); +std::string asset_path_(const std::string& asset_id); +bool parse_asset_path_(std::string_view path, std::string& out_id); +int from_b64_(char c); + +} // namespace + +bool is_ezy_zip(const std::string& bytes) +{ + return bytes.size() >= 4 && bytes[0] == 'P' && bytes[1] == 'K' && bytes[2] == 0x03 && bytes[3] == 0x04; +} + +bool is_ezy_json(const std::string& bytes) +{ + std::size_t i = 0; + while (i < bytes.size() && (bytes[i] == ' ' || bytes[i] == '\t' || bytes[i] == '\r' || bytes[i] == '\n')) + ++i; + + return i < bytes.size() && bytes[i] == '{'; +} + +std::optional unpack_ezy(const std::string& bytes) +{ + std::vector entries; + if (!zip_read_stored_(bytes, entries)) + return std::nullopt; + + Ezy_unpack_result result; + for (Zip_entry& e : entries) + { + if (e.name == k_ezy_manifest_path) + { + result.manifest_json = std::move(e.data); + continue; + } + + std::string asset_id; + if (parse_asset_path_(e.name, asset_id)) + result.assets.emplace(std::move(asset_id), std::vector(e.data.begin(), e.data.end())); + } + + if (result.manifest_json.empty()) + return std::nullopt; + + return result; +} + +std::vector pack_ezy(const std::string& manifest_json, const Ezy_asset_store& store) +{ + std::vector asset_ids; + try + { + const nlohmann::json j = nlohmann::json::parse(manifest_json); + collect_underlay_asset_ids_(j, asset_ids); + } + catch (...) + { + return {}; + } + + std::sort(asset_ids.begin(), asset_ids.end()); + asset_ids.erase(std::unique(asset_ids.begin(), asset_ids.end()), asset_ids.end()); + + std::vector entries; + entries.push_back({k_ezy_manifest_path, manifest_json}); + + for (const std::string& id : asset_ids) + { + const auto pixels = store.get(id); + if (!pixels) + continue; + + entries.push_back({asset_path_(id), std::string(reinterpret_cast(pixels->data()), pixels->size())}); + } + + return zip_write_stored_(entries); +} + +std::string ezy_base64_encode(const std::vector& bytes) +{ + static const char tbl[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + std::string out; + const std::size_t len = bytes.size(); + out.reserve(((len + 2) / 3) * 4); + for (std::size_t i = 0; i < len; i += 3) + { + const std::size_t n = len - i; + const unsigned b0 = bytes[i]; + const unsigned b1 = n > 1 ? bytes[i + 1] : 0u; + const unsigned b2 = n > 2 ? bytes[i + 2] : 0u; + const unsigned triple = (b0 << 16) | (b1 << 8) | b2; + out.push_back(tbl[(triple >> 18) & 63]); + out.push_back(tbl[(triple >> 12) & 63]); + out.push_back(n > 1 ? tbl[(triple >> 6) & 63] : '='); + out.push_back(n > 2 ? tbl[triple & 63] : '='); + } + return out; +} + +std::vector ezy_base64_decode(const std::string& b64) +{ + std::vector out; + if (b64.empty()) + return out; + + std::size_t len = b64.size(); + while (len > 0 && (b64[len - 1] == '=' || b64[len - 1] == '\n' || b64[len - 1] == '\r' || b64[len - 1] == ' ')) + --len; + + out.reserve((len * 3) / 4); + unsigned buf = 0; + int bits = 0; + for (std::size_t i = 0; i < len; ++i) + { + const char c = b64[i]; + if (c == '\n' || c == '\r' || c == ' ') + continue; + + const int v = from_b64_(c); + if (v < 0) + return {}; + + buf = (buf << 6) | static_cast(v); + bits += 6; + if (bits >= 8) + { + bits -= 8; + out.push_back(static_cast((buf >> bits) & 0xFF)); + } + } + return out; +} + +namespace +{ + +uint32_t crc32_bytes_(const uint8_t* data, std::size_t len) { static uint32_t table[256]; static bool init = false; @@ -49,13 +192,13 @@ uint32_t crc32_bytes(const uint8_t* data, std::size_t len) return crc ^ 0xFFFFFFFFu; } -void write_u16(std::vector& out, uint16_t v) +void write_u16_(std::vector& out, uint16_t v) { out.push_back(static_cast(v & 0xFFu)); out.push_back(static_cast((v >> 8) & 0xFFu)); } -void write_u32(std::vector& out, uint32_t v) +void write_u32_(std::vector& out, uint32_t v) { out.push_back(static_cast(v & 0xFFu)); out.push_back(static_cast((v >> 8) & 0xFFu)); @@ -63,7 +206,7 @@ void write_u32(std::vector& out, uint32_t v) out.push_back(static_cast((v >> 24) & 0xFFu)); } -bool read_u16(const uint8_t* p, std::size_t avail, std::size_t& off, uint16_t& v) +bool read_u16_(const uint8_t* p, std::size_t avail, std::size_t& off, uint16_t& v) { if (off + 2 > avail) return false; @@ -73,7 +216,7 @@ bool read_u16(const uint8_t* p, std::size_t avail, std::size_t& off, uint16_t& v return true; } -bool read_u32(const uint8_t* p, std::size_t avail, std::size_t& off, uint32_t& v) +bool read_u32_(const uint8_t* p, std::size_t avail, std::size_t& off, uint32_t& v) { if (off + 4 > avail) return false; @@ -84,7 +227,7 @@ bool read_u32(const uint8_t* p, std::size_t avail, std::size_t& off, uint32_t& v return true; } -std::vector zip_write_stored(const std::vector& entries) +std::vector zip_write_stored_(const std::vector& entries) { std::vector out; struct Local_rec @@ -104,19 +247,19 @@ std::vector zip_write_stored(const std::vector& entries) rec.name = e.name; rec.name_len = static_cast(e.name.size()); rec.size = static_cast(e.data.size()); - rec.crc = crc32_bytes(reinterpret_cast(e.data.data()), e.data.size()); - - write_u32(out, k_zip_local_sig); - write_u16(out, 20); // version needed to extract - write_u16(out, 0); // flags - write_u16(out, 0); // compression: store - write_u16(out, 0); // mod time - write_u16(out, 0); // mod date - write_u32(out, rec.crc); - write_u32(out, rec.size); - write_u32(out, rec.size); - write_u16(out, rec.name_len); - write_u16(out, 0); // extra len + rec.crc = crc32_bytes_(reinterpret_cast(e.data.data()), e.data.size()); + + write_u32_(out, k_zip_local_sig); + write_u16_(out, 20); // version needed to extract + write_u16_(out, 0); // flags + write_u16_(out, 0); // compression: store + write_u16_(out, 0); // mod time + write_u16_(out, 0); // mod date + write_u32_(out, rec.crc); + write_u32_(out, rec.size); + write_u32_(out, rec.size); + write_u16_(out, rec.name_len); + write_u16_(out, 0); // extra len out.insert(out.end(), e.name.begin(), e.name.end()); out.insert(out.end(), e.data.begin(), e.data.end()); locals.push_back(std::move(rec)); @@ -125,40 +268,40 @@ std::vector zip_write_stored(const std::vector& entries) const uint32_t central_start = static_cast(out.size()); for (const Local_rec& rec : locals) { - write_u32(out, k_zip_central_sig); - write_u16(out, 20); // version made by - write_u16(out, 20); // version needed - write_u16(out, 0); - write_u16(out, 0); - write_u16(out, 0); - write_u16(out, 0); - write_u32(out, rec.crc); - write_u32(out, rec.size); - write_u32(out, rec.size); - write_u16(out, rec.name_len); - write_u16(out, 0); // extra - write_u16(out, 0); // comment - write_u16(out, 0); // disk start - write_u16(out, 0); // int attrs - write_u32(out, 0); // ext attrs - write_u32(out, rec.offset); + write_u32_(out, k_zip_central_sig); + write_u16_(out, 20); // version made by + write_u16_(out, 20); // version needed + write_u16_(out, 0); + write_u16_(out, 0); + write_u16_(out, 0); + write_u16_(out, 0); + write_u32_(out, rec.crc); + write_u32_(out, rec.size); + write_u32_(out, rec.size); + write_u16_(out, rec.name_len); + write_u16_(out, 0); // extra + write_u16_(out, 0); // comment + write_u16_(out, 0); // disk start + write_u16_(out, 0); // int attrs + write_u32_(out, 0); // ext attrs + write_u32_(out, rec.offset); out.insert(out.end(), rec.name.begin(), rec.name.end()); } const uint32_t central_size = static_cast(out.size()) - central_start; - write_u32(out, k_zip_eocd_sig); - write_u16(out, 0); // disk - write_u16(out, 0); // disk with central - write_u16(out, static_cast(locals.size())); - write_u16(out, static_cast(locals.size())); - write_u32(out, central_size); - write_u32(out, central_start); - write_u16(out, 0); // comment len + write_u32_(out, k_zip_eocd_sig); + write_u16_(out, 0); // disk + write_u16_(out, 0); // disk with central + write_u16_(out, static_cast(locals.size())); + write_u16_(out, static_cast(locals.size())); + write_u32_(out, central_size); + write_u32_(out, central_start); + write_u16_(out, 0); // comment len return out; } -bool zip_read_stored(const std::string& bytes, std::vector& out_entries) +bool zip_read_stored_(const std::string& bytes, std::vector& out_entries) { out_entries.clear(); if (bytes.size() < 22) @@ -187,8 +330,8 @@ bool zip_read_stored(const std::string& bytes, std::vector& out_entri std::size_t off = eocd + 4; uint16_t disk_num, disk_with_cd, num_entries_cd, num_entries_total; uint32_t cd_size, cd_offset; - if (!read_u16(data, n, off, disk_num) || !read_u16(data, n, off, disk_with_cd) || !read_u16(data, n, off, num_entries_cd) || - !read_u16(data, n, off, num_entries_total) || !read_u32(data, n, off, cd_size) || !read_u32(data, n, off, cd_offset)) + if (!read_u16_(data, n, off, disk_num) || !read_u16_(data, n, off, disk_with_cd) || !read_u16_(data, n, off, num_entries_cd) || + !read_u16_(data, n, off, num_entries_total) || !read_u32_(data, n, off, cd_size) || !read_u32_(data, n, off, cd_offset)) return false; if (num_entries_cd != num_entries_total || cd_offset + cd_size > n) @@ -198,17 +341,17 @@ bool zip_read_stored(const std::string& bytes, std::vector& out_entri for (uint16_t i = 0; i < num_entries_cd; ++i) { uint32_t sig; - if (!read_u32(data, n, off, sig) || sig != k_zip_central_sig) + if (!read_u32_(data, n, off, sig) || sig != k_zip_central_sig) return false; uint16_t ver_made, ver_need, flags, method, mod_t, mod_d, name_len, extra_len, comment_len, disk_start, int_attr; uint32_t crc, comp_size, uncomp_size, ext_attr, local_offset; - if (!read_u16(data, n, off, ver_made) || !read_u16(data, n, off, ver_need) || !read_u16(data, n, off, flags) || - !read_u16(data, n, off, method) || !read_u16(data, n, off, mod_t) || !read_u16(data, n, off, mod_d) || - !read_u32(data, n, off, crc) || !read_u32(data, n, off, comp_size) || !read_u32(data, n, off, uncomp_size) || - !read_u16(data, n, off, name_len) || !read_u16(data, n, off, extra_len) || !read_u16(data, n, off, comment_len) || - !read_u16(data, n, off, disk_start) || !read_u16(data, n, off, int_attr) || !read_u32(data, n, off, ext_attr) || - !read_u32(data, n, off, local_offset)) + if (!read_u16_(data, n, off, ver_made) || !read_u16_(data, n, off, ver_need) || !read_u16_(data, n, off, flags) || + !read_u16_(data, n, off, method) || !read_u16_(data, n, off, mod_t) || !read_u16_(data, n, off, mod_d) || + !read_u32_(data, n, off, crc) || !read_u32_(data, n, off, comp_size) || !read_u32_(data, n, off, uncomp_size) || + !read_u16_(data, n, off, name_len) || !read_u16_(data, n, off, extra_len) || !read_u16_(data, n, off, comment_len) || + !read_u16_(data, n, off, disk_start) || !read_u16_(data, n, off, int_attr) || !read_u32_(data, n, off, ext_attr) || + !read_u32_(data, n, off, local_offset)) return false; if (off + name_len + extra_len + comment_len > n) @@ -226,10 +369,10 @@ bool zip_read_stored(const std::string& bytes, std::vector& out_entri std::size_t loc = local_offset + 4u; uint16_t loc_ver, loc_flags, loc_method, loc_mod_t, loc_mod_d, loc_name_len, loc_extra_len; uint32_t loc_crc, loc_comp, loc_uncomp; - if (!read_u16(data, n, loc, loc_ver) || !read_u16(data, n, loc, loc_flags) || !read_u16(data, n, loc, loc_method) || - !read_u16(data, n, loc, loc_mod_t) || !read_u16(data, n, loc, loc_mod_d) || !read_u32(data, n, loc, loc_crc) || - !read_u32(data, n, loc, loc_comp) || !read_u32(data, n, loc, loc_uncomp) || !read_u16(data, n, loc, loc_name_len) || - !read_u16(data, n, loc, loc_extra_len)) + if (!read_u16_(data, n, loc, loc_ver) || !read_u16_(data, n, loc, loc_flags) || !read_u16_(data, n, loc, loc_method) || + !read_u16_(data, n, loc, loc_mod_t) || !read_u16_(data, n, loc, loc_mod_d) || !read_u32_(data, n, loc, loc_crc) || + !read_u32_(data, n, loc, loc_comp) || !read_u32_(data, n, loc, loc_uncomp) || !read_u16_(data, n, loc, loc_name_len) || + !read_u16_(data, n, loc, loc_extra_len)) return false; const std::size_t data_start = local_offset + 30u + loc_name_len + loc_extra_len; @@ -245,7 +388,7 @@ bool zip_read_stored(const std::string& bytes, std::vector& out_entri return true; } -void collect_underlay_asset_ids(const nlohmann::json& j, std::vector& out) +void collect_underlay_asset_ids_(const nlohmann::json& j, std::vector& out) { if (!j.contains("sketches") || !j["sketches"].is_array()) return; @@ -261,9 +404,9 @@ void collect_underlay_asset_ids(const nlohmann::json& j, std::vector= 'A' && c <= 'Z') return c - 'A'; @@ -301,129 +444,3 @@ int from_b64(char c) } } // namespace - -bool is_ezy_zip(const std::string& bytes) -{ - return bytes.size() >= 4 && bytes[0] == 'P' && bytes[1] == 'K' && bytes[2] == 0x03 && bytes[3] == 0x04; -} - -bool is_ezy_json(const std::string& bytes) -{ - std::size_t i = 0; - while (i < bytes.size() && (bytes[i] == ' ' || bytes[i] == '\t' || bytes[i] == '\r' || bytes[i] == '\n')) - ++i; - - return i < bytes.size() && bytes[i] == '{'; -} - -std::optional unpack_ezy(const std::string& bytes) -{ - std::vector entries; - if (!zip_read_stored(bytes, entries)) - return std::nullopt; - - Ezy_unpack_result result; - for (Zip_entry& e : entries) - { - if (e.name == k_ezy_manifest_path) - { - result.manifest_json = std::move(e.data); - continue; - } - - std::string asset_id; - if (parse_asset_path(e.name, asset_id)) - result.assets.emplace(std::move(asset_id), std::vector(e.data.begin(), e.data.end())); - } - - if (result.manifest_json.empty()) - return std::nullopt; - - return result; -} - -std::vector pack_ezy(const std::string& manifest_json, const Ezy_asset_store& store) -{ - std::vector asset_ids; - try - { - const nlohmann::json j = nlohmann::json::parse(manifest_json); - collect_underlay_asset_ids(j, asset_ids); - } - catch (...) - { - return {}; - } - - std::sort(asset_ids.begin(), asset_ids.end()); - asset_ids.erase(std::unique(asset_ids.begin(), asset_ids.end()), asset_ids.end()); - - std::vector entries; - entries.push_back({k_ezy_manifest_path, manifest_json}); - - for (const std::string& id : asset_ids) - { - const auto pixels = store.get(id); - if (!pixels) - continue; - - entries.push_back({asset_path(id), std::string(reinterpret_cast(pixels->data()), pixels->size())}); - } - - return zip_write_stored(entries); -} - -std::string ezy_base64_encode(const std::vector& bytes) -{ - static const char tbl[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - std::string out; - const std::size_t len = bytes.size(); - out.reserve(((len + 2) / 3) * 4); - for (std::size_t i = 0; i < len; i += 3) - { - const std::size_t n = len - i; - const unsigned b0 = bytes[i]; - const unsigned b1 = n > 1 ? bytes[i + 1] : 0u; - const unsigned b2 = n > 2 ? bytes[i + 2] : 0u; - const unsigned triple = (b0 << 16) | (b1 << 8) | b2; - out.push_back(tbl[(triple >> 18) & 63]); - out.push_back(tbl[(triple >> 12) & 63]); - out.push_back(n > 1 ? tbl[(triple >> 6) & 63] : '='); - out.push_back(n > 2 ? tbl[triple & 63] : '='); - } - return out; -} - -std::vector ezy_base64_decode(const std::string& b64) -{ - std::vector out; - if (b64.empty()) - return out; - - std::size_t len = b64.size(); - while (len > 0 && (b64[len - 1] == '=' || b64[len - 1] == '\n' || b64[len - 1] == '\r' || b64[len - 1] == ' ')) - --len; - - out.reserve((len * 3) / 4); - unsigned buf = 0; - int bits = 0; - for (std::size_t i = 0; i < len; ++i) - { - const char c = b64[i]; - if (c == '\n' || c == '\r' || c == ' ') - continue; - - const int v = from_b64(c); - if (v < 0) - return {}; - - buf = (buf << 6) | static_cast(v); - bits += 6; - if (bits >= 8) - { - bits -= 8; - out.push_back(static_cast((buf >> bits) & 0xFF)); - } - } - return out; -} diff --git a/src/utl_occt.cpp b/src/utl_occt.cpp index 90dad8b..7a9ec71 100644 --- a/src/utl_occt.cpp +++ b/src/utl_occt.cpp @@ -24,71 +24,9 @@ const char* standard_failure_message(const Standard_Failure& e) namespace { -TopoDS_Shape solid_from_shell(const TopoDS_Shell& shell) -{ - if (shell.IsNull()) - return TopoDS_Shape(); - - BRepBuilderAPI_MakeSolid maker(shell); - if (!maker.IsDone()) - return TopoDS_Shape(); - - TopoDS_Shape solid = maker.Solid(); - return solid; -} - -TopoDS_Shape solids_from_shells(const TopoDS_Shape& shape) -{ - TopoDS_Compound out; - BRep_Builder builder; - builder.MakeCompound(out); - - int solid_count = 0; - for (TopExp_Explorer exp(shape, TopAbs_SHELL); exp.More(); exp.Next()) - { - const TopoDS_Shape solid = solid_from_shell(TopoDS::Shell(exp.Current())); - if (!solid.IsNull()) - { - builder.Add(out, solid); - ++solid_count; - } - } - - if (solid_count == 0) - return TopoDS_Shape(); - - if (solid_count == 1) - { - for (TopExp_Explorer exp(out, TopAbs_SOLID); exp.More(); exp.Next()) - return exp.Current(); - } - - return out; -} - -TopoDS_Shape solid_from_sewn_faces(const TopoDS_Shape& shape) -{ - BRepBuilderAPI_Sewing sewer(Precision::Confusion()); - int face_count = 0; - for (TopExp_Explorer exp(shape, TopAbs_FACE); exp.More(); exp.Next()) - { - sewer.Add(exp.Current()); - ++face_count; - } - - if (face_count == 0) - return TopoDS_Shape(); - - sewer.Perform(); - const TopoDS_Shape sewed = sewer.SewedShape(); - if (sewed.IsNull()) - return TopoDS_Shape(); - - if (sewed.ShapeType() == TopAbs_SHELL) - return solid_from_shell(TopoDS::Shell(sewed)); - - return solids_from_shells(sewed); -} +TopoDS_Shape solid_from_shell_(const TopoDS_Shell& shell); +TopoDS_Shape solids_from_shells_(const TopoDS_Shape& shape); +TopoDS_Shape solid_from_sewn_faces_(const TopoDS_Shape& shape); } // namespace TopoDS_Shape try_make_solid(const TopoDS_Shape& shape) @@ -104,7 +42,7 @@ TopoDS_Shape try_make_solid(const TopoDS_Shape& shape) case TopAbs_SHELL: { - const TopoDS_Shape solid = solid_from_shell(TopoDS::Shell(shape)); + const TopoDS_Shape solid = solid_from_shell_(TopoDS::Shell(shape)); return solid.IsNull() ? shape : solid; } @@ -120,11 +58,11 @@ TopoDS_Shape try_make_solid(const TopoDS_Shape& shape) if (solid_count == 1) return lone_solid; - const TopoDS_Shape from_shells = solids_from_shells(shape); + const TopoDS_Shape from_shells = solids_from_shells_(shape); if (!from_shells.IsNull()) return from_shells; - const TopoDS_Shape from_faces = solid_from_sewn_faces(shape); + const TopoDS_Shape from_faces = solid_from_sewn_faces_(shape); if (!from_faces.IsNull()) return from_faces; @@ -179,3 +117,72 @@ void append_cad_import_bodies(const TopoDS_Shape& shape, std::vector(s.back()))) - s.pop_back(); - - size_t i = 0; - while (i < s.size() && std::isspace(static_cast(s[i]))) - ++i; - - s.erase(0, i); -} - -bool iequals(const std::string& a, const std::string& b) -{ - if (a.size() != b.size()) - return false; - - for (size_t i = 0; i < a.size(); ++i) - if (std::tolower(static_cast(a[i])) != std::tolower(static_cast(b[i]))) - return false; - - return true; -} - enum class ScalarType { Int8, @@ -60,47 +36,6 @@ enum class ScalarType Unknown }; -ScalarType scalar_from_token(const std::string& t) -{ - // clang-format off - if (t == "char" || t == "int8") return ScalarType::Int8; - if (t == "uchar" || t == "uint8") return ScalarType::UInt8; - if (t == "short" || t == "int16") return ScalarType::Int16; - if (t == "ushort" || t == "uint16") return ScalarType::UInt16; - if (t == "int" || t == "int32") return ScalarType::Int32; - if (t == "uint" || t == "uint32") return ScalarType::UInt32; - if (t == "float" || t == "float32") return ScalarType::Float32; - if (t == "double" || t == "float64") return ScalarType::Float64; - // clang-format on - - return ScalarType::Unknown; -} - -int size_of_scalar(ScalarType t) -{ - switch (t) - { - case ScalarType::Int8: - case ScalarType::UInt8: - return 1; - - case ScalarType::Int16: - case ScalarType::UInt16: - return 2; - - case ScalarType::Int32: - case ScalarType::UInt32: - case ScalarType::Float32: - return 4; - - case ScalarType::Float64: - return 8; - - default: - return 0; - } -} - struct ScalarProp { ScalarType type{ScalarType::Unknown}; @@ -122,180 +57,16 @@ struct ElementDesc std::vector lists; }; -bool read_scalar_bin_at(const unsigned char* base, size_t off, const unsigned char* endbuf, ScalarType t, double& out) -{ - const unsigned char* p = base + off; - if (p >= endbuf) - return false; - - auto need = [&](size_t n) -> bool - { - return static_cast(endbuf - p) >= n; - }; - - switch (t) - { - case ScalarType::Int8: - { - if (!need(1)) - return false; - - out = static_cast(*reinterpret_cast(p)); - return true; - } - case ScalarType::UInt8: - { - if (!need(1)) - return false; - - out = static_cast(*p); - return true; - } - case ScalarType::Int16: - { - if (!need(2)) - return false; - - std::int16_t v; - std::memcpy(&v, p, 2); - out = static_cast(v); - return true; - } - case ScalarType::UInt16: - { - if (!need(2)) - return false; - - std::uint16_t v; - std::memcpy(&v, p, 2); - out = static_cast(v); - return true; - } - case ScalarType::Int32: - { - if (!need(4)) - return false; - - std::int32_t v; - std::memcpy(&v, p, 4); - out = static_cast(v); - return true; - } - case ScalarType::UInt32: - { - if (!need(4)) - return false; - - std::uint32_t v; - std::memcpy(&v, p, 4); - out = static_cast(v); - return true; - } - case ScalarType::Float32: - { - if (!need(4)) - return false; - - float v; - std::memcpy(&v, p, 4); - out = static_cast(v); - return true; - } - case ScalarType::Float64: - { - if (!need(8)) - return false; - - std::memcpy(&out, p, 8); - return true; - } - default: - return false; - } -} - -std::uint32_t read_list_count_bin(const unsigned char*& p, const unsigned char* end, ScalarType ct) -{ - switch (ct) - { - case ScalarType::UInt8: - { - if (static_cast(end - p) < 1) - return 0; - - std::uint32_t v = *p; - ++p; - return v; - } - case ScalarType::UInt16: - { - if (static_cast(end - p) < 2) - return 0; - - std::uint16_t v; - std::memcpy(&v, p, 2); - p += 2; - return v; - } - case ScalarType::UInt32: - { - if (static_cast(end - p) < 4) - return 0; - - std::uint32_t v; - std::memcpy(&v, p, 4); - p += 4; - return v; - } - default: - return 0; - } -} - -bool read_face_indices_bin(const unsigned char*& p, const unsigned char* end, ScalarType vt, std::uint32_t n, - std::vector& idx_out) -{ - idx_out.clear(); - idx_out.reserve(n); - for (std::uint32_t i = 0; i < n; ++i) - { - double d = 0; - if (!read_scalar_bin_at(p, 0, end, vt, d)) - return false; - - int sz = size_of_scalar(vt); - if (sz <= 0 || static_cast(end - p) < static_cast(sz)) - return false; - - p += static_cast(sz); - idx_out.push_back(static_cast(d)); - } - return true; -} - -bool append_triangle(TopoDS_Compound& comp, BRep_Builder& bb, const gp_Pnt& p0, const gp_Pnt& p1, const gp_Pnt& p2, int& ntri) -{ - if (p0.IsEqual(p1, Precision::Confusion()) || p1.IsEqual(p2, Precision::Confusion()) || - p2.IsEqual(p0, Precision::Confusion())) - return true; - - BRepBuilderAPI_MakePolygon poly; - poly.Add(p0); - poly.Add(p1); - poly.Add(p2); - poly.Close(); - if (!poly.IsDone()) - return true; - - BRepBuilderAPI_MakeFace face(poly.Wire(), true); - if (!face.IsDone()) - return true; - - bb.Add(comp, face.Shape()); - ++ntri; - return true; -} - +void trim_inplace_(std::string& s); +bool iequals_(const std::string& a, const std::string& b); +ScalarType scalar_from_token_(const std::string& t); +int size_of_scalar_(ScalarType t); +bool read_scalar_bin_at_(const unsigned char* base, size_t off, const unsigned char* endbuf, ScalarType t, double& out); +std::uint32_t read_list_count_bin_(const unsigned char*& p, const unsigned char* end, ScalarType ct); +bool read_face_indices_bin_(const unsigned char*& p, const unsigned char* end, ScalarType vt, std::uint32_t n, + std::vector& idx_out); +bool append_triangle_(TopoDS_Compound& comp, BRep_Builder& bb, const gp_Pnt& p0, const gp_Pnt& p1, const gp_Pnt& p2, + int& ntri); } // namespace Status import_ply_shape(const std::string& file_bytes, TopoDS_Shape& out_shape) @@ -321,7 +92,7 @@ Status import_ply_shape(const std::string& file_bytes, TopoDS_Shape& out_shape) if (c != '\r') line.push_back(c); } - trim_inplace(line); + trim_inplace_(line); }; read_line(); @@ -373,8 +144,8 @@ Status import_ply_shape(const std::string& file_bytes, TopoDS_Shape& out_shape) std::string ctok, vtok, pname; iss >> ctok >> vtok >> pname; ListProp lp; - lp.count_type = scalar_from_token(ctok); - lp.value_type = scalar_from_token(vtok); + lp.count_type = scalar_from_token_(ctok); + lp.value_type = scalar_from_token_(vtok); lp.name = std::move(pname); const bool count_ok = lp.count_type == ScalarType::UInt8 || lp.count_type == ScalarType::UInt16 || lp.count_type == ScalarType::UInt32; @@ -389,9 +160,9 @@ Status import_ply_shape(const std::string& file_bytes, TopoDS_Shape& out_shape) std::string pname; iss >> pname; ScalarProp sp; - sp.type = scalar_from_token(t1); + sp.type = scalar_from_token_(t1); sp.name = std::move(pname); - if (sp.type == ScalarType::Unknown || size_of_scalar(sp.type) <= 0) + if (sp.type == ScalarType::Unknown || size_of_scalar_(sp.type) <= 0) return Status::user_error("PLY: unsupported vertex property type."); cur_el->scalars.push_back(std::move(sp)); @@ -431,18 +202,18 @@ Status import_ply_shape(const std::string& file_bytes, TopoDS_Shape& out_shape) size_t o = 0; for (const ScalarProp& sp : vert_el->scalars) { - const int sz = size_of_scalar(sp.type); - if (iequals(sp.name, "x")) + const int sz = size_of_scalar_(sp.type); + if (iequals_(sp.name, "x")) { off_x = o; tx = sp.type; } - else if (iequals(sp.name, "y")) + else if (iequals_(sp.name, "y")) { off_y = o; ty = sp.type; } - else if (iequals(sp.name, "z")) + else if (iequals_(sp.name, "z")) { off_z = o; tz = sp.type; @@ -474,7 +245,7 @@ Status import_ply_shape(const std::string& file_bytes, TopoDS_Shape& out_shape) if (!std::getline(body, vl)) return Status::user_error("PLY: unexpected EOF in vertices."); - trim_inplace(vl); + trim_inplace_(vl); std::istringstream ls(vl); double x = 0, y = 0, z = 0; for (const ScalarProp& sp : vert_el->scalars) @@ -485,11 +256,11 @@ Status import_ply_shape(const std::string& file_bytes, TopoDS_Shape& out_shape) if (!(ls >> d)) return Status::user_error("PLY: bad vertex data."); - if (iequals(sp.name, "x")) + if (iequals_(sp.name, "x")) x = d; - else if (iequals(sp.name, "y")) + else if (iequals_(sp.name, "y")) y = d; - else if (iequals(sp.name, "z")) + else if (iequals_(sp.name, "z")) z = d; } else if (sp.type == ScalarType::Int8 || sp.type == ScalarType::UInt8 || sp.type == ScalarType::Int16 || @@ -500,11 +271,11 @@ Status import_ply_shape(const std::string& file_bytes, TopoDS_Shape& out_shape) return Status::user_error("PLY: bad vertex data."); const double d = static_cast(v); - if (iequals(sp.name, "x")) + if (iequals_(sp.name, "x")) x = d; - else if (iequals(sp.name, "y")) + else if (iequals_(sp.name, "y")) y = d; - else if (iequals(sp.name, "z")) + else if (iequals_(sp.name, "z")) z = d; } else @@ -519,7 +290,7 @@ Status import_ply_shape(const std::string& file_bytes, TopoDS_Shape& out_shape) if (!std::getline(body, fl)) return Status::user_error("PLY: unexpected EOF in faces."); - trim_inplace(fl); + trim_inplace_(fl); std::istringstream fs(fl); int nidx = 0; fs >> nidx; @@ -537,7 +308,7 @@ Status import_ply_shape(const std::string& file_bytes, TopoDS_Shape& out_shape) if (su0 >= verts.size() || su1 >= verts.size() || su2 >= verts.size()) return Status::user_error("PLY: face vertex index out of range."); - append_triangle(comp, bb, verts[su0], verts[su1], verts[su2], ntri); + append_triangle_(comp, bb, verts[su0], verts[su1], verts[su2], ntri); } if (ntri == 0) return Status::user_error("PLY: no valid triangles."); @@ -556,8 +327,8 @@ Status import_ply_shape(const std::string& file_bytes, TopoDS_Shape& out_shape) return Status::user_error("PLY: truncated vertex data."); double x = 0, y = 0, z = 0; - if (!read_scalar_bin_at(p, off_x, end, tx, x) || !read_scalar_bin_at(p, off_y, end, ty, y) || - !read_scalar_bin_at(p, off_z, end, tz, z)) + if (!read_scalar_bin_at_(p, off_x, end, tx, x) || !read_scalar_bin_at_(p, off_y, end, ty, y) || + !read_scalar_bin_at_(p, off_z, end, tz, z)) return Status::user_error("PLY: bad binary vertex."); verts[vi] = gp_Pnt(x, y, z); @@ -569,12 +340,12 @@ Status import_ply_shape(const std::string& file_bytes, TopoDS_Shape& out_shape) if (static_cast(end - p) < 1u) return Status::user_error("PLY: truncated face data."); - const std::uint32_t n = read_list_count_bin(p, end, face_list.count_type); + const std::uint32_t n = read_list_count_bin_(p, end, face_list.count_type); if (n != 3u) return Status::user_error("PLY: only triangular faces are supported."); std::vector idx; - if (!read_face_indices_bin(p, end, face_list.value_type, n, idx) || idx.size() != 3u) + if (!read_face_indices_bin_(p, end, face_list.value_type, n, idx) || idx.size() != 3u) return Status::user_error("PLY: bad face indices."); const size_t su0 = static_cast(idx[0]); @@ -583,7 +354,7 @@ Status import_ply_shape(const std::string& file_bytes, TopoDS_Shape& out_shape) if (su0 >= verts.size() || su1 >= verts.size() || su2 >= verts.size()) return Status::user_error("PLY: face vertex index out of range."); - append_triangle(comp, bb, verts[su0], verts[su1], verts[su2], ntri); + append_triangle_(comp, bb, verts[su0], verts[su1], verts[su2], ntri); } if (ntri == 0) @@ -669,3 +440,245 @@ Status export_ply_binary_file(const TopoDS_Shape& shape, const std::string& file return Status::ok(); } + +namespace +{ +void trim_inplace_(std::string& s) +{ + while (!s.empty() && std::isspace(static_cast(s.back()))) + s.pop_back(); + + size_t i = 0; + while (i < s.size() && std::isspace(static_cast(s[i]))) + ++i; + + s.erase(0, i); +} + +bool iequals_(const std::string& a, const std::string& b) +{ + if (a.size() != b.size()) + return false; + + for (size_t i = 0; i < a.size(); ++i) + if (std::tolower(static_cast(a[i])) != std::tolower(static_cast(b[i]))) + return false; + + return true; +} + +ScalarType scalar_from_token_(const std::string& t) +{ + // clang-format off + if (t == "char" || t == "int8") return ScalarType::Int8; + if (t == "uchar" || t == "uint8") return ScalarType::UInt8; + if (t == "short" || t == "int16") return ScalarType::Int16; + if (t == "ushort" || t == "uint16") return ScalarType::UInt16; + if (t == "int" || t == "int32") return ScalarType::Int32; + if (t == "uint" || t == "uint32") return ScalarType::UInt32; + if (t == "float" || t == "float32") return ScalarType::Float32; + if (t == "double" || t == "float64") return ScalarType::Float64; + // clang-format on + + return ScalarType::Unknown; +} + +int size_of_scalar_(ScalarType t) +{ + switch (t) + { + case ScalarType::Int8: + case ScalarType::UInt8: + return 1; + + case ScalarType::Int16: + case ScalarType::UInt16: + return 2; + + case ScalarType::Int32: + case ScalarType::UInt32: + case ScalarType::Float32: + return 4; + + case ScalarType::Float64: + return 8; + + default: + return 0; + } +} + +bool read_scalar_bin_at_(const unsigned char* base, size_t off, const unsigned char* endbuf, ScalarType t, double& out) +{ + const unsigned char* p = base + off; + if (p >= endbuf) + return false; + + auto need = [&](size_t n) -> bool + { + return static_cast(endbuf - p) >= n; + }; + + switch (t) + { + case ScalarType::Int8: + { + if (!need(1)) + return false; + + out = static_cast(*reinterpret_cast(p)); + return true; + } + case ScalarType::UInt8: + { + if (!need(1)) + return false; + + out = static_cast(*p); + return true; + } + case ScalarType::Int16: + { + if (!need(2)) + return false; + + std::int16_t v; + std::memcpy(&v, p, 2); + out = static_cast(v); + return true; + } + case ScalarType::UInt16: + { + if (!need(2)) + return false; + + std::uint16_t v; + std::memcpy(&v, p, 2); + out = static_cast(v); + return true; + } + case ScalarType::Int32: + { + if (!need(4)) + return false; + + std::int32_t v; + std::memcpy(&v, p, 4); + out = static_cast(v); + return true; + } + case ScalarType::UInt32: + { + if (!need(4)) + return false; + + std::uint32_t v; + std::memcpy(&v, p, 4); + out = static_cast(v); + return true; + } + case ScalarType::Float32: + { + if (!need(4)) + return false; + + float v; + std::memcpy(&v, p, 4); + out = static_cast(v); + return true; + } + case ScalarType::Float64: + { + if (!need(8)) + return false; + + std::memcpy(&out, p, 8); + return true; + } + default: + return false; + } +} + +std::uint32_t read_list_count_bin_(const unsigned char*& p, const unsigned char* end, ScalarType ct) +{ + switch (ct) + { + case ScalarType::UInt8: + { + if (static_cast(end - p) < 1) + return 0; + + std::uint32_t v = *p; + ++p; + return v; + } + case ScalarType::UInt16: + { + if (static_cast(end - p) < 2) + return 0; + + std::uint16_t v; + std::memcpy(&v, p, 2); + p += 2; + return v; + } + case ScalarType::UInt32: + { + if (static_cast(end - p) < 4) + return 0; + + std::uint32_t v; + std::memcpy(&v, p, 4); + p += 4; + return v; + } + default: + return 0; + } +} + +bool read_face_indices_bin_(const unsigned char*& p, const unsigned char* end, ScalarType vt, std::uint32_t n, + std::vector& idx_out) +{ + idx_out.clear(); + idx_out.reserve(n); + for (std::uint32_t i = 0; i < n; ++i) + { + double d = 0; + if (!read_scalar_bin_at_(p, 0, end, vt, d)) + return false; + + int sz = size_of_scalar_(vt); + if (sz <= 0 || static_cast(end - p) < static_cast(sz)) + return false; + + p += static_cast(sz); + idx_out.push_back(static_cast(d)); + } + return true; +} + +bool append_triangle_(TopoDS_Compound& comp, BRep_Builder& bb, const gp_Pnt& p0, const gp_Pnt& p1, const gp_Pnt& p2, int& ntri) +{ + if (p0.IsEqual(p1, Precision::Confusion()) || p1.IsEqual(p2, Precision::Confusion()) || + p2.IsEqual(p0, Precision::Confusion())) + return true; + + BRepBuilderAPI_MakePolygon poly; + poly.Add(p0); + poly.Add(p1); + poly.Add(p2); + poly.Close(); + if (!poly.IsDone()) + return true; + + BRepBuilderAPI_MakeFace face(poly.Wire(), true); + if (!face.IsDone()) + return true; + + bb.Add(comp, face.Shape()); + ++ntri; + return true; +} +} // namespace