brep_render/engine_state/
component_move.rs1use crate::geometry3d::{add3, cross3 as cross, sub3};
2use super::transform_gizmo::{
3 euler_xyz_deg_from_quat, normalize3, quat_from_axis_angle, quat_from_euler_xyz_deg, quat_mul,
4 parse_drag_delta, Quat, TransformDelta,
5};
6use super::*;
7
8#[derive(Default)]
16pub struct ComponentMoveArm {
17 pub(super) feature_id: Option<String>,
19 select_after_run: bool,
21 pub(super) anchor: [f64; 3],
24 pub(super) drag: Option<ComponentMoveDrag>,
26}
27
28#[derive(Clone)]
32pub(super) struct ComponentMoveDrag {
33 handle: u32,
34 sx: f32,
35 sy: f32,
36 start: ComponentPose,
37 pending: Option<ComponentPose>,
38}
39
40#[derive(Clone, Copy, PartialEq, Debug)]
43pub(super) struct ComponentPose {
44 pub translate: [f64; 3],
45 pub rotate_deg: [f64; 3],
46 pub anchor: [f64; 3],
47}
48
49impl EngineState {
50 pub fn component_move_armed(&self) -> bool {
52 self.component_move.feature_id.is_some()
53 }
54
55 pub fn component_move_armed_feature(&self) -> String {
57 self.component_move.feature_id.clone().unwrap_or_default()
58 }
59
60 pub fn component_move_toggle(&mut self, feature_id: &str) {
65 let Some(info) = self.component_info(feature_id) else {
66 self.push_notice(format!("'{feature_id}' is not an assembly component"));
67 return;
68 };
69 if info.fixed {
70 self.push_notice(format!(
71 "{} ({feature_id}) is fixed — unfix it to move",
72 info.part_name
73 ));
74 return;
75 }
76 if self.component_move.feature_id.as_deref() == Some(feature_id) {
77 self.disarm_transform();
78 } else {
79 self.component_move_arm_widget(feature_id);
80 }
81 }
82
83 pub fn component_move_copy(&mut self, source_id: &str) -> Result<String, String> {
92 let Some(index) = self.history.index_of(source_id).filter(|&index| {
93 matches!(
94 self.history.feature_type(index).as_deref(),
95 Some("ACOMP") | Some("ASSEMBLY COMPONENT")
96 )
97 }) else {
98 let error = format!("'{source_id}' is not an assembly component");
99 self.push_notice(error.clone());
100 return Err(error);
101 };
102 let mut params = self
103 .history
104 .feature_params(index)
105 .unwrap_or_else(|| serde_json::json!({}));
106 let id = self.history.next_feature_id("ACOMP");
107 let Some(map) = params.as_object_mut() else {
108 return Err(format!("component '{source_id}': malformed inputParams"));
109 };
110 map.insert("id".into(), serde_json::Value::String(id.clone()));
111 map.insert("isFixed".into(), serde_json::Value::Bool(false));
112 let feature = serde_json::json!({
113 "type": "ACOMP",
114 "inputParams": params,
115 "persistentData": {}
116 });
117 self.add_feature(&feature.to_string())?;
118 self.component_move_arm_widget(&id);
121 if self.run_pending() {
122 self.component_move.select_after_run = true;
123 } else {
124 self.select_component(&id);
125 }
126 Ok(id)
127 }
128
129 pub(super) fn component_move_arm(&mut self, feature_id: &str) {
133 match self.component_info(feature_id) {
134 Some(info) if !info.fixed => self.component_move_arm_widget(feature_id),
135 _ => {}
136 }
137 }
138
139 pub(super) fn component_move_reset(&mut self) {
143 self.component_move = ComponentMoveArm::default();
144 }
145
146 fn component_move_arm_widget(&mut self, feature_id: &str) {
150 self.transform_gizmo.feature_id = None;
153 self.transform_gizmo.mode = GizmoMode::None;
154 self.transform_gizmo.drag = None;
155 self.clear_feature_dimension_overlay();
156
157 let anchor = self
158 .component_bbox_center(feature_id)
159 .or_else(|| self.component_info(feature_id).map(|info| info.translate))
160 .unwrap_or([0.0; 3]);
161 self.component_move.feature_id = Some(feature_id.to_string());
162 self.component_move.select_after_run = false;
163 self.component_move.anchor = anchor;
164 self.component_move.drag = None;
165 self.feed_component_widget();
166 self.dirty = true;
167 }
168
169 fn feed_component_widget(&mut self) {
171 let Some(id) = self.component_move.feature_id.clone() else {
172 return;
173 };
174 let Some(info) = self.component_info(&id) else {
175 return;
176 };
177 let pose = ComponentPose {
178 translate: info.translate,
179 rotate_deg: info.rotate_deg,
180 anchor: self.component_move.anchor,
181 };
182 let json = component_frame_json(&pose);
183 let _ = self.widgets.set_transform_json(&json);
184 }
185
186 pub(super) fn component_move_sync(&mut self) {
190 let Some(id) = self.component_move.feature_id.clone() else {
191 return;
192 };
193 match self.component_info(&id) {
194 Some(info) if !info.fixed => {
195 if self.component_move.select_after_run && !self.run_pending() {
198 self.component_move.select_after_run = false;
199 self.select_component(&id);
200 }
201 self.component_move.anchor =
202 self.component_bbox_center(&id).unwrap_or(info.translate);
203 self.feed_component_widget();
204 self.dirty = true;
205 }
206 _ => self.disarm_transform(),
207 }
208 }
209
210 pub fn component_press(&mut self, x: f64, y: f64) -> bool {
213 let Some(id) = self.component_move.feature_id.clone() else {
214 return false;
215 };
216 let handle = self.transform_pick(x, y);
217 if handle == 0 {
218 return false;
219 }
220 let Some(info) = self.component_info(&id) else {
221 return false;
222 };
223 self.widgets.set_transform_active(handle);
224 self.component_move.drag = Some(ComponentMoveDrag {
225 handle,
226 sx: x as f32,
227 sy: y as f32,
228 start: ComponentPose {
229 translate: info.translate,
230 rotate_deg: info.rotate_deg,
231 anchor: self.component_move.anchor,
232 },
233 pending: None,
234 });
235 self.dirty = true;
236 true
237 }
238
239 pub fn component_move_dragging(&self) -> bool {
241 self.component_move.drag.is_some()
242 }
243
244 pub fn component_drag_to(&mut self, cx: f64, cy: f64) {
248 let Some(drag) = self.component_move.drag.clone() else {
249 return;
250 };
251 let cam = gizmo_camera(&self.camera);
252 let frame = component_frame_json(&drag.start);
253 let json = self.widgets.transform_drag_json_with_frame(
254 &cam,
255 &frame,
256 drag.handle,
257 drag.sx,
258 drag.sy,
259 cx as f32,
260 cy as f32,
261 );
262 let Some(delta) = parse_drag_delta(&json) else {
263 return;
264 };
265 let pending = compose_component_delta(&drag.start, &delta);
266 let json = component_frame_json(&pending);
269 let _ = self.widgets.set_transform_json(&json);
270 if let Some(live) = self.component_move.drag.as_mut() {
271 live.pending = Some(pending);
272 }
273 self.dirty = true;
274 }
275
276 pub fn component_release(&mut self) {
282 let Some(drag) = self.component_move.drag.take() else {
283 return;
284 };
285 self.widgets.set_transform_active(0);
286 self.dirty = true;
287 let Some(pending) = drag.pending else {
288 return;
289 };
290 let Some(id) = self.component_move.feature_id.clone() else {
291 return;
292 };
293 self.component_move.anchor = pending.anchor;
294 let Some(index) = self.history.index_of(&id) else {
295 return;
296 };
297 let mut params = self
298 .history
299 .feature_params(index)
300 .unwrap_or_else(|| serde_json::json!({}));
301 if !params.get("transform").map(|t| t.is_object()).unwrap_or(false) {
302 if let Some(object) = params.as_object_mut() {
303 object.insert("transform".into(), serde_json::json!({}));
304 }
305 }
306 if let Some(transform) = params.get_mut("transform").and_then(|t| t.as_object_mut()) {
307 transform.insert("translate".into(), serde_json::json!(pending.translate));
308 transform.insert("rotateEulerDeg".into(), serde_json::json!(pending.rotate_deg));
309 }
310 let _ = self.update_feature_params(&id, ¶ms.to_string());
311 }
312
313 pub fn component_move_json(&self) -> String {
316 serde_json::json!({
317 "armed": self.component_move_armed(),
318 "feature": self.component_move_armed_feature(),
319 "anchor": self.component_move.anchor,
320 })
321 .to_string()
322 }
323}
324
325fn component_frame_json(pose: &ComponentPose) -> String {
330 let euler = [
331 pose.rotate_deg[0].to_radians(),
332 pose.rotate_deg[1].to_radians(),
333 pose.rotate_deg[2].to_radians(),
334 ];
335 let x = normalize3(rotate_euler_xyz_f64([1.0, 0.0, 0.0], euler));
336 let y = normalize3(rotate_euler_xyz_f64([0.0, 1.0, 0.0], euler));
337 let z = normalize3(rotate_euler_xyz_f64([0.0, 0.0, 1.0], euler));
338 serde_json::json!({
339 "origin": pose.anchor,
340 "x": x,
341 "y": y,
342 "z": z,
343 "showCenter": true,
344 "showAxes": true,
345 "showRings": true,
346 })
347 .to_string()
348}
349
350pub(super) fn compose_component_delta(
353 start: &ComponentPose,
354 delta: &TransformDelta,
355) -> ComponentPose {
356 match delta {
357 TransformDelta::Translate(d) => ComponentPose {
358 translate: add3(start.translate, *d),
359 rotate_deg: start.rotate_deg,
360 anchor: add3(start.anchor, *d),
361 },
362 TransformDelta::Rotate { axis, radians } => {
363 let dq = quat_from_axis_angle(*axis, *radians);
364 let q0 = quat_from_euler_xyz_deg(start.rotate_deg);
365 let rotate_deg = euler_xyz_deg_from_quat(quat_mul(dq, q0));
366 let offset = sub3(start.translate, start.anchor);
367 ComponentPose {
368 translate: add3(start.anchor, quat_rotate(dq, offset)),
369 rotate_deg,
370 anchor: start.anchor,
371 }
372 }
373 }
374}
375
376fn quat_rotate(q: Quat, v: [f64; 3]) -> [f64; 3] {
378 let u = [q[0], q[1], q[2]];
379 let w = q[3];
380 let t = cross(u, add3(cross(u, v), [w * v[0], w * v[1], w * v[2]]));
381 [v[0] + 2.0 * t[0], v[1] + 2.0 * t[1], v[2] + 2.0 * t[2]]
382}
383