From 6ba086fbf07cb3b141819895c694fa348cfe769f Mon Sep 17 00:00:00 2001 From: trailcode Date: Wed, 6 May 2026 17:46:12 -0600 Subject: [PATCH 1/5] Start --- src/gui.cpp | 44 ++++++++++++++++++++++++++++++ src/gui.h | 3 +++ src/sketch.cpp | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/sketch.h | 8 ++++++ 4 files changed, 127 insertions(+) diff --git a/src/gui.cpp b/src/gui.cpp index 0176313..bfbb914 100644 --- a/src/gui.cpp +++ b/src/gui.cpp @@ -932,6 +932,35 @@ const std::vector& GUI::occt_material_combo_labels_() return names; } +void GUI::sketch_list_inspector_(const Sketch& sketch, int index) +{ + ImGui::Indent(); + ImGui::PushID(index); + + const auto draw_section = [](const char* title, const std::vector& labels) + { + const size_t count = labels.size(); + ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_SpanAvailWidth; + if (count == 0) + flags |= ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen; + + if (ImGui::TreeNodeEx(title, flags, "%s (%zu)", title, count)) + { + for (const std::string& label : labels) + ImGui::BulletText("%s", label.c_str()); + if (count > 0) + ImGui::TreePop(); + } + }; + + draw_section("Dimensions", sketch.inspector_dimension_labels()); + draw_section("Edges", sketch.inspector_edge_labels()); + draw_section("Faces", sketch.inspector_face_labels()); + + ImGui::PopID(); + ImGui::Unindent(); +} + void GUI::sketch_list_() { if (!m_show_sketch_list) @@ -973,6 +1002,18 @@ void GUI::sketch_list_() // Unique ID suffix using index std::string id_suffix = "##" + std::to_string(index); + const Sketch* sk_key = sketch.get(); + bool& expanded = m_sketch_list_expanded[sk_key]; + + ImGui::PushID(("expand" + id_suffix).c_str()); + if (ImGui::SmallButton(expanded ? "v" : ">")) + expanded = !expanded; + if (m_show_tool_tips && ImGui::IsItemHovered()) + ImGui::SetTooltip(expanded ? "Collapse details" : "Expand details"); + ImGui::PopID(); + + ImGui::SameLine(); + // Radio button for selection ImGui::PushID(("select" + id_suffix).c_str()); if (ImGui::RadioButton("", &m_view->curr_sketch() == sketch.get())) @@ -1054,6 +1095,9 @@ void GUI::sketch_list_() ImGui::PopID(); + if (expanded) + sketch_list_inspector_(*sketch, index); + ++index; } diff --git a/src/gui.h b/src/gui.h index ac51e46..9c72a88 100644 --- a/src/gui.h +++ b/src/gui.h @@ -9,6 +9,7 @@ #include #include #include // Added for log messages +#include #include // #include // Added for log storage @@ -169,6 +170,7 @@ class GUI void dist_edit_(); void angle_edit_(); void sketch_list_(); + void sketch_list_inspector_(const Sketch& sketch, int index); void sketch_properties_dialog_(); void shape_list_(); @@ -309,6 +311,7 @@ class GUI Example_file_list m_example_files; bool m_show_sketch_list {true}; + std::unordered_map m_sketch_list_expanded; bool m_show_shape_list {true}; bool m_show_options {true}; bool m_show_settings_dialog {false}; diff --git a/src/sketch.cpp b/src/sketch.cpp index 68baeba..26b2d59 100644 --- a/src/sketch.cpp +++ b/src/sketch.cpp @@ -2518,6 +2518,78 @@ bool Sketch::has_edges() const return !m_edges.empty(); } +size_t Sketch::edge_count() const +{ + return m_edges.size(); +} + +size_t Sketch::face_count() const +{ + return m_faces.size(); +} + +size_t Sketch::length_dimension_count() const +{ + return m_length_dimensions.size(); +} + +std::vector Sketch::inspector_edge_labels() const +{ + std::vector labels; + labels.reserve(m_edges.size()); + size_t idx = 0; + for (const Edge& e : m_edges) + { + std::string lbl = "E" + std::to_string(idx) + ": "; + if (e.circle_arc) + lbl += "Circle arc"; + else if (e.node_idx_arc.has_value()) + lbl += "3pt arc"; + else + lbl += "Line"; + + lbl += " n" + std::to_string(e.node_idx_a) + "->"; + if (e.node_idx_b.has_value()) + lbl += "n" + std::to_string(*e.node_idx_b); + else + lbl += "?"; + + if (e.node_idx_arc.has_value()) + lbl += " (arc n" + std::to_string(*e.node_idx_arc) + ")"; + + labels.push_back(std::move(lbl)); + ++idx; + } + return labels; +} + +std::vector Sketch::inspector_face_labels() const +{ + std::vector labels; + labels.reserve(m_faces.size()); + for (size_t i = 0; i < m_faces.size(); ++i) + { + const Sketch_face_shp_ptr& f = m_faces[i]; + const size_t nv = f ? f->verts_3d.size() : 0; + const std::string text = "F" + std::to_string(i) + ": " + std::to_string(nv) + " verts"; + labels.push_back(text); + } + return labels; +} + +std::vector Sketch::inspector_dimension_labels() const +{ + std::vector labels; + labels.reserve(m_length_dimensions.size()); + for (size_t i = 0; i < m_length_dimensions.size(); ++i) + { + const Length_dimension& d = m_length_dimensions[i]; + labels.push_back("D" + std::to_string(i) + ": n" + std::to_string(d.node_idx_lo) + "<->n" + + std::to_string(d.node_idx_hi)); + } + return labels; +} + bool Sketch::has_underlay() const { return m_underlay && m_underlay->has_image(); diff --git a/src/sketch.h b/src/sketch.h index 4d0309a..3e96ef5 100644 --- a/src/sketch.h +++ b/src/sketch.h @@ -9,6 +9,7 @@ #include #include #include +#include #include #include "shp.h" @@ -117,6 +118,13 @@ class Sketch /// True if this sketch has at least one edge (used e.g. to pick mode after undo/redo). bool has_edges() const; + size_t edge_count() const; + size_t face_count() const; + size_t length_dimension_count() const; + + std::vector inspector_edge_labels() const; + std::vector inspector_face_labels() const; + std::vector inspector_dimension_labels() const; void on_mode(); Mode get_mode() const; From 1607f27bb29009d73ec6c26e26ed0bfc2ad79f9b Mon Sep 17 00:00:00 2001 From: trailcode Date: Wed, 6 May 2026 17:59:25 -0600 Subject: [PATCH 2/5] Dimension toggle --- src/gui.cpp | 27 +++++++++++++++++++++++++-- src/gui.h | 2 +- src/sketch.cpp | 38 ++++++++++++++++++++++++++++++++------ src/sketch.h | 6 +++++- src/sketch_json.cpp | 7 ++++--- 5 files changed, 67 insertions(+), 13 deletions(-) diff --git a/src/gui.cpp b/src/gui.cpp index bfbb914..338a62d 100644 --- a/src/gui.cpp +++ b/src/gui.cpp @@ -932,7 +932,7 @@ const std::vector& GUI::occt_material_combo_labels_() return names; } -void GUI::sketch_list_inspector_(const Sketch& sketch, int index) +void GUI::sketch_list_inspector_(Sketch& sketch, int index) { ImGui::Indent(); ImGui::PushID(index); @@ -953,7 +953,30 @@ void GUI::sketch_list_inspector_(const Sketch& sketch, int index) } }; - draw_section("Dimensions", sketch.inspector_dimension_labels()); + { + const std::vector labels = sketch.inspector_dimension_labels(); + const size_t count = labels.size(); + ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_SpanAvailWidth; + if (count == 0) + flags |= ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen; + + if (ImGui::TreeNodeEx("Dimensions", flags, "Dimensions (%zu)", count)) + { + for (size_t i = 0; i < count; ++i) + { + bool visible = sketch.dimension_visible(i); + ImGui::PushID(static_cast(i)); + if (ImGui::Checkbox("##dim_visible", &visible)) + sketch.set_dimension_visible(i, visible); + ImGui::SameLine(); + ImGui::TextUnformatted(labels[i].c_str()); + ImGui::PopID(); + } + if (count > 0) + ImGui::TreePop(); + } + } + draw_section("Edges", sketch.inspector_edge_labels()); draw_section("Faces", sketch.inspector_face_labels()); diff --git a/src/gui.h b/src/gui.h index 9c72a88..6b8c0d4 100644 --- a/src/gui.h +++ b/src/gui.h @@ -170,7 +170,7 @@ class GUI void dist_edit_(); void angle_edit_(); void sketch_list_(); - void sketch_list_inspector_(const Sketch& sketch, int index); + void sketch_list_inspector_(Sketch& sketch, int index); void sketch_properties_dialog_(); void shape_list_(); diff --git a/src/sketch.cpp b/src/sketch.cpp index 26b2d59..1db0aae 100644 --- a/src/sketch.cpp +++ b/src/sketch.cpp @@ -1401,7 +1401,7 @@ void Sketch::rebuild_length_dimension_display_(Length_dimension& d) const double dist = m_nodes[d.node_idx_lo].Distance(m_nodes[d.node_idx_hi]); d.dim->SetCustomValue(dist / m_view.get_dimension_scale()); - if (m_visible) + if (m_visible && m_show_dims && d.visible) m_ctx.Display(d.dim, false); } @@ -1470,7 +1470,7 @@ void Sketch::add_or_toggle_length_dim_between_node_indices_(size_t node_a, size_ rebuild_length_dimension_display_(m_length_dimensions.back()); } -void Sketch::json_add_length_dimension_(size_t node_a, size_t node_b) +void Sketch::json_add_length_dimension_(size_t node_a, size_t node_b, const bool visible) { const size_t lo = std::min(node_a, node_b); const size_t hi = std::max(node_a, node_b); @@ -1482,6 +1482,7 @@ void Sketch::json_add_length_dimension_(size_t node_a, size_t node_b) Length_dimension d; d.node_idx_lo = lo; d.node_idx_hi = hi; + d.visible = visible; m_length_dimensions.push_back(std::move(d)); rebuild_length_dimension_display_(m_length_dimensions.back()); } @@ -2326,9 +2327,10 @@ void Sketch::set_visible(bool state) if (m_len_dim_rubber_shp && m_len_dim_pick_anchor_node.has_value()) m_ctx.Display(m_len_dim_rubber_shp, AIS_WireFrame, 0, false); - for (Length_dimension& ld : m_length_dimensions) - if (!ld.dim.IsNull()) - m_ctx.Display(ld.dim, false); + if (m_show_dims) + for (Length_dimension& ld : m_length_dimensions) + if (!ld.dim.IsNull() && ld.visible) + m_ctx.Display(ld.dim, false); if (m_originating_face) m_ctx.Display(m_originating_face, AIS_WireFrame, 0, false); @@ -2407,10 +2409,11 @@ void Sketch::set_show_edges(bool show) void Sketch::set_show_dims(bool show) { + m_show_dims = show; if (show && m_visible) { for (Length_dimension& ld : m_length_dimensions) - if (!ld.dim.IsNull()) + if (!ld.dim.IsNull() && ld.visible) m_ctx.Display(ld.dim, false); } else @@ -2421,6 +2424,29 @@ void Sketch::set_show_dims(bool show) } } +bool Sketch::dimension_visible(size_t dim_index) const +{ + EZY_ASSERT(dim_index < m_length_dimensions.size()); + return m_length_dimensions[dim_index].visible; +} + +void Sketch::set_dimension_visible(size_t dim_index, bool visible) +{ + EZY_ASSERT(dim_index < m_length_dimensions.size()); + Length_dimension& d = m_length_dimensions[dim_index]; + if (d.visible == visible) + return; + + d.visible = visible; + if (!d.dim.IsNull()) + { + if (visible && m_visible && m_show_dims) + m_ctx.Display(d.dim, false); + else + m_ctx.Erase(d.dim, false); + } +} + void Sketch::refresh_edge_dimension_line_widths(const double line_width) { for (Length_dimension& ld : m_length_dimensions) diff --git a/src/sketch.h b/src/sketch.h index 3e96ef5..d03c0b7 100644 --- a/src/sketch.h +++ b/src/sketch.h @@ -90,6 +90,8 @@ class Sketch void set_show_faces(bool show); void set_show_edges(bool show); void set_show_dims(bool show); + bool dimension_visible(size_t dim_index) const; + void set_dimension_visible(size_t dim_index, bool visible); /// Apply global dimension line width to edge annotations and in-progress rubber-band dim. void refresh_edge_dimension_line_widths(double line_width); @@ -175,6 +177,7 @@ class Sketch size_t node_idx_lo {}; size_t node_idx_hi {}; PrsDim_LengthDimension_ptr dim; + bool visible {true}; }; struct Edge @@ -308,7 +311,7 @@ class Sketch void remove_length_dimensions_referencing_node_(size_t node_idx); /// Add if missing, remove if present (same unordered node pair). void add_or_toggle_length_dim_between_node_indices_(size_t node_a, size_t node_b); - void json_add_length_dimension_(size_t node_a, size_t node_b); + void json_add_length_dimension_(size_t node_a, size_t node_b, bool visible = true); /// Average of non-deleted node positions (3D on sketch plane); used to place edge dimensions outside loops. std::optional approx_sketch_interior_ref_3d_() const; @@ -374,6 +377,7 @@ class Sketch AIS_Shape_ptr m_tmp_shp; PrsDim_LengthDimension_ptr m_tmp_dim_anno; bool m_show_faces {true}; + bool m_show_dims {true}; std::unique_ptr m_underlay; }; diff --git a/src/sketch_json.cpp b/src/sketch_json.cpp index a1960b2..0088d30 100644 --- a/src/sketch_json.cpp +++ b/src/sketch_json.cpp @@ -206,7 +206,7 @@ nlohmann::json Sketch_json::to_json(const Sketch& sketch) json& len_dims_json = j["length_dimensions"] = json::array(); for (const Sketch::Length_dimension& ld : sketch.m_length_dimensions) - len_dims_json.push_back(json::array({remap(ld.node_idx_lo), remap(ld.node_idx_hi)})); + len_dims_json.push_back(json::array({remap(ld.node_idx_lo), remap(ld.node_idx_hi), ld.visible})); if (sketch.m_underlay && sketch.m_underlay->has_image()) j["underlay"] = sketch.m_underlay->to_json(); @@ -256,8 +256,9 @@ std::shared_ptr Sketch_json::from_json(Occt_view& view, const nlohmann:: if (j.contains("length_dimensions") && j["length_dimensions"].is_array()) for (const auto& pair_json : j["length_dimensions"]) { - EZY_ASSERT(pair_json.is_array() && pair_json.size() == 2); - ret->json_add_length_dimension_(pair_json[0].get(), pair_json[1].get()); + EZY_ASSERT(pair_json.is_array() && (pair_json.size() == 2 || pair_json.size() == 3)); + const bool visible = pair_json.size() == 3 ? pair_json[2].get() : true; + ret->json_add_length_dimension_(pair_json[0].get(), pair_json[1].get(), visible); } if (j.contains("underlay") && j["underlay"].is_object()) From 085853c615b4c9714c859dddb3449ceb43e4f7f2 Mon Sep 17 00:00:00 2001 From: trailcode Date: Wed, 6 May 2026 18:08:37 -0600 Subject: [PATCH 3/5] Dim set offset UI. --- src/gui.cpp | 7 +++++++ src/sketch.cpp | 38 +++++++++++++++++++++++++++++++++++++- src/sketch.h | 8 +++++++- src/sketch_json.cpp | 21 +++++++++++++++++---- 4 files changed, 68 insertions(+), 6 deletions(-) diff --git a/src/gui.cpp b/src/gui.cpp index 338a62d..ace3d4c 100644 --- a/src/gui.cpp +++ b/src/gui.cpp @@ -965,11 +965,18 @@ void GUI::sketch_list_inspector_(Sketch& sketch, int index) for (size_t i = 0; i < count; ++i) { bool visible = sketch.dimension_visible(i); + double offset = sketch.dimension_offset(i); ImGui::PushID(static_cast(i)); if (ImGui::Checkbox("##dim_visible", &visible)) sketch.set_dimension_visible(i, visible); ImGui::SameLine(); ImGui::TextUnformatted(labels[i].c_str()); + ImGui::SameLine(); + ImGui::SetNextItemWidth(92.f); + if (ImGui::InputDouble("##dim_offset", &offset, 0.5, 2.0, "%.2f")) + sketch.set_dimension_offset(i, offset); + if (m_show_tool_tips && ImGui::IsItemHovered()) + ImGui::SetTooltip("Label offset from edge. 0 = automatic."); ImGui::PopID(); } if (count > 0) diff --git a/src/sketch.cpp b/src/sketch.cpp index 1db0aae..40ded94 100644 --- a/src/sketch.cpp +++ b/src/sketch.cpp @@ -1401,6 +1401,13 @@ void Sketch::rebuild_length_dimension_display_(Length_dimension& d) const double dist = m_nodes[d.node_idx_lo].Distance(m_nodes[d.node_idx_hi]); d.dim->SetCustomValue(dist / m_view.get_dimension_scale()); + if (d.flyout_offset.has_value() && *d.flyout_offset > 0.0) + { + const Standard_Real f = d.dim->GetFlyout(); + const double sign = f < 0.0 ? -1.0 : 1.0; + d.dim->SetFlyout(static_cast(sign * *d.flyout_offset)); + } + if (m_visible && m_show_dims && d.visible) m_ctx.Display(d.dim, false); } @@ -1470,7 +1477,10 @@ void Sketch::add_or_toggle_length_dim_between_node_indices_(size_t node_a, size_ rebuild_length_dimension_display_(m_length_dimensions.back()); } -void Sketch::json_add_length_dimension_(size_t node_a, size_t node_b, const bool visible) +void Sketch::json_add_length_dimension_(size_t node_a, + size_t node_b, + const bool visible, + const std::optional flyout_offset) { const size_t lo = std::min(node_a, node_b); const size_t hi = std::max(node_a, node_b); @@ -1483,6 +1493,7 @@ void Sketch::json_add_length_dimension_(size_t node_a, size_t node_b, const bool d.node_idx_lo = lo; d.node_idx_hi = hi; d.visible = visible; + d.flyout_offset = flyout_offset; m_length_dimensions.push_back(std::move(d)); rebuild_length_dimension_display_(m_length_dimensions.back()); } @@ -2447,6 +2458,31 @@ void Sketch::set_dimension_visible(size_t dim_index, bool visible) } } +double Sketch::dimension_offset(size_t dim_index) const +{ + EZY_ASSERT(dim_index < m_length_dimensions.size()); + const Length_dimension& d = m_length_dimensions[dim_index]; + if (d.flyout_offset.has_value()) + return *d.flyout_offset; + if (!d.dim.IsNull()) + return std::abs(static_cast(d.dim->GetFlyout())); + return 0.0; +} + +void Sketch::set_dimension_offset(size_t dim_index, const double offset) +{ + EZY_ASSERT(dim_index < m_length_dimensions.size()); + Length_dimension& d = m_length_dimensions[dim_index]; + + const double off = std::max(0.0, offset); + if (off <= 0.0) + d.flyout_offset.reset(); + else + d.flyout_offset = off; + + rebuild_length_dimension_display_(d); +} + void Sketch::refresh_edge_dimension_line_widths(const double line_width) { for (Length_dimension& ld : m_length_dimensions) diff --git a/src/sketch.h b/src/sketch.h index d03c0b7..8de4369 100644 --- a/src/sketch.h +++ b/src/sketch.h @@ -92,6 +92,8 @@ class Sketch void set_show_dims(bool show); bool dimension_visible(size_t dim_index) const; void set_dimension_visible(size_t dim_index, bool visible); + double dimension_offset(size_t dim_index) const; + void set_dimension_offset(size_t dim_index, double offset); /// Apply global dimension line width to edge annotations and in-progress rubber-band dim. void refresh_edge_dimension_line_widths(double line_width); @@ -178,6 +180,7 @@ class Sketch size_t node_idx_hi {}; PrsDim_LengthDimension_ptr dim; bool visible {true}; + std::optional flyout_offset; }; struct Edge @@ -311,7 +314,10 @@ class Sketch void remove_length_dimensions_referencing_node_(size_t node_idx); /// Add if missing, remove if present (same unordered node pair). void add_or_toggle_length_dim_between_node_indices_(size_t node_a, size_t node_b); - void json_add_length_dimension_(size_t node_a, size_t node_b, bool visible = true); + void json_add_length_dimension_(size_t node_a, + size_t node_b, + bool visible = true, + std::optional flyout_offset = std::nullopt); /// Average of non-deleted node positions (3D on sketch plane); used to place edge dimensions outside loops. std::optional approx_sketch_interior_ref_3d_() const; diff --git a/src/sketch_json.cpp b/src/sketch_json.cpp index 0088d30..62aa2b8 100644 --- a/src/sketch_json.cpp +++ b/src/sketch_json.cpp @@ -206,7 +206,12 @@ nlohmann::json Sketch_json::to_json(const Sketch& sketch) json& len_dims_json = j["length_dimensions"] = json::array(); for (const Sketch::Length_dimension& ld : sketch.m_length_dimensions) - len_dims_json.push_back(json::array({remap(ld.node_idx_lo), remap(ld.node_idx_hi), ld.visible})); + { + json e = json::array({remap(ld.node_idx_lo), remap(ld.node_idx_hi), ld.visible}); + if (ld.flyout_offset.has_value()) + e.push_back(*ld.flyout_offset); + len_dims_json.push_back(std::move(e)); + } if (sketch.m_underlay && sketch.m_underlay->has_image()) j["underlay"] = sketch.m_underlay->to_json(); @@ -256,9 +261,17 @@ std::shared_ptr Sketch_json::from_json(Occt_view& view, const nlohmann:: if (j.contains("length_dimensions") && j["length_dimensions"].is_array()) for (const auto& pair_json : j["length_dimensions"]) { - EZY_ASSERT(pair_json.is_array() && (pair_json.size() == 2 || pair_json.size() == 3)); - const bool visible = pair_json.size() == 3 ? pair_json[2].get() : true; - ret->json_add_length_dimension_(pair_json[0].get(), pair_json[1].get(), visible); + EZY_ASSERT(pair_json.is_array() && (pair_json.size() == 2 || pair_json.size() == 3 || pair_json.size() == 4)); + const bool visible = pair_json.size() >= 3 ? pair_json[2].get() : true; + std::optional flyout_offset; + if (pair_json.size() == 4) + { + const double v = pair_json[3].get(); + if (v > 0.0) + flyout_offset = v; + } + ret->json_add_length_dimension_(pair_json[0].get(), pair_json[1].get(), visible, + flyout_offset); } if (j.contains("underlay") && j["underlay"].is_object()) From 55bfd52e9025ec5fb5cbf0ee779b2632529d7e85 Mon Sep 17 00:00:00 2001 From: trailcode Date: Wed, 6 May 2026 18:20:51 -0600 Subject: [PATCH 4/5] Dim arrow size settings. --- src/geom.cpp | 37 ++++++++++++++--- src/geom.h | 8 +++- src/gui.cpp | 55 +++++++++++++++++--------- src/gui.h | 94 +++++++++++++++++++++++--------------------- src/gui_settings.cpp | 45 ++++++++++++++++++++- src/occt_view.cpp | 7 ++++ src/occt_view.h | 1 + src/shp_extrude.cpp | 3 +- src/sketch.cpp | 42 ++++++++++++++------ src/sketch.h | 30 +++++++------- 10 files changed, 223 insertions(+), 99 deletions(-) diff --git a/src/geom.cpp b/src/geom.cpp index d8f291e..c12b0a6 100644 --- a/src/geom.cpp +++ b/src/geom.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -589,6 +590,29 @@ void apply_length_dimension_line_width(const PrsDim_LengthDimension_ptr& dim, co dim->SetDimensionAspect(aspect); } +void apply_length_dimension_arrow_size(const PrsDim_LengthDimension_ptr& dim, const double arrow_size) +{ + if (dim.IsNull()) + return; + + const Handle(Prs3d_DimensionAspect)& cur = dim->DimensionAspect(); + Handle(Prs3d_DimensionAspect) aspect; + if (!cur.IsNull()) + aspect = new Prs3d_DimensionAspect(*cur); + else + aspect = new Prs3d_DimensionAspect(); + + Handle(Prs3d_ArrowAspect) arrow; + if (const Handle(Prs3d_ArrowAspect)& cur_arrow = aspect->ArrowAspect(); !cur_arrow.IsNull()) + arrow = new Prs3d_ArrowAspect(*cur_arrow); + else + arrow = new Prs3d_ArrowAspect(); + + arrow->SetLength(static_cast(arrow_size)); + aspect->SetArrowAspect(arrow); + dim->SetDimensionAspect(aspect); +} + // OCCT draws the dimension on the side given by (plane_normal x edge_vector) for positive flyout. // When that side faces the sketch interior, negate flyout so the annotation sits outside the loop. static void orient_length_dimension_flyout_outward(const PrsDim_LengthDimension_ptr& dim, @@ -690,7 +714,8 @@ PrsDim_LengthDimension_ptr create_distance_annotation(const gp_Pnt& const Prs3d_DimensionTextHorizontalPosition text_h_pos, const std::optional& interior_ref, const std::vector* sketch_faces_for_flyout, - const double dimension_line_width) + const double dimension_line_width, + const double dimension_arrow_size) { // Check if points are too close (invalid for dimension) EZY_ASSERT(unique(p1, p2)); @@ -699,6 +724,7 @@ PrsDim_LengthDimension_ptr create_distance_annotation(const gp_Pnt& PrsDim_LengthDimension_ptr dim = new PrsDim_LengthDimension(p1, p2, pln); apply_length_dimension_text_h_position(dim, text_h_pos); apply_length_dimension_line_width(dim, static_cast(dimension_line_width)); + apply_length_dimension_arrow_size(dim, static_cast(dimension_arrow_size)); bool used_faces = false; if (sketch_faces_for_flyout && !sketch_faces_for_flyout->empty()) @@ -715,13 +741,14 @@ PrsDim_LengthDimension_ptr create_distance_annotation(const gp_Pnt2d& const Prs3d_DimensionTextHorizontalPosition text_h_pos, const std::optional& interior_ref, const std::vector* sketch_faces_for_flyout, - const double dimension_line_width) + const double dimension_line_width, + const double dimension_arrow_size) { gp_Pnt point_1 = to_3d(pln, p1); gp_Pnt point_2 = to_3d(pln, p2); return create_distance_annotation(point_1, point_2, pln, text_h_pos, interior_ref, sketch_faces_for_flyout, - dimension_line_width); + dimension_line_width, dimension_arrow_size); } const gp_Pnt& closest_to_camera(const V3d_View_ptr& view, const std::vector& pnts) @@ -997,7 +1024,7 @@ bool operator<(const gp_Pnt2d& lhs, const gp_Pnt2d& rhs) // Lexicographical ordering: compare X first, then Y if X is equal within tolerance if (std::abs(lhs.X() - rhs.X()) > tolerance) return lhs.X() < rhs.X(); - + return lhs.Y() < rhs.Y(); } @@ -1029,7 +1056,7 @@ bool is_clockwise(const boost_geom::ring_2d& ring) double sum = 0.0; for (size_t i = 0; i < ring.size() - 1; ++i) sum += (ring[i + 1].x() - ring[i].x()) * (ring[i + 1].y() + ring[i].y()); - + return sum > 0.0; } diff --git a/src/geom.h b/src/geom.h index fb7ff9b..8fb54ec 100644 --- a/src/geom.h +++ b/src/geom.h @@ -125,6 +125,8 @@ Prs3d_DimensionTextHorizontalPosition edge_dim_text_h_pos_from_index(int idx); /// Rebuild dimension line aspect with \a line_width (call `Redisplay` on the AIS object after). void apply_length_dimension_line_width(const PrsDim_LengthDimension_ptr& dim, double line_width); +/// Rebuild dimension arrow aspect with \a arrow_size (call `Redisplay` on the AIS object after). +void apply_length_dimension_arrow_size(const PrsDim_LengthDimension_ptr& dim, double arrow_size); /// When `sketch_faces_for_flyout` is non-null and non-empty, edge dimensions offset to the side that is /// void (not TopAbs_IN) relative to those faces - fixes concave / notch edges where the node centroid lies @@ -136,7 +138,8 @@ PrsDim_LengthDimension_ptr create_distance_annotation(const gp_Pnt& Prs3d_DimensionTextHorizontalPosition text_h_pos = Prs3d_DTHP_Fit, const std::optional& interior_ref = std::nullopt, const std::vector* sketch_faces_for_flyout = nullptr, - double dimension_line_width = 1.0); + double dimension_line_width = 1.0, + double dimension_arrow_size = 6.0); PrsDim_LengthDimension_ptr create_distance_annotation(const gp_Pnt2d& p1, const gp_Pnt2d& p2, @@ -144,7 +147,8 @@ PrsDim_LengthDimension_ptr create_distance_annotation(const gp_Pnt2d& Prs3d_DimensionTextHorizontalPosition text_h_pos = Prs3d_DTHP_Fit, const std::optional& interior_ref = std::nullopt, const std::vector* sketch_faces_for_flyout = nullptr, - double dimension_line_width = 1.0); + double dimension_line_width = 1.0, + double dimension_arrow_size = 6.0); const gp_Pnt& closest_to_camera(const V3d_View_ptr& view, const std::vector& pnts); diff --git a/src/gui.cpp b/src/gui.cpp index ace3d4c..05ed1a1 100644 --- a/src/gui.cpp +++ b/src/gui.cpp @@ -939,7 +939,7 @@ void GUI::sketch_list_inspector_(Sketch& sketch, int index) const auto draw_section = [](const char* title, const std::vector& labels) { - const size_t count = labels.size(); + const size_t count = labels.size(); ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_SpanAvailWidth; if (count == 0) flags |= ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen; @@ -962,22 +962,39 @@ void GUI::sketch_list_inspector_(Sketch& sketch, int index) if (ImGui::TreeNodeEx("Dimensions", flags, "Dimensions (%zu)", count)) { - for (size_t i = 0; i < count; ++i) + if (count > 0 && ImGui::BeginTable("sketch_dim_rows", 3, ImGuiTableFlags_SizingStretchProp)) { - bool visible = sketch.dimension_visible(i); - double offset = sketch.dimension_offset(i); - ImGui::PushID(static_cast(i)); - if (ImGui::Checkbox("##dim_visible", &visible)) - sketch.set_dimension_visible(i, visible); - ImGui::SameLine(); - ImGui::TextUnformatted(labels[i].c_str()); - ImGui::SameLine(); - ImGui::SetNextItemWidth(92.f); - if (ImGui::InputDouble("##dim_offset", &offset, 0.5, 2.0, "%.2f")) - sketch.set_dimension_offset(i, offset); - if (m_show_tool_tips && ImGui::IsItemHovered()) - ImGui::SetTooltip("Label offset from edge. 0 = automatic."); - ImGui::PopID(); + ImGui::TableSetupColumn("show", ImGuiTableColumnFlags_WidthFixed, 28.f); + ImGui::TableSetupColumn("dim", ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("offset", ImGuiTableColumnFlags_WidthFixed, 132.f); + + for (size_t i = 0; i < count; ++i) + { + bool visible = sketch.dimension_visible(i); + double offset = sketch.dimension_offset(i); + + ImGui::PushID(static_cast(i)); + ImGui::TableNextRow(); + + ImGui::TableSetColumnIndex(0); + if (ImGui::Checkbox("##dim_visible", &visible)) + sketch.set_dimension_visible(i, visible); + + ImGui::TableSetColumnIndex(1); + ImGui::AlignTextToFramePadding(); + ImGui::TextUnformatted(labels[i].c_str()); + + ImGui::TableSetColumnIndex(2); + ImGui::SetNextItemWidth(86.f); + if (ImGui::InputDouble("##dim_offset", &offset, 0.5, 2.0, "%.2f")) + sketch.set_dimension_offset(i, offset); + if (m_show_tool_tips && ImGui::IsItemHovered()) + ImGui::SetTooltip("Label offset from edge. 0 = automatic."); + + ImGui::PopID(); + } + + ImGui::EndTable(); } if (count > 0) ImGui::TreePop(); @@ -1032,8 +1049,8 @@ void GUI::sketch_list_() // Unique ID suffix using index std::string id_suffix = "##" + std::to_string(index); - const Sketch* sk_key = sketch.get(); - bool& expanded = m_sketch_list_expanded[sk_key]; + const Sketch* sk_key = sketch.get(); + bool& expanded = m_sketch_list_expanded[sk_key]; ImGui::PushID(("expand" + id_suffix).c_str()); if (ImGui::SmallButton(expanded ? "v" : ">")) @@ -1812,7 +1829,7 @@ void GUI::shape_list_() for (int mi = 0; mi < nmat; ++mi) mat_label_w_max = std::max(mat_label_w_max, ImGui::CalcTextSize(mat_names[static_cast(mi)].c_str()).x); - + const ImGuiStyle& st_mat = ImGui::GetStyle(); const float mat_popup_w = std::min( 440.0f, diff --git a/src/gui.h b/src/gui.h index 6b8c0d4..f4c8402 100644 --- a/src/gui.h +++ b/src/gui.h @@ -56,15 +56,17 @@ struct Example_file }; /// Default OCCT line-width scale for length dimensions when `edge_dim_line_width` is missing from settings JSON. -inline constexpr float k_gui_edge_dim_line_width_default = 1.0f; +inline constexpr float k_gui_edge_dim_line_width_default = 1.0f; +/// Default OCCT arrow length for length dimensions when `edge_dim_arrow_size` is missing from settings JSON. +inline constexpr float k_gui_edge_dim_arrow_size_default = 6.0f; /// Allowed range and default for `gui.view_roll_step_deg` (view roll and numpad orbit steps; must match Settings slider). -inline constexpr double k_gui_view_roll_step_deg_min = 0.1; -inline constexpr double k_gui_view_roll_step_deg_max = 180.0; -inline constexpr double k_gui_view_roll_step_deg_default = 45.0; +inline constexpr double k_gui_view_roll_step_deg_min = 0.1; +inline constexpr double k_gui_view_roll_step_deg_max = 180.0; +inline constexpr double k_gui_view_roll_step_deg_default = 45.0; /// Allowed range and default for `gui.view_zoom_scroll_scale` (wheel/keyboard zoom units; must match Settings slider). -inline constexpr double k_gui_view_zoom_scroll_scale_min = 0.25; -inline constexpr double k_gui_view_zoom_scroll_scale_max = 64.0; -inline constexpr double k_gui_view_zoom_scroll_scale_default = 4.0; +inline constexpr double k_gui_view_zoom_scroll_scale_min = 0.25; +inline constexpr double k_gui_view_zoom_scroll_scale_max = 64.0; +inline constexpr double k_gui_view_zoom_scroll_scale_default = 4.0; class GUI { @@ -103,6 +105,8 @@ class GUI int edge_dim_label_h() const { return m_edge_dim_label_h; } /// OCCT scale factor for sketch/extrude length dimension lines (1.0 = default thickness). float edge_dim_line_width() const { return m_edge_dim_line_width; } + /// OCCT arrow length for sketch/extrude length dimensions. + float edge_dim_arrow_size() const { return m_edge_dim_arrow_size; } bool get_hide_all_shapes() const { return m_hide_all_shapes; } void set_hide_all_shapes(bool hide) { m_hide_all_shapes = hide; } bool get_dark_mode() const { return m_dark_mode; } @@ -143,7 +147,8 @@ class GUI void save_occt_view_settings(); - /// JSON for scripting: `occt_view` (background, grid) plus `gui.edge_dim_label_h` / `gui.edge_dim_line_width` (same keys as `ezycad_settings.json`). Asserts if the OCCT view is missing. + /// JSON for scripting: `occt_view` (background, grid) plus `gui.edge_dim_label_h` / `gui.edge_dim_line_width` / + /// `gui.edge_dim_arrow_size` (same keys as `ezycad_settings.json`). Asserts if the OCCT view is missing. [[nodiscard]] std::string occt_view_settings_json() const; /// Default RGB (0-255) for sketch underlay line tint when importing a new image (see Settings). @@ -281,6 +286,7 @@ class GUI Fillet_mode m_fillet_mode = Fillet_mode::Shape; int m_edge_dim_label_h {3}; // Prs3d_DTHP_Fit float m_edge_dim_line_width {k_gui_edge_dim_line_width_default}; + float m_edge_dim_arrow_size {k_gui_edge_dim_arrow_size_default}; /// Degrees per numpad orbit (8/2/4/6) and Blender-style roll (Shift+NumPad 4/6); persisted in `gui.view_roll_step_deg`. double m_view_roll_step_deg {k_gui_view_roll_step_deg_default}; /// Multiplier for `UpdateZoom(Aspect_ScrollDelta(..., int(y * scale)))`; persisted in `gui.view_zoom_scroll_scale`. @@ -310,43 +316,43 @@ class GUI using Example_file_list = std::vector; Example_file_list m_example_files; - bool m_show_sketch_list {true}; + bool m_show_sketch_list {true}; std::unordered_map m_sketch_list_expanded; - 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}; - std::string m_about_markdown; - uint32_t m_about_splash_gl {0}; - int m_about_splash_w {512}; - int m_about_splash_h {512}; - bool m_about_assets_loaded {false}; - bool m_open_add_box_popup {false}; - double m_add_box_origin_x {0}; - double m_add_box_origin_y {0}; - double m_add_box_origin_z {0}; - double m_add_box_width {1}; - double m_add_box_length {1}; - double m_add_box_height {1}; - bool m_open_add_pyramid_popup {false}; - double m_add_pyramid_origin_x {0}, m_add_pyramid_origin_y {0}, m_add_pyramid_origin_z {0}; - double m_add_pyramid_side {1}; - bool m_open_add_sphere_popup {false}; - double m_add_sphere_origin_x {0}, m_add_sphere_origin_y {0}, m_add_sphere_origin_z {0}; - double m_add_sphere_radius {1}; - bool m_open_add_cylinder_popup {false}; - double m_add_cylinder_origin_x {0}, m_add_cylinder_origin_y {0}, m_add_cylinder_origin_z {0}; - double m_add_cylinder_radius {1}, m_add_cylinder_height {1}; - bool m_open_add_cone_popup {false}; - double m_add_cone_origin_x {0}, m_add_cone_origin_y {0}, m_add_cone_origin_z {0}; - double m_add_cone_R1 {1}, m_add_cone_R2 {0}, m_add_cone_height {1}; - bool m_open_add_torus_popup {false}; - double m_add_torus_origin_x {0}, m_add_torus_origin_y {0}, m_add_torus_origin_z {0}; - double m_add_torus_R1 {1}, m_add_torus_R2 {0.5}; - bool m_hide_all_shapes {false}; - bool m_show_tool_tips {true}; - bool m_dark_mode {false}; + 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}; + std::string m_about_markdown; + uint32_t m_about_splash_gl {0}; + int m_about_splash_w {512}; + int m_about_splash_h {512}; + bool m_about_assets_loaded {false}; + bool m_open_add_box_popup {false}; + double m_add_box_origin_x {0}; + double m_add_box_origin_y {0}; + double m_add_box_origin_z {0}; + double m_add_box_width {1}; + double m_add_box_length {1}; + double m_add_box_height {1}; + bool m_open_add_pyramid_popup {false}; + double m_add_pyramid_origin_x {0}, m_add_pyramid_origin_y {0}, m_add_pyramid_origin_z {0}; + double m_add_pyramid_side {1}; + bool m_open_add_sphere_popup {false}; + double m_add_sphere_origin_x {0}, m_add_sphere_origin_y {0}, m_add_sphere_origin_z {0}; + double m_add_sphere_radius {1}; + bool m_open_add_cylinder_popup {false}; + double m_add_cylinder_origin_x {0}, m_add_cylinder_origin_y {0}, m_add_cylinder_origin_z {0}; + double m_add_cylinder_radius {1}, m_add_cylinder_height {1}; + bool m_open_add_cone_popup {false}; + double m_add_cone_origin_x {0}, m_add_cone_origin_y {0}, m_add_cone_origin_z {0}; + double m_add_cone_R1 {1}, m_add_cone_R2 {0}, m_add_cone_height {1}; + bool m_open_add_torus_popup {false}; + double m_add_torus_origin_x {0}, m_add_torus_origin_y {0}, m_add_torus_origin_z {0}; + double m_add_torus_R1 {1}, m_add_torus_R2 {0.5}; + bool m_hide_all_shapes {false}; + bool m_show_tool_tips {true}; + bool m_dark_mode {false}; #ifndef NDEBUG bool m_show_dbg {false}; #endif diff --git a/src/gui_settings.cpp b/src/gui_settings.cpp index 2cd4aee..355362f 100644 --- a/src/gui_settings.cpp +++ b/src/gui_settings.cpp @@ -42,6 +42,7 @@ std::string GUI::occt_view_settings_json() const j["gui"] = { { "edge_dim_label_h", m_edge_dim_label_h}, { "edge_dim_line_width", m_edge_dim_line_width}, + { "edge_dim_arrow_size", m_edge_dim_arrow_size}, { "view_roll_step_deg", m_view_roll_step_deg}, {"view_zoom_scroll_scale", m_view_zoom_scroll_scale}, }; @@ -76,6 +77,7 @@ void GUI::save_occt_view_settings() { "show_python_console", m_show_python_console}, { "edge_dim_label_h", m_edge_dim_label_h}, { "edge_dim_line_width", m_edge_dim_line_width}, + { "edge_dim_arrow_size", m_edge_dim_arrow_size}, {"load_last_opened_on_startup", m_load_last_opened_on_startup}, { "last_opened_project_path", m_last_opened_project_path}, { "imgui_rounding_general", m_imgui_rounding_general}, @@ -181,6 +183,13 @@ void GUI::parse_gui_panes_settings_(const std::string& content) if (v >= 0.5f && v <= 8.0f) m_edge_dim_line_width = v; } + m_edge_dim_arrow_size = k_gui_edge_dim_arrow_size_default; + if (g.contains("edge_dim_arrow_size") && g["edge_dim_arrow_size"].is_number()) + { + const float v = g["edge_dim_arrow_size"].get(); + if (v >= 1.0f && v <= 24.0f) + m_edge_dim_arrow_size = v; + } m_load_last_opened_on_startup = b("load_last_opened_on_startup", b("load_last_saved_on_startup", false)); if (g.contains("last_opened_project_path") && g["last_opened_project_path"].is_string()) m_last_opened_project_path = g["last_opened_project_path"].get(); @@ -535,8 +544,9 @@ void GUI::settings_() if (ImGui::CollapsingHeader("Sketch")) { - bool ul_changed = false; - bool dim_lw_changed = false; + bool ul_changed = false; + bool dim_lw_changed = false; + bool dim_arrow_changed = false; if (ImGui::BeginTable("settings_sketch", 2, ImGuiTableFlags_SizingStretchProp)) { ImGui::TableSetupColumn("label", ImGuiTableColumnFlags_WidthFixed, k_label_col_w); @@ -567,6 +577,31 @@ void GUI::settings_() } } + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGui::AlignTextToFramePadding(); + ImGui::TextUnformatted("Dimension arrow size"); + ImGui::TableSetColumnIndex(1); + { + float arrow = m_edge_dim_arrow_size; + if (ImGui::SliderFloat("##edge_dim_arrow", &arrow, 1.0f, 24.0f, "%.2f")) + { + m_edge_dim_arrow_size = arrow; + dim_arrow_changed = true; + } + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + ImGui::TextDisabled("(?)"); + if (ImGui::IsItemHovered()) + { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::TextDisabled( + "Arrow head length for sketch and extrude length dimensions (Open CASCADE display units)."); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } + } + ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); ImGui::AlignTextToFramePadding(); @@ -594,6 +629,12 @@ void GUI::settings_() if (m_view) m_view->refresh_all_length_dimension_line_widths(static_cast(m_edge_dim_line_width)); } + if (dim_arrow_changed) + { + save_occt_view_settings(); + if (m_view) + m_view->refresh_all_length_dimension_arrow_sizes(static_cast(m_edge_dim_arrow_size)); + } if (ul_changed) { uint8_t hr, hg, hb; diff --git a/src/occt_view.cpp b/src/occt_view.cpp index 0ff6983..9aaf4ce 100644 --- a/src/occt_view.cpp +++ b/src/occt_view.cpp @@ -1072,6 +1072,13 @@ void Occt_view::refresh_all_length_dimension_line_widths(const double line_width sk->refresh_edge_dimension_line_widths(line_width); } +void Occt_view::refresh_all_length_dimension_arrow_sizes(const double arrow_size) +{ + for (const Sketch_ptr& sk : m_sketches) + if (sk) + sk->refresh_edge_dimension_arrow_sizes(arrow_size); +} + void Occt_view::dimension_input(const ScreenCoords& screen_coords) { switch (get_mode()) diff --git a/src/occt_view.h b/src/occt_view.h index 4b1fa51..8783a3b 100644 --- a/src/occt_view.h +++ b/src/occt_view.h @@ -143,6 +143,7 @@ class Occt_view : protected AIS_ViewController void dimension_input(const ScreenCoords& screen_coords); void angle_input(const ScreenCoords& screen_coords); void refresh_all_length_dimension_line_widths(double line_width); + void refresh_all_length_dimension_arrow_sizes(double arrow_size); double get_dimension_scale() const; bool get_show_dim_input() const; void set_show_dim_input(bool show); diff --git a/src/shp_extrude.cpp b/src/shp_extrude.cpp index a40d197..7014b4f 100644 --- a/src/shp_extrude.cpp +++ b/src/shp_extrude.cpp @@ -151,7 +151,8 @@ void Shp_extrude::_update_extrude_preview_(const double extrude_dist, const Plan Prs3d_DTHP_Fit, std::nullopt, nullptr, - gui().edge_dim_line_width()); + gui().edge_dim_line_width(), + gui().edge_dim_arrow_size()); m_tmp_dim->SetCustomValue(extrude_dist / view().get_dimension_scale()); diff --git a/src/sketch.cpp b/src/sketch.cpp index 40ded94..27215e0 100644 --- a/src/sketch.cpp +++ b/src/sketch.cpp @@ -477,7 +477,8 @@ void Sketch::move_line_string_pt_(const ScreenCoords& screen_coords) m_tmp_dim_anno = create_distance_annotation( pt_a, final_pt_b, m_pln, edge_dim_text_h_pos_from_index(m_view.gui().edge_dim_label_h()), approx_sketch_interior_ref_3d_(), - m_dim_classifier_faces.empty() ? nullptr : &m_dim_classifier_faces, m_view.gui().edge_dim_line_width()); + m_dim_classifier_faces.empty() ? nullptr : &m_dim_classifier_faces, m_view.gui().edge_dim_line_width(), + m_view.gui().edge_dim_arrow_size()); m_tmp_dim_anno->SetCustomValue(dist); m_ctx.Display(m_tmp_dim_anno, true); @@ -1397,7 +1398,8 @@ void Sketch::rebuild_length_dimension_display_(Length_dimension& d) d.dim = create_distance_annotation( m_nodes[d.node_idx_lo], m_nodes[d.node_idx_hi], m_pln, edge_dim_text_h_pos_from_index(m_view.gui().edge_dim_label_h()), approx_sketch_interior_ref_3d_(), - m_dim_classifier_faces.empty() ? nullptr : &m_dim_classifier_faces, m_view.gui().edge_dim_line_width()); + m_dim_classifier_faces.empty() ? nullptr : &m_dim_classifier_faces, m_view.gui().edge_dim_line_width(), + m_view.gui().edge_dim_arrow_size()); const double dist = m_nodes[d.node_idx_lo].Distance(m_nodes[d.node_idx_hi]); d.dim->SetCustomValue(dist / m_view.get_dimension_scale()); @@ -1477,9 +1479,9 @@ void Sketch::add_or_toggle_length_dim_between_node_indices_(size_t node_a, size_ rebuild_length_dimension_display_(m_length_dimensions.back()); } -void Sketch::json_add_length_dimension_(size_t node_a, - size_t node_b, - const bool visible, +void Sketch::json_add_length_dimension_(size_t node_a, + size_t node_b, + const bool visible, const std::optional flyout_offset) { const size_t lo = std::min(node_a, node_b); @@ -1490,9 +1492,9 @@ void Sketch::json_add_length_dimension_(size_t node_a, if (x.node_idx_lo == lo && x.node_idx_hi == hi) return; Length_dimension d; - d.node_idx_lo = lo; - d.node_idx_hi = hi; - d.visible = visible; + d.node_idx_lo = lo; + d.node_idx_hi = hi; + d.visible = visible; d.flyout_offset = flyout_offset; m_length_dimensions.push_back(std::move(d)); rebuild_length_dimension_display_(m_length_dimensions.back()); @@ -2499,6 +2501,22 @@ void Sketch::refresh_edge_dimension_line_widths(const double line_width) } } +void Sketch::refresh_edge_dimension_arrow_sizes(const double arrow_size) +{ + for (Length_dimension& ld : m_length_dimensions) + if (!ld.dim.IsNull()) + { + apply_length_dimension_arrow_size(ld.dim, arrow_size); + m_ctx.Redisplay(ld.dim, true); + } + + if (!m_tmp_dim_anno.IsNull()) + { + apply_length_dimension_arrow_size(m_tmp_dim_anno, arrow_size); + m_ctx.Redisplay(m_tmp_dim_anno, true); + } +} + bool Sketch::is_current() const { return this == &m_view.curr_sketch(); @@ -2945,10 +2963,10 @@ bool Sketch::underlay_axes_orthogonal() const { if (!m_underlay || !m_underlay->has_image()) return true; - const gp_Vec2d au = m_underlay->axis_u(); - const gp_Vec2d av = m_underlay->axis_v(); - const double dot = au.X() * av.X() + au.Y() * av.Y(); - const double scale = au.Magnitude() * av.Magnitude(); + const gp_Vec2d au = m_underlay->axis_u(); + const gp_Vec2d av = m_underlay->axis_v(); + const double dot = au.X() * av.X() + au.Y() * av.Y(); + const double scale = au.Magnitude() * av.Magnitude(); if (scale < 1e-24) return true; return std::abs(dot) < 1e-9 * scale; diff --git a/src/sketch.h b/src/sketch.h index 8de4369..b9265f4 100644 --- a/src/sketch.h +++ b/src/sketch.h @@ -85,18 +85,20 @@ class Sketch void on_enter(); // For finalizing manual distance input. // Visibility related - void set_visible(bool state); - bool is_visible() const; - void set_show_faces(bool show); - void set_show_edges(bool show); - void set_show_dims(bool show); - bool dimension_visible(size_t dim_index) const; - void set_dimension_visible(size_t dim_index, bool visible); + void set_visible(bool state); + bool is_visible() const; + void set_show_faces(bool show); + void set_show_edges(bool show); + void set_show_dims(bool show); + bool dimension_visible(size_t dim_index) const; + void set_dimension_visible(size_t dim_index, bool visible); double dimension_offset(size_t dim_index) const; void set_dimension_offset(size_t dim_index, double offset); /// Apply global dimension line width to edge annotations and in-progress rubber-band dim. void refresh_edge_dimension_line_widths(double line_width); + /// Apply global dimension arrow size to edge annotations and in-progress rubber-band dim. + void refresh_edge_dimension_arrow_sizes(double arrow_size); // Revolve related Shp_rslt revolve_selected(const double angle); @@ -121,7 +123,7 @@ class Sketch void set_name(const std::string& name); /// True if this sketch has at least one edge (used e.g. to pick mode after undo/redo). - bool has_edges() const; + bool has_edges() const; size_t edge_count() const; size_t face_count() const; size_t length_dimension_count() const; @@ -140,7 +142,7 @@ class Sketch [[nodiscard]] int underlay_image_h() const; [[nodiscard]] bool load_underlay_image(const std::string& file_bytes); void clear_underlay(); - void underlay_set_center_extents_rotation(const glm::dvec2& center, const glm::dvec2& half_extents, double rot_deg); + void underlay_set_center_extents_rotation(const glm::dvec2& center, const glm::dvec2& half_extents, double rot_deg); void underlay_set_opacity(float opaque01); void underlay_set_visible(bool v); [[nodiscard]] float underlay_opacity() const; @@ -153,7 +155,7 @@ class Sketch void underlay_line_tint_rgb(uint8_t& r, uint8_t& g, uint8_t& b) const; void underlay_ui_params(double& cx, double& cy, double& half_w, double& half_h, double& rot_deg) const; /// True when texture U and V directions are perpendicular (no shear). Orthogonal UI assumes this. - [[nodiscard]] bool underlay_axes_orthogonal() const; + [[nodiscard]] bool underlay_axes_orthogonal() const; void underlay_rebuild_display(); /// Same snap / plane rules as the line-edge tool (for underlay calibration clicks). @@ -166,7 +168,7 @@ class Sketch [[nodiscard]] bool underlay_rescale_v_chord_to_length(const gp_Pnt2d& y0, const gp_Pnt2d& y1, double target_len); [[nodiscard]] gp_Vec2d underlay_axis_u_vec() const; /// Datum on sketch plane: bitmap (0,0) at \a origin; +U toward \a along_u_point; keeps |axis_u|, |axis_v| and winding. - [[nodiscard]] bool underlay_set_datum_origin_and_u_direction(const gp_Pnt2d& origin, const gp_Pnt2d& along_u_point); + [[nodiscard]] bool underlay_set_datum_origin_and_u_direction(const gp_Pnt2d& origin, const gp_Pnt2d& along_u_point); // private: friend class Sketch_json; @@ -314,9 +316,9 @@ class Sketch void remove_length_dimensions_referencing_node_(size_t node_idx); /// Add if missing, remove if present (same unordered node pair). void add_or_toggle_length_dim_between_node_indices_(size_t node_a, size_t node_b); - void json_add_length_dimension_(size_t node_a, - size_t node_b, - bool visible = true, + void json_add_length_dimension_(size_t node_a, + size_t node_b, + bool visible = true, std::optional flyout_offset = std::nullopt); /// Average of non-deleted node positions (3D on sketch plane); used to place edge dimensions outside loops. From 35736396a21379c4beff597fb69a559cd7fe66b2 Mon Sep 17 00:00:00 2001 From: trailcode Date: Thu, 7 May 2026 16:46:46 -0600 Subject: [PATCH 5/5] AI PR --- ...mension-controls-and-dim-arrow-settings.md | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 agents/prs/002-sketch-list-dimension-controls-and-dim-arrow-settings.md diff --git a/agents/prs/002-sketch-list-dimension-controls-and-dim-arrow-settings.md b/agents/prs/002-sketch-list-dimension-controls-and-dim-arrow-settings.md new file mode 100644 index 0000000..b26c1dd --- /dev/null +++ b/agents/prs/002-sketch-list-dimension-controls-and-dim-arrow-settings.md @@ -0,0 +1,47 @@ +# PR - `Trailcode/sketch_list_improvements` + +## Title + +Sketch list dimension controls and dimension arrow-size settings + +## Summary + +- Add expandable sketch inspector sections in Sketch List for dimensions, edges, and faces. +- Add per-dimension controls in Sketch List: visibility checkbox and numeric offset input (flyout distance). +- Persist per-dimension visibility/offset in sketch JSON with backward-compatible loading for legacy arrays. +- Add a new Settings -> Sketch control for dimension arrow size, persist it in GUI settings JSON, and apply it live. +- Apply global arrow-size setting to sketch dimensions and extrude preview dimensions. + +## Files Changed + +- `src/geom.cpp` +- `src/geom.h` +- `src/gui.cpp` +- `src/gui.h` +- `src/gui_settings.cpp` +- `src/occt_view.cpp` +- `src/occt_view.h` +- `src/shp_extrude.cpp` +- `src/sketch.cpp` +- `src/sketch.h` +- `src/sketch_json.cpp` + +## Related + +- Compare: https://github.com/trailcode/EzyCad/compare/main...Trailcode/sketch_list_improvements +- Issue draft: `issue.md` + +## Test Plan + +- [ ] Build `EzyCad_lib` in Release config. +- [ ] Open Sketch List and verify per-sketch expand/collapse works. +- [ ] Expand `Dimensions` and verify each row has visibility and offset controls. +- [ ] Set offset to non-zero and verify label flyout distance updates. +- [ ] Set offset to `0` and verify automatic flyout behavior is restored. +- [ ] Save and reopen project; verify per-dimension visibility/offset persist. +- [ ] Open Settings -> Sketch; adjust `Dimension arrow size` and verify existing dimensions update live. +- [ ] Verify extrude preview dimension arrow size follows the same setting. + +## Notes + +- Untracked workspace directory exists: `third_party/ImGuiColorTextEdit/`.