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
1 change: 1 addition & 0 deletions bugs.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
* Create sketch from face needs a temporary face select mode. When mode is changed it should go back to the previous mode. Other tools like chamfer need to do this also.
* Add sketch node should have the ability to allow the user to specify placement distance from another node.
* Ability to show/hide sketch nodes
* Underlay: edge calibration can introduce shear (non-orthogonal bitmap axes). The transform panel currently assumes center + half extents + rotation (orthogonal). Need a full six-parameter affine in the UI so users can adjust position, scale, rotation, and shear without losing calibration.

* For selection mode, not all of the current modes are useful.
* Scale mode broken.
Expand Down
151 changes: 124 additions & 27 deletions src/gui.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
// Must be here to prevent compiler warning
#include <GLFW/glfw3.h>

using namespace glm;

GUI* gui_instance = nullptr;

GUI::GUI()
Expand Down Expand Up @@ -627,7 +629,7 @@ void GUI::set_dist_edit(float dist, std::function<void(float, bool)>&& callback,
if (screen_coords.has_value())
m_dist_edit_loc = *screen_coords;
else
m_dist_edit_loc = ScreenCoords(glm::dvec2(ImGui::GetIO().MousePos.x, ImGui::GetIO().MousePos.y));
m_dist_edit_loc = ScreenCoords(dvec2(ImGui::GetIO().MousePos.x, ImGui::GetIO().MousePos.y));
}

m_dist_callback = std::move(callback);
Expand Down Expand Up @@ -713,7 +715,7 @@ void GUI::set_angle_edit(float angle, std::function<void(float, bool)>&& callbac
if (screen_coords.has_value())
m_angle_edit_loc = *screen_coords;
else
m_angle_edit_loc = ScreenCoords(glm::dvec2(ImGui::GetIO().MousePos.x, ImGui::GetIO().MousePos.y));
m_angle_edit_loc = ScreenCoords(dvec2(ImGui::GetIO().MousePos.x, ImGui::GetIO().MousePos.y));
}

m_angle_callback = std::move(callback);
Expand Down Expand Up @@ -1155,59 +1157,86 @@ void GUI::sketch_underlay_panel_settings_(const std::shared_ptr<Sketch>& sk)
const bool pick_y = m_underlay_calib_phase == Underlay_calib_phase::PickY1 ||
m_underlay_calib_phase == Underlay_calib_phase::PickY2 ||
m_underlay_calib_phase == Underlay_calib_phase::AwaitDistY;
const bool pick_datum = m_underlay_calib_phase == Underlay_calib_phase::PickDatumO ||
m_underlay_calib_phase == Underlay_calib_phase::PickDatumU;

if (pick_x || pick_y)
if (pick_x || pick_y || pick_datum)
{
const char* hint = "";
switch (m_underlay_calib_phase)
{
case Underlay_calib_phase::PickX1:
hint = "Click first point: corner at bitmap (0,0).";
hint = "First click: bitmap corner (0,0) in the current underlay transform (sliders / rotation).";
break;
case Underlay_calib_phase::PickX2:
hint = "Click second point: end of width (bitmap +U), like a line edge.";
hint = "Second click: along bitmap +U from that corner; calibration refines scale from this placement.";
break;
case Underlay_calib_phase::AwaitDistX:
hint = "Enter the drawing distance for X (dimension popup). Y length is set from image aspect (H:W).";
hint =
"Enter the drawing distance for X (dimension popup). If Y is not calibrated yet, its scale still follows "
"image aspect.";
break;
case Underlay_calib_phase::PickY1:
hint = "Click first point along image height (+V).";
hint = "First click: along bitmap height (+V) using the current underlay transform.";
break;
case Underlay_calib_phase::PickY2:
hint = "Click second point: end of height (+V).";
hint = "Second click: farther along +V; calibration refines Y from this placement.";
break;
case Underlay_calib_phase::AwaitDistY:
hint = "Enter the drawing distance for Y (dimension popup).";
break;
case Underlay_calib_phase::PickDatumO:
hint = "Datum: first click places bitmap corner (0,0) on the sketch plane (underlay origin).";
break;
case Underlay_calib_phase::PickDatumU:
hint = "Datum: second click sets direction along bitmap +U from that origin; half width and height are kept.";
break;
default:
break;
}
ImGui::TextColored(ImVec4(1.0f, 0.82f, 0.25f, 1.0f), "%s", hint);
}

