Volume do not store angle of text any more.

(it is stored inside of volume transformation and si calculated on the fly)
This commit is contained in:
Filip Sykala - NTB T15p 2023-03-02 14:25:06 +01:00
parent e428dcc57e
commit 191f670dbd
9 changed files with 167 additions and 103 deletions

View file

@ -1264,15 +1264,17 @@ ExPolygons Emboss::text2shapes(FontFileWithCache &font_with_cache,
return result;
}
void Emboss::apply_transformation(const FontProp &font_prop,
Transform3d &transformation)
{
if (font_prop.angle.has_value()) {
double angle_z = *font_prop.angle;
void Emboss::apply_transformation(const FontProp &font_prop, Transform3d &transformation){
apply_transformation(font_prop.angle, font_prop.distance, transformation);
}
void Emboss::apply_transformation(const std::optional<float>& angle, const std::optional<float>& distance, Transform3d &transformation) {
if (angle.has_value()) {
double angle_z = *angle;
transformation *= Eigen::AngleAxisd(angle_z, Vec3d::UnitZ());
}
if (font_prop.distance.has_value()) {
Vec3d translate = Vec3d::UnitZ() * (*font_prop.distance);
if (distance.has_value()) {
Vec3d translate = Vec3d::UnitZ() * (*distance);
transformation.translate(translate);
}
}
@ -1583,7 +1585,7 @@ std::optional<float> Emboss::calc_up(const Transform3d &tr, double up_limit)
m.row(2) = normal;
double det = m.determinant();
return atan2(det, dot);
return -atan2(det, dot);
}
Transform3d Emboss::create_transformation_onto_surface(const Vec3d &position,

View file

@ -193,6 +193,7 @@ namespace Emboss
/// Z-rotation as angle to Y axis(FontProp::angle)</param>
/// <param name="transformation">In / Out transformation to modify by property</param>
void apply_transformation(const FontProp &font_prop, Transform3d &transformation);
void apply_transformation(const std::optional<float> &angle, const std::optional<float> &distance, Transform3d &transformation);
/// <summary>
/// Read information from naming table of font file

View file

@ -49,10 +49,12 @@ struct FontProp
// used for move over model surface
// When not set value is zero and is not stored
std::optional<float> distance; // [in mm]
// change up vector direction of font
// Angle of rotation around emboss direction (Z axis)
// It is calculate on the fly from volume world transformation
// only StyleManager keep actual value for comparision with style
// When not set value is zero and is not stored
std::optional<float> angle; // [in radians]
std::optional<float> angle; // [in radians] form -Pi to Pi
// Parameter for True Type Font collections
// Select index of font in collection

View file

@ -325,9 +325,13 @@ bool GLGizmoEmboss::on_mouse_for_rotation(const wxMouseEvent &mouse_event)
if (!m_dragging) return used;
if (mouse_event.Dragging()) {
auto &angle_opt = m_volume->text_configuration->style.prop.angle;
if (!m_rotate_start_angle.has_value())
m_rotate_start_angle = angle_opt.has_value() ? *angle_opt : 0.f;
if (!m_rotate_start_angle.has_value()) {
// when m_rotate_start_angle is not set mean it is not Dragging
// when angle_opt is not set mean angle is Zero
const std::optional<float> &angle_opt = m_style_manager.get_font_prop().angle;
m_rotate_start_angle = angle_opt.has_value() ? *angle_opt : 0.f;
}
double angle = m_rotate_gizmo.get_angle();
angle -= PI / 2; // Grabber is upward
@ -339,18 +343,15 @@ bool GLGizmoEmboss::on_mouse_for_rotation(const wxMouseEvent &mouse_event)
angle += *m_rotate_start_angle;
// move to range <-M_PI, M_PI>
priv::to_range_pi_pi(angle);
// propagate angle into property
angle_opt = static_cast<float>(angle);
// do not store zero
if (is_approx(*angle_opt, 0.f))
angle_opt.reset();
// set into activ style
assert(m_style_manager.is_active_font());
if (m_style_manager.is_active_font())
if (m_style_manager.is_active_font()) {
std::optional<float> angle_opt;
if (!is_approx(angle, 0.))
angle_opt = angle;
m_style_manager.get_font_prop().angle = angle_opt;
}
}
return used;
}
@ -361,9 +362,11 @@ bool GLGizmoEmboss::on_mouse_for_translate(const wxMouseEvent &mouse_event)
if (m_volume == nullptr)
return false;
std::optional<double> up_limit;
if (m_keep_up) up_limit = priv::up_limit;
const Camera &camera = wxGetApp().plater()->get_camera();
bool was_dragging = m_surface_drag.has_value();
bool res = on_mouse_surface_drag(mouse_event, camera, m_surface_drag, m_parent, m_raycast_manager);
bool res = on_mouse_surface_drag(mouse_event, camera, m_surface_drag, m_parent, m_raycast_manager, up_limit);
bool is_dragging = m_surface_drag.has_value();
// End with surface dragging?
@ -387,6 +390,17 @@ bool GLGizmoEmboss::on_mouse_for_translate(const wxMouseEvent &mouse_event)
else if (was_dragging && is_dragging) {
// update scale of selected volume --> should be approx the same
calculate_scale();
// Recalculate angle for GUI
if (!m_keep_up) {
const GLVolume *gl_volume = get_selected_gl_volume(m_parent.get_selection());
assert(gl_volume != nullptr);
assert(m_style_manager.is_active_font());
if (gl_volume == nullptr || !m_style_manager.is_active_font())
return res;
m_style_manager.get_style().prop.angle = calc_up(gl_volume->world_matrix(), priv::up_limit);
}
}
return res;
}
@ -693,6 +707,13 @@ void GLGizmoEmboss::on_stop_dragging()
// apply rotation
m_parent.do_rotate(L("Text-Rotate"));
// Re-Calculate current angle of up vector
const GLVolume *gl_volume = get_selected_gl_volume(m_parent.get_selection());
assert(m_style_manager.is_active_font());
assert(gl_volume != nullptr);
if (m_style_manager.is_active_font() && gl_volume != nullptr)
m_style_manager.get_font_prop().angle = calc_up(gl_volume->world_matrix(), priv::up_limit);
m_rotate_start_angle.reset();
// recalculate for surface cut
@ -723,32 +744,37 @@ GLGizmoEmboss::GuiCfg GLGizmoEmboss::create_gui_configuration()
int count_letter_M_in_input = 12;
cfg.input_width = letter_m_size.x * count_letter_M_in_input;
GuiCfg::Translations &tr = cfg.translations;
tr.font = _u8L("Font");
tr.size = _u8L("Height");
tr.depth = _u8L("Depth");
tr.font = _u8L("Font");
tr.height = _u8L("Height");
tr.depth = _u8L("Depth");
float max_text_width = std::max({
ImGui::CalcTextSize(tr.font.c_str()).x,
ImGui::CalcTextSize(tr.size.c_str()).x,
ImGui::CalcTextSize(tr.height.c_str()).x,
ImGui::CalcTextSize(tr.depth.c_str()).x});
cfg.indent = static_cast<float>(cfg.icon_width);
cfg.input_offset = style.WindowPadding.x + cfg.indent + max_text_width + space;
tr.use_surface = _u8L("Use surface");
tr.char_gap = _u8L("Char gap");
tr.line_gap = _u8L("Line gap");
tr.boldness = _u8L("Boldness");
tr.italic = _u8L("Skew ratio");
tr.surface_distance = _u8L("Z-move");
tr.angle = _u8L("Z-rot");
tr.collection = _u8L("Collection");
tr.use_surface = _u8L("Use surface");
tr.char_gap = _u8L("Char gap");
tr.line_gap = _u8L("Line gap");
tr.boldness = _u8L("Boldness");
tr.skew_ration = _u8L("Skew ratio");
tr.from_surface = _u8L("From surface");
tr.rotation = _u8L("Rotation");
tr.keep_up = _u8L("Keep Up");
tr.collection = _u8L("Collection");
float max_advanced_text_width = std::max({
ImGui::CalcTextSize(tr.use_surface.c_str()).x,
ImGui::CalcTextSize(tr.char_gap.c_str()).x,
ImGui::CalcTextSize(tr.line_gap.c_str()).x,
ImGui::CalcTextSize(tr.boldness.c_str()).x,
ImGui::CalcTextSize(tr.italic.c_str()).x,
ImGui::CalcTextSize(tr.surface_distance.c_str()).x,
ImGui::CalcTextSize(tr.angle.c_str()).x,
ImGui::CalcTextSize(tr.skew_ration.c_str()).x,
ImGui::CalcTextSize(tr.from_surface.c_str()).x,
ImGui::CalcTextSize(tr.rotation.c_str()).x,
ImGui::CalcTextSize(tr.keep_up.c_str()).x,
ImGui::CalcTextSize(tr.collection.c_str()).x });
cfg.advanced_input_offset = max_advanced_text_width
+ 3 * space + cfg.indent;
@ -774,9 +800,9 @@ GLGizmoEmboss::GuiCfg GLGizmoEmboss::create_gui_configuration()
+ 2 * (cfg.icon_width + space);
cfg.minimal_window_size = ImVec2(window_width, window_height);
// 6 = charGap, LineGap, Bold, italic, surfDist, angle
// 9 = useSurface, charGap, lineGap, bold, italic, surfDist, rotation, keepUp, textFaceToCamera
// 4 = 1px for fix each edit image of drag float
float advance_height = input_height * 8 + 8;
float advance_height = input_height * 9 + 8;
cfg.minimal_window_size_with_advance =
ImVec2(cfg.minimal_window_size.x,
cfg.minimal_window_size.y + advance_height);
@ -867,9 +893,17 @@ void GLGizmoEmboss::set_default_text(){ m_text = _u8L("Embossed text"); }
void GLGizmoEmboss::set_volume_by_selection()
{
const Selection &selection = m_parent.get_selection();
ModelVolume *vol = get_selected_volume(selection);
// is same volume selected?
if (vol != nullptr && vol->id() == m_volume_id)
const GLVolume *gl_volume = get_selected_gl_volume(selection);
if (gl_volume == nullptr)
return reset_volume();
const ModelObjectPtrs &objects = selection.get_model()->objects;
ModelVolume *volume =get_model_volume(*gl_volume, objects);
if (volume == nullptr)
return reset_volume();
// is same volume as actual selected?
if (volume->id() == m_volume_id)
return;
// for changed volume notification is NOT valid
@ -877,30 +911,14 @@ void GLGizmoEmboss::set_volume_by_selection()
// Do not use focused input value when switch volume(it must swith value)
if (m_volume != nullptr &&
m_volume != vol) // when update volume it changed id BUT not pointer
m_volume != volume) // when update volume it changed id BUT not pointer
ImGuiWrapper::left_inputs();
if (vol == nullptr) {
reset_volume();
return;
}
// Is selected volume text volume?
const std::optional<TextConfiguration>& tc_opt = volume->text_configuration;
if (!tc_opt.has_value())
return reset_volume();
// is select embossed volume?
set_volume(vol);
// Check if user changed up vector by rotation or scale out of emboss gizmo
if (m_volume != nullptr) {
Transform3d world = selection.get_first_volume()->world_matrix();
std::optional<float> angle = calc_up(world, priv::up_limit);
m_volume->text_configuration->style.prop.angle = angle;
}
}
bool GLGizmoEmboss::set_volume(ModelVolume *volume)
{
assert(volume != nullptr);
const std::optional<TextConfiguration> tc_opt = volume->text_configuration;
if (!tc_opt.has_value()) return false;
const TextConfiguration &tc = *tc_opt;
const EmbossStyle &style = tc.style;
@ -992,7 +1010,7 @@ bool GLGizmoEmboss::set_volume(ModelVolume *volume)
// The change of volume could show or hide part with setter on volume type
if (m_volume == nullptr ||
get_model_volume(m_volume_id, m_parent.get_selection().get_model()->objects) == nullptr ||
get_model_volume(m_volume_id, objects) == nullptr ||
(m_volume->get_object()->volumes.size() == 1) !=
(volume->get_object()->volumes.size() == 1)){
m_should_set_minimal_windows_size = true;
@ -1008,9 +1026,13 @@ bool GLGizmoEmboss::set_volume(ModelVolume *volume)
m_volume = volume;
m_volume_id = volume->id();
// Calculate current angle of up vector
assert(m_style_manager.is_active_font());
if (m_style_manager.is_active_font())
m_style_manager.get_font_prop().angle = calc_up(gl_volume->world_matrix(), priv::up_limit);
// calculate scale for height and depth inside of scaled object instance
calculate_scale();
return true;
calculate_scale();
}
void GLGizmoEmboss::reset_volume()
@ -1020,9 +1042,9 @@ void GLGizmoEmboss::reset_volume()
m_volume = nullptr;
m_volume_id.id = 0;
// TODO: check if it is neccessary to set default text
// Idea is to set default text when create object
set_default_text();
// No more need of current notification
remove_notification_not_valid_font();
}
void GLGizmoEmboss::calculate_scale() {
@ -2677,7 +2699,7 @@ void GLGizmoEmboss::draw_height(bool use_inch)
const float *stored = ((stored_style)? &stored_style->prop.size_in_mm : nullptr);
const char *size_format = ((use_inch) ? "%.2f in" : "%.1f mm");
const std::string revert_text_size = _u8L("Revert text size.");
const std::string& name = m_gui_cfg->translations.size;
const std::string& name = m_gui_cfg->translations.height;
if (rev_input_mm(name, value, stored, revert_text_size, 0.1f, 1.f, size_format, use_inch, m_scale_height))
if (set_height())
process();
@ -2915,7 +2937,7 @@ void GLGizmoEmboss::draw_advanced()
// input italic
auto def_skew = stored_style ?
&stored_style->prop.skew : nullptr;
if (rev_slider(tr.italic, font_prop.skew, def_skew, _u8L("Undo letter's skew"),
if (rev_slider(tr.skew_ration, font_prop.skew, def_skew, _u8L("Undo letter's skew"),
priv::limits.skew.gui.min, priv::limits.skew.gui.max, "%.2f", _L("Italic strength ratio"))){
if (!priv::Limits::apply(font_prop.skew, priv::limits.skew.values) ||
!m_volume->text_configuration->style.prop.skew.has_value() ||
@ -2949,7 +2971,7 @@ void GLGizmoEmboss::draw_advanced()
}
min_distance *= ObjectManipulation::mm_to_in;
max_distance *= ObjectManipulation::mm_to_in;
if (rev_slider(tr.surface_distance, distance_inch, def_distance, undo_move_tooltip, min_distance, max_distance, "%.3f in", move_tooltip)) {
if (rev_slider(tr.from_surface, distance_inch, def_distance, undo_move_tooltip, min_distance, max_distance, "%.3f in", move_tooltip)) {
if (distance_inch.has_value()) {
font_prop.distance = *distance_inch * ObjectManipulation::in_to_mm;
} else {
@ -2958,7 +2980,7 @@ void GLGizmoEmboss::draw_advanced()
is_moved = true;
}
} else {
if (rev_slider(tr.surface_distance, distance, def_distance, undo_move_tooltip,
if (rev_slider(tr.from_surface, distance, def_distance, undo_move_tooltip,
min_distance, max_distance, "%.2f mm", move_tooltip)) is_moved = true;
}
@ -2971,33 +2993,50 @@ void GLGizmoEmboss::draw_advanced()
// slider for Clock-wise angle in degress
// stored angle is optional CCW and in radians
std::optional<float> &angle = font_prop.angle;
float prev_angle = angle.has_value() ? *angle : .0f;
// Convert stored value to degress
// minus create clock-wise roation from CCW
float angle_deg = angle.has_value() ?
static_cast<float>(-(*angle) * 180 / M_PI) : .0f;
const std::optional<float> &angle_opt = m_style_manager.get_font_prop().angle;
float angle = angle_opt.has_value() ? *angle_opt: 0.f;
float angle_deg = static_cast<float>(-angle * 180 / M_PI);
float def_angle_deg_val =
(!stored_style || !stored_style->prop.angle.has_value()) ?
0.f : (*stored_style->prop.angle * -180 / M_PI);
float* def_angle_deg = stored_style ?
&def_angle_deg_val : nullptr;
if (rev_slider(tr.angle, angle_deg, def_angle_deg, _u8L("Undo rotation"),
if (rev_slider(tr.rotation, angle_deg, def_angle_deg, _u8L("Undo rotation"),
priv::limits.angle.min, priv::limits.angle.max, u8"%.2f °",
_L("Rotate text Clock-wise."))) {
// convert back to radians and CCW
angle = -angle_deg * M_PI / 180.0;
priv::to_range_pi_pi(*angle);
if (is_approx(*angle, 0.f))
angle.reset();
float angle_rad = static_cast<float>(-angle_deg * M_PI / 180.0);
priv::to_range_pi_pi(angle_rad);
float diff_angle = angle_rad - angle;
do_rotate(diff_angle);
// calc angle after rotation
const GLVolume *gl_volume = get_selected_gl_volume(m_parent.get_selection());
assert(gl_volume != nullptr);
assert(m_style_manager.is_active_font());
if (m_style_manager.is_active_font() && gl_volume != nullptr)
m_style_manager.get_font_prop().angle = calc_up(gl_volume->world_matrix(), priv::up_limit);
m_volume->text_configuration->style.prop.angle = angle;
float act_angle = angle.has_value() ? *angle : .0f;
do_rotate(act_angle - prev_angle);
// recalculate for surface cut
if (font_prop.use_surface) process();
if (font_prop.use_surface)
process();
}
ImGui::Text("%s", tr.keep_up.c_str());
ImGui::SameLine(m_gui_cfg->advanced_input_offset);
if (ImGui::Checkbox("##keep_up", &m_keep_up)) {
if (m_keep_up) {
// copy angle to volume
m_volume->text_configuration->style.prop.angle = font_prop.angle;
}
}
if (ImGui::IsItemHovered())
ImGui::SetTooltip("%s", _u8L("Keep text orientation during surface dragging.\nNot stable between horizontal and vertical alignment.").c_str());
// when more collection add selector
if (ff.font_file->infos.size() > 1) {
ImGui::Text("%s", tr.collection.c_str());

View file

@ -83,8 +83,6 @@ private:
void set_default_text();
void set_volume_by_selection();
// load text configuration from volume into gizmo
bool set_volume(ModelVolume *volume);
void reset_volume();
// create volume from text - main functionality
@ -198,7 +196,7 @@ private:
struct Translations
{
std::string font;
std::string size;
std::string height;
std::string depth;
std::string use_surface;
@ -206,9 +204,10 @@ private:
std::string char_gap;
std::string line_gap;
std::string boldness;
std::string italic;
std::string surface_distance;
std::string angle;
std::string skew_ration;
std::string from_surface;
std::string rotation;
std::string keep_up;
std::string collection;
};
Translations translations;
@ -286,7 +285,10 @@ private:
static void init_truncated_names(Facenames &face_names, float max_width);
// Text to emboss
std::string m_text;
std::string m_text; // Sequence of Unicode UTF8 symbols
// When true keep up vector otherwise relative rotation
bool m_keep_up = true;
// current selected volume
// NOTE: Be carefull could be uninitialized (removed from Model)

View file

@ -502,6 +502,9 @@ void UpdateJob::update_volume(ModelVolume *volume,
volume->calculate_convex_hull();
volume->get_object()->invalidate_bounding_box();
volume->text_configuration = text_configuration;
// discard information about rotation, should not be stored in volume
volume->text_configuration->style.prop.angle.reset();
GUI_App &app = wxGetApp(); // may be move to input
GLCanvas3D *canvas = app.plater()->canvas3D();
@ -615,6 +618,10 @@ void priv::create_volume(
volume->name = data.volume_name; // copy
volume->text_configuration = data.text_configuration; // copy
// discard information about rotation, should not be stored in volume
volume->text_configuration->style.prop.angle.reset();
volume->set_transformation(trmat);
// update printable state on canvas

View file

@ -3366,9 +3366,11 @@ void Selection::transform_volume_relative(GLVolume& volume, const VolumeCache& v
ModelVolume *get_selected_volume(const Selection &selection)
{
const GLVolume *vol_gl = get_selected_gl_volume(selection);
const GLVolume *gl_volume = get_selected_gl_volume(selection);
if (gl_volume == nullptr)
return nullptr;
const ModelObjectPtrs &objects = selection.get_model()->objects;
return get_model_volume(*vol_gl, objects);
return get_model_volume(*gl_volume, objects);
}
const GLVolume *get_selected_gl_volume(const Selection &selection)

View file

@ -69,7 +69,8 @@ bool on_mouse_surface_drag(const wxMouseEvent &mouse_event,
const Camera &camera,
std::optional<SurfaceDrag> &surface_drag,
GLCanvas3D &canvas,
RaycastManager &raycast_manager)
RaycastManager &raycast_manager,
std::optional<double> up_limit)
{
// Fix when leave window during dragging
// Fix when click right button
@ -153,7 +154,10 @@ bool on_mouse_surface_drag(const wxMouseEvent &mouse_event,
Transform3d instance_tr = instance->get_matrix();
Transform3d instance_tr_inv = instance_tr.inverse();
Transform3d world_tr = instance_tr * volume_tr;
surface_drag = SurfaceDrag{mouse_offset, world_tr, instance_tr_inv, gl_volume, condition};
std::optional<float> start_angle;
if (up_limit.has_value())
start_angle = Emboss::calc_up(world_tr, *up_limit);
surface_drag = SurfaceDrag{mouse_offset, world_tr, instance_tr_inv, gl_volume, condition, start_angle};
// disable moving with object by mouse
canvas.enable_moving(false);
@ -195,11 +199,11 @@ bool on_mouse_surface_drag(const wxMouseEvent &mouse_event,
Transform3d world_new = z_rotation * surface_drag->world;
auto world_new_linear = world_new.linear();
// Fix direction of up vector
{
// Fix direction of up vector to zero initial rotation
if(up_limit.has_value()){
Vec3d z_world = world_new_linear.col(2);
z_world.normalize();
Vec3d wanted_up = Emboss::suggest_up(z_world);
Vec3d wanted_up = Emboss::suggest_up(z_world, *up_limit);
Vec3d y_world = world_new_linear.col(1);
auto y_rotation = Eigen::Quaternion<double, Eigen::DontAlign>::FromTwoVectors(y_world, wanted_up);
@ -229,7 +233,7 @@ bool on_mouse_surface_drag(const wxMouseEvent &mouse_event,
volume_new = volume_new * (*tc.fix_3mf_tr);
// apply move in Z direction and rotation by up vector
Emboss::apply_transformation(tc.style.prop, volume_new);
Emboss::apply_transformation(surface_drag->start_angle, tc.style.prop.distance, volume_new);
}
// Update transformation for all instances

View file

@ -34,6 +34,9 @@ struct SurfaceDrag
// condition for raycaster
RaycastManager::AllowVolumes condition;
// initial rotation in Z axis of volume
std::optional<float> start_angle;
// Flag whether coordinate hit some volume
bool exist_hit = true;
};
@ -48,12 +51,14 @@ struct SurfaceDrag
/// <param name="canvas">Contain gl_volumes and selection</param>
/// <param name="raycast_manager">AABB trees for raycast in object
/// Refresh state inside of function </param>
/// <param name="up_limit">When set than use correction of up vector</param>
/// <returns>True when event is processed otherwise false</returns>
bool on_mouse_surface_drag(const wxMouseEvent &mouse_event,
const Camera &camera,
std::optional<SurfaceDrag> &surface_drag,
GLCanvas3D &canvas,
RaycastManager &raycast_manager);
RaycastManager &raycast_manager,
std::optional<double> up_limit = {});
/// <summary>
/// Calculate translation of volume onto surface of model