-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathapproximationUtils.fs
214 lines (195 loc) · 7.91 KB
/
approximationUtils.fs
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
FeatureScript ✨; /* Automatically generated version */
// This module is part of the FeatureScript Standard Library and is distributed under the MIT License.
// See the LICENSE tab for the license text.
// Copyright (c) 2013-Present PTC Inc.
import(path : "onshape/std/valueBounds.fs", version : "✨");
import(path : "onshape/std/feature.fs", version : "✨");
import(path : "onshape/std/path.fs", version : "✨");
import(path : "onshape/std/splineUtils.fs", version : "✨");
import(path : "onshape/std/containers.fs", version : "✨");
import(path : "onshape/std/curveGeometry.fs", version : "✨");
import(path : "onshape/std/math.fs", version : "✨");
import(path : "onshape/std/evaluate.fs", version : "✨");
/**
* Number of sample taken on the curve to perform the approximation.
*/
export const APPROXIMATION_SAMPLES = 200;
/**
* Maximum number of control points to approximate the curve with.
*/
export const MAX_CONTROL_POINTS = 100;
/**
* Maximum degree of the curve.
*/
export const MAX_DEGREE = 15;
/**
* A `LengthBoundSpec` for approximation tolerance.
*/
export const TOLERANCE_BOUND =
{
(meter) : [1e-8, 1e-5, 1],
(centimeter) : 1e-3,
(millimeter) : 1e-2,
(inch) : 2e-3,
(foot) : 2e-4,
(yard) : 1e-5
} as LengthBoundSpec;
/**
* An `IntegerBoundSpec` for curve degree.
*/
export const DEGREE_BOUND =
{
(unitless) : [2, 3, MAX_DEGREE]
} as IntegerBoundSpec;
/**
* A predicate to add curve approximation parameters to a feature.
*/
export predicate curveApproximationPredicate(definition is map)
{
annotation { "Name" : "Approximate" }
definition.approximate is boolean;
annotation { "Group Name" : "Approximation parameters", "Driving Parameter" : "approximate", "Collapsed By Default" : false }
{
if (definition.approximate)
{
annotation { "Name" : "Target degree", "Column Name" : "Approximation target degree" }
isInteger(definition.approximationDegree, DEGREE_BOUND);
annotation { "Name" : "Maximum control points" }
isInteger(definition.approximationMaxCPs, { (unitless) : [4, 15, MAX_CONTROL_POINTS] } as IntegerBoundSpec);
annotation { "Name" : "Tolerance" }
isLength(definition.approximationTolerance, TOLERANCE_BOUND);
annotation { "Name" : "Keep start derivative" }
definition.keepStartDerivative is boolean;
annotation { "Name" : "Keep end derivative" }
definition.keepEndDerivative is boolean;
annotation { "Name" : "Show deviation" }
definition.approximationShowDeviation is boolean;
annotation { "Group Name" : "Show deviation", "Driving Parameter" : "approximationShowDeviation", "Collapsed By Default" : false }
{
if (definition.approximationShowDeviation)
{
annotation { "Name" : "Maximum deviation", "UIHint" : UIHint.READ_ONLY }
isLength(definition.maxDeviation, NONNEGATIVE_ZERO_DEFAULT_LENGTH_BOUNDS);
}
}
}
}
}
/**
* Checks approximation options to see if the approximation would succeed and highlights the issue if not.
*/
export function checkApproximationParameters(definition is map, path is Path)
{
if (definition.approximationDegree < 2)
{
throw regenError(ErrorStringEnum.EDIT_CURVE_APPROXIMATION_DEGREE_TOO_SMALL, ["approximationDegree"]);
}
var extraCtrlPts = 0;
if (definition.keepStartDerivative)
{
extraCtrlPts += 1;
}
if (definition.keepEndDerivative)
{
extraCtrlPts += 1;
}
if (path.closed && extraCtrlPts > 0)
{
throw regenError(ErrorStringEnum.EDIT_CURVE_CLOSED_APPROXIMATION_NO_DERIVATIVE);
}
// This check comes from the server-side approximation code.
const minControlPoints = max(4, definition.approximationDegree + 1) + (path.closed ? definition.approximationDegree - 1 : 0) + extraCtrlPts;
if (definition.approximationMaxCPs < minControlPoints)
{
throw regenError("Approximation needs at least " ~ minControlPoints ~ " control points for a " ~ (path.closed ? "closed " : "") ~ "curve of degree " ~ definition.approximationDegree ~ ".", ["approximationMaxCPs"]);
}
}
/** @internal */
export function makeApproximationTarget(context is Context, path is Path, keepStartDerivative is boolean, keepEndDerivative is boolean) returns ApproximationTarget
{
var points = [];
var parameters = makeArray(APPROXIMATION_SAMPLES, 0);
for (var i = 1; i < APPROXIMATION_SAMPLES; i += 1)
{
parameters[i] = i / (APPROXIMATION_SAMPLES - 1);
}
const tangentLines = evPathTangentLines(context, path, parameters).tangentLines;
for (var i = 0; i < APPROXIMATION_SAMPLES; i += 1)
{
points = append(points, tangentLines[i].origin);
}
// Create the map for positions and optionally add derivatives
var approximateMap = { 'positions' : points };
if (keepStartDerivative)
{
approximateMap.startDerivative = tangentLines[0].direction;
}
if (keepEndDerivative)
{
approximateMap.endDerivative = tangentLines[APPROXIMATION_SAMPLES - 1].direction;
}
return approximationTarget(approximateMap);
}
function approximateOneCurve(context is Context, curve is Query, definition is map) returns BSplineCurve
{
var path;
try silent
{
path = constructPath(context, qOwnedByBody(curve, EntityType.EDGE), { "tolerance" : 1e-5 * meter }).path;
}
catch (error)
{
throw regenError(error, curve);
}
checkApproximationParameters(definition, path);
const approximationTarget = makeApproximationTarget(context, path, definition.keepStartDerivative, definition.keepEndDerivative);
return approximateSpline(context, {
"degree" : definition.approximationDegree,
"tolerance" : definition.approximationTolerance,
"isPeriodic" : path.closed,
"targets" : [approximationTarget],
"maxControlPoints" : definition.approximationMaxCPs
})[0];
}
/**
* Approximates the curves created by the feature id.
* This meant to be used along with `curveApproximationPredicate` and it expects `definition` to have the parameters defined in the predicate.
*/
export function approximateResults(context is Context, id is Id, definition is map)
{
const curves = evaluateQuery(context, qCreatedBy(id, EntityType.BODY)->qBodyType(BodyType.WIRE));
const numCurves = size(curves);
if (numCurves == 0)
{
return;
}
var maxOfMaxDeviations = 0;
for (var i = 0; i < numCurves; i += 1)
{
const bspline = approximateOneCurve(context, curves[i], definition);
const baseId = id + "approximate" + i;
opCreateBSplineCurve(context, baseId + "bSplineCurve", {
"bSplineCurve" : bspline
});
if (definition.approximationShowDeviation)
{
const maxDeviationResult = evMaxPathDeviation(context, {
"side1" : qCreatedBy(baseId + "bSplineCurve", EntityType.EDGE),
"side2" : curves[i],
"showDeviation" : true });
if (maxOfMaxDeviations < maxDeviationResult.deviation)
{
maxOfMaxDeviations = maxDeviationResult.deviation;
}
}
opEditCurve(context, baseId + "editCurve", {
"wire" : curves[i],
"edge" : qCreatedBy(baseId + "bSplineCurve", EntityType.EDGE),
"showCurves" : true
});
opDeleteBodies(context, baseId + "deleteBSplineCurve", {
"entities" : qCreatedBy(baseId + "bSplineCurve", EntityType.BODY)
});
}
setFeatureComputedParameter(context, id, { "name" : "maxDeviation", "value" : maxOfMaxDeviations });
}