spine-rigc 2.19.0 → 2.19.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/meshquality.ts +147 -14
- package/src/meshreduce.ts +92 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spine-rigc",
|
|
3
|
-
"version": "2.19.
|
|
3
|
+
"version": "2.19.1",
|
|
4
4
|
"description": "AI-authored Spine 2D rigging and animation, verified before it is written. rigc compiles a rig spec into Spine 4.3 skeleton data, gates it with named assertions held to a spine-core round trip's verdicts, and emits nothing that fails. Output imports into the Spine editor. Ships as an agent skill.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/meshquality.ts
CHANGED
|
@@ -473,6 +473,20 @@ const HAUSDORFF_TOLERANCE = 1e-9;
|
|
|
473
473
|
/** The predicate epsilon `segmentsMeet` and `prunePolygon` use (`src/mesh.ts`), for "on the boundary". */
|
|
474
474
|
const ON_BOUNDARY = 1e-9;
|
|
475
475
|
|
|
476
|
+
/**
|
|
477
|
+
* How near a region's band's outer boundary, in drawing px, an edge's nearest
|
|
478
|
+
* point has to come to count as touching it rather than entering the band —
|
|
479
|
+
* the precision of "a single point on the outer boundary" in
|
|
480
|
+
* `edgeIsHeldByRegion`, ten units of the `r6` grid. The refinement puts the
|
|
481
|
+
* vertex it splits an edge at half of this inside the outer boundary and then
|
|
482
|
+
* on the grid, which moves it by at most √2 × 5e-7 px, so the piece beyond it
|
|
483
|
+
* lands inside this tolerance from either side.
|
|
484
|
+
*/
|
|
485
|
+
export const BAND_CONTACT_TOLERANCE = 1e-5;
|
|
486
|
+
|
|
487
|
+
/** Halvings of the parameter interval in the convex searches along an edge (`edgeIsHeldByRegion`): 2^-60 of an edge is below a double's resolution of it. */
|
|
488
|
+
const EDGE_SEARCH_STEPS = 60;
|
|
489
|
+
|
|
476
490
|
// ---------------------------------------------------------------------------
|
|
477
491
|
// refusals
|
|
478
492
|
// ---------------------------------------------------------------------------
|
|
@@ -708,6 +722,110 @@ function segmentToPolygon(a: Pt, b: Pt, poly: readonly Pt[]): number {
|
|
|
708
722
|
return best;
|
|
709
723
|
}
|
|
710
724
|
|
|
725
|
+
/**
|
|
726
|
+
* The parameter in `[0, 1]` along `a`–`b` nearest which `c`–`d` lies — the
|
|
727
|
+
* minimum of the distance from `a + t(b − a)` to the segment `c`–`d`, which is
|
|
728
|
+
* convex in `t`, found by golden-section search. A flat minimum (parallel
|
|
729
|
+
* segments) returns some point of it; the callers never read one there.
|
|
730
|
+
*/
|
|
731
|
+
function nearestParameter(a: Pt, b: Pt, c: Pt, d: Pt): number {
|
|
732
|
+
const at = (t: number): number => distanceToSegment([a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t], c, d);
|
|
733
|
+
const g = (Math.sqrt(5) - 1) / 2;
|
|
734
|
+
let lo = 0;
|
|
735
|
+
let hi = 1;
|
|
736
|
+
for (let i = 0; i < EDGE_SEARCH_STEPS; i++) {
|
|
737
|
+
const p = hi - g * (hi - lo);
|
|
738
|
+
const q = lo + g * (hi - lo);
|
|
739
|
+
if (at(p) <= at(q)) hi = q;
|
|
740
|
+
else lo = p;
|
|
741
|
+
}
|
|
742
|
+
return (lo + hi) / 2;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
/**
|
|
746
|
+
* Does `a`–`b` run along the outer boundary of a band of width `transition`
|
|
747
|
+
* for a positive length — some polygon edge it overlaps in projection by more
|
|
748
|
+
* than `BAND_CONTACT_TOLERANCE`, at that edge's perpendicular distance
|
|
749
|
+
* `transition` (within the tolerance) at both ends of the overlap, so at every
|
|
750
|
+
* point between them? The caller has already found the edge's nearest approach
|
|
751
|
+
* to the polygon within the tolerance of `transition`.
|
|
752
|
+
*/
|
|
753
|
+
function runsAlongOuterBoundary(a: Pt, b: Pt, poly: readonly Pt[], transition: number): boolean {
|
|
754
|
+
const length = Math.hypot(b[0] - a[0], b[1] - a[1]);
|
|
755
|
+
const n = poly.length;
|
|
756
|
+
for (let i = 0; i < n; i++) {
|
|
757
|
+
const c = poly[i];
|
|
758
|
+
const e = poly[(i + 1) % n];
|
|
759
|
+
const side = Math.hypot(e[0] - c[0], e[1] - c[1]);
|
|
760
|
+
if (side === 0) continue;
|
|
761
|
+
const ux = (e[0] - c[0]) / side;
|
|
762
|
+
const uy = (e[1] - c[1]) / side;
|
|
763
|
+
const sa = (a[0] - c[0]) * ux + (a[1] - c[1]) * uy;
|
|
764
|
+
const sb = (b[0] - c[0]) * ux + (b[1] - c[1]) * uy;
|
|
765
|
+
let t0 = 0;
|
|
766
|
+
let t1 = 1;
|
|
767
|
+
if (sa === sb) {
|
|
768
|
+
if (sa < 0 || sa > side) continue;
|
|
769
|
+
} else {
|
|
770
|
+
const p = (0 - sa) / (sb - sa);
|
|
771
|
+
const q = (side - sa) / (sb - sa);
|
|
772
|
+
t0 = Math.max(0, Math.min(p, q));
|
|
773
|
+
t1 = Math.min(1, Math.max(p, q));
|
|
774
|
+
}
|
|
775
|
+
if ((t1 - t0) * length <= BAND_CONTACT_TOLERANCE) continue;
|
|
776
|
+
const off = (t: number): number => Math.abs(ux * (a[1] + (b[1] - a[1]) * t - c[1]) - uy * (a[0] + (b[0] - a[0]) * t - c[0]));
|
|
777
|
+
if (Math.abs(off(t0) - transition) <= BAND_CONTACT_TOLERANCE && Math.abs(off(t1) - transition) <= BAND_CONTACT_TOLERANCE) return true;
|
|
778
|
+
}
|
|
779
|
+
return false;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
/**
|
|
783
|
+
* [agreed, spine-parts#126] Is the edge `a`–`b` held by `region` — does the
|
|
784
|
+
* region's density bound (`MQ_MAX_EDGE` or `MQ_TRANSITION`) apply to it? The
|
|
785
|
+
* one definition both `measureMeshQuality` and `reduceMesh`'s refinement read.
|
|
786
|
+
*
|
|
787
|
+
* The region's active domain is its closed polygon and, when `transition > 0`,
|
|
788
|
+
* the band of that width outside it. An edge is held when it meets that domain,
|
|
789
|
+
* except for one case: its intersection with the domain is a **single point on
|
|
790
|
+
* the band's outer boundary** and the rest of the edge lies outside — an edge
|
|
791
|
+
* that only touches the band from outside is exempt.
|
|
792
|
+
*
|
|
793
|
+
* - An edge that meets the closed polygon is held, whatever the band.
|
|
794
|
+
* - With `transition: 0` there is no outer band, and a contact with the
|
|
795
|
+
* authored boundary is held under the closed-region rule — never exempt.
|
|
796
|
+
* - A positive-length intersection is held, including a segment lying along
|
|
797
|
+
* the band's outer boundary.
|
|
798
|
+
* - Two separate touches of the outer boundary are two points, not one, and
|
|
799
|
+
* the edge is held.
|
|
800
|
+
*
|
|
801
|
+
* "On the outer boundary" is within `BAND_CONTACT_TOLERANCE`. Each region is
|
|
802
|
+
* read on its own: an exemption from one never removes another's bound.
|
|
803
|
+
*/
|
|
804
|
+
export function edgeIsHeldByRegion(a: readonly [number, number], b: readonly [number, number], region: RefinementRegion): boolean {
|
|
805
|
+
const poly: readonly Pt[] = region.polygon;
|
|
806
|
+
if (segmentMeetsPolygon(a, b, poly)) return true;
|
|
807
|
+
const transition = region.transition;
|
|
808
|
+
if (!(transition > 0)) return false;
|
|
809
|
+
const d = segmentToPolygon(a, b, poly);
|
|
810
|
+
if (d > transition) return false;
|
|
811
|
+
if (d < transition - BAND_CONTACT_TOLERANCE) return true;
|
|
812
|
+
if (runsAlongOuterBoundary(a, b, poly, transition)) return true;
|
|
813
|
+
// Where on the edge each polygon side is touched: one point, or several apart.
|
|
814
|
+
const length = Math.hypot(b[0] - a[0], b[1] - a[1]);
|
|
815
|
+
let first = Infinity;
|
|
816
|
+
let last = -Infinity;
|
|
817
|
+
const n = poly.length;
|
|
818
|
+
for (let i = 0; i < n; i++) {
|
|
819
|
+
const c = poly[i];
|
|
820
|
+
const e = poly[(i + 1) % n];
|
|
821
|
+
if (segmentDistance(a, b, c, e) > transition) continue;
|
|
822
|
+
const t = nearestParameter(a, b, c, e);
|
|
823
|
+
first = Math.min(first, t);
|
|
824
|
+
last = Math.max(last, t);
|
|
825
|
+
}
|
|
826
|
+
return (last - first) * length > BAND_CONTACT_TOLERANCE;
|
|
827
|
+
}
|
|
828
|
+
|
|
711
829
|
/** Distance from a point to a closed polyline (the polygon's boundary), and the edge that realises it. */
|
|
712
830
|
function pointToBoundary(p: Pt, poly: readonly Pt[]): { d: number; edge: number } {
|
|
713
831
|
let d = Infinity;
|
|
@@ -1201,9 +1319,12 @@ interface Edge {
|
|
|
1201
1319
|
* graded band, only where the band has width) and `MQ_FILL_DISTANCE` (`h(R)`,
|
|
1202
1320
|
* sampled, informational).
|
|
1203
1321
|
*
|
|
1204
|
-
*
|
|
1205
|
-
*
|
|
1206
|
-
*
|
|
1322
|
+
* Which edges a region holds is `edgeIsHeldByRegion`'s answer and nothing
|
|
1323
|
+
* else's: an edge that only touches the band's outer boundary at one point,
|
|
1324
|
+
* from outside, is not held (spine-parts#126). One bound per edge, the smallest
|
|
1325
|
+
* applicable anywhere on it (P16): `L0` of every region whose closed polygon it
|
|
1326
|
+
* meets, and `L0 + grade·d` of every other region that holds it, `d` its
|
|
1327
|
+
* distance from that region. A row's value is the edge whose
|
|
1207
1328
|
* length most exceeds its own bound — so the row fails exactly when some edge
|
|
1208
1329
|
* it covers is over — and the row names that edge and that bound.
|
|
1209
1330
|
*/
|
|
@@ -1242,15 +1363,29 @@ function regionRows(input: MeshMeasureInput, outline: MeshOutline, hullPolygon:
|
|
|
1242
1363
|
else refusals.set(region.name, `${refusal.code}: ${refusal.message}`);
|
|
1243
1364
|
}
|
|
1244
1365
|
|
|
1245
|
-
/**
|
|
1366
|
+
/**
|
|
1367
|
+
* Per measurable region, per edge (in `edges` order): the edge's distance
|
|
1368
|
+
* from the closed polygon when the region holds it (`edgeIsHeldByRegion`,
|
|
1369
|
+
* the one definition the refinement reads too), null when it does not.
|
|
1370
|
+
*/
|
|
1371
|
+
const holds = new Map<RefinementRegion, Array<number | null>>();
|
|
1372
|
+
for (const region of live) {
|
|
1373
|
+
holds.set(
|
|
1374
|
+
region,
|
|
1375
|
+
edges.map((e) => (edgeIsHeldByRegion(points[e.a], points[e.b], region) ? segmentToPolygon(points[e.a], points[e.b], region.polygon) : null)),
|
|
1376
|
+
);
|
|
1377
|
+
}
|
|
1378
|
+
const edgeIndex = new Map(edges.map((e, i) => [e, i]));
|
|
1379
|
+
|
|
1380
|
+
/** Every bound that applies to an edge, from every measurable region that holds it, and the smallest. */
|
|
1246
1381
|
const boundOf = (e: Edge): number | null => {
|
|
1247
1382
|
let bound: number | null = null;
|
|
1383
|
+
const i = edgeIndex.get(e)!;
|
|
1248
1384
|
for (const region of live) {
|
|
1249
|
-
const d =
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
if (here !== null && (bound === null || here < bound)) bound = here;
|
|
1385
|
+
const d = holds.get(region)![i];
|
|
1386
|
+
if (d === null) continue;
|
|
1387
|
+
const here = d === 0 ? region.maxEdgeLength : r6(region.maxEdgeLength + region.grade * d);
|
|
1388
|
+
if (bound === null || here < bound) bound = here;
|
|
1254
1389
|
}
|
|
1255
1390
|
return bound;
|
|
1256
1391
|
};
|
|
@@ -1280,7 +1415,8 @@ function regionRows(input: MeshMeasureInput, outline: MeshOutline, hullPolygon:
|
|
|
1280
1415
|
continue;
|
|
1281
1416
|
}
|
|
1282
1417
|
const poly: Pt[] = region.polygon;
|
|
1283
|
-
const
|
|
1418
|
+
const held = holds.get(region)!;
|
|
1419
|
+
const inside = edges.filter((_, i) => held[i] === 0);
|
|
1284
1420
|
const maxSpec = spec('MQ_MAX_EDGE', 'px', region.name);
|
|
1285
1421
|
const inWorst = worstOf(inside);
|
|
1286
1422
|
if (inWorst === null) {
|
|
@@ -1289,10 +1425,7 @@ function regionRows(input: MeshMeasureInput, outline: MeshOutline, hullPolygon:
|
|
|
1289
1425
|
out.push(measuredRow(maxSpec, r6(inWorst.edge.length), { op: '<=', value: inWorst.bound }, { at: { edge: [inWorst.edge.a, inWorst.edge.b] } }, true));
|
|
1290
1426
|
}
|
|
1291
1427
|
if (region.transition > 0) {
|
|
1292
|
-
const banded = edges.filter((
|
|
1293
|
-
const d = segmentToPolygon(points[e.a], points[e.b], poly);
|
|
1294
|
-
return d > 0 && d <= region.transition;
|
|
1295
|
-
});
|
|
1428
|
+
const banded = edges.filter((_, i) => held[i] !== null && held[i]! > 0);
|
|
1296
1429
|
const bandWorst = worstOf(banded);
|
|
1297
1430
|
const transitionSpec = spec('MQ_TRANSITION', 'px', region.name);
|
|
1298
1431
|
if (bandWorst === null) {
|
package/src/meshreduce.ts
CHANGED
|
@@ -42,9 +42,13 @@
|
|
|
42
42
|
* to meet `L(R)`, and removing one is undoing the refinement.
|
|
43
43
|
*
|
|
44
44
|
* The refinement measures, takes the first failing region row in report order
|
|
45
|
-
* (code, then region name) and splits that row's worst edge
|
|
46
|
-
*
|
|
47
|
-
* the
|
|
45
|
+
* (code, then region name) and splits that row's worst edge. When an end of
|
|
46
|
+
* the edge lies outside the region and its band, the split is where the edge
|
|
47
|
+
* leaves the band, so the piece to that end only touches the band's outer
|
|
48
|
+
* boundary and `edgeIsHeldByRegion` — the one definition the rows read —
|
|
49
|
+
* exempts it (spine-parts#126). Otherwise the split is at the midpoint when
|
|
50
|
+
* the midpoint lies in the region or its band, else at the point of the edge
|
|
51
|
+
* inside them nearest the midpoint. A region no edge meets — one
|
|
48
52
|
* inside a single triangle — gets the first vertex of its polygon inserted into
|
|
49
53
|
* the triangle that holds it. Every inserted position is on the `r6` grid.
|
|
50
54
|
*
|
|
@@ -79,6 +83,8 @@ import {
|
|
|
79
83
|
checkHullOrder,
|
|
80
84
|
} from './mesh.ts';
|
|
81
85
|
import {
|
|
86
|
+
BAND_CONTACT_TOLERANCE,
|
|
87
|
+
edgeIsHeldByRegion,
|
|
82
88
|
measureMeshQuality,
|
|
83
89
|
type AttachmentRef,
|
|
84
90
|
type ArtFitBounds,
|
|
@@ -159,6 +165,9 @@ const ON_BOUNDARY = 1e-9;
|
|
|
159
165
|
/** How many halvings toward a point known to lie in a region's band the split search tries before it says none was found. */
|
|
160
166
|
const SPLIT_SEARCH_STEPS = 40;
|
|
161
167
|
|
|
168
|
+
/** Iterations of the convex searches `offsetExit` runs along an edge: 2^-60 of an edge is below a double's resolution of it. */
|
|
169
|
+
const EXIT_SEARCH_STEPS = 60;
|
|
170
|
+
|
|
162
171
|
function nameOf(ref: AttachmentRef): string {
|
|
163
172
|
return `${ref.skin ?? '(no skin)'}/${ref.slot}/${ref.attachment}`;
|
|
164
173
|
}
|
|
@@ -797,6 +806,64 @@ function splitPoint(a: Pt, b: Pt, region: RefinementRegion): Pt | null {
|
|
|
797
806
|
return null;
|
|
798
807
|
}
|
|
799
808
|
|
|
809
|
+
/**
|
|
810
|
+
* The parameter, from `inner` (0) to `outer` (1), of the point of the edge
|
|
811
|
+
* nearest `outer` that lies within `level` of `region`'s polygon — where the
|
|
812
|
+
* edge last leaves that offset of the polygon on its way to `outer` — or null
|
|
813
|
+
* when no point of it comes that near. `outer` lies further than `level`, so
|
|
814
|
+
* walking back from it the offset is first met where some polygon side is
|
|
815
|
+
* exactly `level` away: the largest such parameter over the sides. Each side's
|
|
816
|
+
* distance is convex along the edge, so its minimum is found by golden section
|
|
817
|
+
* and the last parameter within `level` by bisection from there towards `outer`.
|
|
818
|
+
*/
|
|
819
|
+
function offsetExit(inner: Pt, outer: Pt, poly: readonly Pt[], level: number): number | null {
|
|
820
|
+
const at = (t: number): Pt => [inner[0] + (outer[0] - inner[0]) * t, inner[1] + (outer[1] - inner[1]) * t];
|
|
821
|
+
const g = (Math.sqrt(5) - 1) / 2;
|
|
822
|
+
let best: number | null = null;
|
|
823
|
+
for (let i = 0; i < poly.length; i++) {
|
|
824
|
+
const c = poly[i];
|
|
825
|
+
const e = poly[(i + 1) % poly.length];
|
|
826
|
+
const f = (t: number): number => distanceToSegment(at(t), c, e);
|
|
827
|
+
let lo = 0;
|
|
828
|
+
let hi = 1;
|
|
829
|
+
for (let k = 0; k < EXIT_SEARCH_STEPS; k++) {
|
|
830
|
+
const p = hi - g * (hi - lo);
|
|
831
|
+
const q = lo + g * (hi - lo);
|
|
832
|
+
if (f(p) <= f(q)) hi = q;
|
|
833
|
+
else lo = p;
|
|
834
|
+
}
|
|
835
|
+
let inside = (lo + hi) / 2;
|
|
836
|
+
if (f(inside) > level) continue;
|
|
837
|
+
let outside = 1;
|
|
838
|
+
for (let k = 0; k < EXIT_SEARCH_STEPS; k++) {
|
|
839
|
+
const mid = (inside + outside) / 2;
|
|
840
|
+
if (f(mid) <= level) inside = mid;
|
|
841
|
+
else outside = mid;
|
|
842
|
+
}
|
|
843
|
+
if (best === null || inside > best) best = inside;
|
|
844
|
+
}
|
|
845
|
+
return best;
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
/**
|
|
849
|
+
* [agreed, spine-parts#126] A split of `inner`–`outer` where it leaves
|
|
850
|
+
* `region`'s band, so that the piece to `outer` only touches the band's outer
|
|
851
|
+
* boundary and is exempt by `edgeIsHeldByRegion` — the one definition the
|
|
852
|
+
* measurement reads. The split is put half of `BAND_CONTACT_TOLERANCE` inside
|
|
853
|
+
* the outer boundary, then on the `r6` grid, so it lies inside the band (P16's
|
|
854
|
+
* checkable form) and within the tolerance of its edge. Null when there is no
|
|
855
|
+
* band, no point of the edge comes that near the polygon, or the point found
|
|
856
|
+
* is an end or fails either condition — each re-checked rather than assumed.
|
|
857
|
+
*/
|
|
858
|
+
function outerSplit(inner: Pt, outer: Pt, region: RefinementRegion): Pt | null {
|
|
859
|
+
if (!(region.transition > 0)) return null;
|
|
860
|
+
const t = offsetExit(inner, outer, region.polygon, region.transition - BAND_CONTACT_TOLERANCE / 2);
|
|
861
|
+
if (t === null) return null;
|
|
862
|
+
const q: Pt = [r6(inner[0] + (outer[0] - inner[0]) * t), r6(inner[1] + (outer[1] - inner[1]) * t)];
|
|
863
|
+
if ((q[0] === inner[0] && q[1] === inner[1]) || (q[0] === outer[0] && q[1] === outer[1])) return null;
|
|
864
|
+
return inRegionOrBand(q, region) && !edgeIsHeldByRegion(q, outer, region) ? q : null;
|
|
865
|
+
}
|
|
866
|
+
|
|
800
867
|
/** Add a vertex at `p` with what interpolation gives it, refusing it under a deform run (P18). */
|
|
801
868
|
function addVertex(run: Run, p: Pt): number {
|
|
802
869
|
const got = interpolate(p, run.input, run.boneRank, run.tally, run.who);
|
|
@@ -892,16 +959,35 @@ function refineRegions(run: Run): PhaseEnd {
|
|
|
892
959
|
const [ra, rb] = target.worst!.at.edge!;
|
|
893
960
|
const a = canon.order[ra];
|
|
894
961
|
const b = canon.order[rb];
|
|
895
|
-
// An end outside the region and its band
|
|
896
|
-
//
|
|
962
|
+
// An end outside the region and its band: split where the edge leaves the band, so the piece to that
|
|
963
|
+
// end only touches the band's outer boundary and is exempt (spine-parts#126) — the same predicate the
|
|
964
|
+
// measurement reads decides it, so the next measurement agrees.
|
|
965
|
+
let exited = false;
|
|
966
|
+
for (const [inner, outer] of [[b, a], [a, b]] as const) {
|
|
967
|
+
if (inRegionOrBand(run.work.pos[outer], region)) continue;
|
|
968
|
+
const q = outerSplit(run.work.pos[inner], run.work.pos[outer], region);
|
|
969
|
+
if (q === null) continue;
|
|
970
|
+
splitEdge(run, a, b, q);
|
|
971
|
+
exited = true;
|
|
972
|
+
break;
|
|
973
|
+
}
|
|
974
|
+
if (exited) continue;
|
|
975
|
+
// An end outside the region and its band further beyond it than the edge's own bound, with no split on
|
|
976
|
+
// the band's outer boundary that frees the piece to it — always so with transition 0, where a contact
|
|
977
|
+
// with the authored boundary stays held: every split keeps a held edge from a vertex inside the region
|
|
978
|
+
// and its band to that end, so no insertion P16 allows can meet it.
|
|
897
979
|
for (const [end, id] of [[ra, a], [rb, b]] as const) {
|
|
898
980
|
const at = run.work.pos[id];
|
|
899
981
|
if (inRegionOrBand(at, region)) continue;
|
|
900
982
|
const beyond = distanceToBoundary(at, region.polygon) - region.transition;
|
|
901
983
|
if (beyond > target.bound!.value) {
|
|
984
|
+
const why =
|
|
985
|
+
region.transition > 0
|
|
986
|
+
? 'no point of the edge on the band\'s outer boundary leaves the piece to it exempt, so an edge from inside the region and its band to it stays over the bound'
|
|
987
|
+
: 'with no band, an edge from the region to it touches the authored boundary and stays held (spine-parts#126), so it stays over the bound';
|
|
902
988
|
return {
|
|
903
989
|
kind: 'stuck',
|
|
904
|
-
constraint: `${rowName(target)} (vertex ${end} of edge ${ra}–${rb} lies ${r6(beyond)} px beyond region "${region.name}"'s ${region.transition} px band, further than the edge's bound ${target.bound!.value}:
|
|
990
|
+
constraint: `${rowName(target)} (vertex ${end} of edge ${ra}–${rb} lies ${r6(beyond)} px beyond region "${region.name}"'s ${region.transition} px band, further than the edge's bound ${target.bound!.value}: ${why}, and the refinement inserts only inside them — P16)`,
|
|
905
991
|
};
|
|
906
992
|
}
|
|
907
993
|
}
|