mirror of
https://github.com/jhbruhn/respira.git
synced 2026-01-27 02:13:41 +00:00
fix: add more tests for lock stitch at start, properly place the lock stitch after the first imported stitch
This commit is contained in:
parent
4fb2b40cba
commit
6048a61230
2 changed files with 56 additions and 51 deletions
|
|
@ -4,6 +4,7 @@ import {
|
|||
calculateLockDirection,
|
||||
generateLockStitches,
|
||||
encodeStitchesToPen,
|
||||
LOCK_STITCH_JUMP_SIZE
|
||||
} from './encoder';
|
||||
import { decodeAllPenStitches } from './decoder';
|
||||
import { STITCH, MOVE, TRIM, END } from '../import/constants';
|
||||
|
|
@ -270,19 +271,17 @@ describe('encodeStitchesToPen', () => {
|
|||
|
||||
let idx = 0;
|
||||
|
||||
// 0. 8 starting lock stitches at (0, 0)
|
||||
for (let i = 0; i < 8; i++) {
|
||||
expect(decoded[idx].x).toBeCloseTo(0, 1);
|
||||
expect(decoded[idx].y).toBeCloseTo(0, 1);
|
||||
idx++;
|
||||
}
|
||||
|
||||
// 1. First stitch (0, 0)
|
||||
expect(decoded[idx].x).toBe(0);
|
||||
expect(decoded[idx].y).toBe(0);
|
||||
expect(decoded[idx].isFeed).toBe(false);
|
||||
expect(decoded[idx].isCut).toBe(false);
|
||||
idx++;
|
||||
|
||||
// 0. 8 starting lock stitches at (0, 0)
|
||||
for (; idx < 9; idx++) {
|
||||
expect(decoded[idx].x).to.be.closeTo(0, LOCK_STITCH_JUMP_SIZE);
|
||||
expect(decoded[idx].y).to.be.closeTo(0, LOCK_STITCH_JUMP_SIZE);
|
||||
}
|
||||
|
||||
// 2. Second stitch (10, 0) - last before color change
|
||||
expect(decoded[idx].x).toBe(10);
|
||||
|
|
@ -292,8 +291,8 @@ describe('encodeStitchesToPen', () => {
|
|||
// 3. 8 finishing lock stitches (should be around position 10, 0)
|
||||
for (let i = 0; i < 8; i++) {
|
||||
const lockStitch = decoded[idx];
|
||||
expect(lockStitch.x).toBeCloseTo(10, 1); // Allow some deviation due to rotation
|
||||
expect(lockStitch.y).toBeCloseTo(0, 1);
|
||||
expect(lockStitch.x).to.be.closeTo(10, LOCK_STITCH_JUMP_SIZE); // Allow some deviation due to rotation
|
||||
expect(lockStitch.y).to.be.closeTo(0, LOCK_STITCH_JUMP_SIZE);
|
||||
expect(lockStitch.isFeed).toBe(false);
|
||||
expect(lockStitch.isCut).toBe(false);
|
||||
idx++;
|
||||
|
|
@ -316,8 +315,8 @@ describe('encodeStitchesToPen', () => {
|
|||
// 6. 8 starting lock stitches for new color
|
||||
for (let i = 0; i < 8; i++) {
|
||||
const lockStitch = decoded[idx];
|
||||
expect(lockStitch.x).toBeCloseTo(10, 1);
|
||||
expect(lockStitch.y).toBeCloseTo(0, 1);
|
||||
expect(lockStitch.x).to.be.closeTo(10, LOCK_STITCH_JUMP_SIZE);
|
||||
expect(lockStitch.y).to.be.closeTo(0, LOCK_STITCH_JUMP_SIZE);
|
||||
idx++;
|
||||
}
|
||||
|
||||
|
|
@ -349,8 +348,8 @@ describe('encodeStitchesToPen', () => {
|
|||
// After second stitch, should have:
|
||||
// 1. 8 finishing lock stitches at (10, 0)
|
||||
for (let i = 0; i < 8; i++) {
|
||||
expect(decoded[idx].x).toBeCloseTo(10, 1);
|
||||
expect(decoded[idx].y).toBeCloseTo(0, 1);
|
||||
expect(decoded[idx].x).to.be.closeTo(10, LOCK_STITCH_JUMP_SIZE);
|
||||
expect(decoded[idx].y).to.be.closeTo(0, LOCK_STITCH_JUMP_SIZE);
|
||||
idx++;
|
||||
}
|
||||
|
||||
|
|
@ -373,8 +372,8 @@ describe('encodeStitchesToPen', () => {
|
|||
|
||||
// 5. 8 starting lock stitches at (30, 10)
|
||||
for (let i = 0; i < 8; i++) {
|
||||
expect(decoded[idx].x).toBeCloseTo(30, 1);
|
||||
expect(decoded[idx].y).toBeCloseTo(10, 1);
|
||||
expect(decoded[idx].x).to.be.closeTo(30, LOCK_STITCH_JUMP_SIZE);
|
||||
expect(decoded[idx].y).to.be.closeTo(10, LOCK_STITCH_JUMP_SIZE);
|
||||
idx++;
|
||||
}
|
||||
|
||||
|
|
@ -408,16 +407,17 @@ describe('encodeStitchesToPen', () => {
|
|||
// 8. First stitch of new color at (50, 20)
|
||||
// 9. Last stitch at (60, 20) with END flag
|
||||
|
||||
let idx = 8; // Skip 8 starting lock stitches
|
||||
let idx = 0;
|
||||
|
||||
// 1-2. First two stitches
|
||||
expect(decoded[idx++].x).toBe(0);
|
||||
idx = 9; // skip startingLock
|
||||
expect(decoded[idx++].x).toBe(10);
|
||||
|
||||
// 3. 8 finishing lock stitches at (10, 0)
|
||||
for (let i = 0; i < 8; i++) {
|
||||
expect(decoded[idx].x).toBeCloseTo(10, 1);
|
||||
expect(decoded[idx].y).toBeCloseTo(0, 1);
|
||||
expect(decoded[idx].x).to.be.closeTo(10, LOCK_STITCH_JUMP_SIZE);
|
||||
expect(decoded[idx].y).to.be.closeTo(0, LOCK_STITCH_JUMP_SIZE);
|
||||
idx++;
|
||||
}
|
||||
|
||||
|
|
@ -441,8 +441,8 @@ describe('encodeStitchesToPen', () => {
|
|||
|
||||
// 7. 8 starting lock stitches at (50, 20)
|
||||
for (let i = 0; i < 8; i++) {
|
||||
expect(decoded[idx].x).toBeCloseTo(50, 1);
|
||||
expect(decoded[idx].y).toBeCloseTo(20, 1);
|
||||
expect(decoded[idx].x).to.be.closeTo(50, LOCK_STITCH_JUMP_SIZE);
|
||||
expect(decoded[idx].y).to.be.closeTo(20, LOCK_STITCH_JUMP_SIZE);
|
||||
idx++;
|
||||
}
|
||||
|
||||
|
|
@ -480,17 +480,18 @@ describe('encodeStitchesToPen', () => {
|
|||
// 6. Stitch at (110, 0)
|
||||
// 7. Stitch at (120, 0) with END flag
|
||||
|
||||
let idx = 8; // Skip 8 starting lock stitches
|
||||
let idx = 0;
|
||||
|
||||
// 1-2. First two stitches
|
||||
expect(decoded[idx++].x).toBe(0);
|
||||
idx = 9; // Skip 8 starting lock stitches
|
||||
expect(decoded[idx++].x).toBe(10);
|
||||
|
||||
// 3. 8 finishing lock stitches at (10, 0)
|
||||
for (let i = 0; i < 8; i++) {
|
||||
const lockStitch = decoded[idx];
|
||||
expect(lockStitch.x).toBeCloseTo(10, 1);
|
||||
expect(lockStitch.y).toBeCloseTo(0, 1);
|
||||
expect(lockStitch.x).to.be.closeTo(10, LOCK_STITCH_JUMP_SIZE);
|
||||
expect(lockStitch.y).to.be.closeTo(0, LOCK_STITCH_JUMP_SIZE);
|
||||
expect(lockStitch.isFeed).toBe(false);
|
||||
expect(lockStitch.isCut).toBe(false);
|
||||
idx++;
|
||||
|
|
@ -508,8 +509,8 @@ describe('encodeStitchesToPen', () => {
|
|||
// 5. 8 starting lock stitches at (100, 0)
|
||||
for (let i = 0; i < 8; i++) {
|
||||
const lockStitch = decoded[idx];
|
||||
expect(lockStitch.x).toBeCloseTo(100, 1);
|
||||
expect(lockStitch.y).toBeCloseTo(0, 1);
|
||||
expect(lockStitch.x).to.be.closeTo(100, LOCK_STITCH_JUMP_SIZE);
|
||||
expect(lockStitch.y).to.be.closeTo(0, LOCK_STITCH_JUMP_SIZE);
|
||||
idx++;
|
||||
}
|
||||
|
||||
|
|
@ -566,12 +567,13 @@ describe('encodeStitchesToPen', () => {
|
|||
const result = encodeStitchesToPen(stitches);
|
||||
const decoded = decodeAllPenStitches(result.penBytes);
|
||||
|
||||
let idx = 8; // Skip 8 starting lock stitches
|
||||
let idx = 0;
|
||||
|
||||
// Verify sequence:
|
||||
// 1. Regular stitch at (0, 0)
|
||||
const firstIdx = idx;
|
||||
expect(decoded[idx++].x).toBe(0);
|
||||
idx = 9; // Skip 8 starting lock stitches
|
||||
expect(decoded[firstIdx].isCut).toBe(false);
|
||||
|
||||
// 2. TRIM command at (10, 0) - should have CUT flag
|
||||
|
|
@ -657,31 +659,33 @@ describe('encodeStitchesToPen', () => {
|
|||
// Matching C# behavior: Nuihajime_TomeDataPlus is called when counter <= 2
|
||||
// This adds starting lock stitches to secure the thread at pattern start
|
||||
const stitches = [
|
||||
[10, 20, STITCH, 0],
|
||||
[10, 20, MOVE, 0],
|
||||
[20, 20, STITCH, 0],
|
||||
[30, 20, STITCH | END, 0],
|
||||
];
|
||||
|
||||
const result = encodeStitchesToPen(stitches);
|
||||
const decoded = decodeAllPenStitches(result.penBytes);
|
||||
|
||||
console.log(decoded);
|
||||
// Expected sequence:
|
||||
// 0. Feed move to proper location (should always happen here)
|
||||
// 1. 8 starting lock stitches at (10, 20)
|
||||
// 2. First actual stitch at (10, 20)
|
||||
// 3. Second stitch at (20, 20)
|
||||
// 4. Last stitch at (30, 20)
|
||||
expect(decoded[0].x).toBe(10);
|
||||
expect(decoded[0].y).toBe(20);
|
||||
expect(decoded[0].isFeed).toBe(true);
|
||||
|
||||
// First 8 stitches should be lock stitches around the starting position
|
||||
for (let i = 0; i < 8; i++) {
|
||||
expect(decoded[i].x).toBeCloseTo(10, 1);
|
||||
expect(decoded[i].y).toBeCloseTo(20, 1);
|
||||
for (let i = 1; i < 1 + 8; i++) {
|
||||
expect(decoded[i].x).to.be.closeTo(10, LOCK_STITCH_JUMP_SIZE);
|
||||
expect(decoded[i].y).to.be.closeTo(20, LOCK_STITCH_JUMP_SIZE);
|
||||
expect(decoded[i].isFeed).toBe(false);
|
||||
expect(decoded[i].isCut).toBe(false);
|
||||
}
|
||||
|
||||
// Then the actual stitches
|
||||
expect(decoded[8].x).toBe(10);
|
||||
expect(decoded[8].y).toBe(20);
|
||||
expect(decoded[9].x).toBe(20);
|
||||
expect(decoded[9].y).toBe(20);
|
||||
expect(decoded[10].x).toBe(30);
|
||||
|
|
|
|||
|
|
@ -17,7 +17,8 @@ const PEN_DATA_END = 0x05; // Last stitch of entire pattern
|
|||
const FEED_LENGTH = 50; // Long jump threshold requiring lock stitches and cut
|
||||
const TARGET_LENGTH = 8.0; // Target accumulated length for lock stitch direction
|
||||
const MAX_POINTS = 5; // Maximum points to accumulate for lock stitch direction
|
||||
const LOCK_STITCH_SCALE = 0.4 / 8.0; // Scale the magnitude-8 vector down to 0.4
|
||||
export const LOCK_STITCH_JUMP_SIZE = 4.0;
|
||||
const LOCK_STITCH_SCALE = LOCK_STITCH_JUMP_SIZE / 8.0; // Scale the magnitude-8 vector down to 4
|
||||
|
||||
export interface StitchData {
|
||||
x: number;
|
||||
|
|
@ -153,7 +154,9 @@ export function generateLockStitches(x: number, y: number, dirX: number, dirY: n
|
|||
for (let i = 0; i < 8; i++) {
|
||||
// Alternate between forward (+) and backward (-) direction
|
||||
const sign = (i % 2 === 0) ? 1 : -1;
|
||||
lockBytes.push(...encodeStitchPosition(x + scaledDirX * sign, y + scaledDirY * sign));
|
||||
const xAdd = scaledDirX * sign;
|
||||
const yAdd = scaledDirY * sign;
|
||||
lockBytes.push(...encodeStitchPosition(x + xAdd, y + yAdd));
|
||||
}
|
||||
|
||||
return lockBytes;
|
||||
|
|
@ -177,20 +180,7 @@ export function encodeStitchesToPen(stitches: number[][]): PenEncodingResult {
|
|||
// Track position for calculating jump distances
|
||||
let prevX = 0;
|
||||
let prevY = 0;
|
||||
|
||||
// Add starting lock stitches at the very beginning of the pattern
|
||||
// Matches C# behavior: Nuihajime_TomeDataPlus is called when counter <= 2
|
||||
// Find the first non-MOVE stitch to place the starting locks
|
||||
const firstStitchIndex = stitches.findIndex(s => (s[2] & MOVE) === 0);
|
||||
if (firstStitchIndex !== -1) {
|
||||
const firstStitch = stitches[firstStitchIndex];
|
||||
const startX = Math.round(firstStitch[0]);
|
||||
const startY = Math.round(firstStitch[1]);
|
||||
|
||||
// Calculate direction for starting locks (look forward into the pattern)
|
||||
const startDir = calculateLockDirection(stitches, firstStitchIndex, true);
|
||||
penStitches.push(...generateLockStitches(startX, startY, startDir.dirX, startDir.dirY));
|
||||
}
|
||||
|
||||
|
||||
for (let i = 0; i < stitches.length; i++) {
|
||||
const stitch = stitches[i];
|
||||
|
|
@ -208,11 +198,13 @@ export function encodeStitchesToPen(stitches: number[][]): PenEncodingResult {
|
|||
maxY = Math.max(maxY, absY);
|
||||
}
|
||||
|
||||
const isFirstStitch = i == 0;
|
||||
|
||||
// Check for long jumps that need lock stitches and cuts
|
||||
if (cmd & MOVE) {
|
||||
const jumpDist = Math.sqrt((absX - prevX) ** 2 + (absY - prevY) ** 2);
|
||||
|
||||
if (jumpDist > FEED_LENGTH) {
|
||||
if (!isFirstStitch && jumpDist > FEED_LENGTH) {
|
||||
// Long jump - add finishing lock stitches at previous position
|
||||
// Loop B: End/Cut Vector - Look BACKWARD at previous stitches
|
||||
// This hides the knot inside the embroidery we just finished
|
||||
|
|
@ -285,6 +277,15 @@ export function encodeStitchesToPen(stitches: number[][]): PenEncodingResult {
|
|||
prevX = absX;
|
||||
prevY = absY;
|
||||
|
||||
if (isFirstStitch) {
|
||||
// Add starting lock stitches at the very beginning of the pattern
|
||||
// Matches C# behavior: Nuihajime_TomeDataPlus is called when counter <= 2
|
||||
|
||||
// Calculate direction for starting locks (look forward into the pattern)
|
||||
const startDir = calculateLockDirection(stitches, i, true);
|
||||
penStitches.push(...generateLockStitches(absX, absY, startDir.dirX, startDir.dirY));
|
||||
}
|
||||
|
||||
// Handle color change: finishing lock, cut, jump, COLOR_END, starting lock
|
||||
if (isColorChange) {
|
||||
const nextStitchCmd = nextStitch[2];
|
||||
|
|
|
|||
Loading…
Reference in a new issue