ImGui::BeginDisabled(!sk_is_cur || pick_y);
ImGui::BeginDisabled(!sk_is_cur || pick_y || pick_datum);
if (ImGui::Button("Set X from edge..."))
begin_underlay_calib_set_x_(sk);

ImGui::EndDisabled();
if (m_show_tool_tips && ImGui::IsItemHovered())
ImGui::SetTooltip(
"Two clicks along width (+U), then type the real drawing distance (same units as sketch dimensions). "
"Y is set from image aspect until you use Set Y.");
"You can use Set Y before or after; until both are set, the other axis still follows default aspect.");

ImGui::SameLine();
ImGui::BeginDisabled(!sk_is_cur || !m_underlay_calib_have_x || pick_x);
ImGui::BeginDisabled(!sk_is_cur || pick_x || pick_datum);
if (ImGui::Button("Set Y from edge..."))
begin_underlay_calib_set_y_(sk);

ImGui::EndDisabled();
if (m_show_tool_tips && ImGui::IsItemHovered())
ImGui::SetTooltip("After Set X: two clicks along height (+V), then enter the drawing distance for Y.");
ImGui::SetTooltip(
"Two clicks along height (+V), then enter the drawing distance for Y. Order vs. Set X does not matter.");

ImGui::BeginDisabled(!sk_is_cur || pick_x || pick_y);
if (ImGui::Button("Define underlay datum..."))
begin_underlay_calib_define_datum_(sk);

ImGui::EndDisabled();
if (m_show_tool_tips && ImGui::IsItemHovered())
ImGui::SetTooltip(
"Two picks on the sketch plane: first sets bitmap corner (0,0); second sets the +U axis direction. "
"Keeps current half width and height; aligns the image to your sketch datum.");
}

ImGui::Separator();
ImGui::TextUnformatted("Transform (sketch plane, vs. current view)");
{
const bool ul_ortho = sk->underlay_axes_orthogonal();
if (!ul_ortho)
ImGui::TextWrapped(
"Edge calibration produced shear (bitmap U and V are not perpendicular). These sliders only describe an "
"orthogonal transform; they stay off so they cannot overwrite your calibrated affine.");

const ImGuiIO& io = ImGui::GetIO();
double min_u = 0., min_v = 0., max_u = 1., max_v = 1.;
const bool have_view =
Expand Down Expand Up @@ -1235,7 +1264,12 @@ void GUI::sketch_underlay_panel_settings_(const std::shared_ptr<Sketch>& sk)
double rot_max = 180.0;

auto apply_ul_xform = [&]()
{ sk->underlay_set_center_extents_rotation(m_ul_cx, m_ul_cy, m_ul_hw, m_ul_hh, m_ul_rot); };
{
if (!sk->underlay_axes_orthogonal())
return;

sk->underlay_set_center_extents_rotation(dvec2(m_ul_cx, m_ul_cy), dvec2(m_ul_hw, m_ul_hh), m_ul_rot);
};

auto transform_slider = [&](const char* label, ImGuiDataType type, void* p_data, const void* p_min,
const void* p_max, const char* format, ImGuiSliderFlags flags = 0)
Expand All @@ -1248,6 +1282,21 @@ void GUI::sketch_underlay_panel_settings_(const std::shared_ptr<Sketch>& sk)
apply_ul_xform();
};

auto transform_input_double = [&](const char* label, double* p_data, double v_min, double v_max,
const char* format)
{
const bool changed = ImGui::InputDouble(label, p_data, 0.0, 0.0, format);
if (ImGui::IsItemActivated())
m_view->push_undo_snapshot();

if (changed)
{
*p_data = std::clamp(*p_data, v_min, v_max);
apply_ul_xform();
}
};

