mirror of
https://github.com/jhbruhn/respira.git
synced 2026-01-27 02:13:41 +00:00
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:
parent
3ec9dda235
commit
2b5e1d763b
12 changed files with 287 additions and 36 deletions
148
src/App.tsx
148
src/App.tsx
|
|
@ -8,6 +8,13 @@ import { LeftSidebar } from "./components/LeftSidebar";
|
|||
import { PatternCanvas } from "./components/PatternCanvas";
|
||||
import { PatternCanvasPlaceholder } from "./components/PatternCanvasPlaceholder";
|
||||
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";
|
||||
|
||||
function App() {
|
||||
|
|
@ -25,10 +32,20 @@ function App() {
|
|||
);
|
||||
|
||||
// Pattern store - for auto-loading cached pattern
|
||||
const { pesData, setPattern, setPatternOffset } = usePatternStore(
|
||||
const {
|
||||
pesData,
|
||||
uploadedPesData,
|
||||
setPattern,
|
||||
setUploadedPattern,
|
||||
setPatternRotation,
|
||||
setPatternOffset,
|
||||
} = usePatternStore(
|
||||
useShallow((state) => ({
|
||||
pesData: state.pesData,
|
||||
uploadedPesData: state.uploadedPesData,
|
||||
setPattern: state.setPattern,
|
||||
setUploadedPattern: state.setUploadedPattern,
|
||||
setPatternRotation: state.setPatternRotation,
|
||||
setPatternOffset: state.setPatternOffset,
|
||||
})),
|
||||
);
|
||||
|
|
@ -47,23 +64,130 @@ function App() {
|
|||
|
||||
// Auto-load cached pattern when available
|
||||
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(
|
||||
"[App] Loading resumed pattern:",
|
||||
resumeFileName,
|
||||
"Offset:",
|
||||
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
|
||||
if (resumedPattern.patternOffset) {
|
||||
setPatternOffset(
|
||||
resumedPattern.patternOffset.x,
|
||||
resumedPattern.patternOffset.y,
|
||||
|
||||
const originalPesData = resumedPattern.pesData;
|
||||
const cachedUploadedPesData = resumedPattern.uploadedPesData;
|
||||
const rotation = resumedPattern.patternRotation || 0;
|
||||
const originalOffset = resumedPattern.patternOffset || { x: 0, y: 0 };
|
||||
|
||||
// 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 (
|
||||
<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 */}
|
||||
<div className="flex flex-col lg:overflow-hidden lg:h-full">
|
||||
{pesData ? <PatternCanvas /> : <PatternCanvasPlaceholder />}
|
||||
{pesData || uploadedPesData ? (
|
||||
<PatternCanvas />
|
||||
) : (
|
||||
<PatternCanvasPlaceholder />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -206,11 +206,14 @@ export function FileUpload() {
|
|||
setUploadedPattern(pesDataForUpload, adjustedOffset);
|
||||
|
||||
// Upload the pattern with offset
|
||||
// IMPORTANT: Pass original unrotated pesData for caching, rotated pesData for upload
|
||||
uploadPattern(
|
||||
penDataToUpload,
|
||||
pesDataForUpload,
|
||||
displayFileName,
|
||||
adjustedOffset,
|
||||
patternRotation,
|
||||
pesData, // Original unrotated pattern for caching
|
||||
);
|
||||
|
||||
return; // Early return to skip the upload below
|
||||
|
|
@ -226,6 +229,8 @@ export function FileUpload() {
|
|||
pesDataForUpload,
|
||||
displayFileName,
|
||||
patternOffset,
|
||||
0, // No rotation
|
||||
// No need to pass originalPesData since it's the same as pesDataForUpload
|
||||
);
|
||||
}
|
||||
}, [
|
||||
|
|
|
|||
|
|
@ -13,14 +13,16 @@ export function LeftSidebar() {
|
|||
})),
|
||||
);
|
||||
|
||||
const { pesData } = usePatternStore(
|
||||
const { pesData, uploadedPesData } = usePatternStore(
|
||||
useShallow((state) => ({
|
||||
pesData: state.pesData,
|
||||
uploadedPesData: state.uploadedPesData,
|
||||
})),
|
||||
);
|
||||
|
||||
// Derived state: pattern is uploaded if machine has pattern info
|
||||
const patternUploaded = usePatternUploaded();
|
||||
const hasPattern = pesData || uploadedPesData;
|
||||
|
||||
return (
|
||||
<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 />}
|
||||
|
||||
{/* Compact Pattern Summary - Show after upload (during sewing stages) */}
|
||||
{isConnected && patternUploaded && pesData && <PatternSummaryCard />}
|
||||
{isConnected && patternUploaded && hasPattern && <PatternSummaryCard />}
|
||||
|
||||
{/* Progress Monitor - Show when pattern is uploaded */}
|
||||
{isConnected && patternUploaded && (
|
||||
|
|
|
|||
|
|
@ -190,7 +190,9 @@ export function PatternCanvas() {
|
|||
</Layer>
|
||||
|
||||
{/* Original pattern layer: draggable with transformer (shown before upload starts) */}
|
||||
<Layer visible={!isUploading && !patternUploaded}>
|
||||
<Layer
|
||||
visible={!isUploading && !patternUploaded && !uploadedPesData}
|
||||
>
|
||||
{pesData && (
|
||||
<PatternLayer
|
||||
pesData={pesData}
|
||||
|
|
@ -209,7 +211,9 @@ export function PatternCanvas() {
|
|||
</Layer>
|
||||
|
||||
{/* Uploaded pattern layer: locked, rotation baked in (shown during and after upload) */}
|
||||
<Layer visible={isUploading || patternUploaded}>
|
||||
<Layer
|
||||
visible={isUploading || patternUploaded || !!uploadedPesData}
|
||||
>
|
||||
{uploadedPesData && (
|
||||
<PatternLayer
|
||||
pesData={uploadedPesData}
|
||||
|
|
@ -241,12 +245,12 @@ export function PatternCanvas() {
|
|||
|
||||
<PatternPositionIndicator
|
||||
offset={
|
||||
isUploading || patternUploaded
|
||||
isUploading || patternUploaded || uploadedPesData
|
||||
? initialUploadedPatternOffset
|
||||
: localPatternOffset
|
||||
}
|
||||
rotation={localPatternRotation}
|
||||
isLocked={patternUploaded}
|
||||
isLocked={patternUploaded || !!uploadedPesData}
|
||||
isUploading={isUploading}
|
||||
/>
|
||||
|
||||
|
|
@ -257,7 +261,10 @@ export function PatternCanvas() {
|
|||
onZoomReset={handleZoomReset}
|
||||
onCenterPattern={handleCenterPattern}
|
||||
canCenterPattern={
|
||||
!!pesData && !patternUploaded && !isUploading
|
||||
!!pesData &&
|
||||
!patternUploaded &&
|
||||
!isUploading &&
|
||||
!uploadedPesData
|
||||
}
|
||||
/>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -25,14 +25,16 @@ export function PatternSummaryCard() {
|
|||
);
|
||||
|
||||
// Pattern store
|
||||
const { pesData, currentFileName } = usePatternStore(
|
||||
const { pesData, uploadedPesData, currentFileName } = usePatternStore(
|
||||
useShallow((state) => ({
|
||||
pesData: state.pesData,
|
||||
uploadedPesData: state.uploadedPesData,
|
||||
currentFileName: state.currentFileName,
|
||||
})),
|
||||
);
|
||||
|
||||
if (!pesData) return null;
|
||||
const displayPattern = uploadedPesData || pesData;
|
||||
if (!displayPattern) return null;
|
||||
|
||||
const canDelete = canDeletePattern(machineStatus);
|
||||
return (
|
||||
|
|
@ -52,7 +54,7 @@ export function PatternSummaryCard() {
|
|||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="px-4 pt-0 pb-4">
|
||||
<PatternInfo pesData={pesData} />
|
||||
<PatternInfo pesData={displayPattern} />
|
||||
|
||||
{canDelete && (
|
||||
<Button
|
||||
|
|
|
|||
|
|
@ -52,6 +52,8 @@ export function ProgressMonitor() {
|
|||
|
||||
// Pattern store
|
||||
const pesData = usePatternStore((state) => state.pesData);
|
||||
const uploadedPesData = usePatternStore((state) => state.uploadedPesData);
|
||||
const displayPattern = uploadedPesData || pesData;
|
||||
const currentBlockRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// State indicators
|
||||
|
|
@ -60,8 +62,8 @@ export function ProgressMonitor() {
|
|||
|
||||
// Use PEN stitch count as fallback when machine reports 0 total stitches
|
||||
const totalStitches = patternInfo
|
||||
? patternInfo.totalStitches === 0 && pesData?.penStitches
|
||||
? pesData.penStitches.stitches.length
|
||||
? patternInfo.totalStitches === 0 && displayPattern?.penStitches
|
||||
? displayPattern.penStitches.stitches.length
|
||||
: patternInfo.totalStitches
|
||||
: 0;
|
||||
|
||||
|
|
@ -72,7 +74,7 @@ export function ProgressMonitor() {
|
|||
|
||||
// Calculate color block information from decoded penStitches
|
||||
const colorBlocks = useMemo(() => {
|
||||
if (!pesData || !pesData.penStitches) return [];
|
||||
if (!displayPattern || !displayPattern.penStitches) return [];
|
||||
|
||||
const blocks: Array<{
|
||||
colorIndex: number;
|
||||
|
|
@ -87,8 +89,8 @@ export function ProgressMonitor() {
|
|||
}> = [];
|
||||
|
||||
// Use the pre-computed color blocks from decoded PEN data
|
||||
for (const penBlock of pesData.penStitches.colorBlocks) {
|
||||
const thread = pesData.threads[penBlock.colorIndex];
|
||||
for (const penBlock of displayPattern.penStitches.colorBlocks) {
|
||||
const thread = displayPattern.threads[penBlock.colorIndex];
|
||||
blocks.push({
|
||||
colorIndex: penBlock.colorIndex,
|
||||
threadHex: thread?.hex || "#000000",
|
||||
|
|
@ -103,7 +105,7 @@ export function ProgressMonitor() {
|
|||
}
|
||||
|
||||
return blocks;
|
||||
}, [pesData]);
|
||||
}, [displayPattern]);
|
||||
|
||||
// Determine current color block based on current stitch
|
||||
const currentStitch = sewingProgress?.currentStitch || 0;
|
||||
|
|
|
|||
|
|
@ -15,8 +15,10 @@ export class BrowserStorageService implements IStorageService {
|
|||
pesData: PesPatternData,
|
||||
fileName: string,
|
||||
patternOffset?: { x: number; y: number },
|
||||
patternRotation?: number,
|
||||
uploadedPesData?: PesPatternData,
|
||||
): Promise<void> {
|
||||
PatternCacheService.savePattern(uuid, pesData, fileName, patternOffset);
|
||||
PatternCacheService.savePattern(uuid, pesData, fileName, patternOffset, patternRotation, uploadedPesData);
|
||||
}
|
||||
|
||||
async getPatternByUUID(uuid: string): Promise<ICachedPattern | null> {
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ export class ElectronStorageService implements IStorageService {
|
|||
pesData: PesPatternData,
|
||||
fileName: string,
|
||||
patternOffset?: { x: number; y: number },
|
||||
patternRotation?: number,
|
||||
uploadedPesData?: PesPatternData,
|
||||
): Promise<void> {
|
||||
// Convert Uint8Array to array for JSON serialization over IPC
|
||||
const serializable = {
|
||||
|
|
@ -28,9 +30,16 @@ export class ElectronStorageService implements IStorageService {
|
|||
...pesData,
|
||||
penData: Array.from(pesData.penData),
|
||||
},
|
||||
uploadedPesData: uploadedPesData
|
||||
? {
|
||||
...uploadedPesData,
|
||||
penData: Array.from(uploadedPesData.penData),
|
||||
}
|
||||
: undefined,
|
||||
fileName,
|
||||
timestamp: Date.now(),
|
||||
patternOffset,
|
||||
patternRotation,
|
||||
};
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
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;
|
||||
} catch (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);
|
||||
}
|
||||
|
||||
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;
|
||||
} catch (err) {
|
||||
console.error("[ElectronStorage] Failed to get latest pattern:", err);
|
||||
|
|
|
|||
|
|
@ -2,10 +2,12 @@ import type { PesPatternData } from "../../formats/import/pesImporter";
|
|||
|
||||
export interface ICachedPattern {
|
||||
uuid: string;
|
||||
pesData: PesPatternData;
|
||||
pesData: PesPatternData; // Original unrotated pattern data
|
||||
uploadedPesData?: PesPatternData; // Pattern with rotation applied (what was uploaded to machine)
|
||||
fileName: string;
|
||||
timestamp: number;
|
||||
patternOffset?: { x: number; y: number };
|
||||
patternRotation?: number; // Rotation angle in degrees
|
||||
}
|
||||
|
||||
export interface IStorageService {
|
||||
|
|
@ -14,6 +16,8 @@ export interface IStorageService {
|
|||
pesData: PesPatternData,
|
||||
fileName: string,
|
||||
patternOffset?: { x: number; y: number },
|
||||
patternRotation?: number,
|
||||
uploadedPesData?: PesPatternData,
|
||||
): Promise<void>;
|
||||
|
||||
getPatternByUUID(uuid: string): Promise<ICachedPattern | null>;
|
||||
|
|
|
|||
|
|
@ -2,10 +2,12 @@ import type { PesPatternData } from "../formats/import/pesImporter";
|
|||
|
||||
interface CachedPattern {
|
||||
uuid: string;
|
||||
pesData: PesPatternData;
|
||||
pesData: PesPatternData; // Original unrotated pattern data
|
||||
uploadedPesData?: PesPatternData; // Pattern with rotation applied (what was uploaded to machine)
|
||||
fileName: string;
|
||||
timestamp: number;
|
||||
patternOffset?: { x: number; y: number };
|
||||
patternRotation?: number; // Rotation angle in degrees
|
||||
}
|
||||
|
||||
const CACHE_KEY = "brother_pattern_cache";
|
||||
|
|
@ -39,6 +41,8 @@ export class PatternCacheService {
|
|||
pesData: PesPatternData,
|
||||
fileName: string,
|
||||
patternOffset?: { x: number; y: number },
|
||||
patternRotation?: number,
|
||||
uploadedPesData?: PesPatternData,
|
||||
): void {
|
||||
try {
|
||||
// Convert penData Uint8Array to array for JSON serialization
|
||||
|
|
@ -47,12 +51,22 @@ export class PatternCacheService {
|
|||
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 = {
|
||||
uuid,
|
||||
pesData: pesDataWithArrayPenData,
|
||||
uploadedPesData: uploadedPesDataWithArrayPenData,
|
||||
fileName,
|
||||
timestamp: Date.now(),
|
||||
patternOffset,
|
||||
patternRotation,
|
||||
};
|
||||
|
||||
localStorage.setItem(CACHE_KEY, JSON.stringify(cached));
|
||||
|
|
@ -63,6 +77,10 @@ export class PatternCacheService {
|
|||
uuid,
|
||||
"Offset:",
|
||||
patternOffset,
|
||||
"Rotation:",
|
||||
patternRotation,
|
||||
"Has uploaded data:",
|
||||
!!uploadedPesData,
|
||||
);
|
||||
} catch (err) {
|
||||
console.error("[PatternCache] Failed to save pattern:", err);
|
||||
|
|
@ -101,11 +119,18 @@ export class PatternCacheService {
|
|||
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(
|
||||
"[PatternCache] Found cached pattern:",
|
||||
pattern.fileName,
|
||||
"UUID:",
|
||||
uuid,
|
||||
"Has uploaded data:",
|
||||
!!pattern.uploadedPesData,
|
||||
);
|
||||
return pattern;
|
||||
} catch (err) {
|
||||
|
|
@ -131,6 +156,11 @@ export class PatternCacheService {
|
|||
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;
|
||||
} catch (err) {
|
||||
console.error("[PatternCache] Failed to retrieve pattern:", err);
|
||||
|
|
|
|||
|
|
@ -43,7 +43,9 @@ interface MachineState {
|
|||
resumeFileName: string | null;
|
||||
resumedPattern: {
|
||||
pesData: PesPatternData;
|
||||
uploadedPesData?: PesPatternData;
|
||||
patternOffset?: { x: number; y: number };
|
||||
patternRotation?: number;
|
||||
} | null;
|
||||
|
||||
// Error state
|
||||
|
|
@ -67,9 +69,11 @@ interface MachineState {
|
|||
refreshServiceCount: () => Promise<void>;
|
||||
uploadPattern: (
|
||||
penData: Uint8Array,
|
||||
pesData: PesPatternData,
|
||||
uploadedPesData: PesPatternData, // Pattern with rotation applied (for machine upload)
|
||||
fileName: string,
|
||||
patternOffset?: { x: number; y: number },
|
||||
patternRotation?: number,
|
||||
originalPesData?: PesPatternData, // Original unrotated pattern (for caching)
|
||||
) => Promise<void>;
|
||||
startMaskTrace: () => Promise<void>;
|
||||
startSewing: () => Promise<void>;
|
||||
|
|
@ -78,7 +82,9 @@ interface MachineState {
|
|||
checkResume: () => Promise<PesPatternData | null>;
|
||||
loadCachedPattern: () => Promise<{
|
||||
pesData: PesPatternData;
|
||||
uploadedPesData?: PesPatternData;
|
||||
patternOffset?: { x: number; y: number };
|
||||
patternRotation?: number;
|
||||
} | null>;
|
||||
|
||||
// Internal methods
|
||||
|
|
@ -137,6 +143,10 @@ export const useMachineStore = create<MachineState>((set, get) => ({
|
|||
cached.fileName,
|
||||
"Offset:",
|
||||
cached.patternOffset,
|
||||
"Rotation:",
|
||||
cached.patternRotation,
|
||||
"Has uploaded data:",
|
||||
!!cached.uploadedPesData,
|
||||
);
|
||||
console.log("[Resume] Auto-loading cached pattern...");
|
||||
set({
|
||||
|
|
@ -144,7 +154,9 @@ export const useMachineStore = create<MachineState>((set, get) => ({
|
|||
resumeFileName: cached.fileName,
|
||||
resumedPattern: {
|
||||
pesData: cached.pesData,
|
||||
uploadedPesData: cached.uploadedPesData,
|
||||
patternOffset: cached.patternOffset,
|
||||
patternRotation: cached.patternRotation,
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -302,9 +314,11 @@ export const useMachineStore = create<MachineState>((set, get) => ({
|
|||
// Upload pattern to machine
|
||||
uploadPattern: async (
|
||||
penData: Uint8Array,
|
||||
pesData: PesPatternData,
|
||||
uploadedPesData: PesPatternData, // Pattern with rotation applied (for machine upload)
|
||||
fileName: string,
|
||||
patternOffset?: { x: number; y: number },
|
||||
patternRotation?: number,
|
||||
originalPesData?: PesPatternData, // Original unrotated pattern (for caching)
|
||||
) => {
|
||||
const {
|
||||
isConnected,
|
||||
|
|
@ -321,20 +335,31 @@ export const useMachineStore = create<MachineState>((set, get) => ({
|
|||
try {
|
||||
set({ error: null, uploadProgress: 0, isUploading: true });
|
||||
|
||||
// Upload to machine using the rotated bounds
|
||||
const uuid = await service.uploadPattern(
|
||||
penData,
|
||||
(progress) => {
|
||||
set({ uploadProgress: progress });
|
||||
},
|
||||
pesData.bounds,
|
||||
uploadedPesData.bounds,
|
||||
patternOffset,
|
||||
);
|
||||
|
||||
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);
|
||||
storageService.savePattern(uuidStr, pesData, fileName, patternOffset);
|
||||
storageService.savePattern(
|
||||
uuidStr,
|
||||
pesDataToCache,
|
||||
fileName,
|
||||
patternOffset,
|
||||
patternRotation,
|
||||
uploadedPesData, // Cache the exact uploaded data
|
||||
);
|
||||
console.log(
|
||||
"[Cache] Saved pattern:",
|
||||
fileName,
|
||||
|
|
@ -342,6 +367,9 @@ export const useMachineStore = create<MachineState>((set, get) => ({
|
|||
uuidStr,
|
||||
"Offset:",
|
||||
patternOffset,
|
||||
"Rotation:",
|
||||
patternRotation,
|
||||
"(cached original unrotated data + uploaded data)",
|
||||
);
|
||||
|
||||
// Clear resume state since we just uploaded
|
||||
|
|
@ -440,6 +468,7 @@ export const useMachineStore = create<MachineState>((set, get) => ({
|
|||
uploadProgress: 0,
|
||||
resumeAvailable: false,
|
||||
resumeFileName: null,
|
||||
resumedPattern: null, // Clear this to prevent auto-reload
|
||||
});
|
||||
|
||||
// Clear uploaded pattern data in pattern store
|
||||
|
|
@ -458,7 +487,9 @@ export const useMachineStore = create<MachineState>((set, get) => ({
|
|||
// Load cached pattern
|
||||
loadCachedPattern: async (): Promise<{
|
||||
pesData: PesPatternData;
|
||||
uploadedPesData?: PesPatternData;
|
||||
patternOffset?: { x: number; y: number };
|
||||
patternRotation?: number;
|
||||
} | null> => {
|
||||
const { resumeAvailable, service, storageService, refreshPatternInfo } =
|
||||
get();
|
||||
|
|
@ -477,9 +508,18 @@ export const useMachineStore = create<MachineState>((set, get) => ({
|
|||
cached.fileName,
|
||||
"Offset:",
|
||||
cached.patternOffset,
|
||||
"Rotation:",
|
||||
cached.patternRotation,
|
||||
"Has uploaded data:",
|
||||
!!cached.uploadedPesData,
|
||||
);
|
||||
await refreshPatternInfo();
|
||||
return { pesData: cached.pesData, patternOffset: cached.patternOffset };
|
||||
return {
|
||||
pesData: cached.pesData,
|
||||
uploadedPesData: cached.uploadedPesData,
|
||||
patternOffset: cached.patternOffset,
|
||||
patternRotation: cached.patternRotation,
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ interface PatternState {
|
|||
setUploadedPattern: (
|
||||
uploadedData: PesPatternData,
|
||||
uploadedOffset: { x: number; y: number },
|
||||
fileName?: string,
|
||||
) => void;
|
||||
clearUploadedPattern: () => void;
|
||||
resetPatternOffset: () => void;
|
||||
|
|
@ -69,23 +70,32 @@ export const usePatternStore = create<PatternState>((set) => ({
|
|||
setUploadedPattern: (
|
||||
uploadedData: PesPatternData,
|
||||
uploadedOffset: { x: number; y: number },
|
||||
fileName?: string,
|
||||
) => {
|
||||
set({
|
||||
uploadedPesData: uploadedData,
|
||||
uploadedPatternOffset: uploadedOffset,
|
||||
patternUploaded: true,
|
||||
// Optionally set filename if provided (for resume/reconnect scenarios)
|
||||
...(fileName && { currentFileName: fileName }),
|
||||
});
|
||||
console.log("[PatternStore] Uploaded pattern set");
|
||||
},
|
||||
|
||||
// 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: () => {
|
||||
console.log("[PatternStore] CLEARING uploaded pattern...");
|
||||
set({
|
||||
uploadedPesData: null,
|
||||
uploadedPatternOffset: { x: 0, y: 0 },
|
||||
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
|
||||
|
|
|
|||
Loading…
Reference in a new issue