Merge pull request #1184 from Ramox81/codex/hatchedit-command-parity

This commit is contained in:
Hakan Seven committed 2026-09-11 15:14:43 +03:00
commit 753fa17b68
24 files changed
+926 -132

No files matched your search

+140 -48
View File
@@ -5165,7 +5165,41 @@ impl OpenCADStudio {
self.command_line
.push_error(crate::t!("HATCHEDIT: hatch entity not found.").as_ref());
} else {
use crate::command::HatchEditOperation;
use crate::command::{CadCommand, HatchEditOperation};
if matches!(&operation,HatchEditOperation::BeginAssociate) {
let associative=matches!(self.tabs[i].scene.document.get_entity(handle),Some(acadrust::EntityType::Hatch(h)) if h.is_associative);
if associative {
self.command_line.push_info("HATCHEDIT: hatch is already associative.");
self.tabs[i].active_cmd=None;
}else{
let plane=match self.tabs[i].scene.document.get_entity(handle) {
Some(acadrust::EntityType::Hatch(h))=>{
let storage=crate::entities::curve::ocs_plane(h.normal,h.elevation);
crate::command::WorkingPlane::new(glam::DVec3::from_array(storage.origin),glam::DVec3::from_array(storage.x_axis),glam::DVec3::from_array(storage.y_axis))
},
_=>self.tabs[i].ucs_xform().working_plane(),
};
let mut sources=self.tabs[i].scene.boundary_sources_on_plane(plane,1e-6);
sources.remove(&handle);
let command=crate::modules::draw::draw::hatchedit::HatcheditCommand::for_association(handle,name,scale,angle,plane,sources);
self.tabs[i].scene.deselect_all();
self.command_line.push_info(&command.prompt());
self.tabs[i].active_cmd=Some(Box::new(command));
}
return Task::none();
}
if let HatchEditOperation::DrawOrderBoundary{above}=&operation {
let references:Vec<_>=match self.tabs[i].scene.document.get_entity(handle) {
Some(acadrust::EntityType::Hatch(h))=>h.paths.iter().flat_map(|p|p.boundary_handles.iter())
.filter(|h|self.tabs[i].scene.document.get_entity(**h).is_some()).map(|h|format!("{:X}",h.value())).collect(),
_=>Vec::new(),
};
self.tabs[i].active_cmd=None;
if references.is_empty(){self.command_line.push_info("HATCHEDIT: no associated boundary objects.");return Task::none();}
self.tabs[i].scene.deselect_all();self.tabs[i].scene.select_entity(handle,false);
let command=format!("DRAWORDER {} {}",if *above{"ABOVE"}else{"UNDER"},references.join(" "));
return self.dispatch_view(&command,i).unwrap_or_else(Task::none);
}
if matches!(
&operation,
HatchEditOperation::DrawOrderFront | HatchEditOperation::DrawOrderBack
@@ -5182,12 +5216,38 @@ impl OpenCADStudio {
}
self.push_undo_snapshot(i, "HATCHEDIT");
match operation {
HatchEditOperation::Appearance { color, layer, transparency } => {
let layer=layer.map(|name|if name=="."{self.tabs[i].active_layer.clone()}else{name});
if layer.as_ref().is_some_and(|name|self.tabs[i].scene.document.layers.get(name).is_none()) {
self.discard_last_undo_entry(i);
self.command_line.push_error("HATCHEDIT: layer not found.");
return Task::none();
}
if let Some(entity)=self.tabs[i].scene.document.get_entity_mut(handle) {
let common=entity.common_mut();
if let Some(value)=color {common.color=value;common.color_name=None;common.color_book_handle=None;}
if let Some(value)=layer {common.layer=value;}
if let Some(value)=transparency {common.transparency=value;}
}
self.tabs[i].scene.bump_entities(&[(handle,crate::scene::ChangeKind::Modified)]);
self.refresh_properties();
}
HatchEditOperation::Update {
origin,
store_origin,
disassociate,
style,
annotative,
} => {
if store_origin {
if let Some((x, y)) = origin {
if !self.tabs[i].scene.document.set_hatch_origin([x, y]) {
self.discard_last_undo_entry(i);
self.command_line.push_error("HATCHEDIT: cannot store default origin.");
return Task::none();
}
}
}
if let Some(acadrust::EntityType::Hatch(hatch)) =
self.tabs[i].scene.document.get_entity_mut(handle)
{
@@ -5195,11 +5255,6 @@ impl OpenCADStudio {
if let Some(entry) =
crate::scene::model::hatch_patterns::find(&name)
{
let old_origin = hatch
.pattern
.lines
.first()
.map(|line| line.base_point);
let mut pattern = crate::scene::model::hatch_patterns::build_dxf_pattern(entry);
crate::entities::hatch::scale_pattern_geometry(
&mut pattern,
@@ -5209,16 +5264,8 @@ impl OpenCADStudio {
&mut pattern,
(angle as f64).to_radians(),
);
if let (Some(old), Some(new)) = (
old_origin,
pattern.lines.first().map(|line| line.base_point),
) {
crate::entities::hatch::translate_pattern_geometry(
&mut pattern,
old.x - new.x,
old.y - new.y,
);
}
let origin = hatch.pattern_origin();
crate::entities::hatch::translate_pattern_geometry(&mut pattern, origin.x, origin.y);
hatch.pattern = pattern;
hatch.is_solid = matches!(
entry.gpu,
@@ -5232,30 +5279,16 @@ impl OpenCADStudio {
let requested_scale = scale.max(1.0e-6) as f64;
if hatch.pattern_scale > 1.0e-12 {
let factor = requested_scale / hatch.pattern_scale;
crate::entities::hatch::scale_pattern_geometry(
&mut hatch.pattern,
factor,
);
hatch.scale_pattern_about_origin(factor);
}
let requested_angle = (angle as f64).to_radians();
let delta = requested_angle - hatch.pattern_angle;
crate::entities::hatch::rotate_pattern_geometry(
&mut hatch.pattern,
delta,
);
hatch.rotate_pattern_about_origin(delta);
}
hatch.pattern_scale = scale.max(1.0e-6) as f64;
hatch.pattern_angle = (angle as f64).to_radians();
if let Some((x, y)) = origin {
if let Some(current) =
hatch.pattern.lines.first().map(|line| line.base_point)
{
crate::entities::hatch::translate_pattern_geometry(
&mut hatch.pattern,
x - current.x,
y - current.y,
);
}
hatch.set_pattern_origin(acadrust::types::Vector2::new(x, y));
}
if disassociate {
for path in &mut hatch.paths {
@@ -5301,7 +5334,15 @@ impl OpenCADStudio {
.scene
.edit_hatch_boundary_handles(handle, &handles, false);
}
HatchEditOperation::RecreateBoundary => {
HatchEditOperation::AssociatePaths(paths) => {
if paths.is_empty()||paths.iter().any(|path|path.boundary_handles.is_empty()||path.boundary_handles.iter().any(|source|self.tabs[i].scene.document.get_entity(*source).is_none())) {
self.discard_last_undo_entry(i);
self.command_line.push_error("HATCHEDIT: associated boundary is no longer available.");
return Task::none();
}
self.tabs[i].scene.replace_hatch_association(handle,paths);
}
HatchEditOperation::RecreateBoundary { associate, region } => {
let source = self.tabs[i].scene.document.get_entity(handle).cloned();
if let Some(acadrust::EntityType::Hatch(source)) = source {
let storage = crate::entities::curve::ocs_plane(
@@ -5313,25 +5354,74 @@ impl OpenCADStudio {
glam::DVec3::from_array(storage.x_axis),
glam::DVec3::from_array(storage.y_axis),
);
let rings = crate::scene::hatch_boundary_rings(&source);
let entities = crate::scene::boundary_entities(&rings, plane);
let mut handles = Vec::new();
for entity in entities {
let (entities, path_groups) = if region {
let loops = source.paths.iter().map(|path| {
path.edges.iter().map(crate::entities::hatch::edge_curve).collect::<Option<Vec<_>>>()
}).collect::<Option<Vec<_>>>();
let Some(loops) = loops.filter(|loops| !loops.is_empty() && loops.iter().all(|ring| !ring.is_empty())) else {
self.discard_last_undo_entry(i);
self.command_line.push_error("HATCHEDIT: boundary cannot form a region.");
return Task::none();
};
let contained = loops.iter().enumerate().map(|(inner, ring)| {
let seed = ring[0].point_at(0.0);
loops.iter().enumerate().map(|(outer, boundary)| {
inner != outer && cadkernel::geom2d::contains(boundary, seed, cadkernel::geom2d::Tolerance::new(1e-6))
}).collect::<Vec<_>>()
}).collect::<Vec<_>>();
let depths = contained.iter().map(|row| row.iter().filter(|inside| **inside).count()).collect::<Vec<_>>();
let groups = depths.iter().enumerate().filter(|(_, depth)| **depth % 2 == 0).map(|(outer, depth)| {
let mut indices = vec![outer];
indices.extend((0..loops.len()).filter(|inner| depths[*inner] == *depth + 1 && contained[*inner][outer]));
indices
}).collect::<Vec<_>>();
let entities = groups.iter().map(|indices| {
let profiles = indices.iter().map(|index| loops[*index].clone()).collect::<Vec<_>>();
crate::scene::model::presspull_model::region_from_loops(&profiles, plane)
}).collect::<Option<Vec<_>>>();
let Some(entities) = entities else {
self.discard_last_undo_entry(i);
self.command_line.push_error("HATCHEDIT: boundary cannot form a region.");
return Task::none();
};
(entities, groups)
} else {
let rings = crate::scene::hatch_boundary_rings(&source);
if rings.len() != source.paths.len() {
self.discard_last_undo_entry(i);
self.command_line.push_error(
"HATCHEDIT: boundary cannot form a polyline.",
);
return Task::none();
}
let entities = crate::scene::boundary_entities(&rings, plane);
if entities.len() != source.paths.len() {
self.discard_last_undo_entry(i);
self.command_line.push_error(
"HATCHEDIT: boundary cannot form a polyline.",
);
return Task::none();
}
let groups = (0..entities.len()).map(|index| vec![index]).collect();
(entities, groups)
};
let mut path_handles = vec![None; source.paths.len()];
for (entity, paths) in entities.into_iter().zip(path_groups) {
if let Some(boundary) = self.commit_entity_handle(entity) {
handles.push(boundary);
for path in paths { if let Some(slot) = path_handles.get_mut(path) { *slot = Some(boundary); } }
}
}
if let Some(acadrust::EntityType::Hatch(hatch)) =
if associate { if let Some(acadrust::EntityType::Hatch(hatch)) =
self.tabs[i].scene.document.get_entity_mut(handle)
{
for (path, boundary) in
hatch.paths.iter_mut().zip(handles.iter().copied())
{
path.boundary_handles = vec![boundary];
path.flags.set_external(true);
for (path, boundary) in hatch.paths.iter_mut().zip(path_handles.iter().copied()) {
if let Some(boundary) = boundary {
path.boundary_handles = vec![boundary];
path.flags.set_external(true);
}
}
hatch.is_associative = !handles.is_empty();
}
hatch.is_associative = path_handles.iter().all(Option::is_some);
} }
self.tabs[i].scene.bump_entities(&[(
handle,
crate::scene::ChangeKind::Modified,
@@ -5360,7 +5450,9 @@ impl OpenCADStudio {
}
}
HatchEditOperation::DrawOrderFront
| HatchEditOperation::DrawOrderBack => unreachable!(),
| HatchEditOperation::DrawOrderBack
| HatchEditOperation::DrawOrderBoundary {..}
| HatchEditOperation::BeginAssociate => unreachable!(),
}
self.tabs[i].dirty = true;
self.command_line
+3 -3
View File
@@ -789,7 +789,7 @@ impl OpenCADStudio {
selected,
inherited,
plane,
);
).with_origin(self.tabs[i].scene.document.hatch_origin());
self.command_line.push_info(&new_cmd.prompt());
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
self.refresh_area_preview(i);
@@ -822,7 +822,7 @@ impl OpenCADStudio {
scale,
angle,
annotative,
);
).with_appearance(entity,self.tabs[i].scene.document.header.current_entity_color,self.tabs[i].scene.document.current_entity_transparency()).with_origin(self.tabs[i].scene.document.hatch_origin());
self.command_line.push_info(&cmd.prompt());
self.tabs[i].active_cmd = Some(Box::new(cmd));
} else {
@@ -830,7 +830,7 @@ impl OpenCADStudio {
.push_error(crate::t!("HATCHEDIT: selected entity is not a hatch.").as_ref());
}
} else {
let cmd = HatcheditCommand::new();
let cmd = HatcheditCommand::new().with_appearance(None,self.tabs[i].scene.document.header.current_entity_color,self.tabs[i].scene.document.current_entity_transparency()).with_origin(self.tabs[i].scene.document.hatch_origin());
self.command_line.push_info(&cmd.prompt());
self.tabs[i].active_cmd = Some(Box::new(cmd));
}
+11 -1
View File
@@ -1387,6 +1387,7 @@ impl OpenCADStudio {
crate::entities::names::dxf_name(e).to_string(),
c.layer.clone(),
c.color,
c.transparency,
c.linetype.clone(),
c.linetype_scale,
c.line_weight,
@@ -1398,7 +1399,7 @@ impl OpenCADStudio {
},
)
});
let Some((verb, kind, layer, color, linetype, lt_scale, lw, template_dimstyle)) = info
let Some((verb, kind, layer, color, transparency, linetype, lt_scale, lw, template_dimstyle)) = info
else {
self.command_line
.push_error(crate::t!("ADDSELECTED: selected object not found.").as_ref());
@@ -1422,6 +1423,7 @@ impl OpenCADStudio {
layer_name: self.tabs[i].scene.document.header.current_layer_name.clone(),
layer_handle: self.tabs[i].scene.document.header.current_layer_handle,
color: self.tabs[i].scene.document.header.current_entity_color,
transparency: self.tabs[i].scene.document.current_entity_transparency(),
linetype_name: self.tabs[i].scene.document.header.current_linetype_name.clone(),
linetype_handle: self.tabs[i].scene.document.header.current_linetype_handle,
line_weight: self.tabs[i].scene.document.header.current_line_weight,
@@ -1436,6 +1438,11 @@ impl OpenCADStudio {
ribbon_lineweight: self.ribbon.active_lineweight,
};
self.add_selected_restore = Some(restore);
if !self.tabs[i].scene.document.set_current_entity_transparency(transparency) {
self.add_selected_restore = None;
self.command_line.push_error("ADDSELECTED: template transparency cannot be adopted.");
return Task::none();
}
// Adopt the template's general properties as the current defaults. The
// entity-creation path stamps new objects from the tab's active layer
@@ -1518,6 +1525,9 @@ impl OpenCADStudio {
h.current_dimstyle_name = r.dimstyle_name;
h.current_dimstyle_handle = r.dimstyle_handle;
}
if !self.tabs[i].scene.document.set_current_entity_transparency(r.transparency) {
self.command_line.push_error("ADDSELECTED: current transparency could not be restored.");
}
self.tabs[i].active_layer = r.tab_active_layer;
self.tabs[i].layers.current_layer = r.tab_layers_current;
self.ribbon.active_layer = r.ribbon_layer;
+1
View File
@@ -369,6 +369,7 @@ inventory::submit!(crate::command::CommandRegistration {
"COLOR",
"COLOUR",
"CECOLOR",
"CETRANSPARENCY",
"DDCOLOR",
"BYLAYER",
// Synchronise block attributes.
+30 -1
View File
@@ -3,6 +3,10 @@ use super::*;
impl OpenCADStudio {
pub(super) fn dispatch_styleprops(&mut self, cmd: &str, i: usize) -> Option<Task<Message>> {
match cmd {
"CETRANSPARENCY" => return self.dispatch_styleprops("SETVAR CETRANSPARENCY", i),
cmd if cmd.starts_with("CETRANSPARENCY ") => {
return self.dispatch_styleprops(&format!("SETVAR {cmd}"), i);
}
"FRAMES0" => return self.dispatch_styleprops("SETVAR FRAME 0", i),
"FRAMES1" => return self.dispatch_styleprops("SETVAR FRAME 1", i),
"FRAMES2" => return self.dispatch_styleprops("SETVAR FRAME 2", i),
@@ -1064,9 +1068,34 @@ 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: LTSCALE CELTSCALE PDMODE PDSIZE TEXTSIZE ORTHOMODE FILLMODE MIRRTEXT FRAME IMAGEFRAME PDFFRAME WIPEOUTFRAME XCLIPFRAME POINTCLOUDCLIPFRAME ZOOMWHEEL ZOOMFACTOR CURSORSIZE PICKBOX CURSORTYPE SNAPANG TEXTFILL CLIPROMPTLINES COMMANDLINEFADETIME ATTREQ ATTDIA DIMASSOC DIMCONTINUEMODE 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 CURSORSIZE PICKBOX CURSORTYPE SNAPANG TEXTFILL CLIPROMPTLINES COMMANDLINEFADETIME ATTREQ ATTDIA DIMASSOC DIMCONTINUEMODE 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" {
let current = self.tabs[i].scene.document.current_entity_transparency();
if let Some(value) = &value {
match crate::scene::creation_style::parse_current_transparency(value) {
Some(transparency) => {
if current != transparency {
self.push_undo_snapshot(i, &name);
if !self.tabs[i].scene.document.set_current_entity_transparency(transparency) {
self.discard_last_undo_entry(i);
self.command_line.push_error("CETRANSPARENCY: drawing variable dictionary is invalid.");
return Some(self.finish_dispatch(cmd));
}
self.tabs[i].dirty = true;
self.refresh_properties();
}
self.command_line.push_output(&format!("CETRANSPARENCY = {}", crate::scene::creation_style::current_transparency_label(transparency)));
}
None => self.command_line.push_error("CETRANSPARENCY: expected ByLayer (-1), ByBlock (-2), or an integer from 0 to 90."),
}
} else {
self.command_line.push_output(&format!("Enter new value for CETRANSPARENCY <{}>:", crate::scene::creation_style::current_transparency_label(current)));
self.pending_setvar = Some(name.clone());
}
return Some(self.finish_dispatch(cmd));
}
if matches!(name.as_str(), "SHOWHIST" | "SOLIDHIST") {
let current = if name == "SHOWHIST" {
self.tabs[i].scene.document.header.show_solid_history.clamp(0, 2)
+1
View File
@@ -305,6 +305,7 @@ struct AddSelectedRestore {
layer_name: String,
layer_handle: acadrust::types::Handle,
color: AcadColor,
transparency: acadrust::types::Transparency,
linetype_name: String,
linetype_handle: acadrust::types::Handle,
line_weight: i16,
+1 -1
View File
@@ -306,7 +306,7 @@ impl OpenCADStudio {
label: t!("Transparency").into_owned(),
field: "transparency",
value: PropValue::EditChoice {
value: "ByLayer".to_string(),
value: crate::scene::creation_style::current_transparency_label(self.tabs[i].scene.document.current_entity_transparency()),
options: vec!["ByLayer".to_string(), "ByBlock".to_string()],
},
},
+24 -15
View File
@@ -2000,9 +2000,6 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
if let Some(acadrust::EntityType::Hatch(dxf)) =
self.tabs[i].scene.document.get_entity_mut(handle)
{
let old_origin = dxf.pattern.lines.first().map(|line| {
(line.base_point.x, line.base_point.y)
});
let mut pattern = hatch_patterns::build_dxf_pattern(entry);
// Stored pattern lines are final world-space
// geometry. Preserve the selected hatch's scale,
@@ -2016,17 +2013,8 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
&mut pattern,
dxf.pattern_angle,
);
if let (Some((old_x, old_y)), Some(new_origin)) =
(old_origin, pattern.lines.first())
{
let dx = old_x - new_origin.base_point.x;
let dy = old_y - new_origin.base_point.y;
crate::entities::hatch::translate_pattern_geometry(
&mut pattern,
dx,
dy,
);
}
let origin = dxf.pattern_origin();
crate::entities::hatch::translate_pattern_geometry(&mut pattern, origin.x, origin.y);
dxf.pattern = pattern;
dxf.is_solid = matches!(
entry.gpu,
@@ -2811,7 +2799,28 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
} else {
match field {
"transparency" => {
self.tabs[i].dirty = true;
let Some(transparency) = crate::scene::creation_style::parse_current_transparency(&value) else {
self.command_line.push_error("Transparency: expected ByLayer, ByBlock, or an integer from 0 to 90.");
self.refresh_properties();
return Task::none();
};
if self.tabs[i].scene.document.current_entity_transparency()
!= transparency
{
self.push_undo_snapshot(i, "CETRANSPARENCY");
if self.tabs[i]
.scene
.document
.set_current_entity_transparency(transparency)
{
self.tabs[i].dirty = true;
} else {
self.discard_last_undo_entry(i);
self.command_line.push_error(
"CETRANSPARENCY: drawing variable dictionary is invalid.",
);
}
}
self.refresh_properties();
}
"material" => {
+16 -7
View File
@@ -3870,13 +3870,22 @@ impl OpenCADStudio {
};
use crate::command::CadCommand;
use crate::modules::draw::draw::hatchedit::HatcheditCommand;
let cmd: Box<dyn CadCommand> = Box::new(HatcheditCommand::with_handle(
handle,
model.name.clone(),
scale,
angle,
annotative,
));
let current_color =
self.tabs[i].scene.document.header.current_entity_color;
let current_transparency =
self.tabs[i].scene.document.current_entity_transparency();
let current_origin = self.tabs[i].scene.document.hatch_origin();
let cmd: Box<dyn CadCommand> = Box::new(
HatcheditCommand::with_handle(
handle,
model.name.clone(),
scale,
angle,
annotative,
)
.with_appearance(entity, current_color, current_transparency)
.with_origin(current_origin),
);
self.command_line.push_info(&cmd.prompt());
self.tabs[i].active_cmd = Some(cmd);
} else {