ImGui::BeginDisabled(!ul_ortho);
// Labels match typical sketch axes on screen: "Center X" drives plane Y (gp_Pnt2d::Y / view v), "Center Y" plane X (u).
transform_slider("Center X", ImGuiDataType_Double, &m_ul_cy, &min_v, &max_v, "%.4f", ImGuiSliderFlags_ClampOnInput);
transform_slider("Center Y", ImGuiDataType_Double, &m_ul_cx, &min_u, &max_u, "%.4f", ImGuiSliderFlags_ClampOnInput);
Expand All @@ -1257,6 +1306,8 @@ void GUI::sketch_underlay_panel_settings_(const std::shared_ptr<Sketch>& sk)
ImGuiSliderFlags_ClampOnInput | ImGuiSliderFlags_Logarithmic);
transform_slider("Rotation (deg)", ImGuiDataType_Double, &m_ul_rot, &rot_min, &rot_max, "%.1f",
ImGuiSliderFlags_ClampOnInput);
transform_input_double("Rotation value (deg)", &m_ul_rot, rot_min, rot_max, "%.4f");
ImGui::EndDisabled();
}
}

Expand All @@ -1267,7 +1318,6 @@ void GUI::cancel_underlay_calib_()

m_underlay_calib_sketch_wk.reset();

m_underlay_calib_have_x = false;
m_underlay_calib_axis_u = gp_Vec2d(0., 0.);
}

Expand All @@ -1283,40 +1333,56 @@ void GUI::begin_underlay_calib_set_x_(const std::shared_ptr<Sketch>& sk)
}

cancel_underlay_calib_();
sk->underlay_ui_params(m_ul_cx, m_ul_cy, m_ul_hw, m_ul_hh, m_ul_rot);
m_underlay_calib_sketch_wk = sk;
m_underlay_calib_phase = Underlay_calib_phase::PickX1;
show_message(
"Underlay X: click corner (0,0), then point along +U; you will enter the drawing distance for that span.");
"Underlay X: uses the current transform. Click bitmap corner (0,0), then along +U; then enter the distance.");
}

void GUI::begin_underlay_calib_set_y_(const std::shared_ptr<Sketch>& sk)
{
if (!sk || !sk->has_underlay())
return;

if (!m_underlay_calib_have_x)
if (m_view->curr_sketch_shared() != sk)
{
show_message("Use Set X from edge first (two points along image width).");
show_message("Make this sketch current in the sketch list, then try again.");
return;
}

cancel_underlay_calib_();
sk->underlay_ui_params(m_ul_cx, m_ul_cy, m_ul_hw, m_ul_hh, m_ul_rot);
m_underlay_calib_sketch_wk = sk;
m_underlay_calib_phase = Underlay_calib_phase::PickY1;
show_message(
"Underlay Y: uses the current transform. Click two points along +V; then enter the drawing distance.");
}

void GUI::begin_underlay_calib_define_datum_(const std::shared_ptr<Sketch>& sk)
{
if (!sk || !sk->has_underlay())
return;

if (m_view->curr_sketch_shared() != sk)
{
show_message("Make this sketch current in the sketch list, then try again.");
return;
}

cancel_underlay_calib_();
sk->underlay_ui_params(m_ul_cx, m_ul_cy, m_ul_hw, m_ul_hh, m_ul_rot);
m_underlay_calib_sketch_wk = sk;
m_underlay_calib_phase = Underlay_calib_phase::PickY1;
show_message("Underlay Y: click two points along +V, then enter the drawing distance for that span.");
m_underlay_calib_phase = Underlay_calib_phase::PickDatumO;
show_message("Datum: click where bitmap corner (0,0) should lie on the sketch plane.");
}

