Lift the trace-to-shape geometry into @tts/mesh (traceToShape/ traceToUvBounds), parameterized by scale so the web token viewer and @tts/tabletop share one implementation. Move the CORS proxy HTTP helpers (assetUrl, resolveAssetUrl, traceImage) into a new @tts/http package, and make @tts/tabletop's ErrorBoundary the single source used by the web app. This removes the duplicated ErrorBoundary, assetUrl, tabletopHttp, and trace-to-shape code from apps/web and packages/tabletop.
45 lines
1.3 KiB
TypeScript
45 lines
1.3 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import { traceToShape, traceToUvBounds } from './trace.js';
|
|
|
|
describe('traceToShape', () => {
|
|
it('flips y and centers the traced shape', () => {
|
|
const trace = {
|
|
shape: { outline: [[0, 0], [10, 0], [10, 10], [0, 10]] },
|
|
width: 10,
|
|
height: 10,
|
|
};
|
|
const shape = traceToShape(trace, 0.2);
|
|
// Centered at origin, scaled to fit the 2x2 box. The y-flip reverses
|
|
// winding, so the outline is normalized to CCW (order may differ).
|
|
expect(shape.outline).toHaveLength(4);
|
|
expect(shape.outline).toEqual(
|
|
expect.arrayContaining([
|
|
[-1, 1],
|
|
[1, 1],
|
|
[1, -1],
|
|
[-1, -1],
|
|
]),
|
|
);
|
|
});
|
|
|
|
it('maps holes to clockwise winding', () => {
|
|
const trace = {
|
|
shape: {
|
|
outline: [[0, 0], [10, 0], [10, 10], [0, 10]],
|
|
holes: [[[2, 2], [2, 8], [8, 8], [8, 2]]],
|
|
},
|
|
width: 10,
|
|
height: 10,
|
|
};
|
|
const shape = traceToShape(trace, 0.2);
|
|
expect(shape.holes).toHaveLength(1);
|
|
expect(shape.holes![0]).toHaveLength(4);
|
|
});
|
|
});
|
|
|
|
describe('traceToUvBounds', () => {
|
|
it('maps the full image rectangle to the part box', () => {
|
|
const bounds = traceToUvBounds({ width: 10, height: 20 }, 0.2);
|
|
expect(bounds).toEqual({ minX: -1, minY: -2, maxX: 1, maxY: 2 });
|
|
});
|
|
}); |