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