Merge pull request #49 from jhbruhn/fix/48-cached-pattern-disappears-on-reconnect

fix: Store original and uploaded pattern data to prevent rotation inconsistencies
This commit is contained in:
Jan-Henrik Bruhn 2025-12-26 22:50:57 +01:00 committed by GitHub
commit a3cb6a4e5c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 324 additions and 37 deletions

View file

@ -4,7 +4,12 @@
"Bash(npm run build:*)", "Bash(npm run build:*)",
"Bash(npm run lint)", "Bash(npm run lint)",
"Bash(cat:*)", "Bash(cat:*)",
"Bash(npm run dev:electron:*)" "Bash(npm run dev:electron:*)",
"Bash(npm run lint:*)",
"Bash(npm test:*)",
"Bash(npm run:*)",
"Bash(gh issue create:*)",
"Bash(gh label create:*)"
], ],
"deny": [], "deny": [],
"ask": [] "ask": []

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,17 @@ 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,17 @@ 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 +89,17 @@ 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,24 @@ 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 +79,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 +121,23 @@ 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 +163,16 @@ 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