forked from 1b-agent/opencadstudio-upstream
ADJUST: images and underlays, -IMAGEADJUST and PDF/DWF/DGNADJUST
ADJUST gathers raster images or PDF/DWF/DGN underlays (other objects are dropped silently, pickfirst is used, a mixed set is refused), then asks the image option (Brightness default) or the underlay option (last used), with value prompts that offer the image's own value or the session's last underlay fade/contrast, and Monochrome Yes/No. Invalid values re-prompt. One undo step. -IMAGEADJUST, PDFADJUST, DWFADJUST and DGNADJUST take one kind each. Raster image Brightness/Contrast/Fade move to an Image Adjust section as whole numbers with 0-100 validation. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
b1d4c9bd9b
commit
a0a83ad681
32 files changed
+841
-311
No files matched your search
@@ -507,6 +507,32 @@ impl OpenCADStudio {
|
||||
StepInput::SelectionComplete(kept)
|
||||
}
|
||||
|
||||
/// Keeps only the kinds of object the active command takes, dropping
|
||||
/// the rest of a completed selection silently.
|
||||
fn filter_command_entity_selection(&mut self, input: StepInput) -> StepInput {
|
||||
let StepInput::SelectionComplete(handles) = input else {
|
||||
return input;
|
||||
};
|
||||
let i = self.active_tab;
|
||||
let Some(keep) = self.tabs[i]
|
||||
.active_cmd
|
||||
.as_ref()
|
||||
.and_then(|command| command.selection_entity_filter())
|
||||
else {
|
||||
return StepInput::SelectionComplete(handles);
|
||||
};
|
||||
let (kept, dropped): (Vec<Handle>, Vec<Handle>) = handles
|
||||
.into_iter()
|
||||
.partition(|h| self.tabs[i].scene.document.get_entity(*h).is_some_and(keep));
|
||||
for handle in &dropped {
|
||||
self.tabs[i].scene.deselect_entity(*handle);
|
||||
}
|
||||
if !dropped.is_empty() {
|
||||
self.refresh_properties();
|
||||
}
|
||||
StepInput::SelectionComplete(kept)
|
||||
}
|
||||
|
||||
/// Drops locked point clouds from a completed selection when the active
|
||||
/// command would move them, saying which were locked and how many went.
|
||||
fn filter_locked_point_cloud_selection(&mut self, input: StepInput) -> StepInput {
|
||||
@@ -562,6 +588,7 @@ impl OpenCADStudio {
|
||||
let input = self.filter_associative_dimension_selection(input);
|
||||
let input = self.filter_block_reference_selection(input);
|
||||
let input = self.filter_locked_point_cloud_selection(input);
|
||||
let input = self.filter_command_entity_selection(input);
|
||||
// XCLIP / CLIP learn which chosen references carry a clip already.
|
||||
{
|
||||
let i = self.active_tab;
|
||||
|
||||
+130
-91
@@ -1381,101 +1381,120 @@ impl OpenCADStudio {
|
||||
return Some(Task::none());
|
||||
}
|
||||
|
||||
// ADJUST — set brightness / contrast / fade on selected raster images
|
||||
// ADJUST BRIGHTNESS|CONTRAST|FADE <0-100>
|
||||
"ADJUST" => {
|
||||
use crate::command::SelectThenKeywordCommand;
|
||||
let has_sel = !self.tabs[i].scene.selected_entities().is_empty();
|
||||
let c = SelectThenKeywordCommand::new(
|
||||
"ADJUST",
|
||||
"ADJUST [Brightness / Contrast / Fade]:",
|
||||
vec![
|
||||
(
|
||||
"Brightness",
|
||||
"BRIGHTNESS",
|
||||
Some("ADJUST brightness 0-100:"),
|
||||
),
|
||||
("Contrast", "CONTRAST", Some("ADJUST contrast 0-100:")),
|
||||
("Fade", "FADE", Some("ADJUST fade 0-100:")),
|
||||
],
|
||||
has_sel,
|
||||
);
|
||||
self.command_line.push_info(&c.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(c));
|
||||
}
|
||||
cmd if cmd.starts_with("ADJUST ") => {
|
||||
let rest = cmd.trim_start_matches("ADJUST").trim();
|
||||
let parts: Vec<&str> = rest.splitn(2, char::is_whitespace).collect();
|
||||
let action = parts.first().map(|s| s.to_uppercase()).unwrap_or_default();
|
||||
let arg = parts.get(1).copied().unwrap_or("").trim();
|
||||
let handles: Vec<codec::Handle> = self.tabs[i]
|
||||
// ADJUST / -IMAGEADJUST / PDFADJUST / DWFADJUST / DGNADJUST:
|
||||
// chosen images or underlays (pickfirst first), then the option
|
||||
// and the value.
|
||||
"ADJUST" | "-IMAGEADJUST" | "PDFADJUST" | "DWFADJUST" | "DGNADJUST" => {
|
||||
let mode = match cmd {
|
||||
"-IMAGEADJUST" => "I",
|
||||
"PDFADJUST" => "P",
|
||||
"DWFADJUST" => "W",
|
||||
"DGNADJUST" => "G",
|
||||
_ => "A",
|
||||
};
|
||||
let keep = crate::modules::insert::xadjust::keeps(mode);
|
||||
let chosen: Vec<codec::Handle> = self.tabs[i]
|
||||
.scene
|
||||
.selected_entities()
|
||||
.iter()
|
||||
.map(|(h, _)| *h)
|
||||
.filter(|handle| !self.tabs[i].scene.is_layer_locked(*handle))
|
||||
.into_iter()
|
||||
.filter(|(_, e)| keep(e))
|
||||
.map(|(h, _)| h)
|
||||
.collect();
|
||||
if handles.is_empty() {
|
||||
self.command_line
|
||||
.push_error(crate::t!("ADJUST: select raster image(s) first.").as_ref());
|
||||
} else if action.is_empty() {
|
||||
self.command_line.push_info(
|
||||
crate::t!("Usage: ADJUST BRIGHTNESS|CONTRAST|FADE <0-100>").as_ref(),
|
||||
);
|
||||
} else if let Ok(v) = arg.parse::<u8>() {
|
||||
let v = v.min(100);
|
||||
self.push_undo_snapshot(i, "ADJUST");
|
||||
let mut changed = 0usize;
|
||||
let mut changed_handles = Vec::new();
|
||||
for h in &handles {
|
||||
if let Some(codec::EntityType::RasterImage(img)) = self.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.entities_mut()
|
||||
.find(|e| e.common().handle == *h)
|
||||
{
|
||||
match action.as_str() {
|
||||
"BRIGHTNESS" => {
|
||||
img.brightness = v;
|
||||
changed += 1;
|
||||
changed_handles.push(*h);
|
||||
}
|
||||
"CONTRAST" => {
|
||||
img.contrast = v;
|
||||
changed += 1;
|
||||
changed_handles.push(*h);
|
||||
}
|
||||
"FADE" => {
|
||||
img.fade = v;
|
||||
changed += 1;
|
||||
changed_handles.push(*h);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
if changed > 0 {
|
||||
self.tabs[i].dirty = true;
|
||||
for &handle in &changed_handles {
|
||||
self.tabs[i].scene.reseed_derived_caches(handle);
|
||||
}
|
||||
let changes: Vec<_> = changed_handles
|
||||
.into_iter()
|
||||
.map(|handle| (handle, crate::scene::ChangeKind::Modified))
|
||||
.collect();
|
||||
self.tabs[i].scene.bump_entities(&changes);
|
||||
self.command_line.push_output(
|
||||
crate::tf!("ADJUST: {action} = {v} on {changed} image(s).").as_ref(),
|
||||
);
|
||||
} else {
|
||||
self.command_line.push_error(
|
||||
crate::t!("ADJUST: no raster images selected, or unknown property (use BRIGHTNESS|CONTRAST|FADE).").as_ref(),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
self.command_line
|
||||
.push_error(crate::t!("ADJUST: value must be 0-100.").as_ref());
|
||||
if !chosen.is_empty() {
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.replace_selection(chosen.into_iter().collect());
|
||||
return Some(self.dispatch_command(&format!("_ADJUSTSEL {mode}")));
|
||||
}
|
||||
self.start_adjust_selection(i, cmd, mode);
|
||||
}
|
||||
// The gathered objects: one kind at a time, then the option.
|
||||
c if c.starts_with("_ADJUSTSEL ") => {
|
||||
use crate::command::CadCommand;
|
||||
use crate::modules::insert::xadjust::{self, AdjustCommand};
|
||||
let mode = c["_ADJUSTSEL ".len()..].trim().to_string();
|
||||
let keep = xadjust::keeps(&mode);
|
||||
let name = xadjust::command_name(&mode);
|
||||
let objects: Vec<(codec::Handle, codec::EntityType)> = self.tabs[i]
|
||||
.scene
|
||||
.selected_entities()
|
||||
.into_iter()
|
||||
.filter(|(h, e)| keep(e) && !self.tabs[i].scene.is_layer_locked(*h))
|
||||
.map(|(h, e)| (h, e.clone()))
|
||||
.collect();
|
||||
match AdjustCommand::for_objects(&objects) {
|
||||
None => {}
|
||||
Some(Err(message)) => {
|
||||
self.command_line.push_error(crate::t!(message).as_ref());
|
||||
self.start_adjust_selection(i, name, &mode);
|
||||
}
|
||||
Some(Ok(command)) => {
|
||||
self.command_line.push_info(&command.prompt());
|
||||
self.command_line.set_step_options(command.options());
|
||||
self.tabs[i].active_cmd = Some(Box::new(command));
|
||||
}
|
||||
}
|
||||
// Repeating runs the command, not this step.
|
||||
return Some(self.finish_dispatch(name));
|
||||
}
|
||||
// `_ADJUSTAPPLY <BRIGHTNESS|CONTRAST|FADE|MONOCHROME> <value> <handles…>`
|
||||
// writes the value to every listed image or underlay, one undo step.
|
||||
c if c.starts_with("_ADJUSTAPPLY ") => {
|
||||
let mut parts = c.split_whitespace().skip(1);
|
||||
let prop = parts.next().unwrap_or("").to_string();
|
||||
let value: u8 = parts.next().and_then(|v| v.parse().ok()).unwrap_or(0);
|
||||
let handles: Vec<codec::Handle> = parts
|
||||
.filter_map(|h| u64::from_str_radix(h, 16).ok())
|
||||
.map(codec::Handle::new)
|
||||
.collect();
|
||||
self.push_undo_snapshot(i, "ADJUST");
|
||||
let mut changed = Vec::new();
|
||||
for h in &handles {
|
||||
let done = match self.tabs[i].scene.document.get_entity_mut(*h) {
|
||||
Some(codec::EntityType::RasterImage(img)) => match prop.as_str() {
|
||||
"BRIGHTNESS" => {
|
||||
img.brightness = value;
|
||||
true
|
||||
}
|
||||
"CONTRAST" => {
|
||||
img.contrast = value;
|
||||
true
|
||||
}
|
||||
"FADE" => {
|
||||
img.fade = value;
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
},
|
||||
Some(codec::EntityType::Underlay(u)) => match prop.as_str() {
|
||||
"CONTRAST" => {
|
||||
u.set_contrast(value);
|
||||
true
|
||||
}
|
||||
"FADE" => {
|
||||
u.set_fade(value);
|
||||
true
|
||||
}
|
||||
"MONOCHROME" => {
|
||||
u.set_monochrome(value != 0);
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
},
|
||||
_ => false,
|
||||
};
|
||||
if done {
|
||||
self.tabs[i].scene.reseed_derived_caches(*h);
|
||||
changed.push((*h, crate::scene::ChangeKind::Modified));
|
||||
}
|
||||
}
|
||||
if !changed.is_empty() {
|
||||
self.tabs[i].scene.bump_entities(&changed);
|
||||
self.tabs[i].dirty = true;
|
||||
}
|
||||
// The command used the set up; nothing stays chosen.
|
||||
self.tabs[i].scene.deselect_all();
|
||||
self.refresh_properties();
|
||||
}
|
||||
|
||||
// ANNOSCALE / CANNOSCALE <ratio> — set the current annotation scale
|
||||
@@ -2206,6 +2225,26 @@ pub(crate) fn table_to_csv(table: &codec::entities::Table) -> String {
|
||||
.join("\r\n")
|
||||
}
|
||||
|
||||
impl OpenCADStudio {
|
||||
/// The ADJUST family's object prompt, keeping only the kinds `mode`
|
||||
/// takes (see `xadjust::keeps`).
|
||||
fn start_adjust_selection(&mut self, i: usize, name: &str, mode: &str) {
|
||||
use crate::command::CadCommand;
|
||||
use crate::modules::insert::xadjust;
|
||||
self.tabs[i].scene.deselect_all();
|
||||
self.refresh_properties();
|
||||
let command = crate::modules::draw::select::SelectObjectsCommand::filtered(
|
||||
name,
|
||||
&format!("_ADJUSTSEL {mode}"),
|
||||
xadjust::select_prompt(mode),
|
||||
xadjust::keeps(mode),
|
||||
);
|
||||
self.command_line.push_info(&command.prompt());
|
||||
self.command_line.set_step_options(command.options());
|
||||
self.tabs[i].active_cmd = Some(Box::new(command));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::app::OpenCADStudio;
|
||||
|
||||
@@ -3809,7 +3809,9 @@ impl OpenCADStudio {
|
||||
return Task::none();
|
||||
}
|
||||
}
|
||||
if field.starts_with("ul_") {
|
||||
if field.starts_with("ul_")
|
||||
|| matches!(field, "ri_bright" | "ri_contrast" | "ri_fade")
|
||||
{
|
||||
if let Err(message) = crate::entities::underlay::validate_property(field, &val)
|
||||
{
|
||||
self.command_line.push_error(message);
|
||||
|
||||
Reference in new issue
Block a user