forked from 1b-agent/opencadstudio-upstream
fix(mleader): repair creation and editing
This commit is contained in:
commit
ff1f873633
40 files changed
+4504
-1754
No files matched your search
+64
-229
@@ -668,138 +668,87 @@ impl OpenCADStudio {
|
||||
|
||||
"DIMCONTINUE" => {
|
||||
use crate::modules::annotate::dim_continue::DimContinueCommand;
|
||||
let cmd = if let Some((p1, p2, dp, rot, trot)) =
|
||||
find_last_linear_dim(&self.tabs[i].scene)
|
||||
{
|
||||
DimContinueCommand::from_base(p1, p2, dp, rot, trot)
|
||||
} else {
|
||||
DimContinueCommand::new()
|
||||
};
|
||||
let scene = &self.tabs[i].scene;
|
||||
let recent = scene
|
||||
.last_created_dimension
|
||||
.filter(|handle| scene.entity_belongs_to_active_space(*handle))
|
||||
.and_then(|handle| scene.document.get_entity(handle))
|
||||
.cloned();
|
||||
let cmd = DimContinueCommand::new(
|
||||
recent,
|
||||
self.dimension_continue_mode == 1,
|
||||
);
|
||||
self.command_line.push_info(&cmd.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(cmd));
|
||||
}
|
||||
|
||||
"DIMBASELINE" => {
|
||||
use crate::modules::annotate::dim_baseline::DimBaselineCommand;
|
||||
let cmd = if let Some((p1, p2, dp, rot, trot)) =
|
||||
find_last_linear_dim(&self.tabs[i].scene)
|
||||
{
|
||||
let doc = &self.tabs[i].scene.document;
|
||||
let dimdli = doc
|
||||
.dim_styles
|
||||
.iter()
|
||||
.find(|s| {
|
||||
s.name
|
||||
.eq_ignore_ascii_case(&doc.header.current_dimstyle_name)
|
||||
})
|
||||
.map(|s| s.dimdli as f32)
|
||||
.unwrap_or(1.5);
|
||||
DimBaselineCommand::from_base(p1, p2, dp, rot, trot, dimdli)
|
||||
let scene = &self.tabs[i].scene;
|
||||
let recent = scene
|
||||
.last_created_dimension
|
||||
.filter(|handle| scene.entity_belongs_to_active_space(*handle))
|
||||
.and_then(|handle| scene.document.get_entity(handle))
|
||||
.cloned();
|
||||
let dimdli_by_style = scene
|
||||
.document
|
||||
.dim_styles
|
||||
.iter()
|
||||
.map(|style| (style.name.to_ascii_lowercase(), style.dimdli))
|
||||
.collect();
|
||||
let fallback_dimdli = if scene.document.header.measurement == 1 {
|
||||
3.75
|
||||
} else {
|
||||
DimBaselineCommand::new()
|
||||
0.38
|
||||
};
|
||||
let current_style_name = scene.document.header.current_dimstyle_name.clone();
|
||||
let cmd = DimBaselineCommand::new(
|
||||
recent,
|
||||
dimdli_by_style,
|
||||
current_style_name,
|
||||
fallback_dimdli,
|
||||
self.dimension_continue_mode == 1,
|
||||
);
|
||||
self.command_line.push_info(&cmd.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(cmd));
|
||||
}
|
||||
|
||||
"QDIM" => {
|
||||
use crate::modules::annotate::qdim::QdimCommand;
|
||||
let cmd = QdimCommand::new();
|
||||
self.command_line.push_info(&cmd.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(cmd));
|
||||
}
|
||||
|
||||
// QDIM second stage: the front-end relaunched with the picked
|
||||
// dimension-line point and the gathered geometry now selected. Build
|
||||
// a continuous chain of linear dimensions across the endpoints.
|
||||
cmd if cmd.starts_with("QDIM_PLACE ") => {
|
||||
let nums: Vec<f64> = cmd
|
||||
.split_whitespace()
|
||||
.skip(1)
|
||||
.filter_map(|s| s.parse().ok())
|
||||
.collect();
|
||||
if nums.len() < 3 {
|
||||
return Some(Task::none());
|
||||
}
|
||||
let plane = if self.tabs[i].editing_model_space() {
|
||||
self.tabs[i].ucs_xform().working_plane()
|
||||
} else {
|
||||
crate::command::WorkingPlane::default()
|
||||
};
|
||||
let place = plane.to_local(glam::DVec3::new(nums[0], nums[1], nums[2]));
|
||||
let handles: Vec<acadrust::Handle> = self.tabs[i]
|
||||
let document = &self.tabs[i].scene.document;
|
||||
let dim_spacing = document
|
||||
.dim_styles
|
||||
.iter()
|
||||
.find(|style| {
|
||||
style
|
||||
.name
|
||||
.eq_ignore_ascii_case(&document.header.current_dimstyle_name)
|
||||
})
|
||||
.map(|style| style.dimdli)
|
||||
.unwrap_or_else(|| {
|
||||
if document.header.measurement == 1 {
|
||||
3.75
|
||||
} else {
|
||||
0.38
|
||||
}
|
||||
});
|
||||
let selection = self.tabs[i]
|
||||
.scene
|
||||
.selected_entities()
|
||||
.iter()
|
||||
.map(|(h, _)| *h)
|
||||
.into_iter()
|
||||
.map(|(handle, entity)| crate::command::SelectionEntity {
|
||||
handle,
|
||||
entity: entity.clone(),
|
||||
surface_area: None,
|
||||
})
|
||||
.collect();
|
||||
let mut pts: Vec<glam::DVec3> = Vec::new();
|
||||
for h in &handles {
|
||||
if let Some(e) = self.tabs[i].scene.document.get_entity(*h) {
|
||||
qdim_collect_points(e, &mut pts);
|
||||
}
|
||||
}
|
||||
for point in &mut pts {
|
||||
*point = plane.to_local(*point);
|
||||
}
|
||||
if pts.len() < 2 {
|
||||
self.command_line
|
||||
.push_error(crate::t!("QDIM: no dimensionable endpoints in the selection.").as_ref());
|
||||
return Some(Task::none());
|
||||
}
|
||||
// Choose the dimension axis from the points' spread: a wider X
|
||||
// span dimensions horizontally (ordered by X), else vertically.
|
||||
let (minx, maxx) = pts
|
||||
.iter()
|
||||
.fold((f64::MAX, f64::MIN), |(a, b), p| (a.min(p.x), b.max(p.x)));
|
||||
let (miny, maxy) = pts
|
||||
.iter()
|
||||
.fold((f64::MAX, f64::MIN), |(a, b), p| (a.min(p.y), b.max(p.y)));
|
||||
let horizontal = (maxx - minx) >= (maxy - miny);
|
||||
if horizontal {
|
||||
pts.sort_by(|a, b| a.x.total_cmp(&b.x));
|
||||
pts.dedup_by(|a, b| (a.x - b.x).abs() < 1e-4);
|
||||
} else {
|
||||
pts.sort_by(|a, b| a.y.total_cmp(&b.y));
|
||||
pts.dedup_by(|a, b| (a.y - b.y).abs() < 1e-4);
|
||||
}
|
||||
if pts.len() < 2 {
|
||||
self.command_line
|
||||
.push_error(crate::t!("QDIM: endpoints collapse to a single position.").as_ref());
|
||||
return Some(Task::none());
|
||||
}
|
||||
self.push_undo_snapshot(i, "QDIM");
|
||||
let v = |p: glam::DVec3| {
|
||||
acadrust::types::Vector3::new(p.x, p.y, p.z)
|
||||
};
|
||||
let mut made = 0usize;
|
||||
for w in pts.windows(2) {
|
||||
let (p1, p2) = (w[0], w[1]);
|
||||
let mut dim = acadrust::entities::DimensionLinear::new(v(p1), v(p2));
|
||||
dim.rotation = if horizontal {
|
||||
0.0
|
||||
} else {
|
||||
std::f64::consts::FRAC_PI_2
|
||||
};
|
||||
// Dim line passes through the picked perpendicular position.
|
||||
let def = if horizontal {
|
||||
glam::DVec3::new((p1.x + p2.x) * 0.5, place.y, 0.0)
|
||||
} else {
|
||||
glam::DVec3::new(place.x, (p1.y + p2.y) * 0.5, 0.0)
|
||||
};
|
||||
dim.definition_point = v(def);
|
||||
dim.base.definition_point = v(def);
|
||||
dim.base.actual_measurement = dim.measurement();
|
||||
let entity = acadrust::EntityType::Dimension(
|
||||
acadrust::entities::Dimension::Linear(dim),
|
||||
);
|
||||
self.commit_entity(plane.place_entity(entity));
|
||||
made += 1;
|
||||
}
|
||||
self.tabs[i].dirty = true;
|
||||
self.command_line
|
||||
.push_output(crate::tf!("QDIM {made} dimensions created.").as_ref());
|
||||
return Some(Task::none());
|
||||
let cmd = QdimCommand::new(
|
||||
selection,
|
||||
dim_spacing,
|
||||
self.quick_dimension_snap_priority,
|
||||
);
|
||||
self.command_line.push_info(&cmd.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(cmd));
|
||||
}
|
||||
|
||||
"DIMEDIT" => {
|
||||
@@ -1398,120 +1347,6 @@ impl OpenCADStudio {
|
||||
}
|
||||
}
|
||||
|
||||
/// Find the last placed linear or aligned dimension in the document.
|
||||
/// Returns `(first_point, second_point, definition_point, rotation_rad)` in world-space.
|
||||
fn find_last_linear_dim(
|
||||
scene: &crate::scene::Scene,
|
||||
) -> Option<(glam::Vec3, glam::Vec3, glam::Vec3, f64, f64)> {
|
||||
use acadrust::entities::Dimension;
|
||||
let mut best_handle: u64 = 0;
|
||||
let mut result: Option<(glam::Vec3, glam::Vec3, glam::Vec3, f64, f64)> = None;
|
||||
|
||||
for entity in scene.document.entities() {
|
||||
if let acadrust::EntityType::Dimension(dim) = entity {
|
||||
let h = entity.common().handle.value();
|
||||
if h <= best_handle {
|
||||
continue;
|
||||
}
|
||||
let item = match dim {
|
||||
Dimension::Linear(d) => {
|
||||
let p1 = glam::Vec3::new(
|
||||
d.first_point.x as f32,
|
||||
d.first_point.y as f32,
|
||||
d.first_point.z as f32,
|
||||
);
|
||||
let p2 = glam::Vec3::new(
|
||||
d.second_point.x as f32,
|
||||
d.second_point.y as f32,
|
||||
d.second_point.z as f32,
|
||||
);
|
||||
let dp = glam::Vec3::new(
|
||||
d.base.definition_point.x as f32,
|
||||
d.base.definition_point.y as f32,
|
||||
d.base.definition_point.z as f32,
|
||||
);
|
||||
Some((p1, p2, dp, d.rotation, d.base.text_rotation))
|
||||
}
|
||||
Dimension::Aligned(d) => {
|
||||
let p1 = glam::Vec3::new(
|
||||
d.first_point.x as f32,
|
||||
d.first_point.y as f32,
|
||||
d.first_point.z as f32,
|
||||
);
|
||||
let p2 = glam::Vec3::new(
|
||||
d.second_point.x as f32,
|
||||
d.second_point.y as f32,
|
||||
d.second_point.z as f32,
|
||||
);
|
||||
let dp = glam::Vec3::new(
|
||||
d.base.definition_point.x as f32,
|
||||
d.base.definition_point.y as f32,
|
||||
d.base.definition_point.z as f32,
|
||||
);
|
||||
let dx = (d.second_point.x - d.first_point.x) as f32;
|
||||
let dy = (d.second_point.y - d.first_point.y) as f32;
|
||||
let rot = dy.atan2(dx) as f64;
|
||||
Some((p1, p2, dp, rot, d.base.text_rotation))
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
if let Some(data) = item {
|
||||
best_handle = h;
|
||||
result = Some(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/// Collect candidate dimension endpoints from an entity for QDIM — line ends,
|
||||
/// polyline vertices, arc endpoints — in world space.
|
||||
fn qdim_collect_points(e: &acadrust::EntityType, out: &mut Vec<glam::DVec3>) {
|
||||
use acadrust::EntityType as ET;
|
||||
let p = |v: &acadrust::types::Vector3| glam::DVec3::new(v.x, v.y, v.z);
|
||||
let ocs = |point: (f64, f64, f64), normal: acadrust::types::Vector3| {
|
||||
let world = crate::scene::view::transform::ocs_point_to_wcs(
|
||||
point,
|
||||
(normal.x, normal.y, normal.z),
|
||||
);
|
||||
glam::DVec3::new(world.0, world.1, world.2)
|
||||
};
|
||||
match e {
|
||||
ET::Line(l) => {
|
||||
out.push(p(&l.start));
|
||||
out.push(p(&l.end));
|
||||
}
|
||||
ET::LwPolyline(pl) => {
|
||||
for v in &pl.vertices {
|
||||
out.push(ocs((v.location.x, v.location.y, pl.elevation), pl.normal));
|
||||
}
|
||||
}
|
||||
ET::Polyline2D(pl) => {
|
||||
for v in &pl.vertices {
|
||||
out.push(ocs((v.location.x, v.location.y, pl.elevation), pl.normal));
|
||||
}
|
||||
}
|
||||
ET::Polyline(pl) => {
|
||||
for v in &pl.vertices {
|
||||
out.push(p(&v.location));
|
||||
}
|
||||
}
|
||||
ET::Arc(a) => {
|
||||
for &ang in &[a.start_angle, a.end_angle] {
|
||||
out.push(ocs(
|
||||
(
|
||||
a.center.x + a.radius * ang.cos(),
|
||||
a.center.y + a.radius * ang.sin(),
|
||||
a.center.z,
|
||||
),
|
||||
a.normal,
|
||||
));
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
// ── TCASE helpers ──────────────────────────────────────────────────────────
|
||||
fn sentence_case(s: &str) -> String {
|
||||
let mut c = s.chars();
|
||||
|
||||
@@ -633,11 +633,20 @@ impl OpenCADStudio {
|
||||
"CHPROP: invalid value '{}' for {}.",
|
||||
value, prop
|
||||
).as_ref());
|
||||
} else {
|
||||
let known = matches!(
|
||||
prop.as_str(),
|
||||
"LAYER" | "LINETYPE" | "LT" | "LTSCALE" | "COLOR" | "TRANSPARENCY"
|
||||
);
|
||||
if !known {
|
||||
self.command_line.push_error(crate::tf!(
|
||||
"CHPROP: unknown property '{}'. Use: LAYER COLOR LINETYPE LTSCALE TRANSPARENCY", prop
|
||||
).as_ref());
|
||||
} else {
|
||||
let mut changed = 0usize;
|
||||
for handle in &handles {
|
||||
self.apply_property_op(i, "CHPROP", &handles, |app, handle| {
|
||||
if let Some(entity) =
|
||||
self.tabs[i].scene.document.get_entity_mut(*handle)
|
||||
app.tabs[i].scene.document.get_entity_mut(handle)
|
||||
{
|
||||
let common = entity.common_mut();
|
||||
match prop.as_str() {
|
||||
@@ -661,30 +670,21 @@ impl OpenCADStudio {
|
||||
common.transparency = transparency_val.unwrap();
|
||||
changed += 1;
|
||||
}
|
||||
_ => {
|
||||
self.command_line.push_error(crate::tf!(
|
||||
"CHPROP: unknown property '{}'. Use: LAYER COLOR LINETYPE LTSCALE TRANSPARENCY", prop
|
||||
).as_ref());
|
||||
break;
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
if changed > 0 {
|
||||
self.push_undo_snapshot(i, "CHPROP");
|
||||
self.tabs[i].dirty = true;
|
||||
// Colour / linetype / ltscale / transparency /
|
||||
// layer are baked into the cached wire geometry —
|
||||
// re-tessellate the changed entities so they
|
||||
// repaint immediately (issue #231 class).
|
||||
self.invalidate_property_targets(i, &handles);
|
||||
self.command_line.push_output(crate::tf!(
|
||||
"CHPROP: {} entity/entities updated.",
|
||||
changed
|
||||
).as_ref());
|
||||
}
|
||||
});
|
||||
// Colour / linetype / ltscale / transparency /
|
||||
// layer are baked into the cached wire geometry —
|
||||
// re-tessellate the changed entities so they
|
||||
// repaint immediately (issue #231 class).
|
||||
self.command_line.push_output(crate::tf!(
|
||||
"CHPROP: {} entity/entities updated.",
|
||||
changed
|
||||
).as_ref());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -931,6 +931,7 @@ impl OpenCADStudio {
|
||||
| "CENTERCROSSSIZE"
|
||||
| "CENTERCROSSGAP"
|
||||
| "CENTERMARKEXE"
|
||||
| "DIMCONTINUEMODE"
|
||||
) =>
|
||||
{
|
||||
return self.dispatch_styleprops(&format!("SETVAR {cmd}"), i);
|
||||
@@ -953,7 +954,7 @@ impl OpenCADStudio {
|
||||
let value = it.next().map(|s| s.trim().to_string());
|
||||
if name.is_empty() || name == "?" {
|
||||
self.command_line.push_info(
|
||||
"SETVAR: LTSCALE CELTSCALE PDMODE PDSIZE TEXTSIZE ORTHOMODE FILLMODE MIRRTEXT FRAME IMAGEFRAME PDFFRAME WIPEOUTFRAME XCLIPFRAME POINTCLOUDCLIPFRAME ZOOMWHEEL ZOOMFACTOR CURSORSIZE PICKBOX CURSORTYPE SNAPANG TEXTFILL CLIPROMPTLINES ATTREQ ATTDIA DIMASSOC ANGBASE ANGDIR SKETCHINC SKPOLY SKTOLERANCE DONUTID DONUTOD CENTEREXE CENTERLAYER CENTERLTYPE CENTERLTSCALE CENTERLTYPEFILE CENTERCROSSSIZE CENTERCROSSGAP CENTERMARKEXE | CLAYER CELTYPE TEXTSTYLE (read-only)",
|
||||
"SETVAR: LTSCALE CELTSCALE PDMODE PDSIZE TEXTSIZE ORTHOMODE FILLMODE MIRRTEXT FRAME IMAGEFRAME PDFFRAME WIPEOUTFRAME XCLIPFRAME POINTCLOUDCLIPFRAME ZOOMWHEEL ZOOMFACTOR CURSORSIZE PICKBOX CURSORTYPE SNAPANG TEXTFILL CLIPROMPTLINES ATTREQ ATTDIA DIMASSOC DIMCONTINUEMODE ANGBASE ANGDIR SKETCHINC SKPOLY SKTOLERANCE DONUTID DONUTOD CENTEREXE CENTERLAYER CENTERLTYPE CENTERLTSCALE CENTERLTYPEFILE CENTERCROSSSIZE CENTERCROSSGAP CENTERMARKEXE | CLAYER CELTYPE TEXTSTYLE (read-only)",
|
||||
);
|
||||
} else {
|
||||
let frame_kind = crate::scene::frame::kind_for_name(&name);
|
||||
@@ -1069,6 +1070,28 @@ impl OpenCADStudio {
|
||||
}
|
||||
return Some(self.finish_dispatch(cmd));
|
||||
}
|
||||
if name == "DIMCONTINUEMODE" {
|
||||
let current = self.dimension_continue_mode;
|
||||
if let Some(value) = &value {
|
||||
match value.parse::<i16>() {
|
||||
Ok(mode @ 0..=1) => {
|
||||
self.dimension_continue_mode = mode;
|
||||
self.persist_settings_if_changed();
|
||||
self.command_line
|
||||
.push_output(&format!("DIMCONTINUEMODE = {mode}"));
|
||||
}
|
||||
_ => self.command_line.push_error(
|
||||
"SETVAR: DIMCONTINUEMODE requires 0 or 1.",
|
||||
),
|
||||
}
|
||||
} else {
|
||||
self.command_line.push_output(crate::tf!(
|
||||
"Enter new value for {name} <{current}>:"
|
||||
).as_ref());
|
||||
self.pending_setvar = Some(name.clone());
|
||||
}
|
||||
return Some(self.finish_dispatch(cmd));
|
||||
}
|
||||
if matches!(name.as_str(), "DONUTID" | "DONUTOD") {
|
||||
let current = if name == "DONUTID" {
|
||||
crate::modules::draw::defaults::get_donut_inner_diameter()
|
||||
|
||||
Reference in new issue
Block a user