Merge pull request #852 from ramox81/codex/complete-hatch-workflow

This commit is contained in:
Hakan Seven committed 2026-08-21 21:02:14 +03:00
commit bd5f9436ea
18 files changed
+1537 -481

No files matched your search

+241 -39
View File
@@ -1375,6 +1375,13 @@ impl OpenCADStudio {
.collect::<Vec<_>>();
sources.push(handles);
}
if let Some(paths) = hatch.boundary_paths.as_mut() {
for (path, handles) in std::sync::Arc::make_mut(paths).iter_mut().zip(&sources)
{
path.boundary_handles = handles.clone();
path.flags.set_external(!handles.is_empty());
}
}
hatch.boundary_sources = Some(std::sync::Arc::new(sources));
let layer = self.tabs[i].active_layer.clone();
let new_handle =
@@ -1394,6 +1401,33 @@ impl OpenCADStudio {
self.commit_undo_delta(i, pd);
}
}
CmdResult::CommitHatches {
hatches,
entity_style,
} => {
let label = self.history_label_from_active_cmd(i, "HATCH");
let pending = self.begin_undo(i, label, hatches.len(), true);
let layer = self.tabs[i].active_layer.clone();
for hatch in hatches {
let new_handle = self.tabs[i].scene.add_hatch(
hatch,
Some(&layer),
entity_style.clone(),
);
if !new_handle.is_null() {
self.tabs[i].scene.select_entity(new_handle, true);
}
}
self.tabs[i].dirty = true;
self.tabs[i].scene.clear_preview_wire();
self.tabs[i].active_cmd = None;
self.tabs[i].snap_result = None;
self.restore_pre_cmd_tangent();
self.refresh_properties();
if let Some(pd) = pending {
self.commit_undo_delta(i, pd);
}
}
CmdResult::BatchCopy(mut handles, transforms) => {
handles.retain(|handle| !self.tabs[i].scene.is_layer_locked(*handle));
if handles.is_empty() {
@@ -1612,10 +1646,7 @@ impl OpenCADStudio {
.into_iter()
.map(|e| self.tabs[i].scene.add_entity(e))
.collect();
// A replaced dimension carries edited geometry/text but still
// names its old *D block; drop that stale block so the next save
// re-bakes it — otherwise BricsCAD/ODA draw the pre-edit
// graphics while OCS shows the edit. (#181)
// Rebuild replaced dimensions from edited data.
for &nh in &new_handles {
if matches!(
self.tabs[i].scene.document.get_entity(nh),
@@ -1925,11 +1956,6 @@ impl OpenCADStudio {
}
// The command stays active after each apply so more targets
// can keep being picked; Enter / Esc ends it (#362).
// Special (type-specific) properties travel like AutoCAD's
// Special Properties: each is captured from the source when it
// carries it and applied only to destinations that support it
// (#281). Text formatting crosses TEXT ↔ MTEXT (#361); the dim
// style crosses Dimension / Leader / Tolerance.
let src_clone = self.tabs[i].scene.document.get_entity(src).cloned();
let src_common = src_clone.as_ref().map(|e| e.common().clone());
let thickness = src_clone
@@ -3223,42 +3249,218 @@ impl OpenCADStudio {
name,
scale,
angle,
operation,
} => {
if self.reject_locked_edit(i, handle) {
return Task::none();
}
if let Some(mut model) = self.tabs[i].scene.hatches.get(&handle).cloned() {
let layer = self.tabs[i]
.scene
.document
.get_entity(handle)
.map(|entity| entity.as_entity().layer().to_string())
.unwrap_or_else(|| "0".to_string());
// Update model fields
if !name.is_empty() {
use crate::scene::model::hatch_model::HatchPattern;
use crate::scene::model::hatch_patterns;
model.name = name.clone();
if name.to_uppercase() == "SOLID" {
model.pattern = HatchPattern::Solid;
} else if let Some(entry) = hatch_patterns::find(&name) {
model.pattern = entry.gpu.clone();
}
// If not found in catalog, keep existing pattern type
}
model.scale = scale;
model.angle_offset = angle;
self.push_undo_snapshot(i, "HATCHEDIT");
// Remove old hatch (entity + GPU model)
self.tabs[i].scene.erase_entities(&[handle]);
// Re-add with updated model
self.tabs[i].scene.add_hatch(model, Some(&layer), None);
self.tabs[i].dirty = true;
self.command_line.push_output(crate::t!("HATCHEDIT: hatch updated.").as_ref());
} else {
if !matches!(
self.tabs[i].scene.document.get_entity(handle),
Some(acadrust::EntityType::Hatch(_))
) {
self.command_line
.push_error(crate::t!("HATCHEDIT: hatch entity not found.").as_ref());
} else {
use crate::command::HatchEditOperation;
if matches!(
&operation,
HatchEditOperation::DrawOrderFront | HatchEditOperation::DrawOrderBack
) {
let command = if matches!(&operation, HatchEditOperation::DrawOrderFront) {
"DRAWORDER FRONT"
} else {
"DRAWORDER BACK"
};
self.tabs[i].scene.deselect_all();
self.tabs[i].scene.select_entity(handle, false);
self.tabs[i].active_cmd = None;
return self.dispatch_view(command, i).unwrap_or_else(Task::none);
}
self.push_undo_snapshot(i, "HATCHEDIT");
match operation {
HatchEditOperation::Update {
origin,
disassociate,
style,
annotative,
} => {
if let Some(acadrust::EntityType::Hatch(hatch)) =
self.tabs[i].scene.document.get_entity_mut(handle)
{
if !name.is_empty() && name != hatch.pattern.name {
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,
scale.max(1.0e-6) as f64,
);
crate::entities::hatch::rotate_pattern_geometry(
&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,
);
}
hatch.pattern = pattern;
hatch.is_solid = matches!(
entry.gpu,
crate::scene::model::hatch_model::HatchPattern::Solid
);
hatch.pattern_type =
acadrust::entities::HatchPatternType::Predefined;
hatch.gradient_color.enabled = false;
}
} else {
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,
);
}
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.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,
);
}
}
if disassociate {
for path in &mut hatch.paths {
path.boundary_handles.clear();
path.flags.set_external(false);
}
hatch.is_associative = false;
}
if let Some(style) = style {
hatch.style = style;
}
}
if let Some(value) = annotative {
crate::scene::annotative::set_entity_annotative(
&mut self.tabs[i].scene.document,
handle,
value,
);
if value {
if let Some(scale_handle) =
self.tabs[i].scene.creation_annotation_scale_handle()
{
crate::scene::annotative::create_annotation_context(
&mut self.tabs[i].scene.document,
handle,
scale_handle,
);
}
}
}
self.tabs[i].scene.bump_entities(&[(
handle,
crate::scene::ChangeKind::Modified,
)]);
}
HatchEditOperation::AddBoundaries(handles) => {
self.tabs[i]
.scene
.edit_hatch_boundary_handles(handle, &handles, true);
}
HatchEditOperation::RemoveBoundaries(handles) => {
self.tabs[i]
.scene
.edit_hatch_boundary_handles(handle, &handles, false);
}
HatchEditOperation::RecreateBoundary => {
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(
source.normal,
source.elevation,
);
let plane = crate::command::WorkingPlane::new(
glam::DVec3::from_array(storage.origin),
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 {
if let Some(boundary) = self.commit_entity_handle(entity) {
handles.push(boundary);
}
}
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);
}
hatch.is_associative = !handles.is_empty();
}
self.tabs[i].scene.bump_entities(&[(
handle,
crate::scene::ChangeKind::Modified,
)]);
}
}
HatchEditOperation::Separate => {
let source = self.tabs[i].scene.document.get_entity(handle).cloned();
if let Some(acadrust::EntityType::Hatch(hatch)) = source {
let groups = crate::scene::separated_hatch_path_groups(&hatch);
if groups.len() > 1 {
for paths in groups {
let mut separated = hatch.clone();
separated.common.handle = acadrust::Handle::NULL;
separated.paths = paths;
separated.is_associative = separated
.paths
.iter()
.any(|path| !path.boundary_handles.is_empty());
self.tabs[i]
.scene
.add_entity(acadrust::EntityType::Hatch(separated));
}
self.tabs[i].scene.erase_entities(&[handle]);
}
}
}
HatchEditOperation::DrawOrderFront
| HatchEditOperation::DrawOrderBack => unreachable!(),
}
self.tabs[i].dirty = true;
self.command_line
.push_output(crate::t!("HATCHEDIT: hatch updated.").as_ref());
}
self.tabs[i].active_cmd = None;
self.tabs[i].snap_result = None;