-
Notifications
You must be signed in to change notification settings - Fork 5
/
slicer.cs
287 lines (226 loc) · 10.6 KB
/
slicer.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
using System;
using System.Collections.Generic;
using CamBam.Geom;
namespace Matmill
{
class Slice_sequence
{
private double _general_tolerance;
private Ballfield_topographer _topo;
public List<Slice> Slices = new List<Slice>();
public Slice Root_slice { get { return Slices.Count > 0 ? Slices[0] : null; } }
public Slice Last_slice { get { return Slices.Count > 0 ? Slices[Slices.Count - 1] : null; } }
// XXX: refactor me
private static List<Slice> find_lca_path(Slice dst, Slice src)
{
List<Slice> path = new List<Slice>();
List<Slice> src_ancestry = new List<Slice>();
List<Slice> dst_ancestry = new List<Slice>();
for (Slice s = src.Parent; s != null; s = s.Parent)
src_ancestry.Insert(0, s);
for (Slice s = dst.Parent; s != null; s = s.Parent)
dst_ancestry.Insert(0, s);
int lca;
for (lca = 0; lca < Math.Min(src_ancestry.Count, dst_ancestry.Count); lca++)
{
if (src_ancestry[lca] != dst_ancestry[lca])
break;
}
if (lca == 0)
{
; // one of the slices must be the root (no ancestry)
}
else
{
lca -= 1; // the first diverging slices in ancestries were detected, lca is the last same slice, so -1
}
// now lca contains the lca of branches
// collect path up from src to lca and down to dst
for (int i = src_ancestry.Count - 1; i > lca; i--)
path.Add(src_ancestry[i]);
for (int i = lca; i < dst_ancestry.Count - 1; i++)
path.Add(dst_ancestry[i]);
return path;
}
public List<Slice> Find_slices_colliding_with(Slice s)
{
Point2F min = Point2F.Undefined;
Point2F max = Point2F.Undefined;
s.Get_extrema(ref min, ref max);
return _topo.Get_colliding_objects<Slice>(min, max);
}
public List<Point2F> Trace_branch_switch(Slice dst, Slice src)
{
// follow the lca path, while looking for a shortcut to reduce travel time
// TODO: skip parts of path to reduce travel even more
List<Point2F> knots = new List<Point2F>();
Point2F current = src.End;
Point2F end = dst.Start;
foreach (Slice s in find_lca_path(dst, src))
{
if (_topo.Is_line_inside_region(new Line2F(current, end), _general_tolerance))
break;
current = s.Center;
knots.Add(current);
}
return knots;
}
public List<Point2F> Trace_return_to_root()
{
Point2F current = Last_slice.End;
Point2F end = Root_slice.Center;
List<Point2F> path = new List<Point2F>();
if (Last_slice != Root_slice)
{
for (Slice s = Last_slice.Parent; s != Root_slice; s = s.Parent)
{
if (_topo.Is_line_inside_region(new Line2F(current, end), _general_tolerance))
break;
current = s.Center;
path.Add(current);
}
}
path.Add(end);
return path;
}
public void Add(Slice s)
{
Slices.Add(s);
_topo.Add(s.Ball, s);
}
public Slice_sequence(Point2F min, Point2F max, double general_tolerance)
{
_general_tolerance = general_tolerance;
_topo = new Ballfield_topographer(min, max);
}
}
class Branch_slicer
{
public delegate double Get_radius_delegate(Point2F pt);
private const double TED_TOLERANCE_PERCENTAGE = 0.001; // 0.1 %
private const double FINAL_ALLOWED_TED_OVERSHOOT_PERCENTAGE = 0.03; // 3 %
private double _general_tolerance;
private Slice_sequence _sequence;
private double _min_ted;
private double _max_ted;
private double _tool_r;
private RotationDirection _dir;
public double Slice_leadin_angle = 3 * Math.PI / 180;
public double Slice_leadout_angle = 0.5 * Math.PI / 180;
// NOTE: radius getter may return 0 if radius is too small or invalid
public Get_radius_delegate Get_radius = x => 0;
private int evaluate_possible_slice(Slice parent, Point2F pt, ref Slice _candidate)
{
double radius = Get_radius(pt);
if (radius <= 0) return -1; // assuming the branch is always starting from passable mics, so it's a narrow channel and we should be more conservative, go left
Slice s = new Slice(parent, pt, radius, _dir, _tool_r, _sequence.Last_slice.End);
if (s.Placement != Slice_placement.NORMAL)
{
if (s.Placement == Slice_placement.INSIDE_ANOTHER) return 1; // go right
if (s.Placement == Slice_placement.TOO_FAR) return -1; // go left
throw new Exception("unknown slice placement");
}
// intersection
// XXX: is this candidate is better than the last ?
_candidate = s;
s.Refine(_sequence.Find_slices_colliding_with(s), _tool_r, _tool_r);
if (s.Max_ted > _max_ted) return -1; // overshoot, go left
if ((_max_ted - s.Max_ted) / _max_ted > TED_TOLERANCE_PERCENTAGE) return 1; // undershoot outside the strict TED tolerance, go right
return 0; // good slice inside the tolerance, stop search
}
private void trace_branch(Branch branch, Slice parent_slice)
{
double t = 0;
while (true)
{
Slice new_slice = null;
branch.Bisect(pt => evaluate_possible_slice(parent_slice, pt, ref new_slice), ref t, _general_tolerance);
if (new_slice == null)
break;
if (new_slice.Max_ted < _min_ted) // discard slice if outside the specified min TED
break;
// discard slice if outside the final allowed percentage
double err = (new_slice.Max_ted - _max_ted) / _max_ted;
if (err > FINAL_ALLOWED_TED_OVERSHOOT_PERCENTAGE)
{
Logger.warn("failed to create slice within stepover limit. stopping slicing the branch");
break;
}
// if last slice was a root slice, adjust root slice startpoint to remove extra travel and
// append leadout to the candindate. leadin is not needed, since join with root will be exact
// otherwise append both leadin and leadout
if (_sequence.Last_slice == _sequence.Root_slice)
{
Logger.log("changing startpoint of root slice");
_sequence.Root_slice.Change_startpoint(new_slice.Start);
new_slice.Append_leadin_and_leadout(0, Slice_leadout_angle);
}
else
{
new_slice.Append_leadin_and_leadout(Slice_leadin_angle, Slice_leadout_angle);
}
// generate guide move if this slice is not a simple continuation
if (parent_slice != _sequence.Last_slice)
new_slice.Guide = _sequence.Trace_branch_switch(new_slice, _sequence.Last_slice);
_sequence.Add(new_slice);
parent_slice = new_slice;
}
// need to go deeper
foreach (Branch b in branch.Children)
trace_branch(b, parent_slice);
}
public Slice_sequence Run(Branch root, double tool_r, double max_ted, double min_ted, RotationDirection dir)
{
_min_ted = min_ted;
_max_ted = max_ted;
_tool_r = tool_r;
_dir = dir;
Slice root_slice = new Slice(root.Start, Get_radius(root.Start), _dir == RotationDirection.Unknown ? RotationDirection.CCW : _dir);
_sequence.Add(root_slice);
trace_branch(root, root_slice);
return _sequence;
}
public Branch_slicer(Point2F min, Point2F max, double general_tolerance)
{
_sequence = new Slice_sequence(min, max, general_tolerance);
_general_tolerance = general_tolerance;
}
}
class Bypoint_slicer
{
private double _general_tolerance;
private Slice_sequence _sequence;
public double Slice_leadin_angle = 3 * Math.PI / 180;
public double Slice_leadout_angle = 0.5 * Math.PI / 180;
public Slice_sequence Run(List<Point2F> points, double tool_r, double slice_radius, RotationDirection dir)
{
Slice root_slice = new Slice(points[0], slice_radius, dir == RotationDirection.Unknown ? RotationDirection.CCW : dir);
_sequence.Add(root_slice);
for (int i = 1; i < points.Count; i++)
{
Slice new_slice = new Slice(_sequence.Last_slice, points[i], slice_radius, dir, tool_r, _sequence.Last_slice.End);
if (new_slice.Placement != Slice_placement.NORMAL)
throw new Exception("bad slice placement");
if (true)
new_slice.Refine(_sequence.Find_slices_colliding_with(new_slice), tool_r, tool_r);
if (i == 1)
{
Logger.log("changing startpoint of root slice");
_sequence.Root_slice.Change_startpoint(new_slice.Start);
new_slice.Append_leadin_and_leadout(0, Slice_leadout_angle);
}
else
{
new_slice.Append_leadin_and_leadout(Slice_leadin_angle, Slice_leadout_angle);
}
_sequence.Add(new_slice);
}
return _sequence;
}
public Bypoint_slicer(Point2F min, Point2F max, double general_tolerance)
{
_sequence = new Slice_sequence(min, max, general_tolerance);
_general_tolerance = general_tolerance;
}
}
}