1use geop_core_editor::{
8 edit_form::{Edit, EditForm},
9 editing::Choice,
10};
11use geop_core_program::{element::ElementReference, reader::Reader};
12use geop_core_topology::SolidId;
13use serde::{Deserialize, Serialize};
14
15use crate::{BooleanOp, NamedModel, Namer, Solid, ToolReach, model, read_model};
16
17pub type SolidRef = ElementReference<NamedModel, Solid>;
19
20pub fn solid(name: impl Into<String>) -> SolidRef {
22 ElementReference::new(model(), name)
23}
24
25#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
40#[serde(tag = "mode", rename_all = "snake_case")]
41pub enum Combine {
42 #[default]
43 NewBody,
44 With {
45 op: BooleanOp,
46 target: SolidRef,
47 },
48}
49
50impl Combine {
51 pub fn cut(target: impl Into<String>) -> Self {
53 Combine::With {
54 op: BooleanOp::Difference,
55 target: solid(target),
56 }
57 }
58
59 pub fn join(target: impl Into<String>) -> Self {
61 Combine::With {
62 op: BooleanOp::Union,
63 target: solid(target),
64 }
65 }
66
67 pub fn intersect(target: impl Into<String>) -> Self {
69 Combine::With {
70 op: BooleanOp::Intersection,
71 target: solid(target),
72 }
73 }
74
75 pub fn target(&self) -> Option<&str> {
77 match self {
78 Combine::NewBody => None,
79 Combine::With { target, .. } => Some(target.name()),
80 }
81 }
82
83 pub fn new_for(before: &NamedModel) -> Self {
86 match before.newest_solid() {
87 Some(target) => Combine::join(target),
88 None => Combine::NewBody,
89 }
90 }
91
92 pub fn follow_sign(&mut self, from: f64, value: f64) {
99 let crossed = (from > 0.0) != (value > 0.0) || (from < 0.0) != (value < 0.0);
100 if let Combine::With { op, .. } = self
101 && crossed
102 && *op != BooleanOp::Intersection
103 {
104 if value > 0.0 {
105 *op = BooleanOp::Union;
106 } else if value < 0.0 {
107 *op = BooleanOp::Difference;
108 }
109 }
110 }
111
112 pub fn target_field<'a, A, T>(
116 f: &mut EditForm<'a, A, T>,
117 reader: &Reader<'a>,
118 label: &str,
119 target: Option<&SolidRef>,
120 set: impl Fn(Edit<'_, A, T>, SolidRef) + 'a,
121 ) {
122 f.pick_one(TARGET, label, reader, target, set);
123 }
124
125 pub fn fields<'a, A, T>(
129 &self,
130 f: &mut EditForm<'a, A, T>,
131 reader: &Reader<'a>,
132 set: impl Fn(&mut A, Combine) + Clone + 'a,
133 ) {
134 let mode = match self {
135 Combine::NewBody => NEW_BODY,
136 Combine::With { op, .. } => op.value(),
137 };
138 let modes =
139 std::iter::once(Choice::new(NEW_BODY, "New body")).chain(BooleanOp::combine_choices());
140 let newest = read_model(reader)
141 .ok()
142 .and_then(|model| model.newest_solid());
143 let target = (self.target().map(str::to_string))
144 .or(newest)
145 .unwrap_or_default();
146 let set_mode = set.clone();
147 f.select(
148 "combine",
149 "combine",
150 mode,
151 modes.collect(),
152 false,
153 move |edit, mode| {
154 if mode == NEW_BODY {
155 set_mode(edit.args, Combine::NewBody);
156 } else if let Some(op) = BooleanOp::from_value(mode) {
157 let target = solid(&target);
158 set_mode(edit.args, Combine::With { op, target });
159 }
160 },
161 );
162 if let Combine::With { op, target } = self {
163 let op = *op;
164 Self::target_field(f, reader, "target", Some(target), move |edit, target| {
165 set(edit.args, Combine::With { op, target })
166 });
167 }
168 }
169
170 pub fn built_name(&self, namer: &Namer) -> String {
173 match self {
174 Combine::NewBody => namer.root(),
175 Combine::With { .. } => namer.name(&["tool"]),
176 }
177 }
178}
179
180const TARGET: &str = "combine_target";
183
184const NEW_BODY: &str = "new_body";
186
187impl BooleanOp {
188 const ALL: [BooleanOp; 4] = [
190 BooleanOp::Union,
191 BooleanOp::Difference,
192 BooleanOp::Intersection,
193 BooleanOp::SymmetricDifference,
194 ];
195
196 pub fn value(self) -> &'static str {
198 match self {
199 BooleanOp::Union => "union",
200 BooleanOp::Difference => "difference",
201 BooleanOp::Intersection => "intersection",
202 BooleanOp::SymmetricDifference => "symmetric_difference",
203 }
204 }
205
206 pub fn from_value(value: &str) -> Option<BooleanOp> {
208 Self::ALL.into_iter().find(|op| op.value() == value)
209 }
210
211 pub fn choices() -> Vec<Choice> {
214 let label = |op| match op {
215 BooleanOp::Union => "Union",
216 BooleanOp::Difference => "Difference",
217 BooleanOp::Intersection => "Intersection",
218 BooleanOp::SymmetricDifference => "Symmetric difference",
219 };
220 (Self::ALL.into_iter())
221 .map(|op| Choice::new(op.value(), label(op)))
222 .collect()
223 }
224
225 fn combine_choices() -> impl Iterator<Item = Choice> {
227 let label = |op| match op {
228 BooleanOp::Union => "Join",
229 BooleanOp::Difference => "Cut",
230 BooleanOp::Intersection => "Intersect",
231 BooleanOp::SymmetricDifference => "Exclude",
232 };
233 (Self::ALL.into_iter()).map(move |op| Choice::new(op.value(), label(op)))
234 }
235}
236
237#[derive(Clone, Debug)]
241pub struct Tool {
242 pub solid: SolidId,
243 pub reach: ToolReach,
244}
245
246impl Tool {
247 pub fn whole(solid: SolidId) -> Self {
249 Tool {
250 solid,
251 reach: ToolReach::default(),
252 }
253 }
254}