From 2f8d0ae9139a5ea9aac6482158122d73bb056354 Mon Sep 17 00:00:00 2001 From: Trailcode Date: Sun, 5 Jul 2026 19:33:58 -0600 Subject: [PATCH 1/3] Python sierpinski --- docs/scripting.md | 11 ++++++ res/scripts/python/sierpinski.py | 25 +++++++------ src/doc/script.md | 8 ++++ src/gui_occt_view.cpp | 13 +++++++ src/gui_occt_view.h | 5 +++ src/scr_python_console.cpp | 63 ++++++++++++++++++++++++++++++++ src/sketch.cpp | 4 ++ src/sketch.h | 5 +++ 8 files changed, 122 insertions(+), 12 deletions(-) diff --git a/docs/scripting.md b/docs/scripting.md index 5aa13fe..d022207 100644 --- a/docs/scripting.md +++ b/docs/scripting.md @@ -50,6 +50,16 @@ Use **`ezy.help()`** or **`help()`** in either console for the built-in reminder | `view.get_camera()` | Camera vectors: `eye`, `center`, and `up` | | `view.set_camera(ex, ey, ez, cx, cy, cz, ux, uy, uz)` | Set camera vectors | +**Python only** (sketch inspection and creation): + +| Method | Purpose | +| --- | --- | +| `view.curr_sketch_node_count()` / `view.curr_sketch_node(i)` | Read nodes `(x, y)` on the current sketch plane | +| `view.curr_sketch_dim_count()` / `view.curr_sketch_dim(i)` | Read length dimensions | +| `view.add_sketch(plane, offset, base_name)` | New sketch on `XY`, `XZ`, or `YZ` (`offset` in display units; optional name) | +| `view.add_edge(x1, y1, x2, y2)` | Add a linear edge to the current sketch | +| `view.finish_sketch_edges()` | Rebuild closed-face topology after bulk edge import | + **Lua only:** global **`print`** is routed to **`ezy.log`**. **Python only:** **`print`** is similarly redirected to **`ezy.log`** after bootstrap. @@ -57,6 +67,7 @@ Use **`ezy.help()`** or **`help()`** in either console for the built-in reminder - **`res/scripts/lua/basic.lua`** - Defines **`kv(obj)`** to dump tables or list userdata methods via **`ezy.log`**. - **`res/scripts/python/basic.py`** - Example **`dump_view()`** using **`ezy.log`** and **`view`**. +- **`res/scripts/python/sierpinski.py`** - Generates a Sierpinski triangle; call **`create_sierpinski_sketch()`** to build it as a sketch. Copy these as templates for your own files in the same folders. diff --git a/res/scripts/python/sierpinski.py b/res/scripts/python/sierpinski.py index dbff205..bfd45e6 100644 --- a/res/scripts/python/sierpinski.py +++ b/res/scripts/python/sierpinski.py @@ -2,13 +2,8 @@ # Just for fun! # # Usage (after opening Python console, or it runs on load): -# create_sierpinski(order=3, size=20.0) -# -# It will log the unique nodes and the line segments (as pairs of points). -# You can then: -# 1. Create a new sketch in the UI (or use ezy.set_mode("Sketch_add_edge") etc.) -# 2. Use the logged coordinates to add nodes/edges manually, or copy into -# a small loop if more Python bindings for sketch creation are added later. +# create_sierpinski(order=3, size=20.0) # log nodes and segments +# create_sierpinski_sketch(order=3, size=20.0) # build as a new sketch # # The recursion draws the standard Sierpinski gasket lines (connect midpoints # recursively, resulting in 3**order small upward triangles' boundaries). @@ -70,11 +65,7 @@ def create_sierpinski(order=3, size=20.0, center=(0.0, 0.0)): for i, (a, b) in enumerate(unique_lines): ezy.log(f" [{i}] ({a[0]:.6f},{a[1]:.6f}) -> ({b[0]:.6f},{b[1]:.6f})") - ezy.log("To build the sketch:") - ezy.log(" - Create or switch to a sketch (e.g. via UI or ezy.set_mode('Sketch_add_edge'))") - ezy.log(" - Use the node list above to place points (add-node tool or line tool).") - ezy.log(" - Connect them using the listed segments.") - ezy.log(" - Or extend the Python bindings to support sketch creation from code!") + ezy.log("To build the sketch: create_sierpinski_sketch(order, size, center)") # For convenience, also stash the data on the main module so you can access from console import __main__ @@ -83,6 +74,16 @@ def create_sierpinski(order=3, size=20.0, center=(0.0, 0.0)): return node_list, unique_lines +def create_sierpinski_sketch(order=3, size=20.0, center=(0.0, 0.0), plane="XY", offset=0.0, name="Sierpinski"): + """Generate a Sierpinski triangle and add it as a new sketch.""" + node_list, unique_lines = create_sierpinski(order, size, center) + view.add_sketch(plane, offset, name) + for (a, b) in unique_lines: + view.add_edge(a[0], a[1], b[0], b[1]) + view.finish_sketch_edges() + ezy.log(f"Created sketch '{view.curr_sketch_name()}' with {len(unique_lines)} edges.") + return node_list, unique_lines + # Auto-generate a small example on script load (so it's immediately visible in the console tab) # You can call create_sierpinski(4) yourself for higher detail (gets dense quickly). if __name__ == "__main__" or True: # always run on exec diff --git a/src/doc/script.md b/src/doc/script.md index 876cc54..bc7e8a0 100644 --- a/src/doc/script.md +++ b/src/doc/script.md @@ -104,6 +104,14 @@ Lua overrides global `print` to call `ezy.log`. Python bootstrap assigns `builti | `curr_sketch_dim_count()` | Length dimension count | | `curr_sketch_dim(i)` | `(lo, hi, visible, offset, name, distance)` | +**Python only** (sketch creation): + +| Method | Notes | +| --- | --- | +| `add_sketch(plane, offset, base_name)` | `Occt_view::add_sketch_on_ref_plane`; plane `XY`/`XZ`/`YZ` | +| `add_edge(x1,y1,x2,y2)` | `Occt_view::curr_sketch_add_edge` | +| `finish_sketch_edges()` | `Occt_view::curr_sketch_rebuild_faces` | + ### `Shp` object | Method | Lua | Python | diff --git a/src/gui_occt_view.cpp b/src/gui_occt_view.cpp index 2fa10e4..8e96c17 100644 --- a/src/gui_occt_view.cpp +++ b/src/gui_occt_view.cpp @@ -642,6 +642,19 @@ void Occt_view::add_sketch(const gp_Pln& pln, const std::string& base_name) m_gui.set_mode(Mode::Sketch_inspection_mode); } +void Occt_view::add_sketch_on_ref_plane(Sketch_ref_plane plane, double offset_display, const std::string& base_name) +{ + const gp_Pln pln = sketch_reference_plane(plane, offset_display * get_dimension_scale()); + add_sketch(pln, base_name); +} + +void Occt_view::curr_sketch_add_edge(double x1, double y1, double x2, double y2) +{ + curr_sketch().add_linear_edge(gp_Pnt2d(x1, y1), gp_Pnt2d(x2, y2)); +} + +void Occt_view::curr_sketch_rebuild_faces() { curr_sketch().rebuild_faces(); } + // Query related AIS_Shape_ptr Occt_view::get_shape(const ScreenCoords& screen_coords) { diff --git a/src/gui_occt_view.h b/src/gui_occt_view.h index b6fe2eb..8e2ed70 100644 --- a/src/gui_occt_view.h +++ b/src/gui_occt_view.h @@ -27,6 +27,7 @@ #include "shp_scale.h" #include "utl_types.h" #include "utl_asset_store.h" +#include "utl_geom.h" class Delta; class GUI; @@ -140,6 +141,10 @@ class Occt_view : protected AIS_ViewController void remove_sketch(const Sketch_ptr& sketch); /// Empty sketch on \a pln; \a base_name is uniquified (e.g. Sketch_xy, Sketch_xy.001). void add_sketch(const gp_Pln& pln, const std::string& base_name); + /// Like add_sketch; \a offset_display is multiplied by get_dimension_scale(). + void add_sketch_on_ref_plane(Sketch_ref_plane plane, double offset_display, const std::string& base_name); + void curr_sketch_add_edge(double x1, double y1, double x2, double y2); + void curr_sketch_rebuild_faces(); Sketch& curr_sketch(); Sketch_ptr curr_sketch_shared() const; /// Non-owning; null when none selected. Does not create a default sketch. diff --git a/src/scr_python_console.cpp b/src/scr_python_console.cpp index 6a3dc79..7bb72ee 100644 --- a/src/scr_python_console.cpp +++ b/src/scr_python_console.cpp @@ -13,6 +13,7 @@ #include "gui_occt_view.h" #include "shp.h" #include "sketch.h" +#include "utl_geom.h" #ifdef EZYCAD_HAVE_PYTHON #ifdef _MSC_VER @@ -145,6 +146,25 @@ void append_python_exception_to_history(std::vector& history, bool* ++*log_display_version; } +static Sketch_ref_plane parse_sketch_ref_plane(const std::string& plane) +{ + if (plane == "XZ" || plane == "xz") + return Sketch_ref_plane::XZ; + if (plane == "YZ" || plane == "yz") + return Sketch_ref_plane::YZ; + return Sketch_ref_plane::XY; +} + +static const char* default_sketch_base_name(Sketch_ref_plane plane) +{ + switch (plane) + { + case Sketch_ref_plane::XZ: return "Sketch_xz"; + case Sketch_ref_plane::YZ: return "Sketch_yz"; + default: return "Sketch_xy"; + } +} + static const char* k_bootstrap = R"PY( def _ezycad_bootstrap(): import ezycad_native as _n @@ -173,6 +193,12 @@ def _ezycad_bootstrap(): return _n.view_get_camera() def set_camera(self, ex, ey, ez, cx, cy, cz, ux, uy, uz): return _n.view_set_camera(ex, ey, ez, cx, cy, cz, ux, uy, uz) + def add_sketch(self, plane="XY", offset=0.0, base_name=None): + return _n.view_add_sketch(plane, offset, base_name) + def add_edge(self, x1, y1, x2, y2): + return _n.view_add_edge(x1, y1, x2, y2) + def finish_sketch_edges(self): + return _n.view_finish_sketch_edges() class Ezy: def log(self, msg): return _n.ezy_log(msg) @@ -271,6 +297,9 @@ PYBIND11_EMBEDDED_MODULE(ezycad_native, m) " view.curr_sketch_node(i) - (x, y) of node i in current sketch (raises IndexError if out of range)\n" " view.curr_sketch_dim_count() - number of length dimensions in current sketch\n" " view.curr_sketch_dim(i) - (lo, hi, visible, offset, name, distance) for dimension i\n" + " view.add_sketch(plane, offset, base_name) - new sketch on XY/XZ/YZ (offset in display units)\n" + " view.add_edge(x1,y1,x2,y2) - add linear edge to current sketch\n" + " view.finish_sketch_edges() - rebuild face topology after bulk edge import\n" " view.add_box(ox,oy,oz,w,l,h) - add box\n" " view.add_sphere(ox,oy,oz,r) - add sphere\n" " view.get_shape(i) - get shape by 0-based index (raises IndexError if out of range)\n" @@ -376,6 +405,40 @@ PYBIND11_EMBEDDED_MODULE(ezycad_native, m) }, py::arg("i")); + m.def( + "view_add_sketch", + [](const std::string& plane, double offset, const py::object& base_name) + { + Occt_view* view = g_py_gui && g_py_gui->get_view() ? g_py_gui->get_view() : nullptr; + if (!view) + throw std::runtime_error("no 3D view available"); + const Sketch_ref_plane ref = parse_sketch_ref_plane(plane); + const std::string base = + base_name.is_none() ? std::string(default_sketch_base_name(ref)) : base_name.cast(); + view->add_sketch_on_ref_plane(ref, offset, base); + }, + py::arg("plane") = "XY", py::arg("offset") = 0.0, py::arg("base_name") = py::none()); + + m.def( + "view_add_edge", + [](double x1, double y1, double x2, double y2) + { + Occt_view* view = g_py_gui && g_py_gui->get_view() ? g_py_gui->get_view() : nullptr; + if (!view) + throw std::runtime_error("no 3D view available"); + view->curr_sketch_add_edge(x1, y1, x2, y2); + }, + py::arg("x1"), py::arg("y1"), py::arg("x2"), py::arg("y2")); + + m.def("view_finish_sketch_edges", + [] + { + Occt_view* view = g_py_gui && g_py_gui->get_view() ? g_py_gui->get_view() : nullptr; + if (!view) + throw std::runtime_error("no 3D view available"); + view->curr_sketch_rebuild_faces(); + }); + m.def( "view_add_box", [](double ox, double oy, double oz, double w, double len, double h) diff --git a/src/sketch.cpp b/src/sketch.cpp index c0d4625..d841d75 100644 --- a/src/sketch.cpp +++ b/src/sketch.cpp @@ -138,6 +138,10 @@ void Sketch::update_faces_() { m_topo.update_faces(); } void Sketch::add_edge_(const gp_Pnt2d& pt_a, const gp_Pnt2d& pt_b) { m_edges.add_edge(pt_a, pt_b); } +void Sketch::add_linear_edge(const gp_Pnt2d& pt_a, const gp_Pnt2d& pt_b) { add_edge_(pt_a, pt_b); } + +void Sketch::rebuild_faces() { update_faces_(); } + void Sketch::add_edge_(const gp_Pnt2d& pt_a, const gp_Pnt2d& pt_b, Sketch_op_recorder& rec) { m_edges.add_edge(pt_a, pt_b, rec); diff --git a/src/sketch.h b/src/sketch.h index 48762d9..7c36e63 100644 --- a/src/sketch.h +++ b/src/sketch.h @@ -144,6 +144,11 @@ class Sketch static void set_edge_from_center(bool on); static bool get_edge_from_center(); + /// Add a linear edge between plane points (scripting / import). + void add_linear_edge(const gp_Pnt2d& pt_a, const gp_Pnt2d& pt_b); + /// Rebuild closed-face topology after bulk edge import. + void rebuild_faces(); + private: friend class Sketch_json; friend class Sketch_access; From 1ae7fec31cc7bc74010da5434433b62262a3267d Mon Sep 17 00:00:00 2001 From: Trailcode Date: Sun, 5 Jul 2026 19:38:40 -0600 Subject: [PATCH 2/3] Lua version. --- docs/scripting.md | 3 +- res/scripts/lua/sierpinski.lua | 113 +++++++++++++++++++++++ src/doc/script.md | 8 +- src/scr_lua_console.cpp | 158 +++++++++++++++++++++++++++++++++ 4 files changed, 277 insertions(+), 5 deletions(-) create mode 100644 res/scripts/lua/sierpinski.lua diff --git a/docs/scripting.md b/docs/scripting.md index d022207..a4edbb7 100644 --- a/docs/scripting.md +++ b/docs/scripting.md @@ -50,7 +50,7 @@ Use **`ezy.help()`** or **`help()`** in either console for the built-in reminder | `view.get_camera()` | Camera vectors: `eye`, `center`, and `up` | | `view.set_camera(ex, ey, ez, cx, cy, cz, ux, uy, uz)` | Set camera vectors | -**Python only** (sketch inspection and creation): +**Sketch inspection and creation** (Lua and Python; indices are **1-based** in Lua, **0-based** in Python): | Method | Purpose | | --- | --- | @@ -66,6 +66,7 @@ Use **`ezy.help()`** or **`help()`** in either console for the built-in reminder ## Sample scripts - **`res/scripts/lua/basic.lua`** - Defines **`kv(obj)`** to dump tables or list userdata methods via **`ezy.log`**. +- **`res/scripts/lua/sierpinski.lua`** - Generates a Sierpinski triangle; call **`create_sierpinski_sketch()`** to build it as a sketch. - **`res/scripts/python/basic.py`** - Example **`dump_view()`** using **`ezy.log`** and **`view`**. - **`res/scripts/python/sierpinski.py`** - Generates a Sierpinski triangle; call **`create_sierpinski_sketch()`** to build it as a sketch. diff --git a/res/scripts/lua/sierpinski.lua b/res/scripts/lua/sierpinski.lua new file mode 100644 index 0000000..3d0e22f --- /dev/null +++ b/res/scripts/lua/sierpinski.lua @@ -0,0 +1,113 @@ +-- Sierpinski triangle sketch generator for EzyCad Lua console. +-- Just for fun! +-- +-- Usage (runs on startup, or call from the console): +-- create_sierpinski(3, 20.0) -- log nodes and segments +-- create_sierpinski_sketch(3, 20.0) -- build as a new sketch + +local function mid(a, b) + return { (a[1] + b[1]) / 2.0, (a[2] + b[2]) / 2.0 } +end + +local function append_sierpinski_segments(order, p1, p2, p3, lines) + if order <= 0 then + lines[#lines + 1] = { p1, p2 } + lines[#lines + 1] = { p2, p3 } + lines[#lines + 1] = { p3, p1 } + return + end + local m12 = mid(p1, p2) + local m23 = mid(p2, p3) + local m31 = mid(p3, p1) + append_sierpinski_segments(order - 1, p1, m12, m31, lines) + append_sierpinski_segments(order - 1, p2, m23, m12, lines) + append_sierpinski_segments(order - 1, p3, m31, m23, lines) +end + +local function line_key(a, b) + if a[1] < b[1] or (a[1] == b[1] and a[2] < b[2]) then + return string.format("%.9g,%.9g|%.9g,%.9g", a[1], a[2], b[1], b[2]) + end + return string.format("%.9g,%.9g|%.9g,%.9g", b[1], b[2], a[1], a[2]) +end + +function create_sierpinski(order, size, center) + order = order or 3 + size = size or 20.0 + center = center or { 0.0, 0.0 } + local cx, cy = center[1], center[2] + local h = size * math.sqrt(3.0) / 2.0 + local p1 = { cx - size / 2.0, cy - h / 3.0 } + local p2 = { cx + size / 2.0, cy - h / 3.0 } + local p3 = { cx, cy + 2.0 * h / 3.0 } + + local lines = {} + append_sierpinski_segments(order, p1, p2, p3, lines) + + local seen = {} + local unique_lines = {} + for _, seg in ipairs(lines) do + local a, b = seg[1], seg[2] + local key = line_key(a, b) + if not seen[key] then + seen[key] = true + unique_lines[#unique_lines + 1] = { a, b } + end + end + + local node_map = {} + for _, seg in ipairs(unique_lines) do + node_map[string.format("%.9g,%.9g", seg[1][1], seg[1][2])] = seg[1] + node_map[string.format("%.9g,%.9g", seg[2][1], seg[2][2])] = seg[2] + end + local node_list = {} + for _, pt in pairs(node_map) do + node_list[#node_list + 1] = pt + end + table.sort(node_list, function(u, v) + if u[2] ~= v[2] then return u[2] < v[2] end + return u[1] < v[1] + end) + + ezy.log(string.format("Sierpinski triangle: order=%d, size=%g, center=(%g, %g)", order, size, cx, cy)) + ezy.log(string.format(" unique nodes: %d", #node_list)) + for i, pt in ipairs(node_list) do + ezy.log(string.format(" [%d] (%.6f, %.6f)", i, pt[1], pt[2])) + end + ezy.log(string.format(" line segments: %d", #unique_lines)) + for i, seg in ipairs(unique_lines) do + local a, b = seg[1], seg[2] + ezy.log(string.format(" [%d] (%.6f,%.6f) -> (%.6f,%.6f)", i, a[1], a[2], b[1], b[2])) + end + ezy.log("To build the sketch: create_sierpinski_sketch(order, size, center)") + + sierpinski_nodes = node_list + sierpinski_lines = unique_lines + return node_list, unique_lines +end + +function create_sierpinski_sketch(order, size, center, plane, offset, name) + order = order or 3 + size = size or 20.0 + center = center or { 0.0, 0.0 } + plane = plane or "XY" + offset = offset or 0.0 + name = name or "Sierpinski" + local node_list, unique_lines = create_sierpinski(order, size, center) + view.add_sketch(plane, offset, name) + for _, seg in ipairs(unique_lines) do + local a, b = seg[1], seg[2] + view.add_edge(a[1], a[2], b[1], b[2]) + end + view.finish_sketch_edges() + ezy.log(string.format("Created sketch '%s' with %d edges.", view.curr_sketch_name(), #unique_lines)) + return node_list, unique_lines +end + +-- Small default on load so output is visible without spamming. +local ok, err = pcall(function() + create_sierpinski(2, 10.0) +end) +if not ok then + ezy.log("sierpinski auto-gen error: " .. tostring(err)) +end diff --git a/src/doc/script.md b/src/doc/script.md index bc7e8a0..7eb53bf 100644 --- a/src/doc/script.md +++ b/src/doc/script.md @@ -95,16 +95,16 @@ Lua overrides global `print` to call `ezy.log`. Python bootstrap assigns `builti | `set_camera(ex,ey,ez,cx,cy,cz,ux,uy,uz)` | `Occt_view::set_camera` | | `get_shape(i)` | Returns `Shp` wrapper | -**Python only** (current sketch inspection): +**Lua and Python** (current sketch inspection): | Method | Returns | | --- | --- | | `curr_sketch_node_count()` | Node count | -| `curr_sketch_node(i)` | `(x, y)` plane coords | +| `curr_sketch_node(i)` | `(x, y)` plane coords (Lua: 1-based index; Python: 0-based) | | `curr_sketch_dim_count()` | Length dimension count | -| `curr_sketch_dim(i)` | `(lo, hi, visible, offset, name, distance)` | +| `curr_sketch_dim(i)` | `(lo, hi, visible, offset, name, distance)` (Lua: 1-based indices) | -**Python only** (sketch creation): +**Lua and Python** (sketch creation): | Method | Notes | | --- | --- | diff --git a/src/scr_lua_console.cpp b/src/scr_lua_console.cpp index 51db2a0..ad0d3b7 100644 --- a/src/scr_lua_console.cpp +++ b/src/scr_lua_console.cpp @@ -6,6 +6,7 @@ #include "gui_occt_view.h" #include "shp.h" #include "sketch.h" +#include "utl_geom.h" extern "C" { @@ -43,6 +44,25 @@ void push_shp(lua_State* L, const Shp_ptr& shp) Shp_ptr* to_shp(lua_State* L, int index) { return static_cast(luaL_testudata(L, index, k_shp_metatable)); } +static Sketch_ref_plane parse_sketch_ref_plane(const char* plane) +{ + if (plane && (std::strcmp(plane, "XZ") == 0 || std::strcmp(plane, "xz") == 0)) + return Sketch_ref_plane::XZ; + if (plane && (std::strcmp(plane, "YZ") == 0 || std::strcmp(plane, "yz") == 0)) + return Sketch_ref_plane::YZ; + return Sketch_ref_plane::XY; +} + +static const char* default_sketch_base_name(Sketch_ref_plane plane) +{ + switch (plane) + { + case Sketch_ref_plane::XZ: return "Sketch_xz"; + case Sketch_ref_plane::YZ: return "Sketch_yz"; + default: return "Sketch_xy"; + } +} + // ezy.log(msg) -> append to console and log window int l_ezy_log(lua_State* L) { @@ -132,6 +152,123 @@ int l_view_curr_sketch_name(lua_State* L) return 1; } +// view.curr_sketch_node_count() -> number +int l_view_curr_sketch_node_count(lua_State* L) +{ + GUI* gui = get_gui(L); + Occt_view* view = gui ? gui->get_view() : nullptr; + if (!view) + { + lua_pushinteger(L, 0); + return 1; + } + lua_pushinteger(L, static_cast(view->curr_sketch().get_nodes().size())); + return 1; +} + +// view.curr_sketch_node(i) -> x, y (1-based index) +int l_view_curr_sketch_node(lua_State* L) +{ + GUI* gui = get_gui(L); + Occt_view* view = gui ? gui->get_view() : nullptr; + lua_Integer idx = luaL_checkinteger(L, 1); + if (!view) + return luaL_error(L, "no 3D view available"); + if (idx < 1) + return luaL_error(L, "node index must be >= 1"); + Sketch_nodes& nodes = view->curr_sketch().get_nodes(); + if (static_cast(idx) > nodes.size()) + return luaL_error(L, "node index out of range"); + const Sketch_nodes::Node& n = nodes[static_cast(idx - 1)]; + lua_pushnumber(L, n.X()); + lua_pushnumber(L, n.Y()); + return 2; +} + +// view.curr_sketch_dim_count() -> number +int l_view_curr_sketch_dim_count(lua_State* L) +{ + GUI* gui = get_gui(L); + Occt_view* view = gui ? gui->get_view() : nullptr; + if (!view) + { + lua_pushinteger(L, 0); + return 1; + } + lua_pushinteger(L, static_cast(view->curr_sketch().length_dimension_count())); + return 1; +} + +// view.curr_sketch_dim(i) -> lo, hi, visible, offset, name, distance (1-based index) +int l_view_curr_sketch_dim(lua_State* L) +{ + GUI* gui = get_gui(L); + Occt_view* view = gui ? gui->get_view() : nullptr; + lua_Integer idx = luaL_checkinteger(L, 1); + if (!view) + return luaL_error(L, "no 3D view available"); + if (idx < 1) + return luaL_error(L, "dimension index must be >= 1"); + Sketch& sketch = view->curr_sketch(); + if (static_cast(idx) > sketch.length_dimension_count()) + return luaL_error(L, "dimension index out of range"); + const std::size_t i = static_cast(idx - 1); + const std::size_t lo = sketch.dimension_node_lo(i); + const std::size_t hi = sketch.dimension_node_hi(i); + const Sketch_nodes& nodes = sketch.get_nodes(); + const double dist = nodes[lo].Distance(nodes[hi]) / view->get_dimension_scale(); + lua_pushinteger(L, static_cast(lo + 1)); + lua_pushinteger(L, static_cast(hi + 1)); + lua_pushboolean(L, sketch.dimension_visible(i)); + lua_pushnumber(L, sketch.dimension_offset(i)); + lua_pushstring(L, sketch.dimension_name(i).c_str()); + lua_pushnumber(L, dist); + return 6; +} + +// view.add_sketch(plane, offset, base_name) - base_name optional +int l_view_add_sketch(lua_State* L) +{ + GUI* gui = get_gui(L); + Occt_view* view = gui ? gui->get_view() : nullptr; + if (!view) + return luaL_error(L, "no 3D view available"); + const char* plane = luaL_optstring(L, 1, "XY"); + const double offset = luaL_optnumber(L, 2, 0.0); + const Sketch_ref_plane ref = parse_sketch_ref_plane(plane); + std::string base = default_sketch_base_name(ref); + if (lua_gettop(L) >= 3 && !lua_isnil(L, 3)) + base = luaL_checkstring(L, 3); + view->add_sketch_on_ref_plane(ref, offset, base); + return 0; +} + +// view.add_edge(x1, y1, x2, y2) +int l_view_add_edge(lua_State* L) +{ + GUI* gui = get_gui(L); + Occt_view* view = gui ? gui->get_view() : nullptr; + if (!view) + return luaL_error(L, "no 3D view available"); + const double x1 = luaL_checknumber(L, 1); + const double y1 = luaL_checknumber(L, 2); + const double x2 = luaL_checknumber(L, 3); + const double y2 = luaL_checknumber(L, 4); + view->curr_sketch_add_edge(x1, y1, x2, y2); + return 0; +} + +// view.finish_sketch_edges() +int l_view_finish_sketch_edges(lua_State* L) +{ + GUI* gui = get_gui(L); + Occt_view* view = gui ? gui->get_view() : nullptr; + if (!view) + return luaL_error(L, "no 3D view available"); + view->curr_sketch_rebuild_faces(); + return 0; +} + // view.add_box(ox, oy, oz, w, l, h) int l_view_add_box(lua_State* L) { @@ -353,6 +490,13 @@ int l_ezy_help(lua_State* L) " view.sketch_count() - number of sketches\n" " view.shape_count() - number of shapes\n" " view.curr_sketch_name() - current sketch name\n" + " view.curr_sketch_node_count() - number of nodes in current sketch\n" + " view.curr_sketch_node(i) - x, y of node i (1-based index)\n" + " view.curr_sketch_dim_count() - number of length dimensions in current sketch\n" + " view.curr_sketch_dim(i) - lo, hi, visible, offset, name, distance (1-based)\n" + " view.add_sketch(plane, offset, base_name) - new sketch on XY/XZ/YZ (offset in display units)\n" + " view.add_edge(x1,y1,x2,y2) - add linear edge to current sketch\n" + " view.finish_sketch_edges() - rebuild face topology after bulk edge import\n" " view.add_box(ox,oy,oz,w,l,h) - add box\n" " view.add_sphere(ox,oy,oz,r) - add sphere\n" " view.get_shape(i) - get shape by 1-based index (returns Shp or nil)\n" @@ -457,6 +601,20 @@ void Lua_console::register_bindings() lua_setfield(m_L, -2, "shape_count"); lua_pushcfunction(m_L, l_view_curr_sketch_name); lua_setfield(m_L, -2, "curr_sketch_name"); + lua_pushcfunction(m_L, l_view_curr_sketch_node_count); + lua_setfield(m_L, -2, "curr_sketch_node_count"); + lua_pushcfunction(m_L, l_view_curr_sketch_node); + lua_setfield(m_L, -2, "curr_sketch_node"); + lua_pushcfunction(m_L, l_view_curr_sketch_dim_count); + lua_setfield(m_L, -2, "curr_sketch_dim_count"); + lua_pushcfunction(m_L, l_view_curr_sketch_dim); + lua_setfield(m_L, -2, "curr_sketch_dim"); + lua_pushcfunction(m_L, l_view_add_sketch); + lua_setfield(m_L, -2, "add_sketch"); + lua_pushcfunction(m_L, l_view_add_edge); + lua_setfield(m_L, -2, "add_edge"); + lua_pushcfunction(m_L, l_view_finish_sketch_edges); + lua_setfield(m_L, -2, "finish_sketch_edges"); lua_pushcfunction(m_L, l_view_add_box); lua_setfield(m_L, -2, "add_box"); lua_pushcfunction(m_L, l_view_add_sphere); From f3cbcb36b13c69f884898b808d9661ac1be15648 Mon Sep 17 00:00:00 2001 From: Trailcode Date: Sun, 5 Jul 2026 19:39:44 -0600 Subject: [PATCH 3/3] Refactor --- src/main.cpp | 2 +- src/ui_font.h | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) delete mode 100644 src/ui_font.h diff --git a/src/main.cpp b/src/main.cpp index 8649ba0..03ad322 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -15,7 +15,6 @@ #include "imgui.h" #include "imgui_impl_glfw.h" #include "imgui_impl_opengl3.h" -#include "ui_font.h" #define GL_SILENCE_DEPRECATION #if defined(IMGUI_IMPL_OPENGL_ES2) #include @@ -204,6 +203,7 @@ int main(int argc, char** argv) #endif // DroidSans at logical px; do not multiply by main_scale - FontScaleDpi applies HiDPI. + constexpr float k_imgui_base_font_size_px = 13.0f; { #ifdef __EMSCRIPTEN__ ImFont* font = io.Fonts->AddFontFromFileTTF("/DroidSans.ttf", k_imgui_base_font_size_px); diff --git a/src/ui_font.h b/src/ui_font.h deleted file mode 100644 index 7148d80..0000000 --- a/src/ui_font.h +++ /dev/null @@ -1,6 +0,0 @@ -#pragma once - -#include "imgui.h" - -/// Pixel size passed to AddFontFromFileTTF for the default UI fonts (must match main.cpp). -inline constexpr float k_imgui_base_font_size_px = 13.0f;