fix: Store original and uploaded pattern data to prevent rotation inconsistencies

Store both original unrotated pesData and uploaded rotated pesData in cache
to ensure exact consistency on resume and prevent issues from algorithm changes
between versions. This fixes rotation/position reset issues after page reload.

- Cache original unrotated pattern + rotation angle for editing
- Cache exact uploaded pattern data sent to machine
- Restore original offset after loading cached pattern
- Use cached uploaded data on resume instead of recalculating

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Jan-Henrik Bruhn 2025-12-26 22:48:25 +01:00
parent 3ec9dda235
commit 2b5e1d763b
12 changed files with 287 additions and 36 deletions

View file

@ -8,6 +8,13 @@ import { LeftSidebar } from "./components/LeftSidebar";
import { PatternCanvas } from "./components/PatternCanvas"; import { PatternCanvas } from "./components/PatternCanvas";
import { PatternCanvasPlaceholder } from "./components/PatternCanvasPlaceholder"; import { PatternCanvasPlaceholder } from "./components/PatternCanvasPlaceholder";
import { BluetoothDevicePicker } from "./components/BluetoothDevicePicker"; import { BluetoothDevicePicker } from "./components/BluetoothDevicePicker";
import { transformStitchesRotation } from "./utils/rotationUtils";
import { encodeStitchesToPen } from "./formats/pen/encoder";
import { decodePenData } from "./formats/pen/decoder";
import {
calculatePatternCenter,
calculateBoundsFromDecodedStitches,
} from "./components/PatternCanvas/patternCanvasHelpers";
import "./App.css"; import "./App.css";
function App() { function App() {
@ -25,10 +32,20 @@ function App() {
); );
// Pattern store - for auto-loading cached pattern // Pattern store - for auto-loading cached pattern
const { pesData, setPattern, setPatternOffset } = usePatternStore( const {
pesData,
uploadedPesData,
setPattern,
setUploadedPattern,
setPatternRotation,
setPatternOffset,
} = usePatternStore(
useShallow((state) => ({ useShallow((state) => ({
pesData: state.pesData, pesData: state.pesData,
uploadedPesData: state.uploadedPesData,
setPattern: state.setPattern, setPattern: state.setPattern,
setUploadedPattern: state.setUploadedPattern,
setPatternRotation: state.setPatternRotation,
setPatternOffset: state.setPatternOffset, setPatternOffset: state.setPatternOffset,
})), })),
); );
@ -47,23 +64,130 @@ function App() {
// Auto-load cached pattern when available // Auto-load cached pattern when available
useEffect(() => { useEffect(() => {
if (resumedPattern && !pesData) { // Only auto-load if we have a resumed pattern and haven't already loaded it
if (resumedPattern && !uploadedPesData && !pesData) {
if (!resumedPattern.pesData) {
console.error(
"[App] ERROR: resumedPattern has no pesData!",
resumedPattern,
);
return;
}
console.log( console.log(
"[App] Loading resumed pattern:", "[App] Loading resumed pattern:",
resumeFileName, resumeFileName,
"Offset:", "Offset:",
resumedPattern.patternOffset, resumedPattern.patternOffset,
"Rotation:",
resumedPattern.patternRotation,
"Has stitches:",
resumedPattern.pesData.stitches?.length || 0,
"Has cached uploaded data:",
!!resumedPattern.uploadedPesData,
); );
setPattern(resumedPattern.pesData, resumeFileName || "");
// Restore the cached pattern offset const originalPesData = resumedPattern.pesData;
if (resumedPattern.patternOffset) { const cachedUploadedPesData = resumedPattern.uploadedPesData;
setPatternOffset( const rotation = resumedPattern.patternRotation || 0;
resumedPattern.patternOffset.x, const originalOffset = resumedPattern.patternOffset || { x: 0, y: 0 };
resumedPattern.patternOffset.y,
// Set the original pattern data for editing
setPattern(originalPesData, resumeFileName || "");
// Restore the original offset (setPattern resets it to 0,0)
setPatternOffset(originalOffset.x, originalOffset.y);
// Set rotation if present
if (rotation !== 0) {
setPatternRotation(rotation);
}
// Use cached uploadedPesData if available, otherwise recalculate
if (cachedUploadedPesData) {
// Use the exact uploaded data from cache
// Calculate the adjusted offset (same logic as upload)
if (rotation !== 0) {
const originalCenter = calculatePatternCenter(originalPesData.bounds);
const rotatedCenter = calculatePatternCenter(
cachedUploadedPesData.bounds,
);
const centerShiftX = rotatedCenter.x - originalCenter.x;
const centerShiftY = rotatedCenter.y - originalCenter.y;
const adjustedOffset = {
x: originalOffset.x + centerShiftX,
y: originalOffset.y + centerShiftY,
};
setUploadedPattern(
cachedUploadedPesData,
adjustedOffset,
resumeFileName || undefined,
);
} else {
setUploadedPattern(
cachedUploadedPesData,
originalOffset,
resumeFileName || undefined,
);
}
} else if (rotation !== 0) {
// Fallback: recalculate if no cached uploaded data (shouldn't happen for new uploads)
console.warn("[App] No cached uploaded data, recalculating rotation");
const rotatedStitches = transformStitchesRotation(
originalPesData.stitches,
rotation,
originalPesData.bounds,
);
const penResult = encodeStitchesToPen(rotatedStitches);
const penData = new Uint8Array(penResult.penBytes);
const decoded = decodePenData(penData);
const rotatedBounds = calculateBoundsFromDecodedStitches(decoded);
const originalCenter = calculatePatternCenter(originalPesData.bounds);
const rotatedCenter = calculatePatternCenter(rotatedBounds);
const centerShiftX = rotatedCenter.x - originalCenter.x;
const centerShiftY = rotatedCenter.y - originalCenter.y;
const adjustedOffset = {
x: originalOffset.x + centerShiftX,
y: originalOffset.y + centerShiftY,
};
const rotatedPesData = {
...originalPesData,
stitches: rotatedStitches,
penData,
penStitches: decoded,
bounds: rotatedBounds,
};
setUploadedPattern(
rotatedPesData,
adjustedOffset,
resumeFileName || undefined,
);
} else {
// No rotation - uploaded pattern is same as original
setUploadedPattern(
originalPesData,
originalOffset,
resumeFileName || undefined,
); );
} }
} }
}, [resumedPattern, resumeFileName, pesData, setPattern, setPatternOffset]); }, [
resumedPattern,
resumeFileName,
uploadedPesData,
pesData,
setPattern,
setUploadedPattern,
setPatternRotation,
setPatternOffset,
]);
return ( return (
<div className="h-screen flex flex-col bg-gray-100 dark:bg-gray-900 overflow-hidden"> <div className="h-screen flex flex-col bg-gray-100 dark:bg-gray-900 overflow-hidden">
@ -76,7 +200,11 @@ function App() {
{/* Right Column - Pattern Preview */} {/* Right Column - Pattern Preview */}
<div className="flex flex-col lg:overflow-hidden lg:h-full"> <div className="flex flex-col lg:overflow-hidden lg:h-full">
{pesData ? <PatternCanvas /> : <PatternCanvasPlaceholder />} {pesData || uploadedPesData ? (
<PatternCanvas />
) : (
<PatternCanvasPlaceholder />
)}
</div> </div>
</div> </div>

View file

@ -206,11 +206,14 @@ export function FileUpload() {
setUploadedPattern(pesDataForUpload, adjustedOffset); setUploadedPattern(pesDataForUpload, adjustedOffset);
// Upload the pattern with offset // Upload the pattern with offset
// IMPORTANT: Pass original unrotated pesData for caching, rotated pesData for upload
uploadPattern( uploadPattern(
penDataToUpload, penDataToUpload,
pesDataForUpload, pesDataForUpload,
displayFileName, displayFileName,
adjustedOffset, adjustedOffset,
patternRotation,
pesData, // Original unrotated pattern for caching
); );
return; // Early return to skip the upload below return; // Early return to skip the upload below
@ -226,6 +229,8 @@ export function FileUpload() {
pesDataForUpload, pesDataForUpload,
displayFileName, displayFileName,
patternOffset, patternOffset,
0, // No rotation
// No need to pass originalPesData since it's the same as pesDataForUpload
); );
} }
}, [ }, [

View file

@ -13,14 +13,16 @@ export function LeftSidebar() {
})), })),
); );
const { pesData } = usePatternStore( const { pesData, uploadedPesData } = usePatternStore(
useShallow((state) => ({ useShallow((state) => ({
pesData: state.pesData, pesData: state.pesData,
uploadedPesData: state.uploadedPesData,
})), })),
); );
// Derived state: pattern is uploaded if machine has pattern info // Derived state: pattern is uploaded if machine has pattern info
const patternUploaded = usePatternUploaded(); const patternUploaded = usePatternUploaded();
const hasPattern = pesData || uploadedPesData;
return ( return (
<div className="flex flex-col gap-4 md:gap-5 lg:gap-6 lg:overflow-hidden"> <div className="flex flex-col gap-4 md:gap-5 lg:gap-6 lg:overflow-hidden">
@ -31,7 +33,7 @@ export function LeftSidebar() {
{isConnected && !patternUploaded && <FileUpload />} {isConnected && !patternUploaded && <FileUpload />}
{/* Compact Pattern Summary - Show after upload (during sewing stages) */} {/* Compact Pattern Summary - Show after upload (during sewing stages) */}
{isConnected && patternUploaded && pesData && <PatternSummaryCard />} {isConnected && patternUploaded && hasPattern && <PatternSummaryCard />}
{/* Progress Monitor - Show when pattern is uploaded */} {/* Progress Monitor - Show when pattern is uploaded */}
{isConnected && patternUploaded && ( {isConnected && patternUploaded && (

View file

@ -190,7 +190,9 @@ export function PatternCanvas() {
</Layer> </Layer>
{/* Original pattern layer: draggable with transformer (shown before upload starts) */} {/* Original pattern layer: draggable with transformer (shown before upload starts) */}
<Layer visible={!isUploading && !patternUploaded}> <Layer
visible={!isUploading && !patternUploaded && !uploadedPesData}
>
{pesData && ( {pesData && (
<PatternLayer <PatternLayer
pesData={pesData} pesData={pesData}
@ -209,7 +211,9 @@ export function PatternCanvas() {
</Layer> </Layer>
{/* Uploaded pattern layer: locked, rotation baked in (shown during and after upload) */} {/* Uploaded pattern layer: locked, rotation baked in (shown during and after upload) */}
<Layer visible={isUploading || patternUploaded}> <Layer
visible={isUploading || patternUploaded || !!uploadedPesData}
>
{uploadedPesData && ( {uploadedPesData && (
<PatternLayer <PatternLayer
pesData={uploadedPesData} pesData={uploadedPesData}
@ -241,12 +245,12 @@ export function PatternCanvas() {
<PatternPositionIndicator <PatternPositionIndicator
offset={ offset={
isUploading || patternUploaded isUploading || patternUploaded || uploadedPesData
? initialUploadedPatternOffset ? initialUploadedPatternOffset
: localPatternOffset : localPatternOffset
} }
rotation={localPatternRotation} rotation={localPatternRotation}
isLocked={patternUploaded} isLocked={patternUploaded || !!uploadedPesData}
isUploading={isUploading} isUploading={isUploading}
/> />
@ -257,7 +261,10 @@ export function PatternCanvas() {
onZoomReset={handleZoomReset} onZoomReset={handleZoomReset}
onCenterPattern={handleCenterPattern} onCenterPattern={handleCenterPattern}
canCenterPattern={ canCenterPattern={
!!pesData && !patternUploaded && !isUploading !!pesData &&
!patternUploaded &&
!isUploading &&
!uploadedPesData
} }
/> />
</> </>

View file

@ -25,14 +25,16 @@ export function PatternSummaryCard() {
); );
// Pattern store // Pattern store
const { pesData, currentFileName } = usePatternStore( const { pesData, uploadedPesData, currentFileName } = usePatternStore(
useShallow((state) => ({ useShallow((state) => ({
pesData: state.pesData, pesData: state.pesData,
uploadedPesData: state.uploadedPesData,
currentFileName: state.currentFileName, currentFileName: state.currentFileName,
})), })),
); );
if (!pesData) return null; const displayPattern = uploadedPesData || pesData;
if (!displayPattern) return null;
const canDelete = canDeletePattern(machineStatus); const canDelete = canDeletePattern(machineStatus);
return ( return (
@ -52,7 +54,7 @@ export function PatternSummaryCard() {
</div> </div>
</CardHeader> </CardHeader>
<CardContent className="px-4 pt-0 pb-4"> <CardContent className="px-4 pt-0 pb-4">
<PatternInfo pesData={pesData} /> <PatternInfo pesData={displayPattern} />
{canDelete && ( {canDelete && (
<Button <Button

View file

@ -52,6 +52,8 @@ export function ProgressMonitor() {
// Pattern store // Pattern store
const pesData = usePatternStore((state) => state.pesData); const pesData = usePatternStore((state) => state.pesData);
const uploadedPesData = usePatternStore((state) => state.uploadedPesData);
const displayPattern = uploadedPesData || pesData;
const currentBlockRef = useRef<HTMLDivElement>(null); const currentBlockRef = useRef<HTMLDivElement>(null);
// State indicators // State indicators
@ -60,8 +62,8 @@ export function ProgressMonitor() {
// Use PEN stitch count as fallback when machine reports 0 total stitches // Use PEN stitch count as fallback when machine reports 0 total stitches
const totalStitches = patternInfo const totalStitches = patternInfo
? patternInfo.totalStitches === 0 && pesData?.penStitches ? patternInfo.totalStitches === 0 && displayPattern?.penStitches
? pesData.penStitches.stitches.length ? displayPattern.penStitches.stitches.length
: patternInfo.totalStitches : patternInfo.totalStitches
: 0; : 0;
@ -72,7 +74,7 @@ export function ProgressMonitor() {
// Calculate color block information from decoded penStitches // Calculate color block information from decoded penStitches
const colorBlocks = useMemo(() => { const colorBlocks = useMemo(() => {
if (!pesData || !pesData.penStitches) return []; if (!displayPattern || !displayPattern.penStitches) return [];
const blocks: Array<{ const blocks: Array<{
colorIndex: number; colorIndex: number;
@ -87,8 +89,8 @@ export function ProgressMonitor() {
}> = []; }> = [];
// Use the pre-computed color blocks from decoded PEN data // Use the pre-computed color blocks from decoded PEN data
for (const penBlock of pesData.penStitches.colorBlocks) { for (const penBlock of displayPattern.penStitches.colorBlocks) {
const thread = pesData.threads[penBlock.colorIndex]; const thread = displayPattern.threads[penBlock.colorIndex];
blocks.push({ blocks.push({
colorIndex: penBlock.colorIndex, colorIndex: penBlock.colorIndex,
threadHex: thread?.hex || "#000000", threadHex: thread?.hex || "#000000",
@ -103,7 +105,7 @@ export function ProgressMonitor() {
} }
return blocks; return blocks;
}, [pesData]); }, [displayPattern]);
// Determine current color block based on current stitch // Determine current color block based on current stitch
const currentStitch = sewingProgress?.currentStitch || 0; const currentStitch = sewingProgress?.currentStitch || 0;

View file

@ -15,8 +15,10 @@ export class BrowserStorageService implements IStorageService {
pesData: PesPatternData, pesData: PesPatternData,
fileName: string, fileName: string,
patternOffset?: { x: number; y: number }, patternOffset?: { x: number; y: number },
patternRotation?: number,
uploadedPesData?: PesPatternData,
): Promise<void> { ): Promise<void> {
PatternCacheService.savePattern(uuid, pesData, fileName, patternOffset); PatternCacheService.savePattern(uuid, pesData, fileName, patternOffset, patternRotation, uploadedPesData);
} }
async getPatternByUUID(uuid: string): Promise<ICachedPattern | null> { async getPatternByUUID(uuid: string): Promise<ICachedPattern | null> {

View file

@ -20,6 +20,8 @@ export class ElectronStorageService implements IStorageService {
pesData: PesPatternData, pesData: PesPatternData,
fileName: string, fileName: string,
patternOffset?: { x: number; y: number }, patternOffset?: { x: number; y: number },
patternRotation?: number,
uploadedPesData?: PesPatternData,
): Promise<void> { ): Promise<void> {
// Convert Uint8Array to array for JSON serialization over IPC // Convert Uint8Array to array for JSON serialization over IPC
const serializable = { const serializable = {
@ -28,9 +30,16 @@ export class ElectronStorageService implements IStorageService {
...pesData, ...pesData,
penData: Array.from(pesData.penData), penData: Array.from(pesData.penData),
}, },
uploadedPesData: uploadedPesData
? {
...uploadedPesData,
penData: Array.from(uploadedPesData.penData),
}
: undefined,
fileName, fileName,
timestamp: Date.now(), timestamp: Date.now(),
patternOffset, patternOffset,
patternRotation,
}; };
// Fire and forget (sync-like behavior to match interface) // Fire and forget (sync-like behavior to match interface)
@ -51,6 +60,11 @@ export class ElectronStorageService implements IStorageService {
pattern.pesData.penData = new Uint8Array(pattern.pesData.penData); pattern.pesData.penData = new Uint8Array(pattern.pesData.penData);
} }
if (pattern && pattern.uploadedPesData && Array.isArray(pattern.uploadedPesData.penData)) {
// Restore Uint8Array from array for uploadedPesData
pattern.uploadedPesData.penData = new Uint8Array(pattern.uploadedPesData.penData);
}
return pattern; return pattern;
} catch (err) { } catch (err) {
console.error("[ElectronStorage] Failed to get pattern:", err); console.error("[ElectronStorage] Failed to get pattern:", err);
@ -69,6 +83,11 @@ export class ElectronStorageService implements IStorageService {
pattern.pesData.penData = new Uint8Array(pattern.pesData.penData); pattern.pesData.penData = new Uint8Array(pattern.pesData.penData);
} }
if (pattern && pattern.uploadedPesData && Array.isArray(pattern.uploadedPesData.penData)) {
// Restore Uint8Array from array for uploadedPesData
pattern.uploadedPesData.penData = new Uint8Array(pattern.uploadedPesData.penData);
}
return pattern; return pattern;
} catch (err) { } catch (err) {
console.error("[ElectronStorage] Failed to get latest pattern:", err); console.error("[ElectronStorage] Failed to get latest pattern:", err);

View file

@ -2,10 +2,12 @@ import type { PesPatternData } from "../../formats/import/pesImporter";
export interface ICachedPattern { export interface ICachedPattern {
uuid: string; uuid: string;
pesData: PesPatternData; pesData: PesPatternData; // Original unrotated pattern data
uploadedPesData?: PesPatternData; // Pattern with rotation applied (what was uploaded to machine)
fileName: string; fileName: string;
timestamp: number; timestamp: number;
patternOffset?: { x: number; y: number }; patternOffset?: { x: number; y: number };
patternRotation?: number; // Rotation angle in degrees
} }
export interface IStorageService { export interface IStorageService {
@ -14,6 +16,8 @@ export interface IStorageService {
pesData: PesPatternData, pesData: PesPatternData,
fileName: string, fileName: string,
patternOffset?: { x: number; y: number }, patternOffset?: { x: number; y: number },
patternRotation?: number,
uploadedPesData?: PesPatternData,
): Promise<void>; ): Promise<void>;
getPatternByUUID(uuid: string): Promise<ICachedPattern | null>; getPatternByUUID(uuid: string): Promise<ICachedPattern | null>;

View file

@ -2,10 +2,12 @@ import type { PesPatternData } from "../formats/import/pesImporter";
interface CachedPattern { interface CachedPattern {
uuid: string; uuid: string;
pesData: PesPatternData; pesData: PesPatternData; // Original unrotated pattern data
uploadedPesData?: PesPatternData; // Pattern with rotation applied (what was uploaded to machine)
fileName: string; fileName: string;
timestamp: number; timestamp: number;
patternOffset?: { x: number; y: number }; patternOffset?: { x: number; y: number };
patternRotation?: number; // Rotation angle in degrees
} }
const CACHE_KEY = "brother_pattern_cache"; const CACHE_KEY = "brother_pattern_cache";
@ -39,6 +41,8 @@ export class PatternCacheService {
pesData: PesPatternData, pesData: PesPatternData,
fileName: string, fileName: string,
patternOffset?: { x: number; y: number }, patternOffset?: { x: number; y: number },
patternRotation?: number,
uploadedPesData?: PesPatternData,
): void { ): void {
try { try {
// Convert penData Uint8Array to array for JSON serialization // Convert penData Uint8Array to array for JSON serialization
@ -47,12 +51,22 @@ export class PatternCacheService {
penData: Array.from(pesData.penData) as unknown as Uint8Array, penData: Array.from(pesData.penData) as unknown as Uint8Array,
}; };
// Also convert uploadedPesData if present
const uploadedPesDataWithArrayPenData = uploadedPesData
? {
...uploadedPesData,
penData: Array.from(uploadedPesData.penData) as unknown as Uint8Array,
}
: undefined;
const cached: CachedPattern = { const cached: CachedPattern = {
uuid, uuid,
pesData: pesDataWithArrayPenData, pesData: pesDataWithArrayPenData,
uploadedPesData: uploadedPesDataWithArrayPenData,
fileName, fileName,
timestamp: Date.now(), timestamp: Date.now(),
patternOffset, patternOffset,
patternRotation,
}; };
localStorage.setItem(CACHE_KEY, JSON.stringify(cached)); localStorage.setItem(CACHE_KEY, JSON.stringify(cached));
@ -63,6 +77,10 @@ export class PatternCacheService {
uuid, uuid,
"Offset:", "Offset:",
patternOffset, patternOffset,
"Rotation:",
patternRotation,
"Has uploaded data:",
!!uploadedPesData,
); );
} catch (err) { } catch (err) {
console.error("[PatternCache] Failed to save pattern:", err); console.error("[PatternCache] Failed to save pattern:", err);
@ -101,11 +119,18 @@ export class PatternCacheService {
pattern.pesData.penData = new Uint8Array(pattern.pesData.penData); pattern.pesData.penData = new Uint8Array(pattern.pesData.penData);
} }
// Restore Uint8Array from array inside uploadedPesData if present
if (pattern.uploadedPesData && Array.isArray(pattern.uploadedPesData.penData)) {
pattern.uploadedPesData.penData = new Uint8Array(pattern.uploadedPesData.penData);
}
console.log( console.log(
"[PatternCache] Found cached pattern:", "[PatternCache] Found cached pattern:",
pattern.fileName, pattern.fileName,
"UUID:", "UUID:",
uuid, uuid,
"Has uploaded data:",
!!pattern.uploadedPesData,
); );
return pattern; return pattern;
} catch (err) { } catch (err) {
@ -131,6 +156,11 @@ export class PatternCacheService {
pattern.pesData.penData = new Uint8Array(pattern.pesData.penData); pattern.pesData.penData = new Uint8Array(pattern.pesData.penData);
} }
// Restore Uint8Array from array inside uploadedPesData if present
if (pattern.uploadedPesData && Array.isArray(pattern.uploadedPesData.penData)) {
pattern.uploadedPesData.penData = new Uint8Array(pattern.uploadedPesData.penData);
}
return pattern; return pattern;
} catch (err) { } catch (err) {
console.error("[PatternCache] Failed to retrieve pattern:", err); console.error("[PatternCache] Failed to retrieve pattern:", err);

View file

@ -43,7 +43,9 @@ interface MachineState {
resumeFileName: string | null; resumeFileName: string | null;
resumedPattern: { resumedPattern: {
pesData: PesPatternData; pesData: PesPatternData;
uploadedPesData?: PesPatternData;
patternOffset?: { x: number; y: number }; patternOffset?: { x: number; y: number };
patternRotation?: number;
} | null; } | null;
// Error state // Error state
@ -67,9 +69,11 @@ interface MachineState {
refreshServiceCount: () => Promise<void>; refreshServiceCount: () => Promise<void>;
uploadPattern: ( uploadPattern: (
penData: Uint8Array, penData: Uint8Array,
pesData: PesPatternData, uploadedPesData: PesPatternData, // Pattern with rotation applied (for machine upload)
fileName: string, fileName: string,
patternOffset?: { x: number; y: number }, patternOffset?: { x: number; y: number },
patternRotation?: number,
originalPesData?: PesPatternData, // Original unrotated pattern (for caching)
) => Promise<void>; ) => Promise<void>;
startMaskTrace: () => Promise<void>; startMaskTrace: () => Promise<void>;
startSewing: () => Promise<void>; startSewing: () => Promise<void>;
@ -78,7 +82,9 @@ interface MachineState {
checkResume: () => Promise<PesPatternData | null>; checkResume: () => Promise<PesPatternData | null>;
loadCachedPattern: () => Promise<{ loadCachedPattern: () => Promise<{
pesData: PesPatternData; pesData: PesPatternData;
uploadedPesData?: PesPatternData;
patternOffset?: { x: number; y: number }; patternOffset?: { x: number; y: number };
patternRotation?: number;
} | null>; } | null>;
// Internal methods // Internal methods
@ -137,6 +143,10 @@ export const useMachineStore = create<MachineState>((set, get) => ({
cached.fileName, cached.fileName,
"Offset:", "Offset:",
cached.patternOffset, cached.patternOffset,
"Rotation:",
cached.patternRotation,
"Has uploaded data:",
!!cached.uploadedPesData,
); );
console.log("[Resume] Auto-loading cached pattern..."); console.log("[Resume] Auto-loading cached pattern...");
set({ set({
@ -144,7 +154,9 @@ export const useMachineStore = create<MachineState>((set, get) => ({
resumeFileName: cached.fileName, resumeFileName: cached.fileName,
resumedPattern: { resumedPattern: {
pesData: cached.pesData, pesData: cached.pesData,
uploadedPesData: cached.uploadedPesData,
patternOffset: cached.patternOffset, patternOffset: cached.patternOffset,
patternRotation: cached.patternRotation,
}, },
}); });
@ -302,9 +314,11 @@ export const useMachineStore = create<MachineState>((set, get) => ({
// Upload pattern to machine // Upload pattern to machine
uploadPattern: async ( uploadPattern: async (
penData: Uint8Array, penData: Uint8Array,
pesData: PesPatternData, uploadedPesData: PesPatternData, // Pattern with rotation applied (for machine upload)
fileName: string, fileName: string,
patternOffset?: { x: number; y: number }, patternOffset?: { x: number; y: number },
patternRotation?: number,
originalPesData?: PesPatternData, // Original unrotated pattern (for caching)
) => { ) => {
const { const {
isConnected, isConnected,
@ -321,20 +335,31 @@ export const useMachineStore = create<MachineState>((set, get) => ({
try { try {
set({ error: null, uploadProgress: 0, isUploading: true }); set({ error: null, uploadProgress: 0, isUploading: true });
// Upload to machine using the rotated bounds
const uuid = await service.uploadPattern( const uuid = await service.uploadPattern(
penData, penData,
(progress) => { (progress) => {
set({ uploadProgress: progress }); set({ uploadProgress: progress });
}, },
pesData.bounds, uploadedPesData.bounds,
patternOffset, patternOffset,
); );
set({ uploadProgress: 100 }); set({ uploadProgress: 100 });
// Cache the pattern with its UUID and offset // Cache the ORIGINAL unrotated pattern with rotation angle AND the uploaded data
// This allows us to restore the editable state correctly and ensures the exact
// uploaded data is used on resume (prevents inconsistencies from version updates)
const pesDataToCache = originalPesData || uploadedPesData;
const uuidStr = uuidToString(uuid); const uuidStr = uuidToString(uuid);
storageService.savePattern(uuidStr, pesData, fileName, patternOffset); storageService.savePattern(
uuidStr,
pesDataToCache,
fileName,
patternOffset,
patternRotation,
uploadedPesData, // Cache the exact uploaded data
);
console.log( console.log(
"[Cache] Saved pattern:", "[Cache] Saved pattern:",
fileName, fileName,
@ -342,6 +367,9 @@ export const useMachineStore = create<MachineState>((set, get) => ({
uuidStr, uuidStr,
"Offset:", "Offset:",
patternOffset, patternOffset,
"Rotation:",
patternRotation,
"(cached original unrotated data + uploaded data)",
); );
// Clear resume state since we just uploaded // Clear resume state since we just uploaded
@ -440,6 +468,7 @@ export const useMachineStore = create<MachineState>((set, get) => ({
uploadProgress: 0, uploadProgress: 0,
resumeAvailable: false, resumeAvailable: false,
resumeFileName: null, resumeFileName: null,
resumedPattern: null, // Clear this to prevent auto-reload
}); });
// Clear uploaded pattern data in pattern store // Clear uploaded pattern data in pattern store
@ -458,7 +487,9 @@ export const useMachineStore = create<MachineState>((set, get) => ({
// Load cached pattern // Load cached pattern
loadCachedPattern: async (): Promise<{ loadCachedPattern: async (): Promise<{
pesData: PesPatternData; pesData: PesPatternData;
uploadedPesData?: PesPatternData;
patternOffset?: { x: number; y: number }; patternOffset?: { x: number; y: number };
patternRotation?: number;
} | null> => { } | null> => {
const { resumeAvailable, service, storageService, refreshPatternInfo } = const { resumeAvailable, service, storageService, refreshPatternInfo } =
get(); get();
@ -477,9 +508,18 @@ export const useMachineStore = create<MachineState>((set, get) => ({
cached.fileName, cached.fileName,
"Offset:", "Offset:",
cached.patternOffset, cached.patternOffset,
"Rotation:",
cached.patternRotation,
"Has uploaded data:",
!!cached.uploadedPesData,
); );
await refreshPatternInfo(); await refreshPatternInfo();
return { pesData: cached.pesData, patternOffset: cached.patternOffset }; return {
pesData: cached.pesData,
uploadedPesData: cached.uploadedPesData,
patternOffset: cached.patternOffset,
patternRotation: cached.patternRotation,
};
} }
return null; return null;

View file

@ -21,6 +21,7 @@ interface PatternState {
setUploadedPattern: ( setUploadedPattern: (
uploadedData: PesPatternData, uploadedData: PesPatternData,
uploadedOffset: { x: number; y: number }, uploadedOffset: { x: number; y: number },
fileName?: string,
) => void; ) => void;
clearUploadedPattern: () => void; clearUploadedPattern: () => void;
resetPatternOffset: () => void; resetPatternOffset: () => void;
@ -69,23 +70,32 @@ export const usePatternStore = create<PatternState>((set) => ({
setUploadedPattern: ( setUploadedPattern: (
uploadedData: PesPatternData, uploadedData: PesPatternData,
uploadedOffset: { x: number; y: number }, uploadedOffset: { x: number; y: number },
fileName?: string,
) => { ) => {
set({ set({
uploadedPesData: uploadedData, uploadedPesData: uploadedData,
uploadedPatternOffset: uploadedOffset, uploadedPatternOffset: uploadedOffset,
patternUploaded: true, patternUploaded: true,
// Optionally set filename if provided (for resume/reconnect scenarios)
...(fileName && { currentFileName: fileName }),
}); });
console.log("[PatternStore] Uploaded pattern set"); console.log("[PatternStore] Uploaded pattern set");
}, },
// Clear uploaded pattern (called when deleting from machine) // Clear uploaded pattern (called when deleting from machine)
// This reverts to pre-upload state, keeping pesData so user can re-adjust and re-upload
clearUploadedPattern: () => { clearUploadedPattern: () => {
console.log("[PatternStore] CLEARING uploaded pattern...");
set({ set({
uploadedPesData: null, uploadedPesData: null,
uploadedPatternOffset: { x: 0, y: 0 }, uploadedPatternOffset: { x: 0, y: 0 },
patternUploaded: false, patternUploaded: false,
// Keep pesData, currentFileName, patternOffset, patternRotation
// so user can adjust and re-upload
}); });
console.log("[PatternStore] Uploaded pattern cleared"); console.log(
"[PatternStore] Uploaded pattern cleared - back to editable mode",
);
}, },
// Reset pattern offset to default // Reset pattern offset to default