1use std::cell::RefCell;
16use std::collections::BTreeMap;
17
18use wasm_bindgen::prelude::*;
19
20use super::lifecycle::{self, LifecycleOutcome};
21use super::{constraints, mapping, AssemblyState, ConstraintEntry};
22use crate::feature_pipeline::{Env, SceneMap};
23
24#[derive(Clone)]
25struct Session {
26 state: AssemblyState,
27 scene: SceneMap,
28 expressions: String,
29 configurator: serde_json::Value,
30 pose_updates: BTreeMap<String, serde_json::Value>,
34 fixed_updates: BTreeMap<String, bool>,
37 report: serde_json::Value,
39}
40
41thread_local! {
42 static SESSION: RefCell<Option<Session>> = const { RefCell::new(None) };
43 static SESSION_SERIAL: std::cell::Cell<u64> = const { std::cell::Cell::new(0) };
47 static IMPORTED: RefCell<Vec<u32>> = const { RefCell::new(Vec::new()) };
50}
51
52fn replace_session(next: Session) -> RetiredSession {
56 let solids = IMPORTED
57 .with(|imported| std::mem::take(&mut *imported.borrow_mut()))
58 .into_iter()
59 .filter_map(crate::take_registered_solid)
60 .collect();
61 let session = SESSION.with(|session| session.borrow_mut().replace(next));
62 SESSION_SERIAL.with(|serial| serial.set(serial.get() + 1));
63 RetiredSession { _session: session, _solids: solids }
64}
65
66pub struct RetiredSession {
69 _session: Option<Session>,
70 _solids: Vec<crate::BrepSolid>,
71}
72
73pub fn assembly_session_serial() -> u64 {
75 SESSION_SERIAL.with(std::cell::Cell::get)
76}
77
78pub struct SessionTransfer {
83 session: Session,
84 solids: Vec<(u32, crate::BrepSolid)>,
85}
86
87impl std::fmt::Debug for SessionTransfer {
88 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
89 f.debug_struct("SessionTransfer").field("solids", &self.solids.len()).finish()
90 }
91}
92
93pub fn assembly_session_export() -> Option<SessionTransfer> {
95 SESSION.with(|session| {
96 let session = session.borrow();
97 let session = session.as_ref()?;
98 let scene = &session.scene;
99 let mut handles: Vec<u32> = scene
100 .solids
101 .values()
102 .copied()
103 .chain(scene.faces.values().map(|face| face.handle))
104 .chain(scene.edges.values().map(|edge| edge.handle))
105 .collect();
106 handles.sort_unstable();
107 handles.dedup();
108 let solids = handles
109 .into_iter()
110 .filter_map(|handle| {
111 crate::with_registered_solid_str(handle, |solid| Ok(solid.clone())).ok().map(|solid| (handle, solid))
112 })
113 .collect();
114 Some(SessionTransfer { session: session.clone(), solids })
115 })
116}
117
118pub fn assembly_session_import(transfer: SessionTransfer) -> RetiredSession {
123 let SessionTransfer { mut session, solids } = transfer;
124 let mut fresh = std::collections::HashMap::with_capacity(solids.len());
125 let mut owned = Vec::with_capacity(solids.len());
126 for (old, solid) in solids {
127 let handle = crate::register_solid_value(solid);
128 fresh.insert(old, handle);
129 owned.push(handle);
130 }
131 let remap = |handle: &mut u32| *handle = fresh.get(handle).copied().unwrap_or(u32::MAX);
134 let scene = &mut session.scene;
135 scene.solids.values_mut().for_each(remap);
136 scene.faces.values_mut().for_each(|face| remap(&mut face.handle));
137 scene.edges.values_mut().for_each(|edge| remap(&mut edge.handle));
138 let retired = replace_session(session);
139 IMPORTED.with(|imported| *imported.borrow_mut() = owned);
140 retired
141}
142
143pub(super) fn install_session(
147 state: AssemblyState,
148 scene: SceneMap,
149 expressions: String,
150 configurator: serde_json::Value,
151 outcome: LifecycleOutcome,
152) {
153 drop(replace_session(Session {
154 state,
155 scene,
156 expressions,
157 configurator,
158 pose_updates: outcome.pose_updates,
159 fixed_updates: outcome.fixed_updates,
160 report: outcome.report,
161 }));
162}
163
164fn with_session<T>(f: impl FnOnce(&mut Session) -> Result<T, String>) -> Result<T, String> {
165 SESSION.with(|session| {
166 let mut session = session.borrow_mut();
167 let session = session
168 .as_mut()
169 .ok_or_else(|| "no assembly session (run a history first)".to_string())?;
170 f(session)
171 })
172}
173
174fn solve_session(session: &mut Session) -> serde_json::Value {
179 let env = Env::build(&session.expressions, &session.configurator)
180 .unwrap_or_else(Env::poisoned);
181 let outcome = lifecycle::run_constraints(&mut session.state, &mut session.scene, &env);
182 session.pose_updates.extend(outcome.pose_updates);
183 session.fixed_updates.extend(outcome.fixed_updates);
184 session.report = outcome.report.clone();
185 outcome.report
186}
187
188#[wasm_bindgen]
195pub fn assembly_state_json() -> String {
196 SESSION.with(|session| {
197 session
198 .borrow()
199 .as_ref()
200 .map(|session| serde_json::to_value(&session.state).unwrap_or_default())
201 .unwrap_or_else(|| serde_json::to_value(AssemblyState::default()).unwrap_or_default())
202 .to_string()
203 })
204}
205
206#[wasm_bindgen]
209pub fn assembly_statuses_json() -> String {
210 SESSION.with(|session| {
211 let rows: Vec<serde_json::Value> = session
212 .borrow()
213 .as_ref()
214 .map(|session| {
215 session
216 .state
217 .constraints
218 .iter()
219 .map(|entry| {
220 serde_json::json!({
221 "id": entry.id(),
222 "type": entry.constraint_type,
223 "enabled": entry.enabled,
224 "open": entry.open,
225 "status": entry.status(),
226 "message": entry
227 .persistent("message")
228 .and_then(|value| value.as_str())
229 .unwrap_or(""),
230 "satisfied": entry
231 .persistent("satisfied")
232 .and_then(|value| value.as_bool())
233 .unwrap_or(false),
234 "error": entry.persistent("error").cloned(),
235 "conflictWith": entry.persistent("conflictWith").cloned().unwrap_or(serde_json::json!([])),
236 "suppressSuggested": entry
237 .persistent("suppressSuggested")
238 .and_then(|value| value.as_bool())
239 .unwrap_or(false),
240 })
241 })
242 .collect()
243 })
244 .unwrap_or_default();
245 serde_json::Value::Array(rows).to_string()
246 })
247}
248
249#[wasm_bindgen]
252pub fn assembly_dof_json() -> String {
253 SESSION.with(|session| {
254 session
255 .borrow()
256 .as_ref()
257 .map(|session| with_nested_constraints(&session.report, &session.scene).to_string())
258 .unwrap_or_else(|| serde_json::json!({ "ok": true, "mates": 0 }).to_string())
259 })
260}
261
262fn with_nested_constraints(report: &serde_json::Value, scene: &SceneMap) -> serde_json::Value {
268 let rows: Vec<&crate::feature_pipeline::component::NestedConstraint> = scene
269 .iter_components()
270 .flat_map(|record| &record.nested_constraints)
271 .collect();
272 let mut report = report.clone();
273 if let (false, Some(map)) = (rows.is_empty(), report.as_object_mut()) {
274 map.insert(
275 "nestedConstraints".into(),
276 serde_json::to_value(rows).unwrap_or_default(),
277 );
278 }
279 report
280}
281
282#[wasm_bindgen]
287pub fn assembly_pose_updates_json() -> String {
288 SESSION.with(|session| {
289 session
290 .borrow()
291 .as_ref()
292 .map(|session| {
293 serde_json::json!({
294 "poses": session.pose_updates,
295 "isFixed": session.fixed_updates,
296 })
297 .to_string()
298 })
299 .unwrap_or_else(|| serde_json::json!({ "poses": {}, "isFixed": {} }).to_string())
300 })
301}
302
303#[wasm_bindgen]
310pub fn assembly_apply_document_json(document_json: &str) -> Result<String, JsValue> {
311 assembly_apply_document_impl(document_json).map_err(|error| JsValue::from_str(&error))
312}
313
314pub fn assembly_apply_document_impl(document_json: &str) -> Result<String, String> {
315 let mut document: serde_json::Value = serde_json::from_str(document_json)
316 .map_err(|error| format!("assembly document fold: bad document: {error}"))?;
317 with_session(|session| {
318 let object = document
319 .as_object_mut()
320 .ok_or_else(|| "assembly document fold: document must be an object".to_string())?;
321 object.insert(
322 "assembly".into(),
323 serde_json::to_value(&session.state)
324 .map_err(|error| format!("assembly state serialize: {error}"))?,
325 );
326 if let Some(features) = object.get_mut("features").and_then(|v| v.as_array_mut()) {
327 for feature in features {
328 let Some(params) = feature
329 .get_mut("inputParams")
330 .and_then(|value| value.as_object_mut())
331 else {
332 continue;
333 };
334 let Some(id) = params.get("id").and_then(|value| value.as_str()) else {
335 continue;
336 };
337 let id = id.to_string();
338 if let Some(pose) = session.pose_updates.get(&id) {
339 fn has_expression(value: &serde_json::Value) -> bool {
342 match value {
343 serde_json::Value::String(_) => true,
344 serde_json::Value::Array(a) => a.iter().any(has_expression),
345 serde_json::Value::Object(o) => o.values().any(has_expression),
346 _ => false,
347 }
348 }
349 if !params.get("transform").is_some_and(has_expression) {
350 params.insert("transform".into(), pose.clone());
351 }
352 }
353 let prefix = format!("{id}:");
357 for (key, pose) in session.pose_updates.range(prefix.clone()..) {
358 let Some(path) = key.strip_prefix(&prefix) else { break };
359 let poses = params
360 .entry("flexiblePoses")
361 .or_insert_with(|| serde_json::Value::Object(Default::default()));
362 if let Some(poses) = poses.as_object_mut() {
363 poses.insert(path.to_string(), pose.clone());
364 }
365 }
366 if let Some(&fixed) = session.fixed_updates.get(&id) {
367 params.insert("isFixed".into(), serde_json::Value::Bool(fixed));
368 }
369 for (key, &fixed) in session.fixed_updates.range(prefix.clone()..) {
372 let Some(path) = key.strip_prefix(&prefix) else { break };
373 let overrides = params
374 .entry("flexibleFixed")
375 .or_insert_with(|| serde_json::Value::Object(Default::default()));
376 if !overrides.is_object() {
377 *overrides = serde_json::Value::Object(Default::default());
378 }
379 if let Some(overrides) = overrides.as_object_mut() {
380 overrides.insert(path.to_string(), serde_json::Value::Bool(fixed));
381 }
382 }
383 }
384 }
385 Ok(())
386 })?;
387 Ok(document.to_string())
388}
389
390#[wasm_bindgen]
397pub fn assembly_overlay_json() -> String {
398 SESSION.with(|session| {
399 let session = session.borrow();
400 let Some(session) = session.as_ref() else {
401 return serde_json::Value::Array(Vec::new()).to_string();
402 };
403 let env = Env::build(&session.expressions, &session.configurator)
404 .unwrap_or_else(Env::poisoned);
405 let rows: Vec<serde_json::Value> = session
406 .state
407 .constraints
408 .iter()
409 .filter(|entry| entry.enabled)
410 .map(|entry| overlay_row(entry, &session.scene, &env))
411 .collect();
412 serde_json::Value::Array(rows).to_string()
413 })
414}
415
416fn overlay_row(entry: &ConstraintEntry, scene: &SceneMap, env: &Env) -> serde_json::Value {
417 let mut row = serde_json::json!({
418 "id": entry.id(),
419 "type": entry.constraint_type,
420 "status": entry.status(),
421 "message": entry
422 .persistent("message")
423 .and_then(|value| value.as_str())
424 .unwrap_or(""),
425 });
426 let elements = entry.elements();
427 let resolved: Vec<_> = elements
428 .iter()
429 .filter_map(|name| mapping::resolve_element(scene, name).ok())
430 .collect();
431 if resolved.len() != elements.len() || resolved.is_empty() {
432 return row;
433 }
434 let object = row.as_object_mut().expect("row is an object");
435 object.insert(
436 "anchors".into(),
437 serde_json::Value::Array(
438 resolved
439 .iter()
440 .map(|element| {
441 let p = element.world.representative_point();
442 serde_json::json!([p.x, p.y, p.z])
443 })
444 .collect(),
445 ),
446 );
447 object.insert(
448 "directions".into(),
449 serde_json::Value::Array(
450 resolved
451 .iter()
452 .map(|element| match mapping::direction_of(&element.world) {
453 Some(direction) => serde_json::json!([direction.x, direction.y, direction.z]),
454 None => serde_json::Value::Null,
455 })
456 .collect(),
457 ),
458 );
459 object.insert(
464 "geoms".into(),
465 serde_json::Value::Array(
466 resolved
467 .iter()
468 .map(|element| serde_json::json!(geometry_tag(&element.world)))
469 .collect(),
470 ),
471 );
472 let mut scratch = entry.clone();
477 if let Ok(mapped) = mapping::map_constraint(&mut scratch, &resolved, env) {
478 if let Some((value, unit)) = mapped.measured {
479 object.insert("value".into(), serde_json::json!(value));
480 object.insert("unit".into(), serde_json::json!(unit));
481 }
482 if let Some(axis) = mapped.angle_axis {
483 object.insert("angleAxis".into(), serde_json::json!([axis.x, axis.y, axis.z]));
484 }
485 if let Some(target) = mapped.target {
486 object.insert("target".into(), serde_json::json!(target));
487 }
488 if !mapped.groups.is_empty() {
489 object.insert("groups".into(), serde_json::json!(mapped.groups));
490 }
491 }
492 row
493}
494
495fn local_element_json(element: &mapping::ResolvedElement) -> serde_json::Value {
500 let point = mapping::local_point(element);
501 let direction = mapping::direction_of(&element.local)
502 .map(|d| serde_json::json!([d.x, d.y, d.z]))
503 .unwrap_or(serde_json::Value::Null);
504 let radius = match element.local {
505 crate::SelectionGeometry::Axis { radius, .. } => radius,
506 crate::SelectionGeometry::Sphere { radius, .. }
507 | crate::SelectionGeometry::Circle { radius, .. } => Some(radius),
508 _ => None,
509 };
510 serde_json::json!({
511 "ref": element.name,
512 "component": element.component,
513 "geom": geometry_tag(&element.local),
514 "point": point,
515 "direction": direction,
516 "radius": radius,
517 })
518}
519
520#[wasm_bindgen]
527pub fn assembly_resolve_refs_json(refs_json: &str) -> String {
528 let refs: Vec<String> = match serde_json::from_str(refs_json) {
529 Ok(refs) => refs,
530 Err(error) => return refusal(&format!("refs: {error}")),
531 };
532 SESSION.with(|session| {
533 let session = session.borrow();
534 let Some(session) = session.as_ref() else {
535 return refusal("no assembly session (run a history first)");
536 };
537 let rows: Vec<serde_json::Value> = refs
538 .iter()
539 .map(|name| match mapping::resolve_element(&session.scene, name) {
540 Ok(element) => {
541 let mut row = local_element_json(&element);
542 row["ok"] = serde_json::Value::Bool(true);
543 row
544 }
545 Err(failure) => serde_json::json!({
546 "ref": name,
547 "ok": false,
548 "status": failure.status,
549 "error": failure.message,
550 }),
551 })
552 .collect();
553 serde_json::Value::Array(rows).to_string()
554 })
555}
556
557#[wasm_bindgen]
562pub fn assembly_component_entities_json(component_id: &str) -> String {
563 SESSION.with(|session| {
564 let session = session.borrow();
565 let Some(session) = session.as_ref() else {
566 return serde_json::Value::Array(Vec::new()).to_string();
567 };
568 let mut names: Vec<(&'static str, String)> = Vec::new();
569 for (_, handle) in session.scene.component_solids(component_id) {
570 let _ = crate::with_registered_solid_str(handle, |solid| {
571 for face in solid.shells.iter().flat_map(|shell| &shell.faces) {
572 if let Some(name) = &face.name {
573 names.push(("face", name.clone()));
574 }
575 }
576 for edge in &solid.edges {
577 if let Some(name) = &edge.name {
578 names.push(("edge", name.clone()));
579 }
580 }
581 Ok(())
582 });
583 }
584 let rows: Vec<serde_json::Value> = names
585 .into_iter()
586 .filter_map(|(kind, name)| {
587 let element = mapping::resolve_element(&session.scene, &name).ok()?;
588 let mut row = local_element_json(&element);
589 row["kind"] = serde_json::Value::String(kind.into());
590 Some(row)
591 })
592 .collect();
593 serde_json::Value::Array(rows).to_string()
594 })
595}
596
597fn geometry_tag(geometry: &crate::SelectionGeometry) -> &'static str {
600 match geometry {
601 crate::SelectionGeometry::Plane { .. } => "plane",
602 crate::SelectionGeometry::Line { .. } => "line",
603 crate::SelectionGeometry::Axis { .. } => "axis",
604 crate::SelectionGeometry::Circle { .. } => "circle",
605 crate::SelectionGeometry::Sphere { .. } => "sphere",
606 crate::SelectionGeometry::Point { .. } => "point",
607 }
608}
609
610#[wasm_bindgen]
618pub fn assembly_add_constraint_json(
619 constraint_type: &str,
620 params_json: &str,
621) -> Result<String, JsValue> {
622 assembly_add_constraint_impl(constraint_type, params_json).map_err(|error| JsValue::from_str(&error))
623}
624
625pub fn assembly_add_constraint_impl(constraint_type: &str, params_json: &str) -> Result<String, String> {
626 if constraint_type == "fixed" {
627 return Err(super::FIXED_IS_NOT_A_CONSTRAINT.to_string());
628 }
629 let def = constraints::constraint_type(constraint_type)
630 .ok_or_else(|| format!("Unknown constraint type: {constraint_type}"))?;
631 let mut params: serde_json::Value = serde_json::from_str(params_json)
632 .map_err(|error| format!("constraint params: {error}"))?;
633 if !params.is_object() {
634 return Err("constraint params must be a JSON object".to_string());
635 }
636 with_session(|session| {
637 let id = match params.get("id").and_then(|value| value.as_str()) {
638 Some(id) if !id.is_empty() => id.to_string(),
639 _ => {
640 session.state.id_counter += 1;
641 let id = format!("{}{}", def.short_name, session.state.id_counter);
642 params
643 .as_object_mut()
644 .expect("checked object above")
645 .insert("id".into(), serde_json::Value::String(id.clone()));
646 id
647 }
648 };
649 session.state.constraints.push(ConstraintEntry {
650 constraint_type: constraint_type.to_string(),
651 input_params: params.clone(),
652 persistent_data: serde_json::Value::Object(serde_json::Map::new()),
653 enabled: true,
654 open: true,
655 });
656 let report = solve_session(session);
657 Ok(serde_json::json!({ "id": id, "report": report }).to_string())
658 })
659}
660
661#[wasm_bindgen]
664pub fn assembly_update_constraint_json(id: &str, params_json: &str) -> Result<String, JsValue> {
665 assembly_update_constraint_impl(id, params_json).map_err(|error| JsValue::from_str(&error))
666}
667
668pub fn assembly_update_constraint_impl(id: &str, params_json: &str) -> Result<String, String> {
669 let mut params: serde_json::Value = serde_json::from_str(params_json)
670 .map_err(|error| format!("constraint params: {error}"))?;
671 if !params.is_object() {
672 return Err("constraint params must be a JSON object".to_string());
673 }
674 if params.get("id").and_then(|value| value.as_str()).is_none() {
675 params
676 .as_object_mut()
677 .expect("checked object above")
678 .insert("id".into(), serde_json::Value::String(id.to_string()));
679 }
680 with_session(|session| {
681 let entry = find_entry(session, id)?;
682 entry.input_params = params.clone();
683 Ok(solve_session(session).to_string())
684 })
685}
686
687#[wasm_bindgen]
689pub fn assembly_remove_constraint_json(id: &str) -> Result<String, JsValue> {
690 assembly_remove_constraint_impl(id).map_err(|error| JsValue::from_str(&error))
691}
692
693pub fn assembly_remove_constraint_impl(id: &str) -> Result<String, String> {
694 with_session(|session| {
695 let index = find_index(session, id)?;
696 session.state.constraints.remove(index);
697 Ok(solve_session(session).to_string())
698 })
699}
700
701#[wasm_bindgen]
703pub fn assembly_set_constraint_enabled_json(id: &str, enabled: bool) -> Result<String, JsValue> {
704 assembly_set_constraint_enabled_impl(id, enabled).map_err(|error| JsValue::from_str(&error))
705}
706
707pub fn assembly_set_constraint_enabled_impl(id: &str, enabled: bool) -> Result<String, String> {
708 with_session(|session| {
709 find_entry(session, id)?.enabled = enabled;
710 Ok(solve_session(session).to_string())
711 })
712}
713
714#[wasm_bindgen]
716pub fn assembly_set_constraint_open_json(id: &str, open: bool) -> Result<(), JsValue> {
717 assembly_set_constraint_open_impl(id, open).map_err(|error| JsValue::from_str(&error))
718}
719
720pub fn assembly_set_constraint_open_impl(id: &str, open: bool) -> Result<(), String> {
721 with_session(|session| {
722 find_entry(session, id)?.open = open;
723 Ok(())
724 })
725}
726
727#[wasm_bindgen]
729pub fn assembly_move_constraint_json(id: &str, index: usize) -> Result<String, JsValue> {
730 assembly_move_constraint_impl(id, index).map_err(|error| JsValue::from_str(&error))
731}
732
733pub fn assembly_move_constraint_impl(id: &str, index: usize) -> Result<String, String> {
734 with_session(|session| {
735 let from = find_index(session, id)?;
736 let entry = session.state.constraints.remove(from);
737 let to = index.min(session.state.constraints.len());
738 session.state.constraints.insert(to, entry);
739 Ok(solve_session(session).to_string())
740 })
741}
742
743#[wasm_bindgen]
751pub fn assembly_inferable_types_json() -> String {
752 super::infer::inferable_types_json().to_string()
753}
754
755#[wasm_bindgen]
760pub fn assembly_infer_constraints_json(options_json: &str) -> String {
761 match infer_scan(options_json) {
762 Ok(report) => report.to_string(),
763 Err(error) => refusal(&error),
764 }
765}
766
767#[wasm_bindgen]
772pub fn assembly_apply_inferred_constraints_json(options_json: &str) -> String {
773 match infer_apply(options_json) {
774 Ok(report) => report.to_string(),
775 Err(error) => refusal(&error),
776 }
777}
778
779fn refusal(error: &str) -> String {
780 serde_json::json!({ "ok": false, "error": error }).to_string()
781}
782
783fn infer_scan(options_json: &str) -> Result<serde_json::Value, String> {
784 let options = super::infer::InferOptions::parse(options_json)?;
785 with_session(|session| Ok(super::infer::scan(&session.state, &session.scene, &options).report()))
786}
787
788fn infer_apply(options_json: &str) -> Result<serde_json::Value, String> {
789 let options = super::infer::InferOptions::parse(options_json)?;
790 with_session(|session| {
791 let scan = super::infer::scan(&session.state, &session.scene, &options);
792 let report = scan.report();
793 let created = super::infer::apply(&mut session.state, &scan.candidates);
794 let solve = if created.is_empty() {
795 serde_json::Value::Null
796 } else {
797 solve_session(session)
798 };
799 Ok(serde_json::json!({
800 "ok": true,
801 "created": created,
802 "report": report,
803 "solve": solve,
804 }))
805 })
806}
807
808#[wasm_bindgen]
810pub fn assembly_run_solve_json() -> Result<String, JsValue> {
811 assembly_run_solve_impl().map_err(|error| JsValue::from_str(&error))
812}
813
814pub fn assembly_run_solve_impl() -> Result<String, String> {
815 with_session(|session| Ok(solve_session(session).to_string()))
816}
817
818#[wasm_bindgen]
828pub fn assembly_drag_solve_json(request_json: &str) -> Result<String, JsValue> {
829 assembly_drag_solve_impl(request_json).map_err(|error| JsValue::from_str(&error))
830}
831
832pub fn assembly_drag_solve_impl(request_json: &str) -> Result<String, String> {
833 let request: serde_json::Value =
834 serde_json::from_str(request_json).map_err(|error| format!("drag solve request: {error}"))?;
835 let mut drag = BTreeMap::new();
836 for (id, pose) in request
837 .get("poses")
838 .and_then(|poses| poses.as_object())
839 .ok_or("drag solve request: missing poses")?
840 {
841 drag.insert(id.clone(), mapping::pose_params_to_transform(pose)?);
842 }
843 let mut reference = BTreeMap::new();
844 for (id, pose) in request.get("reference").and_then(|poses| poses.as_object()).into_iter().flatten() {
845 reference.insert(id.clone(), mapping::pose_params_to_transform(pose)?);
846 }
847 let unfix: Vec<String> = request
848 .get("unfix")
849 .and_then(|unfix| unfix.as_array())
850 .map(|ids| ids.iter().filter_map(|id| id.as_str().map(str::to_string)).collect())
851 .unwrap_or_default();
852 let explain = request.get("explain").and_then(|explain| explain.as_bool()).unwrap_or(false);
853 with_session(|session| {
854 let env = Env::build(&session.expressions, &session.configurator)
855 .unwrap_or_else(Env::poisoned);
856 let (poses, blocking) =
857 lifecycle::drag_solve(&session.state, &mut session.scene, &env, &drag, &unfix, &reference, explain)?;
858 let poses: serde_json::Map<String, serde_json::Value> = poses
859 .iter()
860 .map(|(id, transform)| (id.clone(), mapping::transform_to_pose_params(transform)))
861 .collect();
862 Ok(serde_json::json!({ "ok": true, "poses": poses, "blocking": blocking }).to_string())
863 })
864}
865
866#[wasm_bindgen]
877pub fn assembly_frame_solve_json(request_json: &str) -> Result<String, JsValue> {
878 assembly_frame_solve_impl(request_json).map_err(|error| JsValue::from_str(&error))
879}
880
881pub fn assembly_frame_solve_impl(request_json: &str) -> Result<String, String> {
882 let request: serde_json::Value =
883 serde_json::from_str(request_json).map_err(|error| format!("frame solve request: {error}"))?;
884 let mut reference = BTreeMap::new();
885 for (id, pose) in request.get("reference").and_then(|poses| poses.as_object()).into_iter().flatten() {
886 reference.insert(id.clone(), mapping::pose_params_to_transform(pose)?);
887 }
888 with_session(|session| {
889 let mut overrides = BTreeMap::new();
890 for (id, change) in request.get("overrides").and_then(|o| o.as_object()).into_iter().flatten() {
891 let mut entry = lifecycle::FrameOverride::default();
892 if let Some(value) = change.get("value").filter(|v| !v.is_null()) {
893 let value = value.as_f64().ok_or_else(|| format!("frame solve: '{id}' value is not a number"))?;
894 let kind = session
895 .state
896 .constraints
897 .iter()
898 .find(|c| c.id() == id)
899 .map(|c| c.constraint_type.as_str())
900 .ok_or_else(|| format!("frame solve: unknown constraint '{id}'"))?;
901 let param = match kind {
902 "distance" => "distance",
903 "angle" => "angle",
904 other => return Err(format!("frame solve: '{id}' is a {other} constraint; a motor drives a distance or angle")),
905 };
906 entry.value = Some((param.to_string(), value));
907 }
908 entry.enabled = change.get("enabled").and_then(|e| e.as_bool());
909 overrides.insert(id.clone(), entry);
910 }
911 let env = Env::build(&session.expressions, &session.configurator).unwrap_or_else(Env::poisoned);
912 let poses = lifecycle::frame_solve(&session.state, &mut session.scene, &env, &overrides, &reference)?;
913 let poses: serde_json::Map<String, serde_json::Value> = poses
914 .iter()
915 .map(|(id, transform)| (id.clone(), mapping::transform_to_pose_params(transform)))
916 .collect();
917 Ok(serde_json::json!({ "ok": true, "poses": poses }).to_string())
918 })
919}
920
921pub fn assembly_rebase_mechanical_impl(request_json: &str) -> Result<String, String> {
928 let request: serde_json::Value =
929 serde_json::from_str(request_json).map_err(|error| format!("rebase request: {error}"))?;
930 let mut poses = BTreeMap::new();
931 for (id, pose) in request.get("poses").and_then(|poses| poses.as_object()).into_iter().flatten() {
932 poses.insert(id.clone(), mapping::pose_params_to_transform(pose)?);
933 }
934 with_session(|session| {
935 let env = Env::build(&session.expressions, &session.configurator).unwrap_or_else(Env::poisoned);
936 let changed = lifecycle::rebase_mechanical(&mut session.state, &mut session.scene, &env, &poses);
937 Ok(if changed {
938 serde_json::to_string(&session.state).map_err(|error| error.to_string())?
939 } else {
940 "null".to_string()
941 })
942 })
943}
944
945#[wasm_bindgen]
951pub fn assembly_flip_plan_json(id: &str) -> String {
952 SESSION.with(|session| {
953 let session = session.borrow();
954 let Some(session) = session.as_ref() else {
955 return refusal("no assembly session (run a history first)");
956 };
957 let Some(entry) = session.state.constraints.iter().find(|entry| entry.id() == id) else {
958 return refusal(&format!("unknown constraint '{id}'"));
959 };
960 let env = Env::build(&session.expressions, &session.configurator).unwrap_or_else(Env::poisoned);
961 let row = overlay_row(entry, &session.scene, &env);
962 let geoms: Vec<&str> = row["geoms"].as_array().into_iter().flatten().filter_map(|g| g.as_str()).collect();
963 let (patch, seed) = match constraints::flip_rule(&entry.constraint_type, &entry.input_params, &geoms) {
964 Ok(rule) => rule,
965 Err(reason) => return serde_json::json!({ "ok": false, "reason": reason }).to_string(),
966 };
967 let elements: Vec<serde_json::Value> = entry
968 .elements()
969 .iter()
970 .enumerate()
971 .map(|(index, name)| {
972 let component = mapping::resolve_element(&session.scene, name).map(|element| element.component).unwrap_or_default();
973 serde_json::json!({
974 "component": component,
975 "fixed": session.scene.component_fixed(&component).unwrap_or(false),
976 "anchor": row["anchors"].get(index).cloned().unwrap_or(serde_json::Value::Null),
977 "direction": row["directions"].get(index).cloned().unwrap_or(serde_json::Value::Null),
978 "geom": row["geoms"].get(index).cloned().unwrap_or(serde_json::Value::Null),
979 })
980 })
981 .collect();
982 serde_json::json!({
983 "ok": true,
984 "patch": patch,
985 "seed": seed,
986 "elements": elements,
987 "value": row["value"],
988 "angleAxis": row["angleAxis"],
989 })
990 .to_string()
991 })
992}
993
994fn find_index(session: &Session, id: &str) -> Result<usize, String> {
995 session
996 .state
997 .constraints
998 .iter()
999 .position(|entry| entry.id() == id)
1000 .ok_or_else(|| format!("unknown constraint '{id}'"))
1001}
1002
1003fn find_entry<'a>(session: &'a mut Session, id: &str) -> Result<&'a mut ConstraintEntry, String> {
1004 let index = find_index(session, id)?;
1005 Ok(&mut session.state.constraints[index])
1006}