void GUI::underlay_calib_prompt_x_distance_(const std::shared_ptr<Sketch>& sk)
{
m_underlay_calib_phase = Underlay_calib_phase::AwaitDistX;
const double L_model = m_underlay_calib_x0.Distance(m_underlay_calib_x1);
const float dist_show = static_cast<float>(L_model / m_view->get_dimension_scale());
const ScreenCoords spos(glm::dvec2(ImGui::GetIO().MousePos.x, ImGui::GetIO().MousePos.y));
const ScreenCoords spos(dvec2(ImGui::GetIO().MousePos.x, ImGui::GetIO().MousePos.y));

std::weak_ptr<Sketch> wk = sk;
auto on_dist = [this, wk](float new_dist, bool is_final)
Expand Down Expand Up @@ -1354,10 +1420,11 @@ void GUI::underlay_calib_prompt_x_distance_(const std::shared_ptr<Sketch>& sk)
s->underlay_ui_params(m_ul_cx, m_ul_cy, m_ul_hw, m_ul_hh, m_ul_rot);
m_underlay_panel_sketch = nullptr;

m_dist_callback = nullptr;
m_underlay_calib_phase = Underlay_calib_phase::None;
m_underlay_calib_have_x = true;
show_message("X scale applied to the picked segment. Use Set Y from edge for the vertical span and its distance.");
m_dist_callback = nullptr;
m_underlay_calib_phase = Underlay_calib_phase::None;
show_message(
"X distance applied to the picked segment. Use Set Y from edge for the vertical span if needed, or adjust "
"transforms.");
};

set_dist_edit(dist_show, std::move(on_dist), spos);
Expand All @@ -1368,7 +1435,7 @@ void GUI::underlay_calib_prompt_y_distance_(const std::shared_ptr<Sketch>& sk)
m_underlay_calib_phase = Underlay_calib_phase::AwaitDistY;
const double L_model = m_underlay_calib_y0.Distance(m_underlay_calib_y1);
const float dist_show = static_cast<float>(L_model / m_view->get_dimension_scale());
const ScreenCoords spos(glm::dvec2(ImGui::GetIO().MousePos.x, ImGui::GetIO().MousePos.y));
const ScreenCoords spos(dvec2(ImGui::GetIO().MousePos.x, ImGui::GetIO().MousePos.y));

std::weak_ptr<Sketch> wk = sk;
auto on_dist = [this, wk](float new_dist, bool is_final)
Expand Down Expand Up @@ -1408,7 +1475,9 @@ void GUI::underlay_calib_prompt_y_distance_(const std::shared_ptr<Sketch>& sk)

m_dist_callback = nullptr;
cancel_underlay_calib_();
show_message("Underlay X/Y calibration complete.");
show_message(
"Y distance applied to the picked segment. Use Set X from edge for the horizontal span if needed, or adjust "
"transforms.");
};

set_dist_edit(dist_show, std::move(on_dist), spos);
Expand Down Expand Up @@ -1487,6 +1556,34 @@ bool GUI::try_underlay_calib_click_(const ScreenCoords& screen_coords)
underlay_calib_prompt_y_distance_(sk);
return true;

case Underlay_calib_phase::PickDatumO:
m_underlay_calib_datum_o = *pt;
m_underlay_calib_phase = Underlay_calib_phase::PickDatumU;
show_message("Datum: click a second point along bitmap +U from that origin (direction only).");
return true;

case Underlay_calib_phase::PickDatumU:
if (too_short(m_underlay_calib_datum_o, *pt))
{
show_message("Datum direction segment too short.");
return true;
}

m_view->push_undo_snapshot();
if (!sk->underlay_set_datum_origin_and_u_direction(m_underlay_calib_datum_o, *pt))
{
m_view->pop_undo_snapshot();
show_message("Could not set underlay datum (degenerate direction or axes).");
cancel_underlay_calib_();
return true;
}

sk->underlay_ui_params(m_ul_cx, m_ul_cy, m_ul_hw, m_ul_hh, m_ul_rot);
m_underlay_panel_sketch = nullptr;
cancel_underlay_calib_();
show_message("Underlay datum set: origin at (0,0) and +U direction; half sizes unchanged.");
return true;

default:
return false;
}
Expand Down Expand Up @@ -2038,7 +2135,7 @@ void GUI::on_mouse_pos(const ScreenCoords& screen_coords)

