Merge pull request #1751 from ramox81/codex/adjust-parity

ADJUST: images and underlays, -IMAGEADJUST and PDF/DWF/DGNADJUST
This commit is contained in:
Hakan Seven committed 2026-10-14 08:42:35 +03:00
commit f6fc244430
33 files changed
+903 -312

No files matched your search

+27
View File
@@ -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
View File
@@ -1391,101 +1391,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
@@ -2216,6 +2235,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;
+3 -1
View File
@@ -3911,7 +3911,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);