Make UV Editor canvas dimensions strictly follow the base texture aspect ratio
This commit is contained in:
+20
-18
@@ -40,6 +40,8 @@ export function UVEditor({ onClose, onSave, targetObject, loadedModel, sceneObje
|
||||
const [mappingType, setMappingType] = useState<string>(targetObject?.materialProps?.mappingType || 'uv');
|
||||
const [bgColor, setBgColor] = useState(initBg);
|
||||
const [bgTransparent, setBgTransparent] = useState(initTrans);
|
||||
const [canvasW, setCanvasW] = useState(1024);
|
||||
const [canvasH, setCanvasH] = useState(1024);
|
||||
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
@@ -90,9 +92,9 @@ export function UVEditor({ onClose, onSave, targetObject, loadedModel, sceneObje
|
||||
|
||||
const renderCanvas = useCallback((lrs: Layer[], transparent: boolean, bg: string): string => {
|
||||
const c = document.createElement('canvas');
|
||||
c.width = c.height = CANVAS_SIZE;
|
||||
c.width = canvasW; c.height = canvasH;
|
||||
const ctx = c.getContext('2d')!;
|
||||
if (!transparent) { ctx.fillStyle = bg; ctx.fillRect(0, 0, CANVAS_SIZE, CANVAS_SIZE); }
|
||||
if (!transparent) { ctx.fillStyle = bg; ctx.fillRect(0, 0, canvasW, canvasH); }
|
||||
lrs.forEach(layer => {
|
||||
if (!layer.img || layer.visible === false) return;
|
||||
const iw = layer.img.width, ih = layer.img.height;
|
||||
@@ -125,7 +127,7 @@ export function UVEditor({ onClose, onSave, targetObject, loadedModel, sceneObje
|
||||
const url = ev.target?.result as string;
|
||||
const img = new Image();
|
||||
img.onload = () => {
|
||||
const nl: Layer = { id: `l-${Date.now()}`, url, img, x: CANVAS_SIZE / 2, y: CANVAS_SIZE / 2, scaleX: 1, scaleY: 1, rotation: 0, cropL: 0, cropT: 0, cropR: 0, cropB: 0, visible: true };
|
||||
const nl: Layer = { id: `l-${Date.now()}`, url, img, x: canvasW / 2, y: canvasH / 2, scaleX: 1, scaleY: 1, rotation: 0, cropL: 0, cropT: 0, cropR: 0, cropB: 0, visible: true };
|
||||
setLayers(p => [...p, nl]);
|
||||
setSelectedId(nl.id);
|
||||
};
|
||||
@@ -140,7 +142,7 @@ export function UVEditor({ onClose, onSave, targetObject, loadedModel, sceneObje
|
||||
|
||||
const getSF = () => {
|
||||
const r = containerRef.current?.getBoundingClientRect();
|
||||
return r ? CANVAS_SIZE / r.width : 1;
|
||||
return r ? canvasW / r.width : 1;
|
||||
};
|
||||
|
||||
const onHandleDown = (e: React.MouseEvent, handle: Handle, layer: Layer) => {
|
||||
@@ -166,13 +168,13 @@ export function UVEditor({ onClose, onSave, targetObject, loadedModel, sceneObje
|
||||
let nx = d.layer.x + dx;
|
||||
let ny = d.layer.y + dy;
|
||||
// Clamp to canvas bounds with soft margin
|
||||
nx = Math.max(0, Math.min(CANVAS_SIZE, nx));
|
||||
ny = Math.max(0, Math.min(CANVAS_SIZE, ny));
|
||||
nx = Math.max(0, Math.min(canvasW, nx));
|
||||
ny = Math.max(0, Math.min(canvasH, ny));
|
||||
// Hold Shift to snap to grid
|
||||
if (isShift) { nx = snap(nx, SNAP_PX); ny = snap(ny, SNAP_PX); }
|
||||
// Center snapping: snap near canvas center
|
||||
if (Math.abs(nx - CANVAS_SIZE / 2) < SNAP_PX) nx = CANVAS_SIZE / 2;
|
||||
if (Math.abs(ny - CANVAS_SIZE / 2) < SNAP_PX) ny = CANVAS_SIZE / 2;
|
||||
if (Math.abs(nx - canvasW / 2) < SNAP_PX) nx = canvasW / 2;
|
||||
if (Math.abs(ny - canvasH / 2) < SNAP_PX) ny = canvasH / 2;
|
||||
updateLayer(d.layer.id, { x: nx, y: ny });
|
||||
} else if (d.handle === 'rotate') {
|
||||
let rot = d.layer.rotation + dx / 2;
|
||||
@@ -228,11 +230,11 @@ export function UVEditor({ onClose, onSave, targetObject, loadedModel, sceneObje
|
||||
const ih = (layer.img?.height ?? 200) * (1 - layer.cropT - layer.cropB);
|
||||
const rect = containerRef.current?.getBoundingClientRect();
|
||||
if (!rect) return null;
|
||||
const pct = (v: number) => `${(v / CANVAS_SIZE) * 100}%`;
|
||||
const w = (iw * layer.scaleX / CANVAS_SIZE) * 100;
|
||||
const h = (ih * layer.scaleY / CANVAS_SIZE) * 100;
|
||||
const lx = (layer.x / CANVAS_SIZE) * 100;
|
||||
const ly = (layer.y / CANVAS_SIZE) * 100;
|
||||
|
||||
const w = (iw * layer.scaleX / canvasW) * 100;
|
||||
const h = (ih * layer.scaleY / canvasH) * 100;
|
||||
const lx = (layer.x / canvasW) * 100;
|
||||
const ly = (layer.y / canvasH) * 100;
|
||||
|
||||
const cornerStyle = (cx: string, cy: string, cursor: string): React.CSSProperties => ({
|
||||
position: 'absolute', left: cx, top: cy, width: 10, height: 10,
|
||||
@@ -411,7 +413,7 @@ export function UVEditor({ onClose, onSave, targetObject, loadedModel, sceneObje
|
||||
onClick={() => setSelectedId(null)}>
|
||||
<div ref={containerRef} className="relative shadow-2xl border border-zinc-700/50"
|
||||
style={{
|
||||
width: 'min(68vh, 90%)', aspectRatio: '1/1',
|
||||
width: 'min(68vh, 90%)', aspectRatio: `${canvasW}/${canvasH}`,
|
||||
background: bgTransparent
|
||||
? 'repeating-conic-gradient(#27272a 0% 25%, #18181b 0% 50%) 0 0 / 16px 16px'
|
||||
: bgColor
|
||||
@@ -428,10 +430,10 @@ export function UVEditor({ onClose, onSave, targetObject, loadedModel, sceneObje
|
||||
onMouseDown={e => { e.stopPropagation(); onHandleDown(e, 'move', layer); }}
|
||||
className="absolute cursor-move"
|
||||
style={{
|
||||
left: `${(layer.x / CANVAS_SIZE) * 100}%`,
|
||||
top: `${(layer.y / CANVAS_SIZE) * 100}%`,
|
||||
width: `${(iw / CANVAS_SIZE) * 100}%`,
|
||||
height: `${(ih / CANVAS_SIZE) * 100}%`,
|
||||
left: `${(layer.x / canvasW) * 100}%`,
|
||||
top: `${(layer.y / canvasH) * 100}%`,
|
||||
width: `${(iw / canvasW) * 100}%`,
|
||||
height: `${(ih / canvasH) * 100}%`,
|
||||
transform: `translate(-50%,-50%) rotate(${layer.rotation}deg) scale(${layer.scaleX},${layer.scaleY})`,
|
||||
zIndex: isSelected ? 4 : 2,
|
||||
transition: 'transform 0.04s linear',
|
||||
|
||||
Reference in New Issue
Block a user