mirror of
https://github.com/jhbruhn/respira.git
synced 2026-01-27 10:23:41 +00:00
Fix PES to PEN conversion and protocol implementation
- Use PyStitch raw stitches with proper command flag handling - Import constants from pystitch.EmbConstant (STITCH, JUMP, TRIM, etc.) - Filter COLOR_CHANGE, STOP, and END command-only stitches - Properly encode jump/trim stitches with PEN_FEED_DATA flag - Add pattern centering with moveX/moveY in layout - Fix color change detection and PEN_COLOR_END marking - Add comprehensive debug logging for pattern analysis - Fix machine state helpers for IDLE and MASK_TRACE_COMPLETE states - Update ProgressMonitor UI for proper button visibility - Add error handling for undefined error codes Machine now successfully uploads patterns, completes mask trace, and transitions to sewing mode. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
acdf87b237
commit
e0fadf69da
6 changed files with 356 additions and 89 deletions
|
|
@ -230,15 +230,17 @@ export function ProgressMonitor({
|
|||
</div>
|
||||
)}
|
||||
|
||||
{/* Mask trace complete - waiting for confirmation */}
|
||||
{/* Mask trace complete - ready to sew */}
|
||||
{isMaskTraceComplete && (
|
||||
<>
|
||||
<div className="status-message success">
|
||||
Mask trace complete!
|
||||
</div>
|
||||
<div className="status-message warning">
|
||||
Press button on machine to confirm (or trace again)
|
||||
</div>
|
||||
{canStartSewing(machineStatus) && (
|
||||
<button onClick={onStartSewing} className="btn-primary">
|
||||
Start Sewing
|
||||
</button>
|
||||
)}
|
||||
{canStartMaskTrace(machineStatus) && (
|
||||
<button onClick={onStartMaskTrace} className="btn-secondary">
|
||||
Trace Again
|
||||
|
|
@ -247,6 +249,20 @@ export function ProgressMonitor({
|
|||
</>
|
||||
)}
|
||||
|
||||
{/* Pattern uploaded, ready to trace */}
|
||||
{machineStatus === MachineStatus.IDLE && (
|
||||
<>
|
||||
<div className="status-message info">
|
||||
Pattern uploaded successfully
|
||||
</div>
|
||||
{canStartMaskTrace(machineStatus) && (
|
||||
<button onClick={onStartMaskTrace} className="btn-secondary">
|
||||
Start Mask Trace
|
||||
</button>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Ready to start (pattern uploaded) */}
|
||||
{machineStatus === MachineStatus.SEWING_WAIT && (
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -202,9 +202,13 @@ export function useBrotherMachine() {
|
|||
try {
|
||||
setError(null);
|
||||
setUploadProgress(0);
|
||||
const uuid = await service.uploadPattern(penData, (progress) => {
|
||||
const uuid = await service.uploadPattern(
|
||||
penData,
|
||||
(progress) => {
|
||||
setUploadProgress(progress);
|
||||
});
|
||||
},
|
||||
pesData.bounds,
|
||||
);
|
||||
setUploadProgress(100);
|
||||
|
||||
// Cache the pattern with its UUID
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ const Commands = {
|
|||
MACHINE_INFO: 0x0000,
|
||||
MACHINE_STATE: 0x0001,
|
||||
SERVICE_COUNT: 0x0100,
|
||||
REGULAR_INSPECTION: 0x0103,
|
||||
PATTERN_UUID_REQUEST: 0x0702,
|
||||
MASK_TRACE: 0x0704,
|
||||
LAYOUT_SEND: 0x0705,
|
||||
|
|
@ -25,13 +26,18 @@ const Commands = {
|
|||
EMB_UUID_SEND: 0x070a,
|
||||
RESUME_FLAG_REQUEST: 0x070b,
|
||||
RESUME: 0x070c,
|
||||
HOOP_AVOIDANCE: 0x070f,
|
||||
START_SEWING: 0x070e,
|
||||
MASK_TRACE_1: 0x0710,
|
||||
EMB_ORG_POINT: 0x0800,
|
||||
FIRM_UPDATE_START: 0x0b00,
|
||||
SET_SETTING_REST: 0x0c00,
|
||||
SET_SETTING_SEND: 0x0c01,
|
||||
MACHINE_SETTING_INFO: 0x0c02,
|
||||
SEND_DATA_INFO: 0x1200,
|
||||
SEND_DATA: 0x1201,
|
||||
CLEAR_ERROR: 0x1300,
|
||||
ERROR_LOG_REPLY: 0x1301,
|
||||
};
|
||||
|
||||
export class BrotherPP1Service {
|
||||
|
|
@ -132,6 +138,38 @@ export class BrotherPP1Service {
|
|||
});
|
||||
}
|
||||
|
||||
private getCommandName(cmdId: number): string {
|
||||
const names: Record<number, string> = {
|
||||
[Commands.MACHINE_INFO]: "MACHINE_INFO",
|
||||
[Commands.MACHINE_STATE]: "MACHINE_STATE",
|
||||
[Commands.SERVICE_COUNT]: "SERVICE_COUNT",
|
||||
[Commands.REGULAR_INSPECTION]: "REGULAR_INSPECTION",
|
||||
[Commands.PATTERN_UUID_REQUEST]: "PATTERN_UUID_REQUEST",
|
||||
[Commands.MASK_TRACE]: "MASK_TRACE",
|
||||
[Commands.LAYOUT_SEND]: "LAYOUT_SEND",
|
||||
[Commands.EMB_SEWING_INFO_REQUEST]: "EMB_SEWING_INFO_REQUEST",
|
||||
[Commands.PATTERN_SEWING_INFO]: "PATTERN_SEWING_INFO",
|
||||
[Commands.EMB_SEWING_DATA_DELETE]: "EMB_SEWING_DATA_DELETE",
|
||||
[Commands.NEEDLE_MODE_INSTRUCTIONS]: "NEEDLE_MODE_INSTRUCTIONS",
|
||||
[Commands.EMB_UUID_SEND]: "EMB_UUID_SEND",
|
||||
[Commands.RESUME_FLAG_REQUEST]: "RESUME_FLAG_REQUEST",
|
||||
[Commands.RESUME]: "RESUME",
|
||||
[Commands.HOOP_AVOIDANCE]: "HOOP_AVOIDANCE",
|
||||
[Commands.START_SEWING]: "START_SEWING",
|
||||
[Commands.MASK_TRACE_1]: "MASK_TRACE_1",
|
||||
[Commands.EMB_ORG_POINT]: "EMB_ORG_POINT",
|
||||
[Commands.FIRM_UPDATE_START]: "FIRM_UPDATE_START",
|
||||
[Commands.SET_SETTING_REST]: "SET_SETTING_REST",
|
||||
[Commands.SET_SETTING_SEND]: "SET_SETTING_SEND",
|
||||
[Commands.MACHINE_SETTING_INFO]: "MACHINE_SETTING_INFO",
|
||||
[Commands.SEND_DATA_INFO]: "SEND_DATA_INFO",
|
||||
[Commands.SEND_DATA]: "SEND_DATA",
|
||||
[Commands.CLEAR_ERROR]: "CLEAR_ERROR",
|
||||
[Commands.ERROR_LOG_REPLY]: "ERROR_LOG_REPLY",
|
||||
};
|
||||
return names[cmdId] || `UNKNOWN(0x${cmdId.toString(16).padStart(4, "0")})`;
|
||||
}
|
||||
|
||||
private async sendCommand(
|
||||
cmdId: number,
|
||||
data: Uint8Array = new Uint8Array(),
|
||||
|
|
@ -148,30 +186,41 @@ export class BrotherPP1Service {
|
|||
command[1] = cmdId & 0xff; // Low byte
|
||||
command.set(data, 2);
|
||||
|
||||
console.log(
|
||||
"Sending command:",
|
||||
Array.from(command)
|
||||
const hexData = Array.from(command)
|
||||
.map((b) => b.toString(16).padStart(2, "0"))
|
||||
.join(" "),
|
||||
.join(" ");
|
||||
|
||||
console.log(
|
||||
`[TX] ${this.getCommandName(cmdId)} (0x${cmdId.toString(16).padStart(4, "0")}):`,
|
||||
hexData,
|
||||
);
|
||||
console.log("Sending command");
|
||||
// Write command and immediately read response
|
||||
await this.writeCharacteristic.writeValueWithoutResponse(command);
|
||||
|
||||
console.log("delay");
|
||||
// Small delay to ensure response is ready
|
||||
// Write command
|
||||
await this.writeCharacteristic.writeValueWithResponse(command);
|
||||
|
||||
// Longer delay to allow machine to prepare response
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
console.log("reading response");
|
||||
|
||||
// Read response
|
||||
const responseData = await this.readCharacteristic.readValue();
|
||||
const response = new Uint8Array(responseData.buffer);
|
||||
|
||||
console.log(
|
||||
"Received response:",
|
||||
Array.from(response)
|
||||
const hexResponse = Array.from(response)
|
||||
.map((b) => b.toString(16).padStart(2, "0"))
|
||||
.join(" "),
|
||||
);
|
||||
.join(" ");
|
||||
|
||||
// Parse response
|
||||
let parsed = "";
|
||||
if (response.length >= 3) {
|
||||
const respCmdId = (response[0] << 8) | response[1];
|
||||
const status = response[2];
|
||||
parsed = ` | Status: 0x${status.toString(16).padStart(2, "0")}`;
|
||||
if (respCmdId !== cmdId) {
|
||||
parsed += ` | WARNING: Response cmd 0x${respCmdId.toString(16).padStart(4, "0")} != request cmd`;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`[RX] ${this.getCommandName(cmdId)}:`, hexResponse, parsed);
|
||||
|
||||
return response;
|
||||
});
|
||||
|
|
@ -289,7 +338,7 @@ export class BrotherPP1Service {
|
|||
}
|
||||
}
|
||||
|
||||
async sendDataChunk(offset: number, data: Uint8Array): Promise<boolean> {
|
||||
async sendDataChunk(offset: number, data: Uint8Array): Promise<void> {
|
||||
const checksum = data.reduce((sum, byte) => (sum + byte) & 0xff, 0);
|
||||
|
||||
const payload = new Uint8Array(4 + data.length + 1);
|
||||
|
|
@ -303,10 +352,81 @@ export class BrotherPP1Service {
|
|||
payload.set(data, 4);
|
||||
payload[4 + data.length] = checksum;
|
||||
|
||||
const response = await this.sendCommand(Commands.SEND_DATA, payload);
|
||||
// Official app approach: Send chunk without waiting for response
|
||||
await this.sendCommandNoResponse(Commands.SEND_DATA, payload);
|
||||
}
|
||||
|
||||
// 0x00 = complete, 0x02 = continue
|
||||
return response[2] === 0x00;
|
||||
private async sendCommandNoResponse(
|
||||
cmdId: number,
|
||||
data: Uint8Array = new Uint8Array(),
|
||||
): Promise<void> {
|
||||
return this.enqueueCommand(async () => {
|
||||
if (!this.writeCharacteristic) {
|
||||
throw new Error("Not connected");
|
||||
}
|
||||
|
||||
// Build command with big-endian command ID
|
||||
const command = new Uint8Array(2 + data.length);
|
||||
command[0] = (cmdId >> 8) & 0xff; // High byte
|
||||
command[1] = cmdId & 0xff; // Low byte
|
||||
command.set(data, 2);
|
||||
|
||||
const hexData = Array.from(command)
|
||||
.map((b) => b.toString(16).padStart(2, "0"))
|
||||
.join(" ");
|
||||
|
||||
console.log(
|
||||
`[TX-NoResp] ${this.getCommandName(cmdId)} (0x${cmdId.toString(16).padStart(4, "0")}):`,
|
||||
hexData.substring(0, 100) + (hexData.length > 100 ? "..." : ""),
|
||||
);
|
||||
|
||||
// Write without reading response
|
||||
await this.writeCharacteristic.writeValueWithResponse(command);
|
||||
|
||||
// Small delay to allow BLE buffer to clear
|
||||
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||
});
|
||||
}
|
||||
|
||||
private async pollForTransferComplete(): Promise<void> {
|
||||
if (!this.readCharacteristic) {
|
||||
throw new Error("Not connected");
|
||||
}
|
||||
|
||||
// Poll until transfer is complete
|
||||
while (true) {
|
||||
const responseData = await this.readCharacteristic.readValue();
|
||||
const response = new Uint8Array(responseData.buffer);
|
||||
|
||||
console.log(
|
||||
"Poll response:",
|
||||
Array.from(response)
|
||||
.map((b) => b.toString(16).padStart(2, "0"))
|
||||
.join(" "),
|
||||
);
|
||||
|
||||
// Check response format: [CMD_HIGH, CMD_LOW, STATUS]
|
||||
if (response.length < 3) {
|
||||
throw new Error("Invalid response length");
|
||||
}
|
||||
|
||||
const status = response[2];
|
||||
|
||||
if (status === 0x01) {
|
||||
// Error
|
||||
throw new Error("Transfer failed");
|
||||
} else if (status === 0x00) {
|
||||
// Complete
|
||||
console.log("Transfer complete");
|
||||
break;
|
||||
} else if (status === 0x02) {
|
||||
// Continue - wait 1 second and poll again (as per official app)
|
||||
console.log("Transfer in progress, waiting...");
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
} else {
|
||||
throw new Error(`Unknown transfer status: 0x${status.toString(16)}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async sendUUID(uuid: Uint8Array): Promise<void> {
|
||||
|
|
@ -325,14 +445,19 @@ export class BrotherPP1Service {
|
|||
rotate: number,
|
||||
flip: number,
|
||||
frame: number,
|
||||
boundLeft: number,
|
||||
boundTop: number,
|
||||
boundRight: number,
|
||||
boundBottom: number,
|
||||
): Promise<void> {
|
||||
const payload = new Uint8Array(12);
|
||||
const payload = new Uint8Array(26);
|
||||
|
||||
const writeInt16LE = (offset: number, value: number) => {
|
||||
payload[offset] = value & 0xff;
|
||||
payload[offset + 1] = (value >> 8) & 0xff;
|
||||
};
|
||||
|
||||
// Position/transformation parameters (12 bytes)
|
||||
writeInt16LE(0, moveX);
|
||||
writeInt16LE(2, moveY);
|
||||
writeInt16LE(4, sizeX);
|
||||
|
|
@ -341,11 +466,41 @@ export class BrotherPP1Service {
|
|||
payload[10] = flip;
|
||||
payload[11] = frame;
|
||||
|
||||
// Pattern bounds (8 bytes)
|
||||
writeInt16LE(12, boundLeft);
|
||||
writeInt16LE(14, boundTop);
|
||||
writeInt16LE(16, boundRight);
|
||||
writeInt16LE(18, boundBottom);
|
||||
|
||||
// Repeat moveX and moveY at the end (6 bytes)
|
||||
writeInt16LE(20, moveX);
|
||||
writeInt16LE(22, moveY);
|
||||
payload[24] = flip;
|
||||
payload[25] = frame;
|
||||
|
||||
console.log('[DEBUG] Layout bounds:', {
|
||||
boundLeft,
|
||||
boundTop,
|
||||
boundRight,
|
||||
boundBottom,
|
||||
moveX,
|
||||
moveY,
|
||||
sizeX,
|
||||
sizeY,
|
||||
});
|
||||
|
||||
await this.sendCommand(Commands.LAYOUT_SEND, payload);
|
||||
}
|
||||
|
||||
async getMachineSettings(): Promise<Uint8Array> {
|
||||
return await this.sendCommand(Commands.MACHINE_SETTING_INFO);
|
||||
}
|
||||
|
||||
async startMaskTrace(): Promise<void> {
|
||||
const payload = new Uint8Array([0x01]);
|
||||
// Query machine settings before starting mask trace (as per official app)
|
||||
await this.getMachineSettings();
|
||||
|
||||
const payload = new Uint8Array([0x00]);
|
||||
await this.sendCommand(Commands.MASK_TRACE, payload);
|
||||
}
|
||||
|
||||
|
|
@ -362,6 +517,7 @@ export class BrotherPP1Service {
|
|||
async uploadPattern(
|
||||
data: Uint8Array,
|
||||
onProgress?: (progress: number) => void,
|
||||
bounds?: { minX: number; maxX: number; minY: number; maxY: number },
|
||||
): Promise<Uint8Array> {
|
||||
// Calculate checksum
|
||||
const checksum = data.reduce((sum, byte) => sum + byte, 0) & 0xffff;
|
||||
|
|
@ -376,34 +532,64 @@ export class BrotherPP1Service {
|
|||
const chunkSize = 500;
|
||||
let offset = 0;
|
||||
|
||||
// Send all chunks without waiting for responses (official app approach)
|
||||
while (offset < data.length) {
|
||||
const chunk = data.slice(
|
||||
offset,
|
||||
Math.min(offset + chunkSize, data.length),
|
||||
);
|
||||
const isComplete = await this.sendDataChunk(offset, chunk);
|
||||
|
||||
await this.sendDataChunk(offset, chunk);
|
||||
offset += chunk.length;
|
||||
|
||||
if (onProgress) {
|
||||
onProgress((offset / data.length) * 100);
|
||||
}
|
||||
|
||||
if (isComplete) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Small delay between chunks
|
||||
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||
}
|
||||
// Wait a bit for machine to finish processing chunks
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
|
||||
// Use provided bounds or default to 0
|
||||
const boundLeft = bounds?.minX ?? 0;
|
||||
const boundTop = bounds?.minY ?? 0;
|
||||
const boundRight = bounds?.maxX ?? 0;
|
||||
const boundBottom = bounds?.maxY ?? 0;
|
||||
|
||||
// Calculate pattern dimensions
|
||||
const patternWidth = boundRight - boundLeft;
|
||||
const patternHeight = boundBottom - boundTop;
|
||||
|
||||
// Calculate center offset to position pattern at machine center
|
||||
// Machine embroidery area center is at (0, 0)
|
||||
// Pattern center should align with machine center
|
||||
const patternCenterX = (boundLeft + boundRight) / 2;
|
||||
const patternCenterY = (boundTop + boundBottom) / 2;
|
||||
|
||||
// moveX/moveY shift the pattern so its center aligns with origin
|
||||
const moveX = -patternCenterX;
|
||||
const moveY = -patternCenterY;
|
||||
|
||||
// Send layout with actual pattern bounds
|
||||
// sizeX/sizeY are scaling factors (100 = 100% = no scaling)
|
||||
await this.sendLayout(
|
||||
Math.round(moveX), // moveX - center the pattern
|
||||
Math.round(moveY), // moveY - center the pattern
|
||||
100, // sizeX (100% - no scaling)
|
||||
100, // sizeY (100% - no scaling)
|
||||
0, // rotate
|
||||
0, // flip
|
||||
1, // frame
|
||||
boundLeft,
|
||||
boundTop,
|
||||
boundRight,
|
||||
boundBottom,
|
||||
);
|
||||
|
||||
// Generate random UUID
|
||||
const uuid = crypto.getRandomValues(new Uint8Array(16));
|
||||
await this.sendUUID(uuid);
|
||||
|
||||
// Send default layout (no transformation)
|
||||
await this.sendLayout(0, 0, 0, 0, 0, 0, 0);
|
||||
|
||||
console.log(
|
||||
"Pattern uploaded successfully with UUID:",
|
||||
Array.from(uuid)
|
||||
|
|
|
|||
|
|
@ -76,7 +76,12 @@ const ERROR_MESSAGES: Record<number, string> = {
|
|||
/**
|
||||
* Get human-readable error message for an error code
|
||||
*/
|
||||
export function getErrorMessage(errorCode: number): string | null {
|
||||
export function getErrorMessage(errorCode: number | undefined): string | null {
|
||||
// Handle undefined or null
|
||||
if (errorCode === undefined || errorCode === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 0xDD (221) is the default "no error" value
|
||||
if (errorCode === SewingMachineError.None) {
|
||||
return null; // No error to display
|
||||
|
|
@ -95,6 +100,6 @@ export function getErrorMessage(errorCode: number): string | null {
|
|||
/**
|
||||
* Check if error code represents an actual error condition
|
||||
*/
|
||||
export function hasError(errorCode: number): boolean {
|
||||
return errorCode !== SewingMachineError.None;
|
||||
export function hasError(errorCode: number | undefined): boolean {
|
||||
return errorCode !== undefined && errorCode !== null && errorCode !== SewingMachineError.None;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -88,6 +88,7 @@ export function canUploadPattern(status: MachineStatus): boolean {
|
|||
export function canStartSewing(status: MachineStatus): boolean {
|
||||
// Only in specific ready states
|
||||
return status === MachineStatus.SEWING_WAIT ||
|
||||
status === MachineStatus.MASK_TRACE_COMPLETE ||
|
||||
status === MachineStatus.PAUSE ||
|
||||
status === MachineStatus.STOP ||
|
||||
status === MachineStatus.SEWING_INTERRUPTION;
|
||||
|
|
@ -97,8 +98,9 @@ export function canStartSewing(status: MachineStatus): boolean {
|
|||
* Determines if mask trace can be started in the current state.
|
||||
*/
|
||||
export function canStartMaskTrace(status: MachineStatus): boolean {
|
||||
// Only when ready or after previous trace
|
||||
return status === MachineStatus.SEWING_WAIT ||
|
||||
// Can start mask trace when IDLE (after upload), SEWING_WAIT, or after previous trace
|
||||
return status === MachineStatus.IDLE ||
|
||||
status === MachineStatus.SEWING_WAIT ||
|
||||
status === MachineStatus.MASK_TRACE_COMPLETE;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -46,45 +46,38 @@ export async function convertPesToPen(file: File): Promise<PesPatternData> {
|
|||
// Read the pattern using PyStitch
|
||||
const result = await pyodide.runPythonAsync(`
|
||||
import pystitch
|
||||
from pystitch.EmbConstant import STITCH, JUMP, TRIM, STOP, END, COLOR_CHANGE
|
||||
|
||||
# Read the PES file
|
||||
pattern = pystitch.read('${filename}')
|
||||
|
||||
# PyStitch groups stitches by color blocks using get_as_stitchblock
|
||||
# This returns tuples of (thread, stitches_list) for each color block
|
||||
# Use the raw stitches list which preserves command flags
|
||||
# Each stitch in pattern.stitches is [x, y, cmd]
|
||||
# We need to assign color indices based on COLOR_CHANGE commands
|
||||
# and filter out COLOR_CHANGE and STOP commands (they're not actual stitches)
|
||||
|
||||
stitches_with_colors = []
|
||||
block_index = 0
|
||||
current_color = 0
|
||||
|
||||
# Iterate through stitch blocks
|
||||
# Each block is a tuple containing (thread, stitch_list)
|
||||
for block in pattern.get_as_stitchblock():
|
||||
if isinstance(block, tuple):
|
||||
# Extract thread and stitch list from tuple
|
||||
thread_obj = None
|
||||
stitches_list = None
|
||||
for i, stitch in enumerate(pattern.stitches):
|
||||
x, y, cmd = stitch
|
||||
|
||||
for elem in block:
|
||||
# Check if this is the thread object (has color or hex_color attributes)
|
||||
if hasattr(elem, 'color') or hasattr(elem, 'hex_color'):
|
||||
thread_obj = elem
|
||||
# Check if this is the stitch list
|
||||
elif isinstance(elem, list) and len(elem) > 0 and isinstance(elem[0], list):
|
||||
stitches_list = elem
|
||||
# Check for color change command - increment color but don't add stitch
|
||||
if cmd == COLOR_CHANGE:
|
||||
current_color += 1
|
||||
continue
|
||||
|
||||
if stitches_list:
|
||||
# Find the index of this thread in the threadlist
|
||||
thread_index = block_index
|
||||
if thread_obj and hasattr(pattern, 'threadlist'):
|
||||
for i, t in enumerate(pattern.threadlist):
|
||||
if t is thread_obj:
|
||||
thread_index = i
|
||||
break
|
||||
# Check for stop command - skip it
|
||||
if cmd == STOP:
|
||||
continue
|
||||
|
||||
for stitch in stitches_list:
|
||||
# stitch is [x, y, command]
|
||||
stitches_with_colors.append([stitch[0], stitch[1], stitch[2], thread_index])
|
||||
# Check for standalone END command (no stitch data)
|
||||
if cmd == END:
|
||||
continue
|
||||
|
||||
block_index += 1
|
||||
# Add actual stitch with color index and command
|
||||
# Keep JUMP/TRIM flags as they indicate jump stitches
|
||||
stitches_with_colors.append([x, y, cmd, current_color])
|
||||
|
||||
# Convert to JSON-serializable format
|
||||
{
|
||||
|
|
@ -98,13 +91,16 @@ for block in pattern.get_as_stitchblock():
|
|||
],
|
||||
'thread_count': len(pattern.threadlist),
|
||||
'stitch_count': len(stitches_with_colors),
|
||||
'block_count': block_index
|
||||
'color_changes': current_color
|
||||
}
|
||||
`);
|
||||
|
||||
// Convert Python result to JavaScript
|
||||
const data = result.toJs({ dict_converter: Object.fromEntries });
|
||||
|
||||
console.log('[DEBUG] PyStitch stitch_count:', data.stitch_count);
|
||||
console.log('[DEBUG] PyStitch color_changes:', data.color_changes);
|
||||
|
||||
// Clean up virtual file
|
||||
try {
|
||||
pyodide.FS.unlink(filename);
|
||||
|
|
@ -117,6 +113,32 @@ for block in pattern.get_as_stitchblock():
|
|||
Array.from(stitch) as number[]
|
||||
);
|
||||
|
||||
console.log('[DEBUG] JavaScript stitches.length:', stitches.length);
|
||||
console.log('[DEBUG] First 5 stitches:', stitches.slice(0, 5));
|
||||
console.log('[DEBUG] Middle 5 stitches:', stitches.slice(Math.floor(stitches.length / 2), Math.floor(stitches.length / 2) + 5));
|
||||
console.log('[DEBUG] Last 5 stitches:', stitches.slice(-5));
|
||||
|
||||
// Count stitch types (PyStitch constants: STITCH=0, JUMP=1, TRIM=2)
|
||||
let jumpCount = 0, normalCount = 0;
|
||||
for (let i = 0; i < stitches.length; i++) {
|
||||
const cmd = stitches[i][2];
|
||||
if (cmd === 1 || cmd === 2) jumpCount++; // JUMP or TRIM
|
||||
else normalCount++; // STITCH (0)
|
||||
}
|
||||
console.log('[DEBUG] Stitch types: normal=' + normalCount + ', jump/trim=' + jumpCount);
|
||||
|
||||
// Calculate min/max of raw stitch values to understand the data
|
||||
let rawMinX = Infinity, rawMaxX = -Infinity, rawMinY = Infinity, rawMaxY = -Infinity;
|
||||
for (let i = 0; i < stitches.length; i++) {
|
||||
const x = stitches[i][0];
|
||||
const y = stitches[i][1];
|
||||
rawMinX = Math.min(rawMinX, x);
|
||||
rawMaxX = Math.max(rawMaxX, x);
|
||||
rawMinY = Math.min(rawMinY, y);
|
||||
rawMaxY = Math.max(rawMaxY, y);
|
||||
}
|
||||
console.log('[DEBUG] Raw stitch value ranges:', { rawMinX, rawMaxX, rawMinY, rawMaxY });
|
||||
|
||||
if (!stitches || stitches.length === 0) {
|
||||
throw new Error('Invalid PES file or no stitches found');
|
||||
}
|
||||
|
|
@ -133,32 +155,35 @@ for block in pattern.get_as_stitchblock():
|
|||
let minY = Infinity;
|
||||
let maxY = -Infinity;
|
||||
|
||||
// Convert to PEN format
|
||||
// PyStitch returns ABSOLUTE coordinates
|
||||
// PEN format uses absolute coordinates, shifted left by 3 bits (as per official app line 780)
|
||||
const penStitches: number[] = [];
|
||||
let currentColor = stitches[0]?.[3] ?? 0; // Track current color using stitch color index
|
||||
|
||||
for (let i = 0; i < stitches.length; i++) {
|
||||
const stitch = stitches[i];
|
||||
const x = Math.round(stitch[0]);
|
||||
const y = Math.round(stitch[1]);
|
||||
const absX = Math.round(stitch[0]);
|
||||
const absY = Math.round(stitch[1]);
|
||||
const cmd = stitch[2];
|
||||
const stitchColor = stitch[3]; // Color index from PyStitch
|
||||
|
||||
// Track bounds for non-jump stitches
|
||||
if ((cmd & MOVE) === 0) {
|
||||
minX = Math.min(minX, x);
|
||||
maxX = Math.max(maxX, x);
|
||||
minY = Math.min(minY, y);
|
||||
maxY = Math.max(maxY, y);
|
||||
// Track bounds for non-jump stitches (cmd=0 is STITCH)
|
||||
if (cmd === 0) {
|
||||
minX = Math.min(minX, absX);
|
||||
maxX = Math.max(maxX, absX);
|
||||
minY = Math.min(minY, absY);
|
||||
maxY = Math.max(maxY, absY);
|
||||
}
|
||||
|
||||
// Encode coordinates with flags in low 3 bits
|
||||
// Encode absolute coordinates with flags in low 3 bits
|
||||
// Shift coordinates left by 3 bits to make room for flags
|
||||
let xEncoded = (x << 3) & 0xFFFF;
|
||||
let yEncoded = (y << 3) & 0xFFFF;
|
||||
// As per official app line 780: buffer[index64] = (byte) ((int) numArray4[index64 / 4, 0] << 3 & (int) byte.MaxValue);
|
||||
let xEncoded = (absX << 3) & 0xFFFF;
|
||||
let yEncoded = (absY << 3) & 0xFFFF;
|
||||
|
||||
// Add jump flag if this is a move command
|
||||
if ((cmd & MOVE) !== 0) {
|
||||
// Add jump flag if this is a JUMP (1) or TRIM (2) command
|
||||
// PyStitch constants: STITCH=0, JUMP=1, TRIM=2
|
||||
if (cmd === 1 || cmd === 2) {
|
||||
yEncoded |= PEN_FEED_DATA;
|
||||
}
|
||||
|
||||
|
|
@ -200,6 +225,35 @@ for block in pattern.get_as_stitchblock():
|
|||
|
||||
const penData = new Uint8Array(penStitches);
|
||||
|
||||
console.log('[DEBUG] PEN data size:', penData.length, 'bytes');
|
||||
console.log('[DEBUG] Encoded stitch count:', penData.length / 4);
|
||||
console.log('[DEBUG] Expected vs Actual:', data.stitch_count, 'vs', penData.length / 4);
|
||||
console.log('[DEBUG] First 20 bytes (5 stitches):',
|
||||
Array.from(penData.slice(0, 20))
|
||||
.map(b => b.toString(16).padStart(2, '0'))
|
||||
.join(' '));
|
||||
console.log('[DEBUG] Last 20 bytes (5 stitches):',
|
||||
Array.from(penData.slice(-20))
|
||||
.map(b => b.toString(16).padStart(2, '0'))
|
||||
.join(' '));
|
||||
console.log('[DEBUG] Calculated bounds from stitches:', {
|
||||
minX,
|
||||
maxX,
|
||||
minY,
|
||||
maxY,
|
||||
});
|
||||
|
||||
// Check for color change markers and end marker
|
||||
let colorChangeCount = 0;
|
||||
let hasEndMarker = false;
|
||||
for (let i = 0; i < penData.length; i += 4) {
|
||||
const xLow = penData[i];
|
||||
const yLow = penData[i + 2];
|
||||
if ((xLow & 0x07) === PEN_COLOR_END) colorChangeCount++;
|
||||
if ((xLow & 0x07) === PEN_DATA_END) hasEndMarker = true;
|
||||
}
|
||||
console.log('[DEBUG] Color changes found:', colorChangeCount, '| Has END marker:', hasEndMarker);
|
||||
|
||||
return {
|
||||
stitches,
|
||||
threads,
|
||||
|
|
|
|||
Loading…
Reference in a new issue