forked from 1b-agent/opencadstudio-upstream
Merge branch 'HakanSeven12:main' into main
This commit is contained in:
54 files changed
+20761
-818
No files matched your search
@@ -294,7 +294,7 @@ impl OpenCADStudio {
|
||||
match req["op"].as_str().unwrap_or("") {
|
||||
"new" => {
|
||||
let i = self.active_tab;
|
||||
self.tabs[i].scene.clear();
|
||||
self.tabs[i].scene.reset_to_new_drawing();
|
||||
self.tabs[i].scene.material_base_dir = None;
|
||||
self.tabs[i].current_path = None;
|
||||
// The headless session starts on the welcome (Start) tab, which
|
||||
|
||||
+396
-2
@@ -3182,9 +3182,24 @@ impl OpenCADStudio {
|
||||
&refs,
|
||||
driving_param.as_ref(),
|
||||
) {
|
||||
self.tabs[i].active_cmd = None;
|
||||
let keep_smooth_selection = kind
|
||||
== crate::scene::parametric_constraints::ConstraintKind::Smooth
|
||||
&& self.tabs[i]
|
||||
.active_cmd
|
||||
.as_ref()
|
||||
.is_some_and(|command| command.name() == "GCSMOOTH");
|
||||
if !keep_smooth_selection {
|
||||
self.tabs[i].active_cmd = None;
|
||||
}
|
||||
self.tabs[i].snap_result = None;
|
||||
self.command_line.push_error(message);
|
||||
if keep_smooth_selection {
|
||||
if let Some(prompt) =
|
||||
self.tabs[i].active_cmd.as_ref().map(|command| command.prompt())
|
||||
{
|
||||
self.command_line.push_info(&prompt);
|
||||
}
|
||||
}
|
||||
return Task::none();
|
||||
}
|
||||
let scope = self.tabs[i].current_parametric_scope();
|
||||
@@ -3229,6 +3244,150 @@ impl OpenCADStudio {
|
||||
self.commit_undo_delta(i, pd);
|
||||
}
|
||||
}
|
||||
CmdResult::AddHorizontalConstraint {
|
||||
selection,
|
||||
direction,
|
||||
label,
|
||||
} => {
|
||||
use crate::command::HorizontalConstraintSelection;
|
||||
use crate::scene::parametric_constraints::{
|
||||
nearest_parametric_point, nearest_parametric_point_on_entity, ConstraintKind,
|
||||
DirectionalAxis, ParametricRef,
|
||||
};
|
||||
|
||||
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) => {
|
||||
let initial_fixed = match reference.directional_axis() {
|
||||
Some(DirectionalAxis::EllipseMajor | DirectionalAxis::EllipseMinor) => {
|
||||
vec![ParametricRef::center(reference.entity)]
|
||||
}
|
||||
Some(DirectionalAxis::TextBaseline) => {
|
||||
vec![ParametricRef::point(reference.entity, 0)]
|
||||
}
|
||||
None => Vec::new(),
|
||||
};
|
||||
(vec![reference], initial_fixed)
|
||||
}
|
||||
HorizontalConstraintSelection::Points(first, second) => {
|
||||
let resolve = |pick: crate::command::CoincidentPick| {
|
||||
if let Some(handle) = pick.handle {
|
||||
nearest_parametric_point_on_entity(
|
||||
&self.tabs[i].scene.document,
|
||||
scope,
|
||||
handle,
|
||||
to_world(pick.point),
|
||||
)
|
||||
} else {
|
||||
nearest_parametric_point(
|
||||
&self.tabs[i].scene.document,
|
||||
scope,
|
||||
to_world(pick.point),
|
||||
None,
|
||||
)
|
||||
}
|
||||
};
|
||||
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();
|
||||
};
|
||||
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.");
|
||||
return Task::none();
|
||||
}
|
||||
let direction = acadrust::types::Vector3::new(
|
||||
direction.x / axis_length,
|
||||
direction.y / axis_length,
|
||||
0.0,
|
||||
);
|
||||
if let Err(message) = self.tabs[i].scene.validate_parametric_constraint(
|
||||
ConstraintKind::Horizontal,
|
||||
&refs,
|
||||
None,
|
||||
) {
|
||||
self.command_line.push_error(message);
|
||||
return Task::none();
|
||||
}
|
||||
if self
|
||||
.tabs[i]
|
||||
.scene
|
||||
.parametric_constraint_set(scope)
|
||||
.is_some_and(|set| {
|
||||
set.contains_axis_constraint(
|
||||
ConstraintKind::Horizontal,
|
||||
&refs,
|
||||
direction,
|
||||
)
|
||||
})
|
||||
{
|
||||
self.tabs[i].active_cmd = None;
|
||||
self.tabs[i].snap_result = None;
|
||||
self.command_line
|
||||
.push_error("Horizontal constraint is already applied.");
|
||||
return Task::none();
|
||||
}
|
||||
let constraints_before = self.tabs[i]
|
||||
.scene
|
||||
.parametric_constraint_set(scope)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| {
|
||||
crate::scene::parametric_constraints::ParametricConstraintSet::new(scope)
|
||||
});
|
||||
let mut touched = Vec::new();
|
||||
for reference in &refs {
|
||||
if !touched.contains(&reference.entity) {
|
||||
touched.push(reference.entity);
|
||||
}
|
||||
}
|
||||
let pending = self.begin_undo(i, label, touched.len(), true);
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.record_undo_parametric_constraints_before(scope, constraints_before);
|
||||
let id = self.tabs[i]
|
||||
.scene
|
||||
.parametric_constraint_set_mut(scope)
|
||||
.add_axis_constraint(ConstraintKind::Horizontal, refs, direction);
|
||||
self.tabs[i].scene.note_parametric_constraint_applied(
|
||||
scope,
|
||||
id,
|
||||
self.constraint_bar_display,
|
||||
);
|
||||
let changes = touched
|
||||
.into_iter()
|
||||
.map(|handle| (handle, crate::scene::ChangeKind::Modified))
|
||||
.collect::<Vec<_>>();
|
||||
self.tabs[i].scene.bump_entities_with_parametric_policy(
|
||||
&changes,
|
||||
&initial_fixed,
|
||||
self.constraint_solve_mode,
|
||||
);
|
||||
self.tabs[i].dirty = true;
|
||||
self.tabs[i].active_cmd = None;
|
||||
self.tabs[i].snap_result = None;
|
||||
self.command_line
|
||||
.push_output("Horizontal constraint applied.");
|
||||
self.refresh_properties();
|
||||
if let Some(pd) = pending {
|
||||
self.commit_undo_delta(i, pd);
|
||||
}
|
||||
}
|
||||
CmdResult::AddPerpendicularConstraint {
|
||||
first,
|
||||
second,
|
||||
@@ -3280,7 +3439,7 @@ impl OpenCADStudio {
|
||||
.into_iter()
|
||||
.map(|handle| (handle, crate::scene::ChangeKind::Modified))
|
||||
.collect::<Vec<_>>();
|
||||
self.tabs[i].scene.bump_entities_with_parametric_policy(
|
||||
self.tabs[i].scene.bump_entities_with_initial_parametric_policy(
|
||||
&changes,
|
||||
&[first_fixed, second_start],
|
||||
true,
|
||||
@@ -3357,6 +3516,88 @@ impl OpenCADStudio {
|
||||
self.commit_undo_delta(i, pd);
|
||||
}
|
||||
}
|
||||
CmdResult::AddConcentricConstraint {
|
||||
first,
|
||||
second,
|
||||
label,
|
||||
} => {
|
||||
use crate::scene::parametric_constraints::ConstraintKind;
|
||||
|
||||
let refs = vec![first, second];
|
||||
if let Err(message) = self.tabs[i].scene.validate_parametric_constraint(
|
||||
ConstraintKind::Concentric,
|
||||
&refs,
|
||||
None,
|
||||
) {
|
||||
self.tabs[i].active_cmd = None;
|
||||
self.tabs[i].snap_result = None;
|
||||
self.command_line.push_error(message);
|
||||
return Task::none();
|
||||
}
|
||||
let scope = self.tabs[i].current_parametric_scope();
|
||||
let duplicate = self.tabs[i]
|
||||
.scene
|
||||
.parametric_constraint_set(scope)
|
||||
.is_some_and(|set| {
|
||||
set.constraints.iter().any(|constraint| {
|
||||
constraint.enabled
|
||||
&& constraint.kind == ConstraintKind::Concentric
|
||||
&& matches!(constraint.refs.as_slice(), [a, b]
|
||||
if (*a == first && *b == second)
|
||||
|| (*a == second && *b == first))
|
||||
})
|
||||
});
|
||||
if duplicate {
|
||||
self.tabs[i].active_cmd = None;
|
||||
self.tabs[i].snap_result = None;
|
||||
self.command_line
|
||||
.push_output("The Concentric constraint already exists.");
|
||||
return Task::none();
|
||||
}
|
||||
let constraints_before = self.tabs[i]
|
||||
.scene
|
||||
.parametric_constraint_set(scope)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| {
|
||||
crate::scene::parametric_constraints::ParametricConstraintSet::new(scope)
|
||||
});
|
||||
let touched = if first.entity == second.entity {
|
||||
vec![first.entity]
|
||||
} else {
|
||||
vec![first.entity, second.entity]
|
||||
};
|
||||
let pending = self.begin_undo(i, label, touched.len(), true);
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.record_undo_parametric_constraints_before(scope, constraints_before);
|
||||
let id = self.tabs[i].scene.parametric_constraint_set_mut(scope).add(
|
||||
ConstraintKind::Concentric,
|
||||
refs,
|
||||
None,
|
||||
);
|
||||
self.tabs[i].scene.note_parametric_constraint_applied(
|
||||
scope,
|
||||
id,
|
||||
self.constraint_bar_display,
|
||||
);
|
||||
let changes = touched
|
||||
.into_iter()
|
||||
.map(|handle| (handle, crate::scene::ChangeKind::Modified))
|
||||
.collect::<Vec<_>>();
|
||||
self.tabs[i].scene.bump_entities_with_initial_parametric_policy(
|
||||
&changes,
|
||||
&[first],
|
||||
true,
|
||||
);
|
||||
self.tabs[i].dirty = true;
|
||||
self.tabs[i].active_cmd = None;
|
||||
self.tabs[i].snap_result = None;
|
||||
self.command_line.push_output("Constraint applied.");
|
||||
self.refresh_properties();
|
||||
if let Some(pd) = pending {
|
||||
self.commit_undo_delta(i, pd);
|
||||
}
|
||||
}
|
||||
CmdResult::AddCoincidentConstraint {
|
||||
first,
|
||||
second,
|
||||
@@ -8450,6 +8691,159 @@ mod parametric_constraint_undo_tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn perpendicular_initial_solve_rotates_parallel_second_line_in_kernel() {
|
||||
let mut app = OpenCADStudio::new_for_test();
|
||||
let _ = app.automation_op(r#"{"op":"new"}"#);
|
||||
let first = add_line(&mut app, 0.0, 0.0, 10.0, 0.0);
|
||||
let second = add_line(&mut app, 20.0, 0.0, 30.0, 0.0);
|
||||
|
||||
let _ = app.apply_cmd_result(CmdResult::AddPerpendicularConstraint {
|
||||
first: ParametricRef::whole(first),
|
||||
second: ParametricRef::whole(second),
|
||||
first_fixed: ParametricRef::whole(first),
|
||||
second_start: ParametricRef::point(second, 0),
|
||||
label: "Perpendicular constraint",
|
||||
});
|
||||
|
||||
let line = |handle| match app.tabs[app.active_tab].scene.document.get_entity(handle) {
|
||||
Some(acadrust::EntityType::Line(line)) => line.clone(),
|
||||
other => panic!("expected a Line, got {other:?}"),
|
||||
};
|
||||
let fixed = line(first);
|
||||
let moving = line(second);
|
||||
assert_eq!(fixed.start, acadrust::types::Vector3::new(0.0, 0.0, 0.0));
|
||||
assert_eq!(fixed.end, acadrust::types::Vector3::new(10.0, 0.0, 0.0));
|
||||
assert!((moving.start - acadrust::types::Vector3::new(20.0, 0.0, 0.0)).length() < 1.0e-9);
|
||||
assert!((moving.length() - 10.0).abs() < 1.0e-7);
|
||||
assert!(
|
||||
(fixed.end - fixed.start)
|
||||
.dot(&(moving.end - moving.start))
|
||||
.abs()
|
||||
< 1.0e-7,
|
||||
"fixed={fixed:?}, moving={moving:?}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn horizontal_initial_solve_uses_the_captured_axis_in_kernel() {
|
||||
let mut app = OpenCADStudio::new_for_test();
|
||||
let _ = app.automation_op(r#"{"op":"new"}"#);
|
||||
let handle = add_line(&mut app, 0.0, 0.0, 5.0, 2.0);
|
||||
let (original_start, original_end, original_length) = match app.tabs[app.active_tab]
|
||||
.scene
|
||||
.document
|
||||
.get_entity(handle)
|
||||
{
|
||||
Some(acadrust::EntityType::Line(line)) => (line.start, line.end, line.length()),
|
||||
other => panic!("expected a Line, got {other:?}"),
|
||||
};
|
||||
let direction = acadrust::types::Vector3::new(3.0, 4.0, 0.0).normalize();
|
||||
|
||||
let _ = app.apply_cmd_result(CmdResult::AddHorizontalConstraint {
|
||||
selection: crate::command::HorizontalConstraintSelection::Reference(
|
||||
ParametricRef::whole(handle),
|
||||
),
|
||||
direction,
|
||||
label: "Horizontal constraint",
|
||||
});
|
||||
|
||||
let line = match app.tabs[app.active_tab].scene.document.get_entity(handle) {
|
||||
Some(acadrust::EntityType::Line(line)) => line,
|
||||
other => panic!("expected a Line, got {other:?}"),
|
||||
};
|
||||
let solved = (line.end - line.start).normalize();
|
||||
assert!(solved.cross(&direction).length() < 1.0e-7);
|
||||
assert!((line.length() - original_length).abs() < 1.0e-7);
|
||||
let constraint = &app.tabs[app.active_tab]
|
||||
.scene
|
||||
.parametric_constraint_set(ParametricScope::ModelSpace)
|
||||
.unwrap()
|
||||
.constraints[0];
|
||||
assert_eq!(constraint.axis_direction, Some(direction));
|
||||
|
||||
app.undo_steps(1);
|
||||
let line = match app.tabs[app.active_tab].scene.document.get_entity(handle) {
|
||||
Some(acadrust::EntityType::Line(line)) => line,
|
||||
other => panic!("expected a Line after undo, got {other:?}"),
|
||||
};
|
||||
assert_eq!((line.start, line.end), (original_start, original_end));
|
||||
assert_eq!(
|
||||
app.tabs[app.active_tab]
|
||||
.scene
|
||||
.parametric_constraint_set(ParametricScope::ModelSpace)
|
||||
.map(|set| set.constraints.len())
|
||||
.unwrap_or(0),
|
||||
0
|
||||
);
|
||||
|
||||
app.redo_steps(1);
|
||||
let line = match app.tabs[app.active_tab].scene.document.get_entity(handle) {
|
||||
Some(acadrust::EntityType::Line(line)) => line,
|
||||
other => panic!("expected a Line after redo, got {other:?}"),
|
||||
};
|
||||
assert!((line.end - line.start).normalize().cross(&direction).length() < 1.0e-7);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn horizontal_two_point_solve_keeps_the_first_point_fixed() {
|
||||
let mut app = OpenCADStudio::new_for_test();
|
||||
let _ = app.automation_op(r#"{"op":"new"}"#);
|
||||
let first = add_line(&mut app, 0.0, 0.0, 0.0, 5.0);
|
||||
let second = add_line(&mut app, 8.0, 3.0, 8.0, 7.0);
|
||||
let pick = |handle, point| crate::command::CoincidentPick {
|
||||
handle: Some(handle),
|
||||
point,
|
||||
whole_curve: false,
|
||||
};
|
||||
|
||||
let _ = app.apply_cmd_result(CmdResult::AddHorizontalConstraint {
|
||||
selection: crate::command::HorizontalConstraintSelection::Points(
|
||||
pick(first, glam::DVec3::ZERO),
|
||||
pick(second, glam::DVec3::new(8.0, 3.0, 0.0)),
|
||||
),
|
||||
direction: acadrust::types::Vector3::UNIT_X,
|
||||
label: "Horizontal constraint",
|
||||
});
|
||||
|
||||
let line = |handle| match app.tabs[app.active_tab].scene.document.get_entity(handle) {
|
||||
Some(acadrust::EntityType::Line(line)) => line,
|
||||
other => panic!("expected a Line, got {other:?}"),
|
||||
};
|
||||
assert_eq!(line(first).start, acadrust::types::Vector3::ZERO);
|
||||
assert!(line(second).start.y.abs() < 1.0e-8);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn horizontal_minor_axis_rotates_an_ellipse_around_its_center() {
|
||||
let mut app = OpenCADStudio::new_for_test();
|
||||
let _ = app.automation_op(r#"{"op":"new"}"#);
|
||||
let mut ellipse = acadrust::entities::Ellipse::default();
|
||||
ellipse.center = acadrust::types::Vector3::new(3.0, 7.0, 0.0);
|
||||
ellipse.major_axis = acadrust::types::Vector3::new(3.0, 4.0, 0.0);
|
||||
ellipse.minor_axis_ratio = 0.4;
|
||||
let handle = app.tabs[app.active_tab]
|
||||
.scene
|
||||
.add_entity(acadrust::EntityType::Ellipse(ellipse));
|
||||
|
||||
let _ = app.apply_cmd_result(CmdResult::AddHorizontalConstraint {
|
||||
selection: crate::command::HorizontalConstraintSelection::Reference(
|
||||
ParametricRef::ellipse_minor_axis(handle),
|
||||
),
|
||||
direction: acadrust::types::Vector3::UNIT_X,
|
||||
label: "Horizontal constraint",
|
||||
});
|
||||
|
||||
let ellipse = match app.tabs[app.active_tab].scene.document.get_entity(handle) {
|
||||
Some(acadrust::EntityType::Ellipse(ellipse)) => ellipse,
|
||||
other => panic!("expected an Ellipse, got {other:?}"),
|
||||
};
|
||||
assert!((ellipse.center - acadrust::types::Vector3::new(3.0, 7.0, 0.0)).length() < 1.0e-9);
|
||||
assert!(ellipse.major_axis.x.abs() < 1.0e-7);
|
||||
assert!((ellipse.major_axis.length() - 5.0).abs() < 1.0e-7);
|
||||
assert!((ellipse.minor_axis_ratio - 0.4).abs() < 1.0e-9);
|
||||
}
|
||||
|
||||
/// Drawing commands must not create parametric constraints implicitly.
|
||||
#[test]
|
||||
fn drawing_a_line_onto_an_existing_endpoint_does_not_add_a_constraint() {
|
||||
|
||||
+147
-33
@@ -1016,6 +1016,26 @@ impl OpenCADStudio {
|
||||
}
|
||||
|
||||
// ── Persistent constraints ────────────────────────────────────
|
||||
"CONSTRAINTBAR" | "CONSTRAINTBAR_OPTIONS" => {
|
||||
use crate::modules::parametric::ConstraintBarOptionCommand;
|
||||
|
||||
let handles = self.tabs[i].scene.selected_handles_in_order();
|
||||
if handles.is_empty() && cmd == "CONSTRAINTBAR" {
|
||||
use crate::modules::draw::select::SelectObjectsCommand;
|
||||
let command =
|
||||
SelectObjectsCommand::routed("CONSTRAINTBAR", "CONSTRAINTBAR_OPTIONS");
|
||||
self.command_line.push_info(&command.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(command));
|
||||
} else if handles.is_empty() {
|
||||
self.command_line.push_output("No objects selected.");
|
||||
} else {
|
||||
let command = ConstraintBarOptionCommand::new(handles);
|
||||
self.command_line.push_info(&command.prompt());
|
||||
self.command_line.set_step_options(command.options());
|
||||
self.tabs[i].active_cmd = Some(Box::new(command));
|
||||
}
|
||||
}
|
||||
|
||||
"GCSHOW" | "GCHIDE" | "DCSHOW" | "DCHIDE" => {
|
||||
let handles = self.tabs[i].scene.selected_handles_in_order();
|
||||
if handles.is_empty() {
|
||||
@@ -1048,6 +1068,24 @@ impl OpenCADStudio {
|
||||
.push_output(format!("{} constraint indicator(s) reset.", count).as_str());
|
||||
}
|
||||
|
||||
"CONSTRAINTBAR_RESET" => {
|
||||
let handles = self.tabs[i].scene.selected_handles_in_order();
|
||||
if handles.is_empty() {
|
||||
self.command_line.push_output("No objects selected.");
|
||||
} else {
|
||||
let scope = self.tabs[i].current_parametric_scope();
|
||||
let count = self.tabs[i].scene.set_parametric_constraint_visibility(
|
||||
scope,
|
||||
Some(&handles),
|
||||
false,
|
||||
true,
|
||||
);
|
||||
self.command_line.push_output(
|
||||
format!("{} constraint bar(s) reset.", count).as_str(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
"GCSHOWALL" | "GCHIDEALL" | "DCSHOWALL" | "DCHIDEALL" => {
|
||||
let scope = self.tabs[i].current_parametric_scope();
|
||||
let dimensional = cmd.starts_with("DC");
|
||||
@@ -1206,17 +1244,38 @@ impl OpenCADStudio {
|
||||
}
|
||||
}
|
||||
|
||||
"SMOOTHCONSTRAINT" => {
|
||||
"GCSMOOTH" => {
|
||||
use crate::modules::parametric::SmoothConstraintCommand;
|
||||
let handles = self.tabs[i].scene.selected_handles_in_order();
|
||||
if handles.is_empty() {
|
||||
use crate::modules::draw::select::SelectObjectsCommand;
|
||||
let sel = SelectObjectsCommand::new(cmd);
|
||||
self.command_line.push_info(&sel.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(sel));
|
||||
let command = SmoothConstraintCommand::new();
|
||||
self.command_line.push_info(&command.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(command));
|
||||
} else if handles.len() != 2 {
|
||||
self.tabs[i].scene.deselect_all();
|
||||
self.command_line.push_error(
|
||||
"Smooth requires an open spline first and a target curve second.",
|
||||
);
|
||||
let command = SmoothConstraintCommand::new();
|
||||
self.command_line.push_info(&command.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(command));
|
||||
} else {
|
||||
let Some(refs) = self.tabs[i].scene.smooth_constraint_refs(&handles) else {
|
||||
self.command_line
|
||||
.push_output("Select one open spline and one open target curve.");
|
||||
let first = self.tabs[i].scene.document.get_entity(handles[0]);
|
||||
let second = self.tabs[i].scene.document.get_entity(handles[1]);
|
||||
let refs = first.zip(second).and_then(|(first, second)| {
|
||||
SmoothConstraintCommand::preselected_refs(
|
||||
(first, handles[0]),
|
||||
(second, handles[1]),
|
||||
)
|
||||
});
|
||||
let Some(refs) = refs else {
|
||||
self.tabs[i].scene.deselect_all();
|
||||
self.command_line.push_error(
|
||||
"Smooth requires an open spline first and a line, arc, polyline segment or open spline second.",
|
||||
);
|
||||
let command = SmoothConstraintCommand::new();
|
||||
self.command_line.push_info(&command.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(command));
|
||||
return None;
|
||||
};
|
||||
use crate::command::CmdResult;
|
||||
@@ -1318,7 +1377,50 @@ impl OpenCADStudio {
|
||||
}
|
||||
}
|
||||
|
||||
"HCONSTRAINT" | "VCONSTRAINT" | "FXCONSTRAINT" => {
|
||||
"GCHORIZONTAL" => {
|
||||
use crate::command::{CmdResult, HorizontalConstraintSelection};
|
||||
use crate::modules::parametric::HorizontalConstraintCommand;
|
||||
|
||||
let handles = self.tabs[i].scene.selected_handles_in_order();
|
||||
if handles.is_empty() {
|
||||
let command = HorizontalConstraintCommand::new();
|
||||
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.");
|
||||
} else {
|
||||
let handle = handles[0];
|
||||
let reference = self.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.get_entity(handle)
|
||||
.and_then(|entity| {
|
||||
HorizontalConstraintCommand::preselected_reference(entity, handle)
|
||||
});
|
||||
if let Some(reference) = reference {
|
||||
let direction = self.tabs[i].ucs_xform().working_plane().x;
|
||||
return Some(self.apply_cmd_result(CmdResult::AddHorizontalConstraint {
|
||||
selection: HorizontalConstraintSelection::Reference(reference),
|
||||
direction: acadrust::types::Vector3::new(
|
||||
direction.x,
|
||||
direction.y,
|
||||
direction.z,
|
||||
),
|
||||
label: "Horizontal constraint",
|
||||
}));
|
||||
}
|
||||
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_info(&command.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(command));
|
||||
}
|
||||
}
|
||||
|
||||
"VCONSTRAINT" | "FXCONSTRAINT" => {
|
||||
let handles = self.tabs[i].scene.selected_handles_in_order();
|
||||
if handles.is_empty() {
|
||||
use crate::modules::draw::select::SelectObjectsCommand;
|
||||
@@ -1331,10 +1433,10 @@ impl OpenCADStudio {
|
||||
} else {
|
||||
use crate::command::CmdResult;
|
||||
use crate::scene::parametric_constraints::{ConstraintKind, ParametricRef};
|
||||
let (kind, label) = match cmd {
|
||||
"HCONSTRAINT" => (ConstraintKind::Horizontal, "Horizontal constraint"),
|
||||
"VCONSTRAINT" => (ConstraintKind::Vertical, "Vertical constraint"),
|
||||
_ => (ConstraintKind::Fixed, "Fixed constraint"),
|
||||
let (kind, label) = if cmd == "VCONSTRAINT" {
|
||||
(ConstraintKind::Vertical, "Vertical constraint")
|
||||
} else {
|
||||
(ConstraintKind::Fixed, "Fixed constraint")
|
||||
};
|
||||
return Some(self.apply_cmd_result(CmdResult::AddParametricConstraint {
|
||||
kind,
|
||||
@@ -1475,29 +1577,41 @@ impl OpenCADStudio {
|
||||
}
|
||||
}
|
||||
|
||||
"NCONSTRAINT" => {
|
||||
"GCCONCENTRIC" => {
|
||||
use crate::command::CmdResult;
|
||||
use crate::modules::parametric::ConcentricConstraintCommand;
|
||||
|
||||
let handles = self.tabs[i].scene.selected_handles_in_order();
|
||||
if handles.is_empty() {
|
||||
use crate::modules::draw::select::SelectObjectsCommand;
|
||||
let sel = SelectObjectsCommand::new(cmd);
|
||||
self.command_line.push_info(&sel.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(sel));
|
||||
} else if handles.len() != 2 {
|
||||
self.command_line.push_output(
|
||||
"Select exactly two circles/arcs, then run this constraint again.",
|
||||
);
|
||||
let command = ConcentricConstraintCommand::new();
|
||||
self.command_line.push_info(&command.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(command));
|
||||
} else {
|
||||
use crate::command::CmdResult;
|
||||
use crate::scene::parametric_constraints::{ConstraintKind, ParametricRef};
|
||||
return Some(self.apply_cmd_result(CmdResult::AddParametricConstraint {
|
||||
kind: ConstraintKind::Concentric,
|
||||
refs: vec![
|
||||
ParametricRef::center(handles[0]),
|
||||
ParametricRef::center(handles[1]),
|
||||
],
|
||||
driving_param: None,
|
||||
label: "Concentric constraint",
|
||||
}));
|
||||
let refs = handles
|
||||
.iter()
|
||||
.filter_map(|handle| {
|
||||
let entity = self.tabs[i].scene.document.get_entity(*handle)?;
|
||||
ConcentricConstraintCommand::preselected_reference(entity, *handle)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
if handles.len() == 2 && refs.len() == 2 && refs[0] != refs[1] {
|
||||
return Some(self.apply_cmd_result(CmdResult::AddConcentricConstraint {
|
||||
first: refs[0],
|
||||
second: refs[1],
|
||||
label: "Concentric constraint",
|
||||
}));
|
||||
}
|
||||
self.tabs[i].scene.deselect_all();
|
||||
self.command_line.push_error(
|
||||
"Invalid selection for Concentric. Select a circle, arc, ellipse or polyline arc segment.",
|
||||
);
|
||||
let command = refs
|
||||
.first()
|
||||
.copied()
|
||||
.map(ConcentricConstraintCommand::with_first)
|
||||
.unwrap_or_else(ConcentricConstraintCommand::new);
|
||||
self.command_line.push_info(&command.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(command));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1051,6 +1051,7 @@ impl OpenCADStudio {
|
||||
| "CONSTRAINTSOLVEMODE"
|
||||
| "CONSTRAINTINFER"
|
||||
| "CONSTRAINTBARDISPLAY"
|
||||
| "CONSTRAINTBARMODE"
|
||||
) =>
|
||||
{
|
||||
return self.dispatch_styleprops(&format!("SETVAR {cmd}"), i);
|
||||
@@ -1073,7 +1074,7 @@ impl OpenCADStudio {
|
||||
let value = it.next().map(|s| s.trim().to_string());
|
||||
if name.is_empty() || name == "?" {
|
||||
self.command_line.push_info(
|
||||
crate::t!("SETVAR: CETRANSPARENCY LTSCALE CELTSCALE PDMODE PDSIZE TEXTSIZE ORTHOMODE FILLMODE MIRRTEXT FRAME IMAGEFRAME PDFFRAME WIPEOUTFRAME XCLIPFRAME POINTCLOUDCLIPFRAME ZOOMWHEEL ZOOMFACTOR SHORTCUTMENU SHORTCUTMENUDURATION CURSORSIZE PICKBOX CURSORTYPE SNAPANG TEXTFILL CLIPROMPTLINES COMMANDLINEFADETIME ATTREQ ATTDIA DIMASSOC DIMCONTINUEMODE CONSTRAINTSOLVEMODE CONSTRAINTINFER CONSTRAINTBARDISPLAY ANGBASE ANGDIR SKETCHINC SKPOLY SKTOLERANCE DONUTID DONUTOD CENTEREXE CENTERLAYER CENTERLTYPE CENTERLTSCALE CENTERLTYPEFILE CENTERCROSSSIZE CENTERCROSSGAP CENTERMARKEXE COLORTHEME SELECTIONAREA SELECTIONAREAOPACITY SELECTIONEFFECT SELECTIONEFFECTCOLOR WINDOWSAREACOLOR CROSSINGAREACOLOR SELECTIONPREVIEW GRIPSIZE GRIPCOLOR GRIPHOT GRIPHOVER GRIPOBJLIMIT | CLAYER CELTYPE TEXTSTYLE (read-only)").as_ref(),
|
||||
crate::t!("SETVAR: CETRANSPARENCY LTSCALE CELTSCALE PDMODE PDSIZE TEXTSIZE ORTHOMODE FILLMODE MIRRTEXT FRAME IMAGEFRAME PDFFRAME WIPEOUTFRAME XCLIPFRAME POINTCLOUDCLIPFRAME ZOOMWHEEL ZOOMFACTOR SHORTCUTMENU SHORTCUTMENUDURATION CURSORSIZE PICKBOX CURSORTYPE SNAPANG TEXTFILL CLIPROMPTLINES COMMANDLINEFADETIME ATTREQ ATTDIA DIMASSOC DIMCONTINUEMODE CONSTRAINTSOLVEMODE CONSTRAINTINFER CONSTRAINTBARDISPLAY CONSTRAINTBARMODE ANGBASE ANGDIR SKETCHINC SKPOLY SKTOLERANCE DONUTID DONUTOD CENTEREXE CENTERLAYER CENTERLTYPE CENTERLTSCALE CENTERLTYPEFILE CENTERCROSSSIZE CENTERCROSSGAP CENTERMARKEXE COLORTHEME SELECTIONAREA SELECTIONAREAOPACITY SELECTIONEFFECT SELECTIONEFFECTCOLOR WINDOWSAREACOLOR CROSSINGAREACOLOR SELECTIONPREVIEW GRIPSIZE GRIPCOLOR GRIPHOT GRIPHOVER GRIPOBJLIMIT | CLAYER CELTYPE TEXTSTYLE (read-only)").as_ref(),
|
||||
);
|
||||
} else {
|
||||
if name == "CETRANSPARENCY" {
|
||||
@@ -1269,15 +1270,23 @@ impl OpenCADStudio {
|
||||
}
|
||||
if matches!(
|
||||
name.as_str(),
|
||||
"CONSTRAINTSOLVEMODE" | "CONSTRAINTINFER" | "CONSTRAINTBARDISPLAY"
|
||||
"CONSTRAINTSOLVEMODE"
|
||||
| "CONSTRAINTINFER"
|
||||
| "CONSTRAINTBARDISPLAY"
|
||||
| "CONSTRAINTBARMODE"
|
||||
) {
|
||||
let current = match name.as_str() {
|
||||
"CONSTRAINTSOLVEMODE" => i16::from(self.constraint_solve_mode),
|
||||
"CONSTRAINTINFER" => i16::from(self.constraint_infer),
|
||||
"CONSTRAINTBARDISPLAY" => self.constraint_bar_display,
|
||||
"CONSTRAINTBARMODE" => self.constraint_bar_mode,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let maximum = if name == "CONSTRAINTBARDISPLAY" { 3 } else { 1 };
|
||||
let maximum = match name.as_str() {
|
||||
"CONSTRAINTBARDISPLAY" => 3,
|
||||
"CONSTRAINTBARMODE" => 4095,
|
||||
_ => 1,
|
||||
};
|
||||
if let Some(value) = &value {
|
||||
match value
|
||||
.parse::<i16>()
|
||||
@@ -1293,6 +1302,7 @@ impl OpenCADStudio {
|
||||
"CONSTRAINTBARDISPLAY" => {
|
||||
self.constraint_bar_display = mode
|
||||
}
|
||||
"CONSTRAINTBARMODE" => self.constraint_bar_mode = mode,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
self.persist_settings_if_changed();
|
||||
|
||||
@@ -220,6 +220,25 @@ fn command_category(name: &str) -> &'static str {
|
||||
"view"
|
||||
} else if name.starts_with("DIM") || ["LEADER", "MLEADER", "TOLERANCE"].contains(&name) {
|
||||
"annotate"
|
||||
} else if [
|
||||
"GEOMCONSTRAINT",
|
||||
"GCCOINCIDENT",
|
||||
"GCCOLLINEAR",
|
||||
"GCPERPENDICULAR",
|
||||
"QCONSTRAINT",
|
||||
"PCONSTRAINT",
|
||||
"GCHORIZONTAL",
|
||||
"VCONSTRAINT",
|
||||
"ECONSTRAINT",
|
||||
"TCONSTRAINT",
|
||||
"GCCONCENTRIC",
|
||||
"NRCONSTRAINT",
|
||||
"LCONSTRAINT",
|
||||
"DELCONSTRAINT",
|
||||
]
|
||||
.contains(&name)
|
||||
{
|
||||
"parametric"
|
||||
} else if [
|
||||
"BOX",
|
||||
"CYLINDER",
|
||||
|
||||
+1
-19
@@ -1,5 +1,4 @@
|
||||
use crate::command::CadCommand;
|
||||
use crate::io::linetypes;
|
||||
use crate::modules::draw::modify::block_edit::BlockEditSession;
|
||||
use crate::modules::draw::modify::refedit::RefEditSession;
|
||||
use crate::scene::pick::grip::GripEdit;
|
||||
@@ -593,24 +592,7 @@ impl DocumentTab {
|
||||
|
||||
pub(super) fn new_drawing(n: usize) -> Self {
|
||||
let mut scene = Scene::new();
|
||||
linetypes::populate_document(&mut scene.document);
|
||||
// Paper layouts start as A4 landscape.
|
||||
for obj in scene.document.objects.values_mut() {
|
||||
if let acadrust::objects::ObjectType::Layout(l) = obj {
|
||||
if l.name != "Model" {
|
||||
l.min_limits = (0.0, 0.0);
|
||||
l.max_limits = (297.0, 210.0);
|
||||
l.min_extents = (0.0, 0.0, 0.0);
|
||||
l.max_extents = (297.0, 210.0, 0.0);
|
||||
l.paper_width = 297.0;
|
||||
l.paper_height = 210.0;
|
||||
l.plot_paper_units = 1;
|
||||
l.plot_scale_numerator = 1.0;
|
||||
l.plot_scale_denominator = 1.0;
|
||||
l.paper_size = "ISO_A4_(297.00_x_210.00_MM)".into();
|
||||
}
|
||||
}
|
||||
}
|
||||
scene.populate_new_drawing_defaults();
|
||||
Self {
|
||||
id: NEXT_DOCUMENT_TAB_ID.fetch_add(1, Ordering::Relaxed),
|
||||
scene,
|
||||
|
||||
@@ -602,6 +602,7 @@ pub(super) struct OpenCADStudio {
|
||||
pub constraint_solve_mode: bool,
|
||||
pub constraint_infer: bool,
|
||||
pub constraint_bar_display: i16,
|
||||
pub constraint_bar_mode: i16,
|
||||
/// Minutes between autosaves to a `.sv$` recovery file (SAVETIME command);
|
||||
/// 0 disables autosave.
|
||||
pub savetime_min: i32,
|
||||
@@ -3906,6 +3907,7 @@ impl OpenCADStudio {
|
||||
constraint_solve_mode: true,
|
||||
constraint_infer: false,
|
||||
constraint_bar_display: 3,
|
||||
constraint_bar_mode: 4095,
|
||||
savetime_min: 10,
|
||||
default_bg_color: None,
|
||||
default_paper_bg_color: None,
|
||||
|
||||
@@ -465,6 +465,9 @@ pub struct UserSettings {
|
||||
/// Constraint bar display bit mask: 1 after applying, 2 on selection.
|
||||
#[serde(default = "default_constraint_bar_display")]
|
||||
pub constraint_bar_display: i16,
|
||||
/// Geometric constraint type bit mask (1..2048, combined; default all).
|
||||
#[serde(default = "default_constraint_bar_mode")]
|
||||
pub constraint_bar_mode: i16,
|
||||
/// Minutes between autosaves to a `.sv$` recovery file (SAVETIME command).
|
||||
/// 0 disables autosave.
|
||||
pub savetime_min: i32,
|
||||
@@ -595,6 +598,10 @@ fn default_constraint_bar_display() -> i16 {
|
||||
3
|
||||
}
|
||||
|
||||
fn default_constraint_bar_mode() -> i16 {
|
||||
4095
|
||||
}
|
||||
|
||||
fn deserialize_clipromptlines<'de, D>(de: D) -> Result<i32, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
@@ -662,6 +669,7 @@ impl Default for UserSettings {
|
||||
constraint_solve_mode: true,
|
||||
constraint_infer: false,
|
||||
constraint_bar_display: 3,
|
||||
constraint_bar_mode: 4095,
|
||||
savetime_min: 10,
|
||||
default_save_format: crate::io::DEFAULT_SAVE_FORMAT.to_string(),
|
||||
pick_add: true,
|
||||
|
||||
@@ -1739,15 +1739,13 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
||||
.get_entity(popup.handle)
|
||||
.is_some_and(|entity| match (item.action, entity) {
|
||||
(GripMenuAction::ShowFit, acadrust::EntityType::Spline(spline)) => {
|
||||
!spline.cv_frame_visible
|
||||
&& crate::entities::spline::uses_fit_method(spline)
|
||||
crate::entities::spline::shows_fit_points(spline)
|
||||
}
|
||||
(
|
||||
GripMenuAction::ShowControlVertices,
|
||||
acadrust::EntityType::Spline(spline),
|
||||
) => {
|
||||
spline.cv_frame_visible
|
||||
|| !crate::entities::spline::uses_fit_method(spline)
|
||||
crate::entities::spline::shows_control_vertices(spline)
|
||||
}
|
||||
_ => false,
|
||||
});
|
||||
|
||||
+718
-230
File diff suppressed because it is too large.
Load diff
@@ -1508,8 +1508,7 @@ impl OpenCADStudio {
|
||||
Message::ClearScene => {
|
||||
let i = self.active_tab;
|
||||
self.push_undo_snapshot(i, "CLEAR");
|
||||
self.tabs[i].scene.clear();
|
||||
crate::io::linetypes::populate_document(&mut self.tabs[i].scene.document);
|
||||
self.tabs[i].scene.reset_to_new_drawing();
|
||||
self.tabs[i].properties = PropertiesPanel::empty();
|
||||
let doc_layers = self.tabs[i].scene.document.layers.clone();
|
||||
let vp_info = self.tabs[i].scene.viewport_list();
|
||||
|
||||
@@ -3051,6 +3051,7 @@ impl OpenCADStudio {
|
||||
vp_size,
|
||||
self.show_constraint_values,
|
||||
self.constraint_bar_display,
|
||||
self.constraint_bar_mode,
|
||||
cursor,
|
||||
)?;
|
||||
let set = self.tabs[i].scene.parametric_constraint_set(scope)?;
|
||||
@@ -4577,6 +4578,7 @@ properties={:.1}ms picked={}",
|
||||
canvas_sz,
|
||||
self.show_constraint_values,
|
||||
self.constraint_bar_display,
|
||||
self.constraint_bar_mode,
|
||||
p_full,
|
||||
)
|
||||
})
|
||||
@@ -6031,24 +6033,7 @@ properties={:.1}ms picked={}",
|
||||
if let acadrust::objects::ObjectType::Layout(l) = obj {
|
||||
if l.name == new_name {
|
||||
l.flags = layout_flags;
|
||||
l.min_limits = (0.0, 0.0);
|
||||
l.max_limits = (297.0, 210.0);
|
||||
l.min_extents = (0.0, 0.0, 0.0);
|
||||
l.max_extents = (297.0, 210.0, 0.0);
|
||||
l.paper_width = 297.0;
|
||||
l.paper_height = 210.0;
|
||||
l.plot_paper_units = 1;
|
||||
l.plot_scale_numerator = 1.0;
|
||||
l.plot_scale_denominator = 1.0;
|
||||
l.plot_scale_type = 16;
|
||||
l.plot_scale_factor = 1.0;
|
||||
l.plot_type = 5;
|
||||
l.plot_flags.use_standard_scale = true;
|
||||
l.plot_flags.print_lineweights = true;
|
||||
l.plot_flags.plot_plot_styles = !plot_style.is_empty();
|
||||
l.plot_flags.show_plot_styles = !plot_style.is_empty();
|
||||
l.plot_style_sheet = plot_style;
|
||||
l.paper_size = "ISO_A4_(297.00_x_210.00_MM)".into();
|
||||
crate::scene::apply_default_page_setup(l, &plot_style);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -6683,6 +6668,7 @@ mod selection_preview_tests {
|
||||
(800.0, 600.0),
|
||||
true,
|
||||
3,
|
||||
4095,
|
||||
)[0].1;
|
||||
let point = (-30..=30).find_map(|y| {
|
||||
(-30..=30).find_map(|x| {
|
||||
|
||||
+21
-1
@@ -611,7 +611,8 @@ bg={bg_ms:.1}ms n={view_count}"
|
||||
};
|
||||
let control_polygon = tab.selected_handle.and_then(|handle| {
|
||||
let spline = match tab.scene.document.get_entity(handle) {
|
||||
Some(acadrust::EntityType::Spline(spline)) if spline.cv_frame_visible => spline,
|
||||
Some(acadrust::EntityType::Spline(spline))
|
||||
if crate::entities::spline::shows_control_vertices(spline) => spline,
|
||||
_ => return None,
|
||||
};
|
||||
if tab
|
||||
@@ -833,6 +834,23 @@ bg={bg_ms:.1}ms n={view_count}"
|
||||
.active_cmd
|
||||
.as_ref()
|
||||
.is_some_and(|cmd| !cmd.needs_entity_pick() && !cmd.is_selection_gathering());
|
||||
let constraint_cursor_badge = if is_paper {
|
||||
None
|
||||
} else {
|
||||
tab.scene.hover_highlight.and_then(|handle| {
|
||||
tab.scene
|
||||
.parametric_constraint_set(tab.current_parametric_scope())
|
||||
.and_then(|set| {
|
||||
set.constraints_touching(handle)
|
||||
.find(|constraint| {
|
||||
constraint.kind
|
||||
== crate::scene::parametric_constraints::ConstraintKind::Concentric
|
||||
})
|
||||
.or_else(|| set.constraints_touching(handle).next())
|
||||
})
|
||||
.map(|constraint| constraint.kind.glyph_symbol().to_string())
|
||||
})
|
||||
};
|
||||
let constraint_glyphs: Vec<(
|
||||
iced::Point,
|
||||
[f32; 2],
|
||||
@@ -850,6 +868,7 @@ bg={bg_ms:.1}ms n={view_count}"
|
||||
sel_ref.vp_size,
|
||||
self.show_constraint_values,
|
||||
self.constraint_bar_display,
|
||||
self.constraint_bar_mode,
|
||||
)
|
||||
.into_iter()
|
||||
.map(|(id, point, direction, label, is_conflicting, hover_points)| {
|
||||
@@ -910,6 +929,7 @@ bg={bg_ms:.1}ms n={view_count}"
|
||||
constraint_glyphs,
|
||||
self.constraint_glyph_tooltip
|
||||
.map(|kind| crate::t!(kind.label()).into_owned()),
|
||||
constraint_cursor_badge,
|
||||
)
|
||||
};
|
||||
|
||||
|
||||
Reference in new issue
Block a user