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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- **Shape local frame**: Shape List right-click on a solid toggles **Show axes** / **Show plane** / **Show up**, **Reset frame to bbox**, **Set from planar/cylindrical face**, and **Flip up** / **Flip axis (Z)**. Frame display flags persist in `.ezy` as `frameDisplay`. Face picks use `Mode::Shape_set_frame` (no toolbar hotkey).

- **Shape List Zoom to**: right-click a shape or group name (or the **M** button on a solid) and choose **Zoom to** to frame that solid, or all descendant solids of a group, in the 3D view while keeping the current camera orientation.

- **Shape copy / paste** (<kbd>Ctrl</kbd>+<kbd>C</kbd> / <kbd>Ctrl</kbd>+<kbd>V</kbd>): in-app clipboard for selected solids, or a Shape List group subtree when the selection matches that group's descendant solids. Paste deep-copies under the current group (new ids, uniquified names, same pose) with one undo step. Survives **New** so you can copy, start a blank project, and paste. Remappable as **`edit.copy`** / **`edit.paste`**. Does not use the OS clipboard; sketch geometry is not copied.
Expand Down
7 changes: 6 additions & 1 deletion docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,12 @@ Each row (left to right):
- **M** - Solids only: material popup; right-click for **Zoom to**, **Shape info...**, or **Delete**.
- **Name** - Expandable tree row with an editable name. Click the row to select that solid (or all descendant solids for a group) and update the current group. **Ctrl+click** toggles multi-select. Drag to reparent (empty area below the list = document root).
- **Copy / Paste** - <kbd>Ctrl</kbd>+<kbd>C</kbd> / <kbd>Ctrl</kbd>+<kbd>V</kbd> (remappable) copy selected solids, or a whole group subtree when you clicked that group (all its descendant solids are selected). Paste inserts an independent deep copy under the current group, at the same pose. If you still have that copied group as the current group, paste places a sibling copy beside it (not nested inside it). This is an in-app clipboard (survives **New**; it does not use the system clipboard). Sketch edges are not copied.
- **Right-click the name** - Solids: **Zoom to** / **Shape info...** / **Delete**. Groups: **Zoom to** (fits all descendant solids; disabled when the group has none) / **Ungroup** (moves **all** direct children to the group's parent, then removes the group) / **Delete** (cascade-deletes the whole subtree). **Zoom to** keeps the current camera orientation and frames the solid or group with a little padding.
- **Right-click the name** - Solids: **Zoom to** / **Shape info...** / local-frame display and edit (below) / **Delete**. Groups: **Zoom to** (fits all descendant solids; disabled when the group has none) / **Ungroup** (moves **all** direct children to the group's parent, then removes the group) / **Delete** (cascade-deletes the whole subtree). **Zoom to** keeps the current camera orientation and frames the solid or group with a little padding.
- **Local frame (solids)** - Each solid has a local `XYZ` frame (default: world-aligned at the bounding-box center; **Z** is the primary axis, **Y** is up). Right-click the name or **M** for:
- **Show axes** / **Show plane** / **Show up** - toggle 3D annotations (off by default; saved in the `.ezy` file; hidden while any sketch tool is active, when **Hide all** is on, or when the solid or an ancestor group is hidden).
- **Reset frame to bbox** - world-aligned frame at the bbox center (turns **Show axes** on).
- **Set from planar face...** / **Set from cylindrical face...** - pick a face on that solid; plane sets **Z** = normal with origin at the face area center (circle center for a disk); cylinder sets **Z** = axis with origin = solid center projected onto the axis.
- **Flip up** / **Flip axis (Z)** - reverse the up direction or primary axis (undoable).

Boolean results stay under the shared parent of their inputs when all inputs share one parent; otherwise they are placed at the document root. **File -> Import** STEP assemblies use **Import as** (default **Preserve hierarchy**) to keep product/assembly groups in the tree, import **Flat solids** at the root, or **Union shapes** into one solid.

Expand Down
5 changes: 3 additions & 2 deletions src/doc/gui.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,11 @@ Typical responsibilities:
| 3 | `sync_sketch_add_mid_pt_edges_if_applicable_()` |
| 4 | Update toolbar active state |

`set_parent_mode()` maps each tool mode back to `Normal` or `Sketch_inspection_mode` via `GUI::parent_mode_of` (see parent map in `gui_mode.cpp`). Undo/redo uses the same map so stored `Move` / `Rotate` / `Scale` restore their parent instead of re-entering the free-drag tool (see [undo-redo.md](undo-redo.md#mode-restoration)).
`set_parent_mode()` maps each tool mode back to `Normal` or `Sketch_inspection_mode` via `GUI::parent_mode_of` (see parent map in `gui_mode.cpp`). Undo/redo uses the same map so stored `Move` / `Rotate` / `Scale` / `Shape_shaft_align` / `Shape_set_frame` restore their parent instead of re-entering a free-drag or face-pick tool (see [undo-redo.md](undo-redo.md#mode-restoration)).

### New mode or toolbar command (hotkeys)

When adding a `Mode` to [`mode.h`](../mode.h) (`EZY_MODE_LIST`), a toolbar button, or a one-shot `Command`, update remappable hotkeys in the **same change**. Skip only for modes that must stay toolbar-only (document that choice).
When adding a `Mode` to [`mode.h`](../mode.h) (`EZY_MODE_LIST`), a toolbar button, or a one-shot `Command`, update remappable hotkeys in the **same change**. Skip only for modes that must stay toolbar-only (document that choice). `Mode::Shape_set_frame` is Shape List-only (no toolbar button / remappable hotkey).

| Step | Touch |
| ---- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
Expand Down Expand Up @@ -182,6 +182,7 @@ See also [`src/doc/sketch.md`](sketch.md) and [`src/doc/shape.md`](shape.md) for
| `Rotate` | `shp_rotate().rotate_selected` |
| `Scale` | `shp_scale().scale_selected` |
| `Shape_shaft_align` | `shp_cyl_align().drag_depth` / `drag_twist` |
| `Shape_set_frame` | `options_shape_set_frame_mode_` (Shape List only; no toolbar/hotkey) |
| `Shape_polar_duplicate` | `shp_polar_dup().move_point` |
| Sketch tool modes (line, arc, rect, dim, axis, ...) | `curr_sketch().sketch_pt_move` |
| `Sketch_face_extrude` | `sketch_face_extrude(..., true)` |
Expand Down
5 changes: 3 additions & 2 deletions src/doc/shape.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ Typical uses:
### Lifetime and ownership

- Shapes are stored in `Occt_view::m_shps` (`std::list<Shp_ptr>`). Access via `get_shapes()` or internal `add_shp_()`.
- Each solid stores a `gp_Ax3` local frame. New geometry defaults to a world-aligned frame at its bounding-box center. Baked move/rotate/scale transforms update the frame; project JSON and shape undo records preserve it.
- Each solid stores a `gp_Ax3` local frame (**Z** = primary axis, **Y** = up). New geometry defaults to a world-aligned frame at its bounding-box center. Baked move/rotate/scale transforms update the frame; project JSON and shape undo records preserve it. Shape List right-click toggles **Show axes** / **Show plane** / **Show up** AIS annotations (`frameDisplay` in `.ezy`), and can **Reset**, **Set from planar/cylindrical face** (`Mode::Shape_set_frame`, no toolbar/hotkey; planar origin = face area centroid), or **Flip up** / **Flip axis** via `Occt_view::set_shape_frame` (frame-only `Shape_geom_delta`). Call `clear_frame_display()` before `Remove` from the interactive context (user delete via `Occt_view::remove`, fillet/chamfer `replace_picked_shape_`, load, view dtor). `~Shp` only Nullifies those handles -- the context may already be dying.
- `Shp_ptr` is `opencascade::handle<Shp>`. New shapes are allocated with `new Shp(ctx(), topo_shape)` then registered through `Occt_view::add_shp_()`. Groups use `Shp::create_group` (empty compound, never displayed).
- Hierarchy: `parent_id` (0 = root) + `sibling_order`. Organizational groups only (no transform inheritance). Helpers: `shape_children`, `shape_descendant_solids`, `group_shapes`, `ungroup_shape`, `reparent_shape`, `would_reparent_create_cycle`.
- **Current group** (`Occt_view::current_group_id`, 0 = root): Shape List click sets it; empty groups are valid. Primitives / extrude / revolve / PLY / unioned STEP import (`Step_import_mode::Union_shapes`) call `add_shp_(..., use_current_group=true)` so new solids land under that group. Hierarchical and flat STEP import place roots at document root (`parent_id` 0). STEP import may prepare geometry off-thread (`prepare_step_import`) then `commit_step_import` on the UI thread. Booleans keep `assign_result_parent_`.
Expand Down Expand Up @@ -122,7 +122,7 @@ class Shp : public AIS_Shape {
};
```

`set_visible` stores the user preference. `Occt_view::sync_sketch_shape_faint_style` applies effective visibility (own flag, ancestor groups, Hide all overlay, sketch faint/hide) via `apply_context_shown` so Hide all does not stomp per-shape flags. `update_display_()` re-binds selection after mode changes.
`set_visible` stores the user preference. `Occt_view::sync_sketch_shape_faint_style` applies effective visibility (own flag, ancestor groups, Hide all overlay, sketch faint/hide) via `apply_context_shown` so Hide all does not stomp per-shape flags. Frame AIS (axes/plane/up) use the same `show` result: `set_frame_display_suppressed(sketch || !show)` and `apply_context_shown(false)` clears overlays without changing the Shape List toggle flags. `update_display_()` re-binds selection after mode changes.

`.ezy` `shapes[]` entries include `id`, `name`, `parentId`, `order`, `visible`, and either `isGroup: true` or `material` + `geom` + `frame`. Undo uses `Shape_rec` (including the local frame) plus `Shape_tree_delta` for reparent/group/ungroup.

Expand Down Expand Up @@ -154,6 +154,7 @@ Protected helpers used by all operation classes:
| `shp_rotate.h` | `Shp_rotate` | Rotate about view axis, global X/Y/Z, or view-to-object; angle Tab entry; optional axis/center AIS guides. |
| `shp_scale.h` | `Shp_scale` | Uniform scale from bbox center vs mouse distance; clamped factor 0.01..100. |
| `shp_cyl_align.h` | `Shp_cyl_align` | Pick two cylindrical faces (first moves); coaxial `cyl_align_trsf`; drag axial depth; Options **Clock rotation** (default off) then LMB / Shift+Tab about shared axis; Options flip; bake like Move. |
| `shp_set_frame.h` | `Shp_set_frame` | Shape List-only: pick planar or cylindrical face to redefine `Shp` local frame; enables Show axes. |
| `shp_fillet.h` | `Shp_fillet` | `add_fillet(..., Fillet_mode)` -- `BRepFilletAPI_MakeFillet`; modes: Shape, Face, Wire, Edge (`mode.h`). |
| `shp_chamfer.h` | `Shp_chamfer` | `add_chamfer(..., Chamfer_mode)` -- diagonal distance converted to setback (`dist/sqrt(2)`). |
| `shp_polar_dup.h` | `Shp_polar_dup` | Arm on sketch plane; `dup()` copies selection at polar steps; options: rotate copies, combine into one solid. |
Expand Down
2 changes: 1 addition & 1 deletion src/doc/undo-redo.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ This prevents re-entrant history pushes while applying a step.

### Mode restoration

After applying a delta or reloading a snapshot, `Occt_view` restores a UI mode via `GUI::set_mode`. Stored `Move` / `Rotate` / `Scale` are mapped to their parent mode (`GUI::parent_mode_of`, normally `Normal`) so undo/redo does not re-enter a free-drag tool that would immediately move the current selection under the cursor. Other stored modes are restored as-is. If the restored mode is `Sketch_inspection_mode`, the sketch list pane is shown again.
After applying a delta or reloading a snapshot, `Occt_view` restores a UI mode via `GUI::set_mode`. Stored `Move` / `Rotate` / `Scale` / `Shape_shaft_align` / `Shape_set_frame` are mapped to their parent mode (`GUI::parent_mode_of`, normally `Normal`) so undo/redo does not re-enter a free-drag tool or face-pick with no target. Other stored modes are restored as-is. If the restored mode is `Sketch_inspection_mode`, the sketch list pane is shown again.

### View camera

Expand Down
98 changes: 98 additions & 0 deletions src/gui.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2735,6 +2735,53 @@ void GUI::shape_list_()
if (ImGui::MenuItem("Shape info..."))
open_shape_info_(shape);

if (true)
{
ImGui::Separator();
bool show_axes = shape->show_frame_axes();
if (ImGui::MenuItem("Show axes", nullptr, show_axes))
shape->set_show_frame_axes(!show_axes);
bool show_plane = shape->show_frame_plane();
if (ImGui::MenuItem("Show plane", nullptr, show_plane))
shape->set_show_frame_plane(!show_plane);
bool show_up = shape->show_frame_up();
if (ImGui::MenuItem("Show up", nullptr, show_up))
shape->set_show_frame_up(!show_up);
ImGui::Separator();
if (ImGui::MenuItem("Reset frame to bbox"))
{
select_shape_row(shape);
m_view->set_shape_frame(shape, Shp::default_frame_for(shape->Shape()));
shape->set_show_frame_axes(true);
}
if (ImGui::MenuItem("Set from planar face..."))
{
select_shape_row(shape);
m_view->shp_set_frame().begin(shape, Shp_set_frame::Pick::Planar_face);
set_mode(Mode::Shape_set_frame);
}
if (ImGui::MenuItem("Set from cylindrical face..."))
{
select_shape_row(shape);
m_view->shp_set_frame().begin(shape, Shp_set_frame::Pick::Cylindrical_face);
set_mode(Mode::Shape_set_frame);
}
if (ImGui::MenuItem("Flip up"))
{
select_shape_row(shape);
gp_Ax3 f = shape->get_frame();
f.XReverse();
m_view->set_shape_frame(shape, f);
}
if (ImGui::MenuItem("Flip axis (Z)"))
{
select_shape_row(shape);
gp_Ax3 f = shape->get_frame();
f.ZReverse();
m_view->set_shape_frame(shape, f);
}
}

if (ImGui::MenuItem("Delete"))
shape_to_delete = shape;

Expand Down Expand Up @@ -2838,6 +2885,53 @@ void GUI::shape_list_()
if (is_group && ImGui::MenuItem("Ungroup"))
shape_to_ungroup_id = shape->get_id();

if (!is_group)
{
ImGui::Separator();
bool show_axes = shape->show_frame_axes();
if (ImGui::MenuItem("Show axes", nullptr, show_axes))
shape->set_show_frame_axes(!show_axes);
bool show_plane = shape->show_frame_plane();
if (ImGui::MenuItem("Show plane", nullptr, show_plane))
shape->set_show_frame_plane(!show_plane);
bool show_up = shape->show_frame_up();
if (ImGui::MenuItem("Show up", nullptr, show_up))
shape->set_show_frame_up(!show_up);
ImGui::Separator();
if (ImGui::MenuItem("Reset frame to bbox"))
{
select_shape_row(shape);
m_view->set_shape_frame(shape, Shp::default_frame_for(shape->Shape()));
shape->set_show_frame_axes(true);
}
if (ImGui::MenuItem("Set from planar face..."))
{
select_shape_row(shape);
m_view->shp_set_frame().begin(shape, Shp_set_frame::Pick::Planar_face);
set_mode(Mode::Shape_set_frame);
}
if (ImGui::MenuItem("Set from cylindrical face..."))
{
select_shape_row(shape);
m_view->shp_set_frame().begin(shape, Shp_set_frame::Pick::Cylindrical_face);
set_mode(Mode::Shape_set_frame);
}
if (ImGui::MenuItem("Flip up"))
{
select_shape_row(shape);
gp_Ax3 f = shape->get_frame();
f.XReverse();
m_view->set_shape_frame(shape, f);
}
if (ImGui::MenuItem("Flip axis (Z)"))
{
select_shape_row(shape);
gp_Ax3 f = shape->get_frame();
f.ZReverse();
m_view->set_shape_frame(shape, f);
}
}

if (ImGui::MenuItem("Delete"))
shape_to_delete = shape;

Expand Down Expand Up @@ -3924,6 +4018,10 @@ void GUI::on_left_click_(const ScreenCoords& screen_coords)
else if (Status s = m_view->shp_cyl_align().pick(screen_coords); !s.is_ok())
show_message(s.message());
break;
case Mode::Shape_set_frame:
if (Status s = m_view->shp_set_frame().pick(screen_coords); !s.is_ok())
show_message(s.message());
break;
case Mode::Sketch_face_extrude: m_view->sketch_face_extrude(screen_coords, false); break;
// clang-format on

Expand Down
1 change: 1 addition & 0 deletions src/gui.h
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,7 @@ class GUI
void options_shape_polar_duplicate_mode_();
void options_shape_cross_section_mode_();
void options_shape_shaft_align_mode_();
void options_shape_set_frame_mode_();
void options_sketch_from_planer_face_mode_();
void options_sketch_operation_axis_mode_();
void options_sketch_face_extrude_mode_();
Expand Down
Loading
Loading