use brep_render::engine_state::EngineState;
pub mod cam;
pub mod live_link;
pub mod owned_save;
pub mod work_part;
pub mod ecad;
pub type EngineFactory = Box<dyn Fn() -> EngineState>;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Access {
Editable,
ReadOnly { reason: String },
}
impl Access {
pub fn read_only(reason: impl Into<String>) -> Self {
Access::ReadOnly { reason: reason.into() }
}
pub fn is_editable(&self) -> bool {
matches!(self, Access::Editable)
}
}
pub const EMPTY_DOCUMENT: &str = r#"{"expressions":"","configurator":{},"features":[]}"#;
fn is_part_workbench(id: &str) -> bool {
!matches!(crate::workbench::resolve(id).id,
"assembly" | "drawing" | "wireHarness" | "diagram" | "pcb" | "symbol" | "pads")
}
pub fn seed_new_part_datum(engine: &mut EngineState) {
if engine.history_len() == 0 && is_part_workbench(&engine.settings.workbench) {
let id = engine.next_feature_id(&brep_render::features::feature_short_name("D"));
let mut params = engine.feature_default_params("D");
params["id"] = serde_json::json!(id);
engine.add_feature(&serde_json::json!({
"type": "D", "inputParams": params, "persistentData": {}
}).to_string()).expect("valid default datum");
}
}
pub struct Document {
pub engine: EngineState,
name: Option<String>,
untitled: u32,
plm_save_access: Option<Access>,
plm_folder: Option<(String, String)>,
opened_from: Option<u64>,
saved_signature: Option<String>,
saved_sources: Option<SavedSources>,
baseline_generation: u64,
baseline_content: Option<ContentKey>,
settle: Option<Settle>,
converge: Option<(Settle, u32)>,
dirty_marker: bool,
marker_key: Option<(u64, u64)>,
dirty_checks: u64,
id: u64,
pub ecad: crate::workbench::ecad::Editors,
pub ecad_blocks: [ecad::BlockSync; 4],
pub ecad_parts_revision: Option<u64>,
pub ecad_parts_reported: Vec<String>,
pub ecad_occurrences: Option<crate::panels::ecad_parts::OccurrenceMark>,
pub ecad_outdated: Vec<String>,
pub ecad_update_requested: bool,
refused_seen: u64,
pub(crate) inserted_noted: Option<((u64, u64), u64)>,
}
pub(crate) type SavedSources = std::collections::HashMap<String, (String, String)>;
fn sources_of(library: &serde_json::Value) -> SavedSources {
library
.as_object()
.map(|entries| {
entries
.iter()
.map(|(name, entry)| {
let field = |key: &str| entry[key].as_str().unwrap_or_default().to_string();
(name.clone(), (field("sourceKey"), field("sourceSignature")))
})
.collect()
})
.unwrap_or_default()
}
fn raw_hash(text: &str) -> u64 {
use std::hash::{Hash, Hasher};
let mut hasher = std::collections::hash_map::DefaultHasher::new();
text.hash(&mut hasher);
hasher.finish()
}
fn signature(engine: &EngineState) -> String {
signature_without(engine, None)
}
#[derive(Clone, Copy, Debug, PartialEq)]
struct Settle {
serial: u64,
generation: u64,
revision: u64,
}
impl Settle {
fn of(engine: &EngineState) -> Self {
Self {
serial: engine.history.edit_serial(),
generation: engine.applied_generation(),
revision: engine.history.revision(),
}
}
}
type ContentKey = ((u64, u64), u64);
fn content_key(engine: &EngineState) -> ContentKey {
((engine.applied_generation(), engine.history_revision()), engine.metadata.stamp())
}
const CONVERGE_RUNS: u32 = 16;
const CONVERGE_TOLERANCE: f64 = 1e-6;
const CONVERGE_DERIVED_DOCUMENT_KEYS: &[&str] = &["thumbnail", "workbench"];
const CONVERGE_DERIVED_COPY_KEYS: &[&str] = &["snapshot"];
fn without_derived(mut document: serde_json::Value) -> serde_json::Value {
fn strip(document: &mut serde_json::Value) {
if let Some(entries) = document.get_mut("partsLibrary").and_then(serde_json::Value::as_object_mut) {
for entry in entries.values_mut() {
if let Some(entry) = entry.as_object_mut() {
for key in CONVERGE_DERIVED_COPY_KEYS {
entry.remove(*key);
}
}
strip(&mut entry["document"]);
}
}
}
if let Some(object) = document.as_object_mut() {
for key in CONVERGE_DERIVED_DOCUMENT_KEYS {
object.remove(*key);
}
}
strip(&mut document);
document
}
fn nearly_equal(a: &serde_json::Value, b: &serde_json::Value) -> bool {
use serde_json::Value;
match (a, b) {
(Value::Number(x), Value::Number(y)) if x.is_f64() && y.is_f64() => match (x.as_f64(), y.as_f64()) {
(Some(x), Some(y)) => (x - y).abs() <= CONVERGE_TOLERANCE * x.abs().max(y.abs()).max(1.0),
_ => false,
},
(Value::Array(x), Value::Array(y)) => x.len() == y.len() && x.iter().zip(y).all(|(x, y)| nearly_equal(x, y)),
(Value::Object(x), Value::Object(y)) => {
x.len() == y.len() && x.iter().all(|(k, v)| y.get(k).is_some_and(|w| nearly_equal(v, w)))
}
_ => a == b,
}
}
fn signature_without(engine: &EngineState, without: Option<&str>) -> String {
let mut document: serde_json::Value =
serde_json::from_str(&engine.history.request_json()).unwrap_or_else(|_| serde_json::json!({}));
if let Some(object) = document.as_object_mut() {
if let Some(key) = without {
object.shift_remove(key);
}
if !engine.metadata.is_empty() {
object.insert("metadata".into(), engine.metadata.to_json());
}
}
without_run_record(&mut document);
if let Some(object) = document.as_object_mut() {
object.shift_remove(owned_save::INSERTED_COPIES_BLOCK);
}
document.to_string()
}
pub(crate) fn without_run_record(document: &mut serde_json::Value) {
without_saved_rows(document);
let Some(constraints) = document
.get_mut("assembly")
.and_then(|assembly| assembly.get_mut("constraints"))
.and_then(serde_json::Value::as_array_mut)
else {
return;
};
for constraint in constraints {
if let Some(record) = constraint.get_mut("persistentData").and_then(serde_json::Value::as_object_mut) {
for key in ["error", "kernel", "message"] {
record.remove(key);
}
}
}
}
fn without_saved_rows(document: &mut serde_json::Value) {
let Some(entries) = document.get_mut("partsLibrary").and_then(serde_json::Value::as_object_mut) else {
return;
};
for entry in entries.values_mut() {
if let Some(object) = entry.as_object_mut() {
object.shift_remove("nestedRows");
}
if let Some(child) = entry.get_mut("document") {
without_saved_rows(child);
}
}
}
fn ecad_blocks() -> [ecad::BlockSync; 4] {
use ecad::Block;
[Block::Diagram, Block::Pcb, Block::Symbol, Block::Pads].map(ecad::BlockSync::new)
}
impl Document {
pub fn fresh(mut engine: EngineState, class: crate::document_class::DocumentClass) -> Self {
engine.set_history_json(EMPTY_DOCUMENT).expect("valid empty document");
crate::document_class::set_document_class(&mut engine, class);
seed_new_part_datum(&mut engine);
Self::new(engine)
}
pub fn new(engine: EngineState) -> Self {
let saved_signature = Some(signature(&engine));
let baseline_content = Some(content_key(&engine));
let saved_sources = Some(sources_of(engine.history.parts_library()));
let settle = Some(Settle::of(&engine));
Self {
engine,
name: None,
plm_save_access: None,
plm_folder: None,
opened_from: None,
baseline_content,
saved_signature,
saved_sources,
baseline_generation: 0,
settle,
converge: None,
dirty_marker: false,
marker_key: None,
dirty_checks: 0,
id: next_document_id(),
untitled: 1,
ecad: crate::workbench::ecad::Editors::new(),
ecad_blocks: ecad_blocks(),
ecad_parts_revision: None,
ecad_parts_reported: Vec::new(),
ecad_occurrences: None,
ecad_outdated: Vec::new(),
ecad_update_requested: false,
refused_seen: 0,
inserted_noted: None,
}
}
pub fn recovered(engine: EngineState, name: Option<String>) -> Self {
Self {
engine,
name,
plm_save_access: None,
plm_folder: None,
opened_from: None,
saved_signature: None,
saved_sources: None,
baseline_generation: 0,
baseline_content: None,
settle: None,
converge: None,
dirty_marker: true,
marker_key: None,
dirty_checks: 0,
id: next_document_id(),
untitled: 1,
ecad: crate::workbench::ecad::Editors::new(),
ecad_blocks: ecad_blocks(),
ecad_parts_revision: None,
ecad_parts_reported: Vec::new(),
ecad_occurrences: None,
ecad_outdated: Vec::new(),
ecad_update_requested: false,
refused_seen: 0,
inserted_noted: None,
}
}
pub fn access(&self) -> Access {
match self.engine.history.locked() {
None => Access::Editable,
Some(reason) => Access::read_only(reason),
}
}
pub fn set_access(&mut self, access: Access) {
match access {
Access::Editable => self.engine.history.unlock(),
Access::ReadOnly { reason } => self.engine.history.lock(reason),
}
}
pub fn save_access(&self) -> Access {
self.plm_save_access.clone().unwrap_or_else(|| self.access())
}
pub(crate) fn has_plm_save_access(&self) -> bool { self.plm_save_access.is_some() }
pub fn set_plm_save_access(&mut self, access: Access) {
self.plm_save_access = Some(access);
self.engine.history.unlock();
}
pub fn take_refused(&mut self) -> Option<String> {
let refused = self.engine.history.refused_user_edits();
if refused == self.refused_seen {
return None;
}
self.refused_seen = refused;
let reason = self.engine.history.locked()?;
Some(format!("This document is read-only: {reason}."))
}
pub fn name(&self) -> Option<&str> {
self.name.as_deref()
}
pub fn set_name(&mut self, name: Option<String>) {
if self.name != name { self.plm_save_access = None; }
self.name = name;
}
pub fn title(&self) -> String {
match &self.name {
Some(name) => crate::panels::plm_host::revision_of(name)
.map(|(part, revision)| format!("{part}/{revision}"))
.unwrap_or_else(|| crate::store::model_display_name(name)),
None => self.untitled_title(),
}
}
pub fn untitled_title(&self) -> String {
match self.untitled {
0 | 1 => "untitled".to_string(),
n => format!("untitled {n}"),
}
}
pub fn mark_clean(&mut self) {
self.saved_signature = Some(signature(&self.engine));
self.saved_sources = Some(sources_of(self.engine.history.parts_library()));
self.baseline_generation += 1;
self.baseline_content = Some(content_key(&self.engine));
self.dirty_marker = false;
self.marker_key = Some(self.edit_key());
self.settle = Some(Settle::of(&self.engine));
}
pub(crate) fn arm_open_settle(&mut self) {
self.settle = Some(Settle::of(&self.engine));
}
pub fn settle_open_runs(&mut self) {
let Some(last) = self.settle else {
return;
};
let now = Settle::of(&self.engine);
if now.serial != last.serial || (now.generation == last.generation && now.revision != last.revision) {
self.settle = None;
return;
}
if now.generation == last.generation || self.engine.run_pending() {
return;
}
if now.revision != last.revision {
self.saved_signature = Some(signature(&self.engine));
self.saved_sources = Some(sources_of(self.engine.history.parts_library()));
self.baseline_generation += 1;
self.baseline_content = Some(content_key(&self.engine));
self.marker_key = None;
}
self.settle = Some(now);
}
pub(crate) fn arm_converge(&mut self) {
self.converge = Some((Settle::of(&self.engine), 0));
}
pub fn converge_runs(&mut self) {
let Some((last, runs)) = self.converge else {
return;
};
let now = Settle::of(&self.engine);
if now.serial != last.serial || runs >= CONVERGE_RUNS {
self.converge = None;
return;
}
if now.generation == last.generation || self.engine.run_pending() {
return;
}
let current = signature(&self.engine);
let near = self.saved_signature.as_deref().is_some_and(|saved| {
match (serde_json::from_str::<serde_json::Value>(saved), serde_json::from_str::<serde_json::Value>(¤t)) {
(Ok(saved), Ok(current)) => nearly_equal(&without_derived(saved), &without_derived(current)),
_ => false,
}
});
if near {
if self.saved_signature.as_deref() != Some(current.as_str()) {
self.saved_signature = Some(current);
self.saved_sources = Some(sources_of(self.engine.history.parts_library()));
self.baseline_generation += 1;
self.baseline_content = Some(content_key(&self.engine));
self.marker_key = None;
}
self.converge = None;
} else {
self.converge = Some((now, runs + 1));
}
}
pub(crate) fn save_signature(&self) -> String { signature(&self.engine) }
pub(crate) fn plm_folder(&self) -> Option<&(String, String)> {
self.plm_folder.as_ref()
}
pub(crate) fn note_opened_from(&mut self, text: &str) {
self.opened_from = Some(raw_hash(text));
}
pub(crate) fn holds_stored(&self, text: &str) -> bool {
self.opened_from == Some(raw_hash(text))
}
pub(crate) fn set_plm_folder(&mut self, folder: Option<(String, String)>) {
self.plm_folder = folder;
}
pub(crate) fn saved_sources(&self) -> SavedSources {
match &self.saved_sources {
Some(sources) => sources.clone(),
None => self
.saved_signature
.as_deref()
.and_then(|saved| serde_json::from_str::<serde_json::Value>(saved).ok())
.map(|saved| sources_of(&saved["partsLibrary"]))
.unwrap_or_default(),
}
}
pub(crate) fn saved_document(&self) -> Option<serde_json::Value> {
serde_json::from_str(self.saved_signature.as_deref()?).ok()
}
pub(crate) fn rebase_saved_document(&mut self, baseline: &serde_json::Value, key: (u64, u64)) {
self.saved_signature = Some(baseline.to_string());
self.saved_sources = None;
self.baseline_generation += 1;
self.baseline_content = None;
if self.marker_key == Some(key) {
self.marker_key = Some(self.edit_key());
}
}
pub(crate) fn save_signature_without(&self, block: &str) -> String {
signature_without(&self.engine, Some(block))
}
pub(crate) fn mark_saved_signature(&mut self, saved: String) {
self.saved_signature = Some(saved);
self.saved_sources = None;
self.baseline_generation += 1;
self.baseline_content = None;
self.marker_key = None;
self.refresh_dirty_marker();
}
pub fn is_recovered(&self) -> bool {
self.saved_signature.is_none()
}
pub(crate) fn mark_write_failed(&mut self) {
self.mark_saved_signature(String::new());
}
pub fn is_dirty(&self) -> bool {
match &self.saved_signature {
Some(saved) => *saved != signature(&self.engine),
None => true,
}
}
pub fn edit_key(&self) -> (u64, u64) {
(self.engine.applied_generation(), self.engine.history_revision())
}
pub(crate) fn is_its_baseline(&self) -> bool {
self.baseline_content == Some(content_key(&self.engine))
}
pub fn refresh_dirty_marker(&mut self) {
let key = self.edit_key();
let previous = self.marker_key.replace(key);
if previous == Some(key) {
return;
}
let run_moved = previous.map_or(true, |(generation, _)| generation != key.0);
if self.dirty_marker && !run_moved {
return;
}
self.dirty_checks += 1;
self.dirty_marker = self.is_dirty();
}
pub fn absorb_open_write(&mut self, before: &serde_json::Value, after: &serde_json::Value, key: (u64, u64)) {
let compared = |document: &serde_json::Value| {
let mut document = document.clone();
without_run_record(&mut document);
document
};
let (before, after) = (&compared(before), &compared(after));
let rebased = self.saved_signature.as_deref().and_then(|saved| {
let mut base: serde_json::Value = serde_json::from_str(saved).ok()?;
let (base_map, before, after) = (base.as_object_mut()?, before.as_object()?, after.as_object()?);
for name in before.keys().chain(after.keys()) {
let (was, is) = (before.get(name), after.get(name));
if was == is || base_map.get(name) != was {
continue;
}
match is {
Some(value) => base_map.insert(name.clone(), value.clone()),
None => base_map.shift_remove(name),
};
}
Some(base.to_string())
});
if let Some(rebased) = rebased {
self.saved_signature = Some(rebased);
self.saved_sources = None;
self.baseline_generation += 1;
self.baseline_content = None;
}
if self.marker_key == Some(key) {
self.marker_key = Some(self.edit_key());
}
}
pub fn dirty_checks(&self) -> u64 {
self.dirty_checks
}
pub fn dirty_marker(&self) -> bool {
self.dirty_marker
}
pub fn id(&self) -> u64 {
self.id
}
}
pub struct Documents {
work_part: Option<work_part::WorkPart>,
work_part_next: Option<(u64, String, work_part::WorkPartMode)>,
work_part_context: std::cell::RefCell<Option<((String, u64), serde_json::Value)>>,
owned_cache: std::cell::RefCell<Option<owned_save::OwnedCacheKey>>,
synced_cache: std::cell::RefCell<Option<owned_save::SyncedCache>>,
learned_signatures: std::cell::RefCell<std::collections::HashMap<((u64, u64), String), String>>,
live: live_link::LiveLinks,
canon: crate::store::Canonicalizer,
open: Vec<Document>,
active: usize,
new_engine: EngineFactory,
}
impl Documents {
pub fn new(new_engine: EngineFactory) -> Self {
let first = Document::fresh(new_engine(), crate::document_class::DocumentClass::Normal);
Self {
open: vec![first],
work_part: None,
work_part_next: None,
work_part_context: Default::default(),
owned_cache: Default::default(),
synced_cache: Default::default(),
learned_signatures: Default::default(),
live: Default::default(),
canon: std::rc::Rc::new(str::to_string),
active: 0,
new_engine,
}
}
pub fn wrap_engine_factory(&mut self, wrap: impl Fn(EngineState) -> EngineState + 'static) {
let old = std::mem::replace(&mut self.new_engine, Box::new(EngineState::new));
self.new_engine = Box::new(move || wrap(old()));
}
pub fn spawn_engine(&self) -> EngineState {
let mut engine = (self.new_engine)();
carry_settings(&self.engine().settings_json(), &mut engine, true);
let (width, height) = self.viewport_size();
engine.resize(width, height);
engine
}
fn viewport_size(&self) -> (f64, f64) {
let camera = &self.engine().camera;
(camera.width, camera.height)
}
pub fn engine(&self) -> &EngineState {
match self.hosted() {
Some(doc) => &doc.engine,
None => &self.open[self.active].engine,
}
}
pub fn engine_mut(&mut self) -> &mut EngineState {
if self.work_part_in_place() {
return &mut self.hosted_mut().unwrap().engine;
}
&mut self.open[self.active].engine
}
pub fn tab_engine(&self) -> &EngineState {
&self.open[self.active].engine
}
pub fn tab_engine_mut(&mut self) -> &mut EngineState {
&mut self.open[self.active].engine
}
pub fn view_id(&self) -> u64 {
self.hosted().map_or_else(|| self.active_id(), Document::id)
}
pub fn active(&self) -> &Document {
&self.open[self.active]
}
pub fn active_mut(&mut self) -> &mut Document {
&mut self.open[self.active]
}
pub fn view(&self) -> &Document {
self.hosted().unwrap_or(&self.open[self.active])
}
pub fn view_mut(&mut self) -> &mut Document {
if self.work_part_in_place() {
return self.hosted_mut().unwrap();
}
&mut self.open[self.active]
}
pub fn active_index(&self) -> usize {
self.active
}
pub fn active_id(&self) -> u64 {
self.open[self.active].id
}
pub fn len(&self) -> usize {
self.open.len()
}
pub fn get(&self, index: usize) -> Option<&Document> {
self.open.get(index)
}
pub fn get_mut(&mut self, index: usize) -> Option<&mut Document> {
self.open.get_mut(index)
}
pub fn iter(&self) -> std::slice::Iter<'_, Document> {
self.open.iter()
}
pub fn iter_mut(&mut self) -> std::slice::IterMut<'_, Document> {
self.open.iter_mut()
}
pub fn pump_all(&mut self) -> bool {
use brep_render::brep_kernel::parts_library_revision;
let mut work_in_flight = false;
let in_place = self.work_part_in_place();
for index in 0..self.open.len() {
let background = index != self.active || in_place;
let doc = &mut self.open[index];
let own_library = background
&& (doc.engine.run_landing()
|| doc.engine.mesh_imports_pending()
|| doc.engine.history.ports_follow_pending());
let doc = &self.open[index];
if own_library {
install_library_of(doc);
}
let revision = parts_library_revision();
let doc = &mut self.open[index];
if background && !own_library && doc.engine.run_pending() {
doc.engine.pump_holding_runs();
} else {
doc.engine.pump();
}
work_in_flight |= doc.engine.run_pending()
|| doc.engine.queries_pending()
|| doc.engine.mesh_imports_pending()
|| doc.engine.step_probes_pending()
|| doc.engine.topology_pending()
|| doc.engine.sheet_lines_pending();
if own_library || (background && parts_library_revision() != revision) {
self.install_active_library();
}
}
if let Some(doc) = self.hosted_mut() {
doc.engine.pump();
let engine = &doc.engine;
work_in_flight |= engine.run_pending()
|| engine.queries_pending()
|| engine.mesh_imports_pending()
|| engine.step_probes_pending()
|| engine.topology_pending()
|| engine.sheet_lines_pending();
}
self.sync_work_part();
self.begin_next_work_part();
work_in_flight
}
pub fn set_canonicalizer(&mut self, canon: crate::store::Canonicalizer) {
self.canon = canon;
self.forget_published();
}
pub fn index_of_source(&self, key: &str) -> Option<usize> {
let wanted = (self.canon)(key);
self.open.iter().position(|doc| doc.name.as_deref().is_some_and(|name| (self.canon)(name) == wanted))
}
pub fn index_of(&self, name: &str) -> Option<usize> {
self.open
.iter()
.position(|doc| doc.name.as_deref() == Some(name))
}
pub fn focus_named(&mut self, name: &str) -> bool {
match self.index_of(name) {
Some(index) => {
self.activate(index);
true
}
None => false,
}
}
pub fn open_document(&mut self, mut doc: Document) -> usize {
if doc.name.is_none() {
let taken: Vec<u32> = self.open.iter().filter(|d| d.name.is_none()).map(|d| d.untitled).collect();
doc.untitled = (1..).find(|n| !taken.contains(n)).unwrap_or(1);
}
self.open.push(doc);
let index = self.open.len() - 1;
self.activate(index);
index
}
pub(crate) fn renumber_untitled(&mut self) {
for (n, doc) in self.open.iter_mut().filter(|d| d.name.is_none()).enumerate() {
doc.untitled = n as u32 + 1;
}
}
pub fn activate(&mut self, index: usize) {
if index >= self.open.len() || index == self.active {
return;
}
if self.work_part.is_some() {
if let Err(error) = self.finish_work_part() {
self.engine_mut().push_notice(format!("Work part: {error}"));
return;
}
}
let settings = self.open[self.active].engine.settings_json();
self.publish_live(self.active);
self.active = index;
carry_settings(&settings, &mut self.open[index].engine, false);
self.pull_live(index);
self.install_active_library();
}
fn install_active_library(&self) {
install_library_of(self.hosted().unwrap_or(&self.open[self.active]));
}
pub fn close(&mut self, index: usize) {
if index >= self.open.len() {
return;
}
if self.work_part.as_ref().is_some_and(|session| {
let id = self.open[index].id();
id == session.assembly_id || id == session.part_id
}) {
self.engine_mut().push_notice("Return to assembly before closing a Work part session document");
return;
}
let settings = self.open[self.active].engine.settings_json();
let viewport = self.viewport_size();
let closed_active = index == self.active;
if closed_active && !self.open[index].is_dirty() {
self.publish_live(index);
}
let closed = self.open.remove(index);
self.forget_live(closed.id());
let refilled = self.open.is_empty();
if refilled {
let mut engine = (self.new_engine)();
carry_settings(&settings, &mut engine, true);
self.open.push(Document::fresh(engine, crate::document_class::DocumentClass::Normal));
self.active = 0;
self.open[0].engine.resize(viewport.0, viewport.1);
} else if closed_active {
self.active = index.min(self.open.len() - 1);
} else if index < self.active {
self.active -= 1;
}
if closed_active {
let active = self.active;
carry_settings(&settings, &mut self.open[active].engine, refilled);
self.pull_live(active);
self.install_active_library();
}
}
pub fn mark_write_failed(&mut self, store: &dyn crate::store::ModelStore, name: &str) -> bool {
let failed = store.canonical_identity(name);
let mut marked = false;
for doc in &mut self.open {
if doc.name().is_some_and(|n| store.canonical_identity(n) == failed) {
doc.mark_write_failed();
marked = true;
}
}
marked
}
pub fn refresh_dirty_markers(&mut self) {
for doc in &mut self.open {
owned_save::note_inserted_copies(doc);
doc.settle_open_runs();
doc.converge_runs();
doc.refresh_dirty_marker();
}
}
}
fn install_library_of(doc: &Document) {
let library = serde_json::from_value(doc.engine.history.parts_library().clone()).unwrap_or_default();
brep_render::brep_kernel::install_parts_library(&library);
}
fn carry_settings(settings_json: &str, target: &mut EngineState, with_workbench: bool) {
let Ok(mut settings) = serde_json::from_str::<serde_json::Value>(settings_json) else {
return;
};
if !with_workbench {
if let Some(object) = settings.as_object_mut() {
object.remove("workbench");
}
}
let _ = target.apply_settings_json(&settings.to_string());
}
fn next_document_id() -> u64 {
use std::sync::atomic::{AtomicU64, Ordering};
static NEXT: AtomicU64 = AtomicU64::new(1);
NEXT.fetch_add(1, Ordering::Relaxed)
}