Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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/`.
37 changes: 32 additions & 5 deletions src/geom.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include <Geom_Plane.hxx>
#include <Geom_TrimmedCurve.hxx>
#include <Graphic3d_AspectLine3d.hxx>
#include <Prs3d_ArrowAspect.hxx>
#include <Prs3d_DimensionAspect.hxx>
#include <Prs3d_LineAspect.hxx>
#include <PrsDim_LengthDimension.hxx>
Expand Down Expand Up @@ -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<Standard_Real>(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,
Expand Down Expand Up @@ -690,7 +714,8 @@ PrsDim_LengthDimension_ptr create_distance_annotation(const gp_Pnt&
const Prs3d_DimensionTextHorizontalPosition text_h_pos,
const std::optional<gp_Pnt>& interior_ref,
const std::vector<TopoDS_Face>* 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));
Expand All @@ -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<Standard_Real>(dimension_line_width));
apply_length_dimension_arrow_size(dim, static_cast<Standard_Real>(dimension_arrow_size));

bool used_faces = false;
if (sketch_faces_for_flyout && !sketch_faces_for_flyout->empty())
Expand All @@ -715,13 +741,14 @@ PrsDim_LengthDimension_ptr create_distance_annotation(const gp_Pnt2d&
const Prs3d_DimensionTextHorizontalPosition text_h_pos,
const std::optional<gp_Pnt>& interior_ref,
const std::vector<TopoDS_Face>* 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<gp_Pnt>& pnts)
Expand Down Expand Up @@ -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();
}

Expand Down Expand Up @@ -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;
}

Expand Down
8 changes: 6 additions & 2 deletions src/geom.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -136,15 +138,17 @@ PrsDim_LengthDimension_ptr create_distance_annotation(const gp_Pnt&
Prs3d_DimensionTextHorizontalPosition text_h_pos = Prs3d_DTHP_Fit,
const std::optional<gp_Pnt>& interior_ref = std::nullopt,
const std::vector<TopoDS_Face>* 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,
const gp_Pln& pln,
Prs3d_DimensionTextHorizontalPosition text_h_pos = Prs3d_DTHP_Fit,
const std::optional<gp_Pnt>& interior_ref = std::nullopt,
const std::vector<TopoDS_Face>* 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<gp_Pnt>& pnts);

Expand Down
93 changes: 92 additions & 1 deletion src/gui.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -932,6 +932,82 @@ const std::vector<std::string>& GUI::occt_material_combo_labels_()
return names;
}

void GUI::sketch_list_inspector_(Sketch& sketch, int index)
{
ImGui::Indent();
ImGui::PushID(index);

const auto draw_section = [](const char* title, const std::vector<std::string>& 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();
}
};

{
const std::vector<std::string> 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))
{
if (count > 0 && ImGui::BeginTable("sketch_dim_rows", 3, ImGuiTableFlags_SizingStretchProp))
{
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<int>(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();
}
}

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)
Expand Down Expand Up @@ -973,6 +1049,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()))
Expand Down Expand Up @@ -1054,6 +1142,9 @@ void GUI::sketch_list_()

ImGui::PopID();

if (expanded)
sketch_list_inspector_(*sketch, index);

++index;
}

Expand Down Expand Up @@ -1738,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<size_t>(mi)].c_str()).x);

const ImGuiStyle& st_mat = ImGui::GetStyle();
const float mat_popup_w = std::min(
440.0f,
Expand Down
Loading
Loading