forked from 1b-agent/opencadstudio-upstream
Merge pull request #852 from ramox81/codex/complete-hatch-workflow
This commit is contained in:
commit
bd5f9436ea
18 files changed
+1537
-481
No files matched your search
+241
-39
@@ -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;
|
||||
|
||||
@@ -691,8 +691,26 @@ impl OpenCADStudio {
|
||||
|
||||
"HATCH" => {
|
||||
use crate::modules::draw::draw::hatch::HatchCommand;
|
||||
let outlines = self.tabs[i].scene.hatch_boundary_outlines();
|
||||
let boundary_sources = self.tabs[i].scene.hatch_boundary_sources();
|
||||
let working_plane = if self.tabs[i].editing_model_space() {
|
||||
self.tabs[i].ucs_xform().working_plane()
|
||||
} else {
|
||||
crate::command::WorkingPlane::default()
|
||||
};
|
||||
let normal = working_plane.z.normalize_or(glam::DVec3::Z);
|
||||
let elevation = working_plane.origin.dot(normal);
|
||||
let storage = crate::entities::curve::ocs_plane(
|
||||
acadrust::types::Vector3::new(normal.x, normal.y, normal.z),
|
||||
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 boundary_sources = self.tabs[i]
|
||||
.scene
|
||||
.boundary_sources_on_plane(plane, 1.0e-6);
|
||||
let outlines = crate::scene::boundary_faces(&boundary_sources, 1.0e-6);
|
||||
let selected = self.tabs[i]
|
||||
.scene
|
||||
.selected_entities()
|
||||
@@ -706,8 +724,13 @@ impl OpenCADStudio {
|
||||
let common = self.tabs[i].scene.document.get_entity(*handle)?.common();
|
||||
Some((model, common.color.clone(), common.transparency))
|
||||
});
|
||||
let new_cmd =
|
||||
HatchCommand::new(outlines, boundary_sources, selected, inherited);
|
||||
let new_cmd = HatchCommand::new(
|
||||
outlines,
|
||||
boundary_sources,
|
||||
selected,
|
||||
inherited,
|
||||
plane,
|
||||
);
|
||||
self.command_line.push_info(&new_cmd.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
||||
self.refresh_area_preview(i);
|
||||
@@ -720,11 +743,26 @@ impl OpenCADStudio {
|
||||
if sel.len() == 1 {
|
||||
let (h, _) = sel[0];
|
||||
if let Some(model) = self.tabs[i].scene.hatches.get(&h).cloned() {
|
||||
let entity = self.tabs[i].scene.document.get_entity(h);
|
||||
let annotative = entity.is_some_and(|entity| {
|
||||
crate::scene::annotative::is_annotative(
|
||||
&self.tabs[i].scene.document,
|
||||
entity,
|
||||
)
|
||||
});
|
||||
let (scale, angle) = match entity {
|
||||
Some(acadrust::EntityType::Hatch(hatch)) => (
|
||||
hatch.pattern_scale as f32,
|
||||
hatch.pattern_angle.to_degrees() as f32,
|
||||
),
|
||||
_ => (model.scale, model.angle_offset.to_degrees()),
|
||||
};
|
||||
let cmd = HatcheditCommand::with_handle(
|
||||
h,
|
||||
model.name.clone(),
|
||||
model.scale,
|
||||
model.angle_offset,
|
||||
scale,
|
||||
angle,
|
||||
annotative,
|
||||
);
|
||||
self.command_line.push_info(&cmd.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(cmd));
|
||||
@@ -741,8 +779,10 @@ impl OpenCADStudio {
|
||||
|
||||
"GRADIENT" => {
|
||||
use crate::modules::draw::draw::hatch::GradientCommand;
|
||||
let outlines = self.tabs[i].scene.hatch_boundary_outlines();
|
||||
let boundary_sources = self.tabs[i].scene.hatch_boundary_sources();
|
||||
let boundary_sources = self.tabs[i]
|
||||
.scene
|
||||
.boundary_sources_on_plane(crate::command::WorkingPlane::default(), 1.0e-6);
|
||||
let outlines = crate::scene::boundary_faces(&boundary_sources, 1.0e-6);
|
||||
let new_cmd = GradientCommand::new(outlines, boundary_sources);
|
||||
self.command_line.push_info(&new_cmd.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use super::*;
|
||||
|
||||
impl OpenCADStudio {
|
||||
pub(super) fn dispatch_view(&mut self, cmd: &str, i: usize) -> Option<Task<Message>> {
|
||||
pub(crate) fn dispatch_view(&mut self, cmd: &str, i: usize) -> Option<Task<Message>> {
|
||||
match cmd {
|
||||
"DONATE" => {
|
||||
self.command_line.push_info(crate::t!("Opening Patreon page...").as_ref());
|
||||
|
||||
@@ -2092,6 +2092,23 @@ pub(super) fn aggregate_sections(
|
||||
for sections in all_sections {
|
||||
result = merge_sections(&result, §ions);
|
||||
}
|
||||
// Sum the filled area while individual Area rows may still vary.
|
||||
if selected.len() > 1
|
||||
&& selected
|
||||
.iter()
|
||||
.all(|(_, entity)| matches!(entity, acadrust::EntityType::Hatch(_)))
|
||||
{
|
||||
let total = selected
|
||||
.iter()
|
||||
.filter_map(|(_, entity)| match entity {
|
||||
acadrust::EntityType::Hatch(hatch) => {
|
||||
Some(crate::entities::hatch::boundary_area(hatch))
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.sum::<f64>();
|
||||
set_row(&mut result, "cumulative_area", format!("{total:.4}"));
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
|
||||
@@ -1726,6 +1726,9 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
||||
entry.gpu,
|
||||
crate::scene::model::hatch_model::HatchPattern::Solid
|
||||
);
|
||||
dxf.pattern_type =
|
||||
acadrust::entities::HatchPatternType::Predefined;
|
||||
dxf.gradient_color.enabled = false;
|
||||
}
|
||||
if let Some(model) = self.tabs[i].scene.hatches.get_mut(&handle) {
|
||||
model.pattern = entry.gpu.clone();
|
||||
|
||||
@@ -3490,13 +3490,28 @@ impl OpenCADStudio {
|
||||
.unwrap_or(false)
|
||||
{
|
||||
if let Some(model) = self.tabs[i].scene.hatches.get(&handle).cloned() {
|
||||
let entity = self.tabs[i].scene.document.get_entity(handle);
|
||||
let annotative = entity.is_some_and(|entity| {
|
||||
crate::scene::annotative::is_annotative(
|
||||
&self.tabs[i].scene.document,
|
||||
entity,
|
||||
)
|
||||
});
|
||||
let (scale, angle) = match entity {
|
||||
Some(acadrust::EntityType::Hatch(hatch)) => (
|
||||
hatch.pattern_scale as f32,
|
||||
hatch.pattern_angle.to_degrees() as f32,
|
||||
),
|
||||
_ => (model.scale, model.angle_offset.to_degrees()),
|
||||
};
|
||||
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(),
|
||||
model.scale,
|
||||
model.angle_offset,
|
||||
scale,
|
||||
angle,
|
||||
annotative,
|
||||
));
|
||||
self.command_line.push_info(&cmd.prompt());
|
||||
self.tabs[i].active_cmd = Some(cmd);
|
||||
|
||||
Reference in new issue
Block a user