forked from 1b-agent/opencadstudio-upstream
Match Vertical geometric constraint behavior
1) VCONSTRAINT (and the new GCVERTICAL name) uses the axis constraint flow: "Select an object or [2Points] <2Points>:", 2Points mode with the reference's re-prompts and messages, and the same object types as Horizontal — line, polyline segment, text, MText, ellipse axis. 2) Object mode turns the object about its first vertex; a two-point relation across two objects slides the second object rigidly onto the axis. 3) Re-applying an axis constraint reports that the constraint already exists; Vertical follows the UCS Y direction. 4) The constraint bar draws Vertical as the reference's axis mark, with the point marker for a two-point relation. Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
1 parent
2b38f798c0
commit
26e458d6f7
6 files changed
+271
-75
No files matched your search
+88
-33
@@ -3245,22 +3245,30 @@ impl OpenCADStudio {
|
||||
}
|
||||
}
|
||||
CmdResult::AddHorizontalConstraint {
|
||||
kind,
|
||||
selection,
|
||||
direction,
|
||||
label,
|
||||
} => {
|
||||
use crate::command::HorizontalConstraintSelection;
|
||||
use crate::command::{EntityTransform, HorizontalConstraintSelection};
|
||||
use crate::modules::parametric::HorizontalConstraintCommand;
|
||||
use crate::scene::parametric_constraints::{
|
||||
nearest_parametric_point, nearest_parametric_point_on_entity, ConstraintKind,
|
||||
DirectionalAxis, ParametricRef,
|
||||
nearest_parametric_point, nearest_parametric_point_on_entity, resolve_point,
|
||||
ConstraintKind, DirectionalAxis, ParametricRef,
|
||||
};
|
||||
|
||||
let vertical = kind == ConstraintKind::Vertical;
|
||||
let axis = if vertical { "Vertical" } else { "Horizontal" };
|
||||
let scope = self.tabs[i].current_parametric_scope();
|
||||
let to_world = |point: glam::DVec3| {
|
||||
acadrust::types::Vector3::new(point.x, point.y, point.z)
|
||||
};
|
||||
let (refs, initial_fixed) = match selection {
|
||||
HorizontalConstraintSelection::Reference(reference) => {
|
||||
// The reference turns the object about its first
|
||||
// vertex: a line's start, the picked polyline
|
||||
// segment's first vertex, a text's insertion point,
|
||||
// an ellipse's center.
|
||||
let initial_fixed = match reference.directional_axis() {
|
||||
Some(DirectionalAxis::EllipseMajor | DirectionalAxis::EllipseMinor) => {
|
||||
vec![ParametricRef::center(reference.entity)]
|
||||
@@ -3268,7 +3276,10 @@ impl OpenCADStudio {
|
||||
Some(DirectionalAxis::TextBaseline) => {
|
||||
vec![ParametricRef::point(reference.entity, 0)]
|
||||
}
|
||||
None => Vec::new(),
|
||||
None => vec![ParametricRef::point(
|
||||
reference.entity,
|
||||
reference.segment_index().map_or(0, |index| index as i32),
|
||||
)],
|
||||
};
|
||||
(vec![reference], initial_fixed)
|
||||
}
|
||||
@@ -3290,26 +3301,42 @@ impl OpenCADStudio {
|
||||
)
|
||||
}
|
||||
};
|
||||
let (Some(first_ref), Some(second_ref)) =
|
||||
(resolve(first), resolve(second))
|
||||
else {
|
||||
self.command_line.push_error(
|
||||
"Horizontal: select supported endpoints, centers, midpoints, or vertices.",
|
||||
);
|
||||
return Task::none();
|
||||
// A miss asks for that point again, keeping a good
|
||||
// first pick; the same point twice asks for another
|
||||
// second point — the reference's own re-prompts.
|
||||
let (first_ref, second_ref) = match (resolve(first), resolve(second)) {
|
||||
(Some(first_ref), Some(second_ref)) if first_ref != second_ref => {
|
||||
(first_ref, second_ref)
|
||||
}
|
||||
(first_ref, second_ref) => {
|
||||
let (message, keep_first) = if first_ref.is_none() {
|
||||
("No valid constraint point found.", None)
|
||||
} else if second_ref.is_none() {
|
||||
("No valid constraint point found.", Some(first))
|
||||
} else {
|
||||
(
|
||||
"The object or point is already selected. Select a different object or constraint point.",
|
||||
Some(first),
|
||||
)
|
||||
};
|
||||
let command = HorizontalConstraintCommand::resume(kind, keep_first);
|
||||
self.command_line.push_error(message);
|
||||
self.command_line
|
||||
.push_info(&crate::command::CadCommand::prompt(&command));
|
||||
self.tabs[i].active_cmd = Some(Box::new(command));
|
||||
self.tabs[i].snap_result = None;
|
||||
return Task::none();
|
||||
}
|
||||
};
|
||||
if first_ref == second_ref {
|
||||
self.command_line
|
||||
.push_error("Horizontal: select two different points.");
|
||||
return Task::none();
|
||||
}
|
||||
(vec![first_ref, second_ref], vec![first_ref])
|
||||
}
|
||||
};
|
||||
let axis_length = direction.x.hypot(direction.y);
|
||||
if axis_length <= 1.0e-12 {
|
||||
self.command_line
|
||||
.push_error("Horizontal: the current UCS X axis is not supported.");
|
||||
self.command_line.push_error(&format!(
|
||||
"{axis}: the current UCS {} axis is not supported.",
|
||||
if vertical { "Y" } else { "X" }
|
||||
));
|
||||
return Task::none();
|
||||
}
|
||||
let direction = acadrust::types::Vector3::new(
|
||||
@@ -3317,11 +3344,9 @@ impl OpenCADStudio {
|
||||
direction.y / axis_length,
|
||||
0.0,
|
||||
);
|
||||
if let Err(message) = self.tabs[i].scene.validate_parametric_constraint(
|
||||
ConstraintKind::Horizontal,
|
||||
&refs,
|
||||
None,
|
||||
) {
|
||||
if let Err(message) =
|
||||
self.tabs[i].scene.validate_parametric_constraint(kind, &refs, None)
|
||||
{
|
||||
self.command_line.push_error(message);
|
||||
return Task::none();
|
||||
}
|
||||
@@ -3329,18 +3354,12 @@ impl OpenCADStudio {
|
||||
.tabs[i]
|
||||
.scene
|
||||
.parametric_constraint_set(scope)
|
||||
.is_some_and(|set| {
|
||||
set.contains_axis_constraint(
|
||||
ConstraintKind::Horizontal,
|
||||
&refs,
|
||||
direction,
|
||||
)
|
||||
})
|
||||
.is_some_and(|set| set.contains_axis_constraint(kind, &refs, direction))
|
||||
{
|
||||
self.tabs[i].active_cmd = None;
|
||||
self.tabs[i].snap_result = None;
|
||||
self.command_line
|
||||
.push_error("Horizontal constraint is already applied.");
|
||||
.push_error("The constraint already exists on the selected objects.");
|
||||
return Task::none();
|
||||
}
|
||||
let constraints_before = self.tabs[i]
|
||||
@@ -3360,10 +3379,43 @@ impl OpenCADStudio {
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.record_undo_parametric_constraints_before(scope, constraints_before);
|
||||
// Two points on different objects: the reference slides the
|
||||
// second object rigidly onto the axis through the first
|
||||
// point instead of re-solving its shape, so move it first
|
||||
// and let the constraint then hold what already fits.
|
||||
if let [first_ref, second_ref] = refs.as_slice() {
|
||||
if first_ref.entity != second_ref.entity {
|
||||
let position = |reference: &ParametricRef| {
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.get_entity(reference.entity)
|
||||
.zip(reference.marker)
|
||||
.and_then(|(entity, marker)| resolve_point(entity, marker))
|
||||
};
|
||||
if let (Some(first_point), Some(second_point)) =
|
||||
(position(first_ref), position(second_ref))
|
||||
{
|
||||
let normal = (-direction.y, direction.x);
|
||||
let offset = (second_point.x - first_point.x) * normal.0
|
||||
+ (second_point.y - first_point.y) * normal.1;
|
||||
if offset.abs() > 1.0e-9 {
|
||||
self.tabs[i].scene.transform_entities(
|
||||
&[second_ref.entity],
|
||||
&EntityTransform::Translate(glam::DVec3::new(
|
||||
-offset * normal.0,
|
||||
-offset * normal.1,
|
||||
0.0,
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let id = self.tabs[i]
|
||||
.scene
|
||||
.parametric_constraint_set_mut(scope)
|
||||
.add_axis_constraint(ConstraintKind::Horizontal, refs, direction);
|
||||
.add_axis_constraint(kind, refs, direction);
|
||||
self.tabs[i].scene.note_parametric_constraint_applied(
|
||||
scope,
|
||||
id,
|
||||
@@ -3382,7 +3434,7 @@ impl OpenCADStudio {
|
||||
self.tabs[i].active_cmd = None;
|
||||
self.tabs[i].snap_result = None;
|
||||
self.command_line
|
||||
.push_output("Horizontal constraint applied.");
|
||||
.push_output(&format!("{axis} constraint applied."));
|
||||
self.refresh_properties();
|
||||
if let Some(pd) = pending {
|
||||
self.commit_undo_delta(i, pd);
|
||||
@@ -8741,6 +8793,7 @@ mod parametric_constraint_undo_tests {
|
||||
let direction = acadrust::types::Vector3::new(3.0, 4.0, 0.0).normalize();
|
||||
|
||||
let _ = app.apply_cmd_result(CmdResult::AddHorizontalConstraint {
|
||||
kind: crate::scene::parametric_constraints::ConstraintKind::Horizontal,
|
||||
selection: crate::command::HorizontalConstraintSelection::Reference(
|
||||
ParametricRef::whole(handle),
|
||||
),
|
||||
@@ -8798,6 +8851,7 @@ mod parametric_constraint_undo_tests {
|
||||
};
|
||||
|
||||
let _ = app.apply_cmd_result(CmdResult::AddHorizontalConstraint {
|
||||
kind: crate::scene::parametric_constraints::ConstraintKind::Horizontal,
|
||||
selection: crate::command::HorizontalConstraintSelection::Points(
|
||||
pick(first, glam::DVec3::ZERO),
|
||||
pick(second, glam::DVec3::new(8.0, 3.0, 0.0)),
|
||||
@@ -8827,6 +8881,7 @@ mod parametric_constraint_undo_tests {
|
||||
.add_entity(acadrust::EntityType::Ellipse(ellipse));
|
||||
|
||||
let _ = app.apply_cmd_result(CmdResult::AddHorizontalConstraint {
|
||||
kind: crate::scene::parametric_constraints::ConstraintKind::Horizontal,
|
||||
selection: crate::command::HorizontalConstraintSelection::Reference(
|
||||
ParametricRef::ellipse_minor_axis(handle),
|
||||
),
|
||||
|
||||
+30
-17
@@ -1377,18 +1377,34 @@ impl OpenCADStudio {
|
||||
}
|
||||
}
|
||||
|
||||
"GCHORIZONTAL" => {
|
||||
"GCHORIZONTAL" | "VCONSTRAINT" | "GCVERTICAL" => {
|
||||
use crate::command::{CmdResult, HorizontalConstraintSelection};
|
||||
use crate::modules::parametric::HorizontalConstraintCommand;
|
||||
use crate::scene::parametric_constraints::ConstraintKind;
|
||||
|
||||
// Vertical is Horizontal mirrored onto the working plane's Y
|
||||
// axis: same picks, same 2Points flow, same reference types.
|
||||
let vertical = cmd != "GCHORIZONTAL";
|
||||
let (kind, axis, label) = if vertical {
|
||||
(ConstraintKind::Vertical, "Vertical", "Vertical constraint")
|
||||
} else {
|
||||
(ConstraintKind::Horizontal, "Horizontal", "Horizontal constraint")
|
||||
};
|
||||
let new_command = || {
|
||||
if vertical {
|
||||
HorizontalConstraintCommand::vertical()
|
||||
} else {
|
||||
HorizontalConstraintCommand::new()
|
||||
}
|
||||
};
|
||||
let handles = self.tabs[i].scene.selected_handles_in_order();
|
||||
if handles.is_empty() {
|
||||
let command = HorizontalConstraintCommand::new();
|
||||
let command = new_command();
|
||||
self.command_line.push_info(&command.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(command));
|
||||
} else if handles.len() != 1 {
|
||||
self.command_line
|
||||
.push_error("Horizontal: select exactly one compatible object.");
|
||||
.push_error(&format!("{axis}: select exactly one compatible object."));
|
||||
} else {
|
||||
let handle = handles[0];
|
||||
let reference = self.tabs[i]
|
||||
@@ -1399,28 +1415,30 @@ impl OpenCADStudio {
|
||||
HorizontalConstraintCommand::preselected_reference(entity, handle)
|
||||
});
|
||||
if let Some(reference) = reference {
|
||||
let direction = self.tabs[i].ucs_xform().working_plane().x;
|
||||
let plane = self.tabs[i].ucs_xform().working_plane();
|
||||
let direction = if vertical { plane.y } else { plane.x };
|
||||
return Some(self.apply_cmd_result(CmdResult::AddHorizontalConstraint {
|
||||
kind,
|
||||
selection: HorizontalConstraintSelection::Reference(reference),
|
||||
direction: acadrust::types::Vector3::new(
|
||||
direction.x,
|
||||
direction.y,
|
||||
direction.z,
|
||||
),
|
||||
label: "Horizontal constraint",
|
||||
label,
|
||||
}));
|
||||
}
|
||||
self.tabs[i].scene.deselect_all();
|
||||
self.command_line.push_error(
|
||||
"Horizontal: select a line, straight polyline segment, text, MText, or an ellipse axis.",
|
||||
);
|
||||
let command = HorizontalConstraintCommand::new();
|
||||
self.command_line.push_error(&format!(
|
||||
"Invalid selection for {axis}. Select a line segment, polyline segment, text, MText, major or minor axis of ellipse or elliptical arc."
|
||||
));
|
||||
let command = new_command();
|
||||
self.command_line.push_info(&command.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(command));
|
||||
}
|
||||
}
|
||||
|
||||
"VCONSTRAINT" | "FXCONSTRAINT" => {
|
||||
"FXCONSTRAINT" => {
|
||||
let handles = self.tabs[i].scene.selected_handles_in_order();
|
||||
if handles.is_empty() {
|
||||
use crate::modules::draw::select::SelectObjectsCommand;
|
||||
@@ -1433,16 +1451,11 @@ impl OpenCADStudio {
|
||||
} else {
|
||||
use crate::command::CmdResult;
|
||||
use crate::scene::parametric_constraints::{ConstraintKind, ParametricRef};
|
||||
let (kind, label) = if cmd == "VCONSTRAINT" {
|
||||
(ConstraintKind::Vertical, "Vertical constraint")
|
||||
} else {
|
||||
(ConstraintKind::Fixed, "Fixed constraint")
|
||||
};
|
||||
return Some(self.apply_cmd_result(CmdResult::AddParametricConstraint {
|
||||
kind,
|
||||
kind: ConstraintKind::Fixed,
|
||||
refs: vec![ParametricRef::whole(handles[0])],
|
||||
driving_param: None,
|
||||
label,
|
||||
label: "Fixed constraint",
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
+6
-3
@@ -1504,10 +1504,12 @@ pub enum CmdResult {
|
||||
/// Undo-history label, e.g. `"Horizontal constraint"`.
|
||||
label: &'static str,
|
||||
},
|
||||
/// Adds a Horizontal relation against the UCS X direction captured when
|
||||
/// the command starts. The direction is persisted with the constraint so
|
||||
/// later edits and save/reopen do not silently fall back to world X.
|
||||
/// Adds a Horizontal or Vertical relation (`kind`) against the UCS X or
|
||||
/// Y direction captured when the command starts. The direction is
|
||||
/// persisted with the constraint so later edits and save/reopen do not
|
||||
/// silently fall back to the world axis.
|
||||
AddHorizontalConstraint {
|
||||
kind: crate::scene::parametric_constraints::ConstraintKind,
|
||||
selection: HorizontalConstraintSelection,
|
||||
direction: acadrust::types::Vector3,
|
||||
label: &'static str,
|
||||
@@ -2797,6 +2799,7 @@ mod constraint_registry_tests {
|
||||
"OCCONSTRAINT",
|
||||
"GCHORIZONTAL",
|
||||
"VCONSTRAINT",
|
||||
"GCVERTICAL",
|
||||
"PCONSTRAINT",
|
||||
"QCONSTRAINT",
|
||||
"GCPERPENDICULAR",
|
||||
|
||||
@@ -5,7 +5,7 @@ use crate::command::{
|
||||
CadCommand, CmdOption, CmdResult, CoincidentPick, HorizontalConstraintSelection, WorkingPlane,
|
||||
};
|
||||
use crate::scene::parametric_constraints::{
|
||||
directional_axis_endpoints, is_parametric_point_near, ParametricRef,
|
||||
directional_axis_endpoints, is_parametric_point_near, ConstraintKind, ParametricRef,
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
@@ -15,8 +15,10 @@ enum Step {
|
||||
SecondPoint(CoincidentPick),
|
||||
}
|
||||
|
||||
/// Interactive front end for the Horizontal geometric constraint.
|
||||
/// Interactive front end for the Horizontal and Vertical geometric
|
||||
/// constraints — one flow, mirrored across the working plane's X or Y axis.
|
||||
pub struct HorizontalConstraintCommand {
|
||||
kind: ConstraintKind,
|
||||
step: Step,
|
||||
picked_entity: Option<EntityType>,
|
||||
plane: WorkingPlane,
|
||||
@@ -24,13 +26,51 @@ pub struct HorizontalConstraintCommand {
|
||||
|
||||
impl HorizontalConstraintCommand {
|
||||
pub fn new() -> Self {
|
||||
Self::for_kind(ConstraintKind::Horizontal)
|
||||
}
|
||||
|
||||
pub fn vertical() -> Self {
|
||||
Self::for_kind(ConstraintKind::Vertical)
|
||||
}
|
||||
|
||||
fn for_kind(kind: ConstraintKind) -> Self {
|
||||
Self {
|
||||
kind,
|
||||
step: Step::ObjectOrTwoPoints,
|
||||
picked_entity: None,
|
||||
plane: WorkingPlane::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Re-enter the 2Points flow after the host rejected a pick: back at
|
||||
/// the first point, or at the second with `first` kept.
|
||||
pub fn resume(kind: ConstraintKind, first: Option<CoincidentPick>) -> Self {
|
||||
let mut command = Self::for_kind(kind);
|
||||
command.step = match first {
|
||||
Some(first) => Step::SecondPoint(first),
|
||||
None => Step::FirstPoint,
|
||||
};
|
||||
command
|
||||
}
|
||||
|
||||
pub fn kind(&self) -> ConstraintKind {
|
||||
self.kind
|
||||
}
|
||||
|
||||
fn axis(&self) -> &'static str {
|
||||
match self.kind {
|
||||
ConstraintKind::Vertical => "Vertical",
|
||||
_ => "Horizontal",
|
||||
}
|
||||
}
|
||||
|
||||
fn label(&self) -> &'static str {
|
||||
match self.kind {
|
||||
ConstraintKind::Vertical => "Vertical constraint",
|
||||
_ => "Horizontal constraint",
|
||||
}
|
||||
}
|
||||
|
||||
fn point_pick(handle: Option<Handle>, point: DVec3) -> CoincidentPick {
|
||||
CoincidentPick {
|
||||
handle,
|
||||
@@ -40,21 +80,22 @@ impl HorizontalConstraintCommand {
|
||||
}
|
||||
|
||||
fn direction(&self) -> acadrust::types::Vector3 {
|
||||
let direction = self.plane.x.normalize_or(DVec3::X);
|
||||
let direction = match self.kind {
|
||||
ConstraintKind::Vertical => self.plane.y.normalize_or(DVec3::Y),
|
||||
_ => self.plane.x.normalize_or(DVec3::X),
|
||||
};
|
||||
acadrust::types::Vector3::new(direction.x, direction.y, direction.z)
|
||||
}
|
||||
|
||||
fn invalid_object() -> CmdResult {
|
||||
CmdResult::ReportError(
|
||||
"Horizontal: select a line, straight polyline segment, text, MText, or an ellipse axis."
|
||||
.to_string(),
|
||||
)
|
||||
fn invalid_object(&self) -> CmdResult {
|
||||
CmdResult::ReportError(format!(
|
||||
"Invalid selection for {}. Select a line segment, polyline segment, text, MText, major or minor axis of ellipse or elliptical arc.",
|
||||
self.axis()
|
||||
))
|
||||
}
|
||||
|
||||
fn invalid_point() -> CmdResult {
|
||||
CmdResult::ReportError(
|
||||
"Horizontal: select an endpoint, center, midpoint, or polyline vertex.".to_string(),
|
||||
)
|
||||
CmdResult::ReportError("No valid constraint point found.".to_string())
|
||||
}
|
||||
|
||||
fn segment_is_straight(entity: &EntityType, index: usize) -> bool {
|
||||
@@ -153,27 +194,40 @@ impl HorizontalConstraintCommand {
|
||||
}
|
||||
}
|
||||
|
||||
fn finish_points(&self, first: CoincidentPick, second: CoincidentPick) -> CmdResult {
|
||||
fn finish_points(&mut self, first: CoincidentPick, second: CoincidentPick) -> CmdResult {
|
||||
// The reference refuses the same pick twice and asks again for the
|
||||
// second point (the host repeats this check on the resolved refs).
|
||||
if first.handle == second.handle && (first.point - second.point).length() <= 1.0e-9 {
|
||||
self.step = Step::SecondPoint(first);
|
||||
return CmdResult::ReportError(
|
||||
"The object or point is already selected. Select a different object or constraint point."
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
CmdResult::AddHorizontalConstraint {
|
||||
kind: self.kind,
|
||||
selection: HorizontalConstraintSelection::Points(first, second),
|
||||
direction: self.direction(),
|
||||
label: "Horizontal constraint",
|
||||
label: self.label(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CadCommand for HorizontalConstraintCommand {
|
||||
fn name(&self) -> &'static str {
|
||||
"GCHORIZONTAL"
|
||||
match self.kind {
|
||||
ConstraintKind::Vertical => "GCVERTICAL",
|
||||
_ => "GCHORIZONTAL",
|
||||
}
|
||||
}
|
||||
|
||||
fn prompt(&self) -> String {
|
||||
match self.step {
|
||||
Step::ObjectOrTwoPoints => {
|
||||
"GCHORIZONTAL Select an object or [2Points] <2Points>:".to_string()
|
||||
format!("{} Select an object or [2Points] <2Points>:", self.name())
|
||||
}
|
||||
Step::FirstPoint => "GCHORIZONTAL Select first point:".to_string(),
|
||||
Step::SecondPoint(_) => "GCHORIZONTAL Select second point:".to_string(),
|
||||
Step::FirstPoint => format!("{} Select first point:", self.name()),
|
||||
Step::SecondPoint(_) => format!("{} Select second point:", self.name()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,12 +291,13 @@ impl CadCommand for HorizontalConstraintCommand {
|
||||
match self.step {
|
||||
Step::ObjectOrTwoPoints => {
|
||||
let Some(reference) = Self::picked_reference(&entity, handle, point) else {
|
||||
return Self::invalid_object();
|
||||
return self.invalid_object();
|
||||
};
|
||||
CmdResult::AddHorizontalConstraint {
|
||||
kind: self.kind,
|
||||
selection: HorizontalConstraintSelection::Reference(reference),
|
||||
direction: self.direction(),
|
||||
label: "Horizontal constraint",
|
||||
label: self.label(),
|
||||
}
|
||||
}
|
||||
Step::FirstPoint | Step::SecondPoint(_) => {
|
||||
@@ -339,5 +394,5 @@ mod tests {
|
||||
}
|
||||
|
||||
inventory::submit!(crate::command::CommandRegistration {
|
||||
names: &["GCHORIZONTAL"]
|
||||
names: &["GCHORIZONTAL", "GCVERTICAL"]
|
||||
});
|
||||
@@ -863,6 +863,19 @@ impl ConstraintKind {
|
||||
}
|
||||
}
|
||||
|
||||
/// Vertical draws the reference bar's axis mark (`src/ui/overlay.rs`): the
|
||||
/// plain symbol for an object, this variant — with the point marker — for a
|
||||
/// two-point relation.
|
||||
pub(crate) const VERTICAL_POINTS_GLYPH: &str = "│·";
|
||||
|
||||
pub(crate) fn vertical_glyph_label(constraint: &ParametricConstraint) -> &'static str {
|
||||
if constraint.refs.len() == 2 {
|
||||
VERTICAL_POINTS_GLYPH
|
||||
} else {
|
||||
ConstraintKind::Vertical.glyph_symbol()
|
||||
}
|
||||
}
|
||||
|
||||
/// The full glyph text for one constraint: its symbol, plus the driving
|
||||
/// value for a dimensional kind (Distance/Angle/Radius).
|
||||
pub(crate) fn glyph_label(constraint: &ParametricConstraint) -> String {
|
||||
@@ -1659,7 +1672,9 @@ impl super::Scene {
|
||||
})
|
||||
.flat_map(|c| {
|
||||
let is_conflicting = set.conflicts.iter().any(|(id, _)| *id == c.id);
|
||||
let label = if show_values {
|
||||
let label = if c.kind == ConstraintKind::Vertical {
|
||||
vertical_glyph_label(c).to_string()
|
||||
} else if show_values {
|
||||
glyph_label(c)
|
||||
} else {
|
||||
c.kind.glyph_symbol().to_string()
|
||||
|
||||
+56
-1
@@ -34,11 +34,16 @@ fn is_compact_coincident_glyph(label: &str) -> bool {
|
||||
matches!(label, "≡" | "∈")
|
||||
}
|
||||
|
||||
/// Vertical's two axis-mark labels (`parametric_constraints::vertical_glyph_label`).
|
||||
fn is_vertical_glyph(label: &str) -> bool {
|
||||
label == "│" || label == crate::scene::parametric_constraints::VERTICAL_POINTS_GLYPH
|
||||
}
|
||||
|
||||
fn constraint_glyph_size(label: &str) -> Size {
|
||||
if is_compact_coincident_glyph(label) {
|
||||
return Size::new(COINCIDENT_GLYPH_SIZE, COINCIDENT_GLYPH_SIZE);
|
||||
}
|
||||
if label == "G²" {
|
||||
if label == "G²" || is_vertical_glyph(label) {
|
||||
let side = CONSTRAINT_GLYPH_SIZE + CONSTRAINT_GLYPH_PAD_Y * 2.0;
|
||||
return Size::new(side, side);
|
||||
}
|
||||
@@ -94,6 +99,49 @@ fn draw_concentric_constraint_glyph(
|
||||
frame.stroke(&canvas::Path::circle(center, 2.15), stroke);
|
||||
}
|
||||
|
||||
/// The reference bar's Vertical mark: a red datum line hatched on its left,
|
||||
/// the constrained direction beside it, plus the orange point marker for a
|
||||
/// two-point relation.
|
||||
fn draw_vertical_constraint_glyph(
|
||||
frame: &mut canvas::Frame,
|
||||
center: Point,
|
||||
text: Color,
|
||||
point_marker: bool,
|
||||
) {
|
||||
let datum = Color::from_rgb8(214, 76, 76);
|
||||
let datum_x = center.x - 2.5;
|
||||
let stroke = canvas::Stroke::default().with_color(datum).with_width(1.5);
|
||||
frame.stroke(
|
||||
&canvas::Path::line(
|
||||
Point::new(datum_x, center.y - 6.5),
|
||||
Point::new(datum_x, center.y + 6.5),
|
||||
),
|
||||
stroke.clone(),
|
||||
);
|
||||
for step in 0..3 {
|
||||
let y = center.y - 4.5 + step as f32 * 4.0;
|
||||
frame.stroke(
|
||||
&canvas::Path::line(Point::new(datum_x, y), Point::new(datum_x - 4.0, y + 3.5)),
|
||||
stroke.clone(),
|
||||
);
|
||||
}
|
||||
frame.stroke(
|
||||
&canvas::Path::line(
|
||||
Point::new(center.x + 2.5, center.y - 6.0),
|
||||
Point::new(center.x + 2.5, center.y + 6.0),
|
||||
),
|
||||
canvas::Stroke::default().with_color(text).with_width(1.4),
|
||||
);
|
||||
if point_marker {
|
||||
frame.stroke(
|
||||
&canvas::Path::rectangle(Point::new(center.x + 4.5, center.y - 2.3), Size::new(4.6, 4.6)),
|
||||
canvas::Stroke::default()
|
||||
.with_color(Color::from_rgb8(224, 130, 62))
|
||||
.with_width(1.2),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn constraint_glyph_box(
|
||||
anchor: Point,
|
||||
outward: [f32; 2],
|
||||
@@ -2020,6 +2068,13 @@ impl canvas::Program<Message> for SelectionCanvas {
|
||||
draw_smooth_constraint_glyph(&mut frame, glyph_center, fg);
|
||||
} else if label == "◎" {
|
||||
draw_concentric_constraint_glyph(&mut frame, glyph_center, fg);
|
||||
} else if is_vertical_glyph(label) {
|
||||
draw_vertical_constraint_glyph(
|
||||
&mut frame,
|
||||
glyph_center,
|
||||
fg,
|
||||
label == crate::scene::parametric_constraints::VERTICAL_POINTS_GLYPH,
|
||||
);
|
||||
} else {
|
||||
frame.fill_text(canvas::Text {
|
||||
content: label.clone(),
|
||||
|
||||
Reference in new issue
Block a user