Merge branch 'main' into feature/parametric-constraint-system

# Conflicts:
#	src/app/automation.rs
#	src/app/mod.rs
#	src/ui/ribbon/widgets.rs
This commit is contained in:
Felix Riestra committed 2026-09-09 13:35:07 +02:00
commit e2d39b049d
114 files changed
+13641 -1685

No files matched your search

+73 -60
View File
@@ -9,21 +9,22 @@ pub(crate) mod config;
pub use automation::{export_headless, serve};
mod command_driver;
pub(crate) mod commands;
#[cfg(not(target_arch = "wasm32"))]
mod doc_api;
mod document;
pub(crate) mod expr_eval;
mod find_replace;
pub(crate) mod helpers;
mod history;
#[cfg(not(target_arch = "wasm32"))]
mod doc_api;
mod layers;
mod model_ops;
mod mtext_editor;
#[cfg(not(target_arch = "wasm32"))]
pub mod plugin_host;
mod properties;
mod presspull_ops;
mod properties;
mod recent;
mod record_api;
pub(crate) mod settings;
mod shortcuts;
mod startup;
@@ -225,7 +226,10 @@ impl From<&QSelectState> for QSelectSettings {
Self {
scope: state.scope,
type_filter: state.type_filter.clone(),
property_field: state.property.as_ref().map(|property| property.field.clone()),
property_field: state
.property
.as_ref()
.map(|property| property.field.clone()),
operator: state.operator,
value: state.value.clone(),
mode: state.mode,
@@ -256,12 +260,12 @@ pub(crate) enum FindMatchKey {
}
use crate::snap::Snapper;
use crate::ui::{CommandLine, Ribbon, StatusBar};
use acadrust::types::{Color as AcadColor, LineWeight};
use acadrust::CadDocument;
use acadrust::types::{Color as AcadColor, LineWeight};
use iced::time::Instant;
use iced::window;
use iced::{mouse, Point, Task, Theme};
use iced::{Point, Task, Theme, mouse};
use std::sync::Arc;
pub(super) const POLY_START_DELAY_MS: u128 = 150;
@@ -318,9 +322,7 @@ struct AddSelectedRestore {
}
/// Which Start-page section a narrow (tabbed) Start page is showing.
#[derive(
Clone, Copy, PartialEq, Eq, Default, Debug, serde::Serialize, serde::Deserialize,
)]
#[derive(Clone, Copy, PartialEq, Eq, Default, Debug, serde::Serialize, serde::Deserialize)]
pub enum StartSection {
Recent,
Videos,
@@ -780,15 +782,13 @@ pub(super) struct OpenCADStudio {
/// Live filter for installed and available plugin cards.
plugin_search_input: String,
/// Installable release tags fetched per linked repo (for the dropdown).
repo_release_tags:
rustc_hash::FxHashMap<String, Vec<crate::plugin::external::ReleaseInfo>>,
repo_release_tags: rustc_hash::FxHashMap<String, Vec<crate::plugin::external::ReleaseInfo>>,
/// The release tag currently selected per linked repo.
repo_selected_tag: rustc_hash::FxHashMap<String, String>,
/// Repository currently shown in the Plugin Manager detail pane.
selected_plugin_repo: Option<String>,
/// Parsed GitHub README content or the last fetch error, cached per repo.
plugin_readmes:
rustc_hash::FxHashMap<String, Result<iced::widget::markdown::Content, String>>,
plugin_readmes: rustc_hash::FxHashMap<String, Result<iced::widget::markdown::Content, String>>,
/// README requests in flight, used to render a deterministic loading state.
plugin_readme_loading: rustc_hash::FxHashSet<String>,
/// Last marketplace status / error line shown in the Plugin Manager.
@@ -1098,8 +1098,7 @@ pub(super) struct OpenCADStudio {
save_job_serial: u64,
/// Destination leases held while Save As workers are active.
#[cfg(not(target_arch = "wasm32"))]
pending_save_leases:
std::collections::HashMap<u64, crate::io::edit_lock::EditLease>,
pending_save_leases: std::collections::HashMap<u64, crate::io::edit_lock::EditLease>,
/// Locked-file failure currently shown in the recovery dialog.
#[cfg(not(target_arch = "wasm32"))]
pending_save_failure: Option<PendingSaveFailure>,
@@ -1448,7 +1447,8 @@ impl ClipboardDeps {
if !objects.is_empty() {
let mut annotation_scales = Vec::new();
for (_, object) in &objects {
let acadrust::objects::ObjectType::ObjectContextData(context) = object else {
let acadrust::objects::ObjectType::ObjectContextData(context) = object
else {
continue;
};
if annotation_scales
@@ -1618,9 +1618,7 @@ impl ClipboardDeps {
fn snapshot_block(doc: &acadrust::CadDocument, name: &str) -> Option<BlockDef> {
use acadrust::EntityType;
let br = doc.block_records.get(name)?;
if name.starts_with("*Model_Space")
|| name.starts_with("*Paper_Space")
|| br.flags.is_xref
if name.starts_with("*Model_Space") || name.starts_with("*Paper_Space") || br.flags.is_xref
{
return None;
}
@@ -1835,10 +1833,7 @@ pub enum Message {
/// Register a font already held by the shared web store with the UI renderer.
ApplyWebFont(crate::scene::text::web_font::Script),
/// Completion of the UI renderer's runtime font registration.
WebUiFontLoaded(
crate::scene::text::web_font::Script,
Result<(), String>,
),
WebUiFontLoaded(crate::scene::text::web_font::Script, Result<(), String>),
/// Ctrl+V. Routed by `update` into an open text editor, the drawing-object
/// clipboard, or the system text clipboard.
PasteShortcut,
@@ -1906,10 +1901,13 @@ pub enum Message {
WebFileOpened(u64, crate::io::WebOpenOutcome),
#[cfg(target_arch = "wasm32")]
WebFileCached(u64, crate::io::WebOpenOutcome, Result<(), String>),
FileOpened(u64, Result<
(String, PathBuf, CadDocument, crate::scene::DerivedCaches),
crate::io::OpenLoadError,
>),
FileOpened(
u64,
Result<
(String, PathBuf, CadDocument, crate::scene::DerivedCaches),
crate::io::OpenLoadError,
>,
),
RecoveryClose,
RecoveryAttempt,
RecoveryDecline,
@@ -2121,6 +2119,10 @@ pub enum Message {
CommandInput(String),
CommandSubmit,
Command(String),
/// Execute one complete line read from a command script. Unlike UI/ribbon
/// dispatch, this accepts an interactive verb and all of its arguments on
/// the same line (`BOX 0,0,0 10,10,0 10`).
ScriptLine(String),
/// Append one typed character to the command-line input from the
/// global key-press subscription. Used when the text-input widget
/// itself isn't focused (focus parked on viewport / button / etc.)
@@ -2150,9 +2152,16 @@ pub enum Message {
},
/// A widget captured Up/Down; resolve it only if the command input owns
/// keyboard focus.
CommandLineArrowProbe { direction: ArrowKey, extend_selection: bool },
CommandLineArrowProbe {
direction: ArrowKey,
extend_selection: bool,
},
/// Result of the command-input focus query for a captured Up/Down key.
CommandLineArrowResolved { direction: ArrowKey, focused: bool, extend_selection: bool },
CommandLineArrowResolved {
direction: ArrowKey,
focused: bool,
extend_selection: bool,
},
/// Toggle the dropdown listing the full command-line history.
CommandHistoryToggle,
/// Grab/move/release the expanded history panel's top resize edge.
@@ -2303,9 +2312,7 @@ pub enum Message {
epoch: u64,
source: usize,
wires: std::sync::Weak<Vec<crate::scene::WireModel>>,
index: std::sync::Arc<
crate::scene::pick::interaction_index::InteractionIndex,
>,
index: std::sync::Arc<crate::scene::pick::interaction_index::InteractionIndex>,
build_ms: f64,
},
WindowResized(f32, f32),
@@ -2567,7 +2574,10 @@ pub enum Message {
PropEditChoiceToggle,
/// User is typing in a block-attribute value field (live buffer update),
/// keyed by the attribute tag.
PropAttrInput { tag: String, value: String },
PropAttrInput {
tag: String,
value: String,
},
/// User committed a block-attribute value edit (Enter pressed).
PropAttrCommit(String),
/// Reports the currently keyboard-focused widget (if any) after a
@@ -2593,7 +2603,10 @@ pub enum Message {
PropColorFieldToggle(String),
/// User picked a colour for a generic per-field colour row (hatch gradient
/// `Color 1` / `Color 2`), routed by the field name.
PropColorFieldChanged { field: String, color: AcadColor },
PropColorFieldChanged {
field: String,
color: AcadColor,
},
/// Collapse the inline color picker dropdown. Fired when another
/// properties-panel dropdown (a combo_box) opens, so at most one panel
/// dropdown is open at a time and they can't overlap. (#235)
@@ -2705,7 +2718,10 @@ pub enum Message {
/// Select the row the Text Options / Properties tabs act on.
AttrEditorSelect(usize),
/// Live edit of the attribute value at row `idx` (Attribute tab).
AttrEditorInput { idx: usize, value: String },
AttrEditorInput {
idx: usize,
value: String,
},
// Text Options — all act on the selected row:
AttrEditorTextStyle(String),
AttrEditorJustify(String),
@@ -2760,9 +2776,7 @@ pub enum Message {
/// GitHub Discussions fetched at boot for the Start page.
DiscussionsFetched(Result<Vec<crate::discussions::DiscussionEntry>, String>),
/// Recent-file DWG preview thumbnails decoded on a background thread.
RecentThumbsLoaded(
Vec<(std::path::PathBuf, Option<iced::widget::image::Handle>)>,
),
RecentThumbsLoaded(Vec<(std::path::PathBuf, Option<iced::widget::image::Handle>)>),
/// Installable releases and manifest API versions fetched for `owner/repo`.
PluginReleasesFetched(
String,
@@ -2803,7 +2817,10 @@ pub enum Message {
InvertSelection,
/// Keyboard modifier state changed — tracks whether Shift is held so the
/// pick path can do subtractive (Shift+click) selection.
SetModifiers { shift: bool, ctrl: bool },
SetModifiers {
shift: bool,
ctrl: bool,
},
// ── In-place MText editor ───────────────────────────────────────────
/// Text-area edit action from the multi-line editor widget.
MTextEdit(iced::widget::text_editor::Action),
@@ -3273,10 +3290,7 @@ impl OpenCADStudio {
.iter()
.filter_map(|v| {
let bytes = v.thumb.clone()?;
Some((
v.id.clone(),
iced::widget::image::Handle::from_bytes(bytes),
))
Some((v.id.clone(), iced::widget::image::Handle::from_bytes(bytes)))
})
.collect();
self.videos = videos;
@@ -3790,8 +3804,7 @@ impl OpenCADStudio {
let state = Self::new();
let (id, open_task) = window::open(window::Settings {
maximized: true,
icon: build_window_icon()
.and_then(|rgba| window::icon::from_rgba(rgba, 32, 32).ok()),
icon: build_window_icon().and_then(|rgba| window::icon::from_rgba(rgba, 32, 32).ok()),
exit_on_close_request: false,
// A Wayland compositor has no StartupWMClass to go on: it resolves a
// window's dock icon by matching the window's app_id against the
@@ -3844,7 +3857,7 @@ impl OpenCADStudio {
Task::batch(
cfg.script_lines
.into_iter()
.map(|line| Task::done(Message::Command(line))),
.map(|line| Task::done(Message::ScriptLine(line))),
)
};
s.queue_startup_prompts();
@@ -3927,24 +3940,17 @@ impl OpenCADStudio {
let mut s = Self::new();
s.queue_startup_prompts();
let focus = s.focus_cmd_input();
let primary_font = crate::scene::text::web_font::preload_language(
&crate::i18n::active_language_tag(),
);
let primary_font =
crate::scene::text::web_font::preload_language(&crate::i18n::active_language_tag());
let fonts = Task::batch([
Task::done(Message::PollWebFonts),
Task::done(Message::ApplyWebFont(primary_font)),
]);
// Web can't reach the Patreon API directly (CORS); fetch the CI-built
// supporters.json served on the same origin instead.
let patrons = Task::perform(
crate::patreon::fetch_patrons_web(),
Message::PatronsFetched,
);
let patrons = Task::perform(crate::patreon::fetch_patrons_web(), Message::PatronsFetched);
s.videos_loading = true;
let videos = Task::perform(
crate::videos::fetch_playlist_web(),
Message::VideosFetched,
);
let videos = Task::perform(crate::videos::fetch_playlist_web(), Message::VideosFetched);
s.discussions_loading = true;
let discussions = Task::perform(
crate::discussions::fetch_discussions_web(),
@@ -3977,9 +3983,14 @@ pub fn run() -> iced::Result {
if let Some(tab) = state.tabs.get(state.active_tab) {
let dot = if tab.dirty { "● " } else { "" };
let name = tab.tab_display_name();
format!("{}OCS-{} - {}", dot, env!("OCS_BUILD_STAMP"), name)
format!(
"{}Open CAD Studio {} - {}",
dot,
env!("OCS_APP_VERSION"),
name
)
} else {
concat!("OCS-", env!("OCS_BUILD_STAMP")).to_string()
concat!("Open CAD Studio ", env!("OCS_APP_VERSION")).to_string()
}
})
.theme(|state: &OpenCADStudio, _| state.active_theme.clone())
@@ -4011,7 +4022,9 @@ pub fn run_web() -> iced::Result {
OpenCADStudio::view_main,
)
.subscription(OpenCADStudio::subscription)
.title(|_state: &OpenCADStudio| concat!("OCS-", env!("OCS_BUILD_STAMP")).to_string())
.title(|_state: &OpenCADStudio| {
concat!("Open CAD Studio ", env!("OCS_APP_VERSION")).to_string()
})
.theme(|state: &OpenCADStudio| state.active_theme.clone())
.backend(iced::Backend::Hardware(iced::backend::Api::OpenGL))
.font(iced_aw::ICED_AW_FONT_BYTES)