Make UV Editor canvas dimensions strictly follow the base texture aspect ratio

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