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

## [Unreleased]

### Project format (`.ezy`)

- **Format v3:** Saved projects are a ZIP archive (`manifest.json` plus deduplicated `assets/*.rgba` underlay blobs). Sketch underlays reference assets by content hash instead of inline base64. Legacy plain-JSON `.ezy` files (v1/v2, including `rgba_b64` underlays) still load. Document snapshots for undo/redo reference shared assets rather than re-embedding pixel data.

### Shape List

- **Shape info...** on the right-click menu for a shape name or **M** button. Opens a dialog with OCCT topology and property details: root type (Solid, Shell, Face, etc.), validity, sub-shape counts, bounding box, volume, center of mass, surface area, and length where applicable. Includes document fields (name, material, display mode, visibility) and a **Refresh** button. Documented in the user guide under [Shape info](docs/usage.md#shape-info).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ Improve discoverability and accuracy of the user guide by ensuring every sketch
**Options pane UI + help links (src/):**

- Ensured *every* mode that renders an Options pane (including Normal/Inspection and Sketch_inspection_mode) now starts with:
- `current_mode_description()` (pulled from m_toolbar_buttons tooltip data, or c_mode_strs as fallback).
- `current_mode_description_()` (pulled from m_toolbar_buttons tooltip data, or c_mode_strs as fallback).
- `options_doc_help_button_()` — a small "?" that calls `open_url_(get_doc_url_for_mode(get_mode()))`.
- Added `get_doc_url_for_mode(Mode)` static map with one entry per Mode (asserted to match Mode::_count at runtime).
- Specific anchors: e.g. Sketch_add_node → usage-sketch.html#add-node-tool, Operation Axis → #operation-axis-tool, Slot (with note on its semantics), etc.
Expand Down Expand Up @@ -69,7 +69,7 @@ Improve discoverability and accuracy of the user guide by ensuring every sketch
- `docs/usage-sketch.md`
- `src/gui_mode.cpp`
- `src/gui.h`
- `src/gui.cpp` (minor, for current_mode_description)
- `src/gui.cpp` (minor, for current_mode_description_)
- `agents/drafts/issues/archive/gh-131-sketch-tool-precise-input-docs-and-options-help.md` (this draft)
- `agents/drafts/prs/archive/gh-132-sketch-tool-input-docs-options-pane-help.md`
- `agents/README.md`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Docs + UI: document Tab + Shift+Tab for Operation Axis / Rectangles / Slot / Cir

- Bring the user guide (usage-sketch.md) up to date for every remaining sketch creation tool so it documents both precise distance (Tab) and angle (Shift+Tab) during placement, with full "Angle Constraint" subsections, updated Features/How-to/Shortcuts/Tips, and notes on use cases (future snapping, etc.).
- Special handling for Slot (first edge = length/orientation of the slot, radius point = the "second edge dim" / arc radius) and for tools that auto-align (e.g. Rectangle Two Points).
- Add (or ensure) a top-level tool/mode header using `current_mode_description()` followed by a small "?" button in the Options pane for *all* modes that render options content, including pure Normal (Inspection) and Sketch_inspection_mode.
- Add (or ensure) a top-level tool/mode header using `current_mode_description_()` followed by a small "?" button in the Options pane for *all* modes that render options content, including pure Normal (Inspection) and Sketch_inspection_mode.
- The "?" buttons use a central `get_doc_url_for_mode(Mode)` map (with runtime size assert vs Mode::_count) so future tools automatically get correct contextual links (e.g. #operation-axis-tool, #slot-creation-tool, #rectangle-tool-center-point, etc.).
- Added the generic `agents/drafts/issues/_template.md` and `agents/drafts/prs/_template.md` templates and referenced them from agents/README.md (for future drafts).
- Minor supporting changes: doc style guide note about the new ? buttons, unit test for link existence, etc.
Expand All @@ -22,7 +22,7 @@ This makes the in-app Options experience (headers + help) and the written user g
- `docs/usage-sketch.md` (main user-guide updates for the listed tools)
- `src/gui_mode.cpp` (header+button calls for Normal + new Sketch_inspection_mode handler, map + assert, options_doc_help_button_ helper)
- `src/gui.h` (declarations)
- `src/gui.cpp` (current_mode_description lookup from toolbar data)
- `src/gui.cpp` (current_mode_description_ lookup from toolbar data)
- `agents/drafts/issues/archive/gh-131-sketch-tool-precise-input-docs-and-options-help.md` (this issue draft)
- `agents/drafts/prs/archive/gh-132-sketch-tool-input-docs-options-pane-help.md` (this PR draft)
- `agents/README.md` (added references to the new generic templates)
Expand Down
26 changes: 26 additions & 0 deletions agents/issues/018-ezy-format-v3-zip-underlay-assets.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# .ezy format v3: zip container + underlay asset refs

**Status:** implemented on `main`

---

## Summary

Upgrade `.ezy` from monolithic JSON to a self-contained ZIP (`ezyFormat: 3`):

- `manifest.json` — sketches, shapes, view (BRep still inline)
- `assets/<hash>.rgba` — deduplicated underlay RGBA8 blobs
- Sketch underlay JSON uses `"asset"` ref instead of `"rgba_b64"`

Legacy plain-JSON `.ezy` files continue to load (`rgba_b64` migrated into the session asset store on read).

## Key files

- `src/ezy_asset_store.h/.cpp` — content-hash registry, shared pixel blobs
- `src/ezy_io.h/.cpp` — zip pack/unpack, format sniff, base64 for Wasm startup storage
- `src/sketch_underlay.*`, `src/sketch_json.cpp`, `src/occt_view.*`, `src/gui.cpp`, `src/utl_settings.cpp`

## Out of scope (phase 2)

- External BRep blobs (`shapes[].geom`, `originating_face`)
- Asset GC across long sessions
2 changes: 1 addition & 1 deletion docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ For shortcuts, sample scripts, binding tables, and limitations, see the **[Scrip
## File Operations

### Supported Formats
- Native format: `.ezy` files
- Native format: `.ezy` files (v3: ZIP archive with `manifest.json` and externalized underlay images; older plain-JSON `.ezy` files still open)
- [Import formats: STEP (`.step`, `.stp`), PLY (`.ply`)](#importing-3d-geometries)
- [Export formats: STEP, IGES, STL (binary), PLY (binary)](#exporting-3d-geometries)

Expand Down
55 changes: 55 additions & 0 deletions src/ezy_asset_store.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#include "ezy_asset_store.h"

#include <iomanip>
#include <sstream>

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<uint64_t>(data[i]);
hash *= 1099511628211ULL;
}
return hash;
}

} // 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<const uint8_t*>(&w), sizeof(w));
hash = fnv1a64_feed(hash, reinterpret_cast<const uint8_t*>(&h), sizeof(h));
if (rgba && len > 0)
hash = fnv1a64_feed(hash, rgba, len);

std::ostringstream oss;
oss << std::hex << std::setfill('0') << std::setw(16) << hash;
return oss.str();
}

std::string Ezy_asset_store::register_rgba(const std::vector<uint8_t>& rgba, int w, int h)
{
const std::string id = make_asset_id(rgba.data(), rgba.size(), w, h);
if (m_by_id.find(id) == m_by_id.end())
m_by_id[id] = std::make_shared<const std::vector<uint8_t>>(rgba);
return id;
}

std::shared_ptr<const std::vector<uint8_t>> Ezy_asset_store::get(const std::string& asset_id) const
{
const auto it = m_by_id.find(asset_id);
if (it == m_by_id.end())
return {};
return it->second;
}

void Ezy_asset_store::import_asset(const std::string& asset_id, std::vector<uint8_t>&& rgba)
{
m_by_id[asset_id] = std::make_shared<const std::vector<uint8_t>>(std::move(rgba));
}

void Ezy_asset_store::clear() { m_by_id.clear(); }
27 changes: 27 additions & 0 deletions src/ezy_asset_store.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#pragma once

#include <cstdint>
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>

/// Session-lifetime deduplicated RGBA blobs referenced from sketch underlay JSON.
class Ezy_asset_store
{
public:
/// Content-addressed id (FNV-1a 64-bit hex) from w, h, and pixel bytes.
[[nodiscard]] std::string register_rgba(const std::vector<uint8_t>& rgba, int w, int h);

[[nodiscard]] std::shared_ptr<const std::vector<uint8_t>> get(const std::string& asset_id) const;

/// Insert or replace bytes for \a asset_id (e.g. when loading a zip archive).
void import_asset(const std::string& asset_id, std::vector<uint8_t>&& rgba);

void clear();

[[nodiscard]] static std::string make_asset_id(const uint8_t* rgba, std::size_t len, int w, int h);

private:
std::unordered_map<std::string, std::shared_ptr<const std::vector<uint8_t>>> m_by_id;
};
Loading
Loading