void GUI::on_mouse_button(int button, int action, int mods)
{
const ScreenCoords screen_coords(glm::dvec2(ImGui::GetIO().MousePos.x, ImGui::GetIO().MousePos.y));
const ScreenCoords screen_coords(dvec2(ImGui::GetIO().MousePos.x, ImGui::GetIO().MousePos.y));

if (button == GLFW_MOUSE_BUTTON_LEFT && action == GLFW_PRESS && mods == 0)
if (try_underlay_calib_click_(screen_coords))
Expand Down
8 changes: 6 additions & 2 deletions src/gui.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,10 @@ enum class Underlay_calib_phase : std::uint8_t
AwaitDistX,
PickY1,
PickY2,
AwaitDistY
AwaitDistY,
/// Two clicks: bitmap corner (0,0), then a point along bitmap +U (datum / origin on plane).
PickDatumO,
PickDatumU
};

/// One entry under File > Examples (menu label + path to `.ezy` on disk).
Expand Down Expand Up @@ -203,6 +206,7 @@ class GUI
bool try_underlay_calib_click_(const ScreenCoords& screen_coords);
void begin_underlay_calib_set_x_(const std::shared_ptr<Sketch>& sk);
void begin_underlay_calib_set_y_(const std::shared_ptr<Sketch>& sk);
void begin_underlay_calib_define_datum_(const std::shared_ptr<Sketch>& sk);
void underlay_calib_prompt_x_distance_(const std::shared_ptr<Sketch>& sk);
void underlay_calib_prompt_y_distance_(const std::shared_ptr<Sketch>& sk);
#if defined(__EMSCRIPTEN__)
Expand Down Expand Up @@ -326,12 +330,12 @@ class GUI
void* m_underlay_panel_sketch {nullptr};
Underlay_calib_phase m_underlay_calib_phase {Underlay_calib_phase::None};
std::weak_ptr<Sketch> m_underlay_calib_sketch_wk {};
bool m_underlay_calib_have_x {false};
gp_Pnt2d m_underlay_calib_x0 {};
gp_Pnt2d m_underlay_calib_x1 {};
gp_Vec2d m_underlay_calib_axis_u {}; // After X distance (model units)
gp_Pnt2d m_underlay_calib_y0 {};
gp_Pnt2d m_underlay_calib_y1 {};
gp_Pnt2d m_underlay_calib_datum_o {};
bool m_sketch_properties_open {false};
std::weak_ptr<Sketch> m_sketch_properties_sketch;
/// If set, next underlay import (menu or async) applies to this sketch; otherwise current sketch.
Expand Down
8 changes: 5 additions & 3 deletions src/gui_mode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
#include "sketch.h"
#include "utl_occt.h"

using namespace glm;

namespace
{

Expand Down Expand Up @@ -99,7 +101,7 @@ void GUI::on_key(int key, int scancode, int action, int mods)
if (action != GLFW_PRESS)
return;

const ScreenCoords screen_coords(glm::dvec2(ImGui::GetIO().MousePos.x, ImGui::GetIO().MousePos.y));
const ScreenCoords screen_coords(dvec2(ImGui::GetIO().MousePos.x, ImGui::GetIO().MousePos.y));

bool ctrl_pressed = (mods & GLFW_MOD_CONTROL) != 0;
if (ctrl_pressed)
Expand Down Expand Up @@ -523,7 +525,7 @@ void GUI::options_shape_polar_duplicate_mode_()

void GUI::on_key_rotate_mode_(int key)
{
const ScreenCoords screen_coords(glm::dvec2(ImGui::GetIO().MousePos.x, ImGui::GetIO().MousePos.y));
const ScreenCoords screen_coords(dvec2(ImGui::GetIO().MousePos.x, ImGui::GetIO().MousePos.y));

switch (key)
{
Expand Down Expand Up @@ -567,7 +569,7 @@ void GUI::on_key_rotate_mode_(int key)
void GUI::on_key_move_mode_(int key)
{
Move_options& opts = m_view->shp_move().get_opts();
const ScreenCoords screen_coords(glm::dvec2(ImGui::GetIO().MousePos.x, ImGui::GetIO().MousePos.y));
const ScreenCoords screen_coords(dvec2(ImGui::GetIO().MousePos.x, ImGui::GetIO().MousePos.y));

switch (key)
{
Expand Down
Loading
Loading