feat(#23): Gate B length axis and 6" widths (TV-4, TV-5)
Pure client axis module: min-area length axis, rotate/confirm session, scale, and perpendicular width samples every 6".
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
import type { MaskBuffer } from "../../lib/pipeline/types.js";
|
||||
|
||||
function blankMask(width: number, height: number): MaskBuffer {
|
||||
return new Uint8Array(width * height);
|
||||
}
|
||||
|
||||
function fillMaskRect(
|
||||
mask: MaskBuffer,
|
||||
width: number,
|
||||
x0: number,
|
||||
y0: number,
|
||||
x1: number,
|
||||
y1: number,
|
||||
): void {
|
||||
for (let y = y0; y < y1; y++) {
|
||||
for (let x = x0; x < x1; x++) {
|
||||
mask[y * width + x] = 255;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TV-4: axis-aligned slab 96×24 px → with lengthIn=96, scale=1 px/in.
|
||||
* Constant width 24" at every 6" station.
|
||||
*/
|
||||
export function tv4RectMask(): {
|
||||
mask: MaskBuffer;
|
||||
width: number;
|
||||
height: number;
|
||||
lengthIn: number;
|
||||
expectedWidthIn: number;
|
||||
expectedStations: number[];
|
||||
} {
|
||||
const width = 120;
|
||||
const height = 48;
|
||||
const mask = blankMask(width, height);
|
||||
fillMaskRect(mask, width, 12, 12, 108, 36);
|
||||
const expectedStations: number[] = [];
|
||||
for (let s = 6; s < 96; s += 6) expectedStations.push(s);
|
||||
return {
|
||||
mask,
|
||||
width,
|
||||
height,
|
||||
lengthIn: 96,
|
||||
expectedWidthIn: 24,
|
||||
expectedStations,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* TV-5: 96×24 slab with square left end and 45° cut on the right end.
|
||||
* Prevailing length remains horizontal; diagonal of the cut must not win.
|
||||
*/
|
||||
export function tv5AngledEndMask(): {
|
||||
mask: MaskBuffer;
|
||||
width: number;
|
||||
height: number;
|
||||
lengthIn: number;
|
||||
bodyWidthIn: number;
|
||||
} {
|
||||
const width = 120;
|
||||
const height = 48;
|
||||
const mask = blankMask(width, height);
|
||||
const x0 = 12;
|
||||
const x1 = 108; // 96 px long
|
||||
const y0 = 12;
|
||||
const y1 = 36; // 24 px wide
|
||||
const cutStart = x1 - 24; // 45° cut over last 24" of length
|
||||
for (let y = y0; y < y1; y++) {
|
||||
for (let x = x0; x < x1; x++) {
|
||||
if (x >= cutStart) {
|
||||
const cutY = y0 + (x - cutStart);
|
||||
if (y < cutY) continue;
|
||||
}
|
||||
mask[y * width + x] = 255;
|
||||
}
|
||||
}
|
||||
return {
|
||||
mask,
|
||||
width,
|
||||
height,
|
||||
lengthIn: 96,
|
||||
bodyWidthIn: 24,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
confirmAxis,
|
||||
createAxisSession,
|
||||
estimateLengthAxis,
|
||||
rotateAxis,
|
||||
sampleWidths,
|
||||
computeScale,
|
||||
} from "../lib/pipeline/axis.js";
|
||||
import { WIDTH_SAMPLE_IN } from "../lib/pipeline/constants.js";
|
||||
import { tv4RectMask, tv5AngledEndMask } from "./fixtures/axis.js";
|
||||
|
||||
describe("TV-4 Width samples at 6\" stations", () => {
|
||||
it("reports min, max, average perpendicular to confirmed axis on 96\" length", () => {
|
||||
const fx = tv4RectMask();
|
||||
const axis = estimateLengthAxis(fx.mask, fx.width, fx.height);
|
||||
const session = confirmAxis(createAxisSession(axis));
|
||||
expect(session.state).toBe("confirmed");
|
||||
|
||||
const scale = computeScale(session.axis.lengthPx, fx.lengthIn);
|
||||
const stats = sampleWidths(
|
||||
fx.mask,
|
||||
fx.width,
|
||||
fx.height,
|
||||
session.axis,
|
||||
fx.lengthIn,
|
||||
);
|
||||
|
||||
console.log(
|
||||
JSON.stringify({
|
||||
case: "TV-4",
|
||||
lengthPx: session.axis.lengthPx,
|
||||
widthPx: session.axis.widthPx,
|
||||
scale,
|
||||
stations: stats.samples.map((s) => s.stationIn),
|
||||
widths: stats.samples.map((s) => s.widthIn),
|
||||
min: stats.min,
|
||||
max: stats.max,
|
||||
average: stats.average,
|
||||
expectedWidthIn: fx.expectedWidthIn,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(WIDTH_SAMPLE_IN).toBe(6);
|
||||
expect(scale).toBeCloseTo(1, 5);
|
||||
expect(stats.samples.map((s) => s.stationIn)).toEqual(fx.expectedStations);
|
||||
expect(stats.samples.length).toBe(15);
|
||||
for (const s of stats.samples) {
|
||||
expect(s.widthIn).toBeCloseTo(fx.expectedWidthIn, 0);
|
||||
}
|
||||
expect(stats.min).toBeCloseTo(fx.expectedWidthIn, 0);
|
||||
expect(stats.max).toBeCloseTo(fx.expectedWidthIn, 0);
|
||||
expect(stats.average).toBeCloseTo(fx.expectedWidthIn, 0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("TV-5 Angled-end length axis", () => {
|
||||
it("follows prevailing length; widths stay perpendicular after confirm", () => {
|
||||
const fx = tv5AngledEndMask();
|
||||
const axis = estimateLengthAxis(fx.mask, fx.width, fx.height);
|
||||
let session = createAxisSession(axis);
|
||||
expect(session.state).toBe("pending");
|
||||
|
||||
expect(Math.abs(session.axis.direction.x)).toBeGreaterThan(
|
||||
Math.abs(session.axis.direction.y),
|
||||
);
|
||||
expect(session.axis.lengthPx).toBeGreaterThan(session.axis.widthPx);
|
||||
|
||||
session = confirmAxis(session);
|
||||
expect(session.state).toBe("confirmed");
|
||||
|
||||
const stats = sampleWidths(
|
||||
fx.mask,
|
||||
fx.width,
|
||||
fx.height,
|
||||
session.axis,
|
||||
fx.lengthIn,
|
||||
);
|
||||
|
||||
const bodySamples = stats.samples.filter((s) => s.stationIn <= 72);
|
||||
expect(bodySamples.length).toBeGreaterThan(0);
|
||||
for (const s of bodySamples) {
|
||||
expect(s.widthIn).toBeCloseTo(fx.bodyWidthIn, 0);
|
||||
}
|
||||
|
||||
const rotated = confirmAxis(rotateAxis(createAxisSession(axis), Math.PI / 2));
|
||||
expect(Math.abs(rotated.axis.direction.y)).toBeGreaterThan(
|
||||
Math.abs(rotated.axis.direction.x) - 1e-9,
|
||||
);
|
||||
|
||||
console.log(
|
||||
JSON.stringify({
|
||||
case: "TV-5",
|
||||
direction: session.axis.direction,
|
||||
lengthPx: session.axis.lengthPx,
|
||||
widthPx: session.axis.widthPx,
|
||||
bodySampleCount: bodySamples.length,
|
||||
bodyWidths: bodySamples.map((s) => s.widthIn),
|
||||
min: stats.min,
|
||||
max: stats.max,
|
||||
average: stats.average,
|
||||
rotatedDirection: rotated.axis.direction,
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,316 @@
|
||||
import { WIDTH_SAMPLE_IN } from "./constants.js";
|
||||
import type {
|
||||
AxisSession,
|
||||
LengthAxis,
|
||||
MaskBuffer,
|
||||
Vec2,
|
||||
WidthSample,
|
||||
WidthStats,
|
||||
} from "./types.js";
|
||||
|
||||
const MASK_ON = 128;
|
||||
|
||||
function isOn(mask: MaskBuffer, width: number, x: number, y: number): boolean {
|
||||
if (x < 0 || y < 0 || x >= width) return false;
|
||||
const h = mask.length / width;
|
||||
if (y >= h) return false;
|
||||
return mask[y * width + x]! >= MASK_ON;
|
||||
}
|
||||
|
||||
function collectPoints(mask: MaskBuffer, width: number, height: number): Vec2[] {
|
||||
const pts: Vec2[] = [];
|
||||
for (let y = 0; y < height; y++) {
|
||||
for (let x = 0; x < width; x++) {
|
||||
if (mask[y * width + x]! >= MASK_ON) {
|
||||
// Pixel as unit square so axis-aligned span matches pixel count.
|
||||
pts.push({ x, y }, { x: x + 1, y }, { x, y: y + 1 }, { x: x + 1, y: y + 1 });
|
||||
}
|
||||
}
|
||||
}
|
||||
return pts;
|
||||
}
|
||||
|
||||
function cross(o: Vec2, a: Vec2, b: Vec2): number {
|
||||
return (a.x - o.x) * (b.y - o.y) - (a.y - o.y) * (b.x - o.x);
|
||||
}
|
||||
|
||||
/** Andrew's monotone chain. */
|
||||
function convexHull(points: Vec2[]): Vec2[] {
|
||||
if (points.length <= 1) return points.slice();
|
||||
const sorted = points.slice().sort((a, b) => (a.x === b.x ? a.y - b.y : a.x - b.x));
|
||||
const lower: Vec2[] = [];
|
||||
for (const p of sorted) {
|
||||
while (lower.length >= 2 && cross(lower[lower.length - 2]!, lower[lower.length - 1]!, p) <= 0) {
|
||||
lower.pop();
|
||||
}
|
||||
lower.push(p);
|
||||
}
|
||||
const upper: Vec2[] = [];
|
||||
for (let i = sorted.length - 1; i >= 0; i--) {
|
||||
const p = sorted[i]!;
|
||||
while (upper.length >= 2 && cross(upper[upper.length - 2]!, upper[upper.length - 1]!, p) <= 0) {
|
||||
upper.pop();
|
||||
}
|
||||
upper.push(p);
|
||||
}
|
||||
lower.pop();
|
||||
upper.pop();
|
||||
return lower.concat(upper);
|
||||
}
|
||||
|
||||
function norm(v: Vec2): number {
|
||||
return Math.hypot(v.x, v.y);
|
||||
}
|
||||
|
||||
function normalize(v: Vec2): Vec2 {
|
||||
const n = norm(v);
|
||||
if (n < 1e-12) return { x: 1, y: 0 };
|
||||
return { x: v.x / n, y: v.y / n };
|
||||
}
|
||||
|
||||
function perp(v: Vec2): Vec2 {
|
||||
return { x: -v.y, y: v.x };
|
||||
}
|
||||
|
||||
function dot(a: Vec2, b: Vec2): number {
|
||||
return a.x * b.x + a.y * b.y;
|
||||
}
|
||||
|
||||
function add(a: Vec2, b: Vec2): Vec2 {
|
||||
return { x: a.x + b.x, y: a.y + b.y };
|
||||
}
|
||||
|
||||
function scale(v: Vec2, s: number): Vec2 {
|
||||
return { x: v.x * s, y: v.y * s };
|
||||
}
|
||||
|
||||
function sub(a: Vec2, b: Vec2): Vec2 {
|
||||
return { x: a.x - b.x, y: a.y - b.y };
|
||||
}
|
||||
|
||||
/** Min-area bounding rect via edge-aligned projections on the hull. */
|
||||
function minAreaRect(hull: Vec2[]): LengthAxis {
|
||||
if (hull.length === 0) {
|
||||
return {
|
||||
center: { x: 0, y: 0 },
|
||||
direction: { x: 1, y: 0 },
|
||||
lengthPx: 0,
|
||||
widthPx: 0,
|
||||
corners: [
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 0, y: 0 },
|
||||
],
|
||||
};
|
||||
}
|
||||
if (hull.length === 1) {
|
||||
const c = hull[0]!;
|
||||
return {
|
||||
center: c,
|
||||
direction: { x: 1, y: 0 },
|
||||
lengthPx: 0,
|
||||
widthPx: 0,
|
||||
corners: [c, c, c, c],
|
||||
};
|
||||
}
|
||||
|
||||
let bestArea = Infinity;
|
||||
let best: LengthAxis | null = null;
|
||||
|
||||
for (let i = 0; i < hull.length; i++) {
|
||||
const a = hull[i]!;
|
||||
const b = hull[(i + 1) % hull.length]!;
|
||||
const edge = normalize(sub(b, a));
|
||||
const normal = perp(edge);
|
||||
|
||||
let minE = Infinity;
|
||||
let maxE = -Infinity;
|
||||
let minN = Infinity;
|
||||
let maxN = -Infinity;
|
||||
for (const p of hull) {
|
||||
const e = dot(p, edge);
|
||||
const n = dot(p, normal);
|
||||
if (e < minE) minE = e;
|
||||
if (e > maxE) maxE = e;
|
||||
if (n < minN) minN = n;
|
||||
if (n > maxN) maxN = n;
|
||||
}
|
||||
|
||||
const len = maxE - minE;
|
||||
const wid = maxN - minN;
|
||||
const area = len * wid;
|
||||
if (area < bestArea) {
|
||||
bestArea = area;
|
||||
const longIsEdge = len >= wid;
|
||||
const direction = longIsEdge ? edge : normal;
|
||||
const lengthPx = longIsEdge ? len : wid;
|
||||
const widthPx = longIsEdge ? wid : len;
|
||||
const center = {
|
||||
x: ((minE + maxE) / 2) * edge.x + ((minN + maxN) / 2) * normal.x,
|
||||
y: ((minE + maxE) / 2) * edge.y + ((minN + maxN) / 2) * normal.y,
|
||||
};
|
||||
const halfL = lengthPx / 2;
|
||||
const halfW = widthPx / 2;
|
||||
const d = direction;
|
||||
const n = perp(d);
|
||||
const corners: [Vec2, Vec2, Vec2, Vec2] = [
|
||||
add(add(center, scale(d, -halfL)), scale(n, -halfW)),
|
||||
add(add(center, scale(d, halfL)), scale(n, -halfW)),
|
||||
add(add(center, scale(d, halfL)), scale(n, halfW)),
|
||||
add(add(center, scale(d, -halfL)), scale(n, halfW)),
|
||||
];
|
||||
// Prefer +x (then +y) so axis orientation is stable for overlays.
|
||||
let dir = direction;
|
||||
if (dir.x < -1e-9 || (Math.abs(dir.x) < 1e-9 && dir.y < 0)) {
|
||||
dir = scale(dir, -1);
|
||||
}
|
||||
best = { center, direction: dir, lengthPx, widthPx, corners };
|
||||
}
|
||||
}
|
||||
|
||||
return best!;
|
||||
}
|
||||
|
||||
/**
|
||||
* TECH-SPEC §4.1: largest foreground → convex hull → min-area rect;
|
||||
* long side = length axis (prevailing length).
|
||||
*/
|
||||
export function estimateLengthAxis(
|
||||
mask: MaskBuffer,
|
||||
width: number,
|
||||
height: number,
|
||||
): LengthAxis {
|
||||
const pts = collectPoints(mask, width, height);
|
||||
if (pts.length === 0) {
|
||||
throw new Error("estimateLengthAxis: empty mask");
|
||||
}
|
||||
const hull = convexHull(pts);
|
||||
return minAreaRect(hull);
|
||||
}
|
||||
|
||||
export function createAxisSession(axis: LengthAxis): AxisSession {
|
||||
return { axis, state: "pending" };
|
||||
}
|
||||
|
||||
export function confirmAxis(session: AxisSession): AxisSession {
|
||||
return { axis: session.axis, state: "confirmed" };
|
||||
}
|
||||
|
||||
/** Rotate length direction about center; swap length/width extents for 90° flips. */
|
||||
export function rotateAxis(session: AxisSession, deltaRad: number): AxisSession {
|
||||
const { axis } = session;
|
||||
const c = Math.cos(deltaRad);
|
||||
const s = Math.sin(deltaRad);
|
||||
const d = axis.direction;
|
||||
let direction = normalize({ x: c * d.x - s * d.y, y: s * d.x + c * d.y });
|
||||
if (direction.x < -1e-9 || (Math.abs(direction.x) < 1e-9 && direction.y < 0)) {
|
||||
direction = scale(direction, -1);
|
||||
}
|
||||
const quarterTurns = Math.round(deltaRad / (Math.PI / 2));
|
||||
const swap = Math.abs(quarterTurns) % 2 === 1;
|
||||
const lengthPx = swap ? axis.widthPx : axis.lengthPx;
|
||||
const widthPx = swap ? axis.lengthPx : axis.widthPx;
|
||||
const halfL = lengthPx / 2;
|
||||
const halfW = widthPx / 2;
|
||||
const n = perp(direction);
|
||||
const corners: [Vec2, Vec2, Vec2, Vec2] = [
|
||||
add(add(axis.center, scale(direction, -halfL)), scale(n, -halfW)),
|
||||
add(add(axis.center, scale(direction, halfL)), scale(n, -halfW)),
|
||||
add(add(axis.center, scale(direction, halfL)), scale(n, halfW)),
|
||||
add(add(axis.center, scale(direction, -halfL)), scale(n, halfW)),
|
||||
];
|
||||
return {
|
||||
axis: { center: axis.center, direction, lengthPx, widthPx, corners },
|
||||
state: "pending",
|
||||
};
|
||||
}
|
||||
|
||||
/** scale = longest_axis_px / L (px per inch). */
|
||||
export function computeScale(lengthPx: number, lengthIn: number): number {
|
||||
if (lengthIn === 0) throw new Error("computeScale: lengthIn is 0");
|
||||
return lengthPx / lengthIn;
|
||||
}
|
||||
|
||||
/** Interior stations every WIDTH_SAMPLE_IN along the length (excludes exact ends). */
|
||||
export function widthStations(lengthIn: number): number[] {
|
||||
const out: number[] = [];
|
||||
for (let s = WIDTH_SAMPLE_IN; s < lengthIn; s += WIDTH_SAMPLE_IN) {
|
||||
out.push(s);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function rayExtent(
|
||||
mask: MaskBuffer,
|
||||
width: number,
|
||||
height: number,
|
||||
origin: Vec2,
|
||||
dir: Vec2,
|
||||
maxT: number,
|
||||
): number {
|
||||
let lo = 0;
|
||||
let hi = maxT;
|
||||
let t = 1;
|
||||
while (t < maxT && isOn(mask, width, Math.floor(origin.x + dir.x * t), Math.floor(origin.y + dir.y * t))) {
|
||||
t *= 2;
|
||||
}
|
||||
hi = Math.min(t, maxT);
|
||||
for (let i = 0; i < 24; i++) {
|
||||
const mid = (lo + hi) / 2;
|
||||
const x = Math.floor(origin.x + dir.x * mid);
|
||||
const y = Math.floor(origin.y + dir.y * mid);
|
||||
if (isOn(mask, width, x, y)) lo = mid;
|
||||
else hi = mid;
|
||||
}
|
||||
return lo;
|
||||
}
|
||||
|
||||
/**
|
||||
* TECH-SPEC §4.4: sample every 6" along confirmed length axis, perpendicular widths.
|
||||
*/
|
||||
export function sampleWidths(
|
||||
mask: MaskBuffer,
|
||||
width: number,
|
||||
height: number,
|
||||
axis: LengthAxis,
|
||||
lengthIn: number,
|
||||
): WidthStats {
|
||||
const scalePx = computeScale(axis.lengthPx, lengthIn);
|
||||
const origin = sub(axis.center, scale(axis.direction, axis.lengthPx / 2));
|
||||
const n = perp(axis.direction);
|
||||
const maxT = Math.hypot(width, height) + 2;
|
||||
const samples: WidthSample[] = [];
|
||||
|
||||
for (const stationIn of widthStations(lengthIn)) {
|
||||
const pos = add(origin, scale(axis.direction, stationIn * scalePx));
|
||||
let probe = pos;
|
||||
if (!isOn(mask, width, Math.floor(probe.x), Math.floor(probe.y))) {
|
||||
const inward = scale(axis.direction, Math.sign(lengthIn / 2 - stationIn) || 1);
|
||||
for (let k = 1; k < 4; k++) {
|
||||
const cand = add(pos, scale(inward, k));
|
||||
if (isOn(mask, width, Math.floor(cand.x), Math.floor(cand.y))) {
|
||||
probe = cand;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
const plus = rayExtent(mask, width, height, probe, n, maxT);
|
||||
const minus = rayExtent(mask, width, height, probe, scale(n, -1), maxT);
|
||||
const widthPx = plus + minus;
|
||||
samples.push({ stationIn, widthIn: widthPx / scalePx });
|
||||
}
|
||||
|
||||
if (samples.length === 0) {
|
||||
return { min: 0, max: 0, average: 0, samples };
|
||||
}
|
||||
let min = samples[0]!.widthIn;
|
||||
let max = samples[0]!.widthIn;
|
||||
let sum = 0;
|
||||
for (const s of samples) {
|
||||
if (s.widthIn < min) min = s.widthIn;
|
||||
if (s.widthIn > max) max = s.widthIn;
|
||||
sum += s.widthIn;
|
||||
}
|
||||
return { min, max, average: sum / samples.length, samples };
|
||||
}
|
||||
@@ -2,3 +2,4 @@ export * from "./constants.js";
|
||||
export * from "./types.js";
|
||||
export * from "./mask.js";
|
||||
export * from "./units.js";
|
||||
export * from "./axis.js";
|
||||
|
||||
@@ -37,3 +37,38 @@ export interface MaskSession {
|
||||
result: MaskResult;
|
||||
state: MaskConfirmState;
|
||||
}
|
||||
|
||||
|
||||
export interface Vec2 {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
/** Min-area long-side axis (TECH-SPEC §4.1). */
|
||||
export interface LengthAxis {
|
||||
center: Vec2;
|
||||
/** Unit vector along prevailing length. */
|
||||
direction: Vec2;
|
||||
lengthPx: number;
|
||||
widthPx: number;
|
||||
corners: [Vec2, Vec2, Vec2, Vec2];
|
||||
}
|
||||
|
||||
export type AxisConfirmState = "pending" | "confirmed";
|
||||
|
||||
export interface AxisSession {
|
||||
axis: LengthAxis;
|
||||
state: AxisConfirmState;
|
||||
}
|
||||
|
||||
export interface WidthSample {
|
||||
stationIn: number;
|
||||
widthIn: number;
|
||||
}
|
||||
|
||||
export interface WidthStats {
|
||||
min: number;
|
||||
max: number;
|
||||
average: number;
|
||||
samples: WidthSample[];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
|
||||
RUN v2.1.9 /workspace/gitea-atd/Ty_Tech/SlabUploader/frontend
|
||||
|
||||
stdout | src/__tests__/tv3-tv10-units.test.ts > TV-3 Scale, sqft, bdft > bdft = sqft × thickness_in with no divide-by-12
|
||||
{"case":"TV-3","lengthIn":96,"thicknessIn":1.5,"sqft":0.64,"bdft":0.96,"expectedBdft":0.96,"identity_1sqft_1in":1}
|
||||
|
||||
stdout | src/__tests__/tv3-tv10-units.test.ts > TV-10 Rounding > half-up / nearest-step matches TECH-SPEC vectors
|
||||
{"case":"TV-10","length":{"raw":96.04,"rounded":96,"expected":96},"sqft":{"raw":0.641,"rounded":0.64,"expected":0.64},"bdft":{"raw":11.525,"rounded":11.53,"expected":11.53},"price":{"raw":12.345,"rounded":12.35,"expected":12.35}}
|
||||
|
||||
✓ src/__tests__/tv3-tv10-units.test.ts (3 tests) 5ms
|
||||
stdout | src/__tests__/tv4-tv5-axis.test.ts > TV-4 Width samples at 6" stations > reports min, max, average perpendicular to confirmed axis on 96" length
|
||||
{"case":"TV-4","lengthPx":96,"widthPx":24,"scale":1,"stations":[6,12,18,24,30,36,42,48,54,60,66,72,78,84,90],"widths":[23.999999046325684,23.999999046325684,23.999999046325684,23.999999046325684,23.999999046325684,23.999999046325684,23.999999046325684,23.999999046325684,23.999999046325684,23.999999046325684,23.999999046325684,23.999999046325684,23.999999046325684,23.999999046325684,23.999999046325684],"min":23.999999046325684,"max":23.999999046325684,"average":23.999999046325684,"expectedWidthIn":24}
|
||||
|
||||
stdout | src/__tests__/tv4-tv5-axis.test.ts > TV-5 Angled-end length axis > follows prevailing length; widths stay perpendicular after confirm
|
||||
{"case":"TV-5","direction":{"x":1,"y":0},"lengthPx":96,"widthPx":24,"bodySampleCount":12,"bodyWidths":[23.999999046325684,23.999999046325684,23.999999046325684,23.999999046325684,23.999999046325684,23.999999046325684,23.999999046325684,23.999999046325684,23.999999046325684,23.999999046325684,23.999999046325684,23.999999046325684],"min":0,"max":23.999999046325684,"average":21.19999910990397,"rotatedDirection":{"x":6.123233995736766e-17,"y":1}}
|
||||
|
||||
✓ src/__tests__/tv4-tv5-axis.test.ts (2 tests) 29ms
|
||||
✓ src/__tests__/tv1-tv2-mask.test.ts (5 tests) 35ms
|
||||
|
||||
Test Files 3 passed (3)
|
||||
Tests 10 passed (10)
|
||||
Start at 06:06:43
|
||||
Duration 460ms (transform 206ms, setup 0ms, collect 319ms, tests 69ms, environment 1ms, prepare 212ms)
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
|
||||
RUN v2.1.9 /workspace/gitea-atd/Ty_Tech/SlabUploader/frontend
|
||||
|
||||
stdout | src/__tests__/tv4-tv5-axis.test.ts > TV-4 Width samples at 6" stations > reports min, max, average perpendicular to confirmed axis on 96" length
|
||||
{"case":"TV-4","lengthPx":96,"widthPx":24,"scale":1,"stations":[6,12,18,24,30,36,42,48,54,60,66,72,78,84,90],"widths":[23.999999046325684,23.999999046325684,23.999999046325684,23.999999046325684,23.999999046325684,23.999999046325684,23.999999046325684,23.999999046325684,23.999999046325684,23.999999046325684,23.999999046325684,23.999999046325684,23.999999046325684,23.999999046325684,23.999999046325684],"min":23.999999046325684,"max":23.999999046325684,"average":23.999999046325684,"expectedWidthIn":24}
|
||||
|
||||
stdout | src/__tests__/tv4-tv5-axis.test.ts > TV-5 Angled-end length axis > follows prevailing length; widths stay perpendicular after confirm
|
||||
{"case":"TV-5","direction":{"x":1,"y":0},"lengthPx":96,"widthPx":24,"bodySampleCount":12,"bodyWidths":[23.999999046325684,23.999999046325684,23.999999046325684,23.999999046325684,23.999999046325684,23.999999046325684,23.999999046325684,23.999999046325684,23.999999046325684,23.999999046325684,23.999999046325684,23.999999046325684],"min":0,"max":23.999999046325684,"average":21.19999910990397,"rotatedDirection":{"x":6.123233995736766e-17,"y":1}}
|
||||
|
||||
✓ src/__tests__/tv4-tv5-axis.test.ts (2 tests) 25ms
|
||||
|
||||
Test Files 1 passed (1)
|
||||
Tests 2 passed (2)
|
||||
Start at 06:06:32
|
||||
Duration 445ms (transform 84ms, setup 0ms, collect 86ms, tests 25ms, environment 0ms, prepare 61ms)
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
|
||||
RUN v2.1.9 /workspace/gitea-atd/Ty_Tech/SlabUploader/frontend
|
||||
|
||||
❯ src/__tests__/tv4-tv5-axis.test.ts (0 test)
|
||||
|
||||
⎯⎯⎯⎯⎯⎯ Failed Suites 1 ⎯⎯⎯⎯⎯⎯⎯
|
||||
|
||||
FAIL src/__tests__/tv4-tv5-axis.test.ts [ src/__tests__/tv4-tv5-axis.test.ts ]
|
||||
Error: Failed to load url ../lib/pipeline/axis.js (resolved id: ../lib/pipeline/axis.js) in /workspace/gitea-atd/Ty_Tech/SlabUploader/frontend/src/__tests__/tv4-tv5-axis.test.ts. Does the file exist?
|
||||
❯ loadAndTransform node_modules/vite/dist/node/chunks/dep-BK3b2jBa.js:51969:17
|
||||
|
||||
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯
|
||||
|
||||
Test Files 1 failed (1)
|
||||
Tests no tests
|
||||
Start at 06:05:51
|
||||
Duration 423ms (transform 53ms, setup 0ms, collect 0ms, tests 0ms, environment 0ms, prepare 72ms)
|
||||
|
||||
Reference in New Issue
Block a user