diff --git a/src/components/floor-plan/modal/circuitTrestle/CircuitTrestleSetting.jsx b/src/components/floor-plan/modal/circuitTrestle/CircuitTrestleSetting.jsx index 44deb1a0..4c09868a 100644 --- a/src/components/floor-plan/modal/circuitTrestle/CircuitTrestleSetting.jsx +++ b/src/components/floor-plan/modal/circuitTrestle/CircuitTrestleSetting.jsx @@ -120,7 +120,44 @@ export default function CircuitTrestleSetting({ id }) { const beforeCapture = (type) => { setCanvasZoom(100) canvas.set({ zoom: 1 }) - canvas.viewportTransform = [1, 0, 0, 1, 0, 0] + + // roof 객체들을 찾아서 중앙점 계산 + const roofs = canvas.getObjects().filter((obj) => obj.name === 'roof') + + if (roofs.length > 0) { + // 모든 roof의 x, y 좌표를 수집 + const allPoints = [] + roofs.forEach((roof) => { + if (roof.getCurrentPoints()) { + roof.getCurrentPoints().forEach((point) => { + allPoints.push({ x: point.x, y: point.y }) + }) + } + }) + + if (allPoints.length > 0) { + // 모든 점들의 중앙값 계산 + const minX = Math.min(...allPoints.map((p) => p.x)) + const maxX = Math.max(...allPoints.map((p) => p.x)) + const minY = Math.min(...allPoints.map((p) => p.y)) + const maxY = Math.max(...allPoints.map((p) => p.y)) + + const centerX = (minX + maxX) / 2 + const centerY = (minY + maxY) / 2 + + // 캔버스 중앙으로 이동하기 위한 오프셋 계산 + const canvasWidth = canvas.getWidth() + const canvasHeight = canvas.getHeight() + const offsetX = canvasWidth / 2 - centerX + const offsetY = canvasHeight / 2 - centerY + + canvas.viewportTransform = [1, 0, 0, 1, offsetX, offsetY] + } else { + canvas.viewportTransform = [1, 0, 0, 1, 0, 0] + } + } else { + canvas.viewportTransform = [1, 0, 0, 1, 0, 0] + } const modules = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.MODULE) const circuitNumberTexts = canvas.getObjects().filter((obj) => obj.name === 'circuitNumber') @@ -139,40 +176,9 @@ export default function CircuitTrestleSetting({ id }) { // roof polygon들의 중간점 계산 const roofPolygons = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF) let x, y - x = 0 //canvas.width / 2 - y = 1000 //canvas.height / 2 - - /*if (roofPolygons.length > 0) { - let minX = Infinity, - minY = Infinity, - maxX = -Infinity, - maxY = -Infinity - - roofPolygons.forEach((obj) => { - const boundingRect = obj.getBoundingRect() - minX = Math.min(minX, boundingRect.left) - minY = Math.min(minY, boundingRect.top) - maxX = Math.max(maxX, boundingRect.left + boundingRect.width) - maxY = Math.max(maxY, boundingRect.top + boundingRect.height) - }) - - x = (minX + maxX) / 2 - y = (minY + maxY) / 2 - } else { - // roof polygon이 없으면 기본 중앙점 사용 - x = canvas.width / 2 - y = canvas.height / 2 - } - - if (x > 1600) { - x = 0 - y = 0 - } - if (y > 1600) { - x = 0 - y = 0 - }*/ - + x = canvas.width / 2 + y = canvas.height / 2 + canvas.zoomToPoint(new fabric.Point(x, y), 0.4) changeFontSize('lengthText', '28') changeFontSize('circuitNumber', '28') diff --git a/src/components/floor-plan/modal/grid/GridCopy.jsx b/src/components/floor-plan/modal/grid/GridCopy.jsx index 805cf186..84eddb15 100644 --- a/src/components/floor-plan/modal/grid/GridCopy.jsx +++ b/src/components/floor-plan/modal/grid/GridCopy.jsx @@ -5,9 +5,9 @@ import { useRecoilValue } from 'recoil' import { contextPopupPositionState } from '@/store/popupAtom' import { useState } from 'react' import { canvasState, currentObjectState } from '@/store/canvasAtom' -import { useGrid } from '@/hooks/common/useGrid' import { gridColorState } from '@/store/gridAtom' import { gridDisplaySelector } from '@/store/settingAtom' + const GRID_PADDING = 5 export default function GridCopy(props) { const contextPopupPosition = useRecoilValue(contextPopupPositionState) @@ -25,10 +25,10 @@ export default function GridCopy(props) { } const copy = (object, length) => { - const lineStartX = object.direction === 'vertical' ? object.x1 + length : 0 - const lineEndX = object.direction === 'vertical' ? object.x2 + length : canvas.width - const lineStartY = object.direction === 'vertical' ? 0 : object.y1 + length - const lineEndY = object.direction === 'vertical' ? canvas.width : object.y1 + length + const lineStartX = object.direction === 'vertical' ? object.x1 + length : object.x1 + const lineEndX = object.direction === 'vertical' ? object.x2 + length : object.x2 + const lineStartY = object.direction === 'vertical' ? object.y1 : object.y1 + length + const lineEndY = object.direction === 'vertical' ? object.y2 : object.y1 + length const line = new fabric.Line([lineStartX, lineStartY, lineEndX, lineEndY], { stroke: gridColor, diff --git a/src/components/floor-plan/modal/grid/GridMove.jsx b/src/components/floor-plan/modal/grid/GridMove.jsx index 52084e00..eb83d2f1 100644 --- a/src/components/floor-plan/modal/grid/GridMove.jsx +++ b/src/components/floor-plan/modal/grid/GridMove.jsx @@ -3,11 +3,9 @@ import { useMessage } from '@/hooks/useMessage' import { usePopup } from '@/hooks/usePopup' import { useRecoilState, useRecoilValue } from 'recoil' import { contextPopupPositionState } from '@/store/popupAtom' -import { useCanvas } from '@/hooks/useCanvas' import { canvasState, currentObjectState } from '@/store/canvasAtom' import { useEffect, useState } from 'react' import { useSwal } from '@/hooks/useSwal' -import { set } from 'react-hook-form' export default function GridMove(props) { const contextPopupPosition = useRecoilValue(contextPopupPositionState) @@ -70,10 +68,10 @@ export default function GridMove(props) { const move = (object, x, y) => { object.set({ ...object, - x1: object.direction === 'vertical' ? object.x1 + x : 0, - x2: object.direction === 'vertical' ? object.x1 + x : canvas.width, - y1: object.direction === 'vertical' ? 0 : object.y1 + y, - y2: object.direction === 'vertical' ? canvas.height : object.y1 + y, + x1: object.direction === 'vertical' ? object.x1 + x : object.x1, + x2: object.direction === 'vertical' ? object.x1 + x : object.x2, + y1: object.direction === 'vertical' ? object.y1 : object.y1 + y, + y2: object.direction === 'vertical' ? object.y2 : object.y1 + y, }) } diff --git a/src/hooks/common/useGrid.js b/src/hooks/common/useGrid.js index c320360f..4d427b21 100644 --- a/src/hooks/common/useGrid.js +++ b/src/hooks/common/useGrid.js @@ -1,15 +1,18 @@ import { useRecoilValue } from 'recoil' -import { canvasState, dotLineGridSettingState } from '@/store/canvasAtom' +import { canvasState, canvasZoomState, dotLineGridSettingState } from '@/store/canvasAtom' import { useEffect } from 'react' import { gridColorState } from '@/store/gridAtom' import { gridDisplaySelector } from '@/store/settingAtom' + const GRID_PADDING = 5 + export function useGrid() { const canvas = useRecoilValue(canvasState) const dotLineGridSetting = useRecoilValue(dotLineGridSettingState) const gridColor = useRecoilValue(gridColorState) const isGridDisplay = useRecoilValue(gridDisplaySelector) + const zoom = useRecoilValue(canvasZoomState) useEffect(() => { if (!canvas) { @@ -90,14 +93,32 @@ export function useGrid() { } if (patternData.lineGridDisplay) { - for (let i = 0; i < 5000 / patternData.gridVertical + 1; i++) { + // 캔버스의 실제 보이는 영역 계산 + const canvasWidth = canvas.getWidth() + const canvasHeight = canvas.getHeight() + const currentZoom = canvas.getZoom() + const viewportTransform = canvas.viewportTransform + + const visibleLeft = -viewportTransform[4] / currentZoom + const visibleTop = -viewportTransform[5] / currentZoom + const visibleRight = visibleLeft + canvasWidth / currentZoom + const visibleBottom = visibleTop + canvasHeight / currentZoom + + // 여유 공간 추가 + const padding = 200 + const gridLeft = visibleLeft - padding + const gridTop = visibleTop - padding + const gridRight = visibleRight + padding + const gridBottom = visibleBottom + padding + + // 가로선 생성 (수평선) + const horizontalGridRange = gridBottom - gridTop + const horizontalGridCount = Math.ceil(horizontalGridRange / patternData.gridVertical) + 2 + + for (let i = 0; i < horizontalGridCount; i++) { + const y = gridTop + i * patternData.gridVertical const horizontalLine = new fabric.Line( - [ - -1500, - -1500 + i * patternData.gridVertical - patternData.gridVertical / 2, - 3000, - -1500 + i * patternData.gridVertical - patternData.gridVertical / 2, - ], + [gridLeft, y, gridRight, y], { stroke: gridColor, strokeWidth: 1, @@ -118,14 +139,14 @@ export function useGrid() { canvas.add(horizontalLine) } - for (let i = 0; i < 5000 / patternData.gridHorizon + 1; i++) { + // 세로선 생성 (수직선) + const verticalGridRange = gridRight - gridLeft + const verticalGridCount = Math.ceil(verticalGridRange / patternData.gridHorizon) + 2 + + for (let i = 0; i < verticalGridCount; i++) { + const x = gridLeft + i * patternData.gridHorizon const verticalLine = new fabric.Line( - [ - -1500 + i * patternData.gridHorizon - patternData.gridHorizon / 2, - -1500, - -1500 + i * patternData.gridHorizon - patternData.gridHorizon / 2, - 3000, - ], + [x, gridTop, x, gridBottom], { stroke: gridColor, strokeWidth: 1, @@ -148,7 +169,7 @@ export function useGrid() { } canvas.renderAll() - }, [dotLineGridSetting]) + }, [dotLineGridSetting, zoom]) const move = (object, x, y) => { object.set({ diff --git a/src/hooks/option/useCanvasSetting.js b/src/hooks/option/useCanvasSetting.js index b63b6ffa..150d53f4 100644 --- a/src/hooks/option/useCanvasSetting.js +++ b/src/hooks/option/useCanvasSetting.js @@ -1,29 +1,29 @@ -import { useEffect, useState, useRef, useContext } from 'react' +import { useContext, useEffect, useState } from 'react' import { useRecoilState, useRecoilValue, useResetRecoilState, useSetRecoilState } from 'recoil' import { adsorptionPointModeState, adsorptionRangeState, - canvasState, - planSizeSettingState, - dotLineGridSettingState, canvasSettingState, + canvasState, currentMenuState, + dotLineGridSettingState, + planSizeSettingState, } from '@/store/canvasAtom' import { globalLocaleStore } from '@/store/localeAtom' import { useMessage } from '@/hooks/useMessage' import { useAxios } from '@/hooks/useAxios' import { useSwal } from '@/hooks/useSwal' import { + addedRoofsState, + basicSettingState, correntObjectNoState, corridorDimensionSelector, - settingModalFirstOptionsState, - settingModalSecondOptionsState, - settingModalGridOptionsState, - basicSettingState, + fetchRoofMaterialsState, roofMaterialsAtom, selectedRoofMaterialSelector, - addedRoofsState, - fetchRoofMaterialsState, + settingModalFirstOptionsState, + settingModalGridOptionsState, + settingModalSecondOptionsState, } from '@/store/settingAtom' import { MENU, POLYGON_TYPE } from '@/common/common' import { globalFontAtom } from '@/store/fontAtom' @@ -339,11 +339,11 @@ export function useCanvasSetting(executeEffect = true) { */ const fetchBasicSettings = async (planNo, openPoint) => { // 지붕재 데이터가 없으면 먼저 로드 - let materials = roofMaterials; + let materials = roofMaterials if (!materials || materials.length === 0) { - logger.log("Waiting for roofMaterials to be loaded..."); - materials = await addRoofMaterials(); - logger.log("roofMaterials loaded:", materials); + logger.log('Waiting for roofMaterials to be loaded...') + materials = await addRoofMaterials() + logger.log('roofMaterials loaded:', materials) } try { @@ -529,8 +529,14 @@ export function useCanvasSetting(executeEffect = true) { ? params.selectedRoofMaterial.raftBaseCd : params.roofsData.raft, roofLayout: params.roofsData.roofLayout === null || params.roofsData.roofLayout === undefined ? 'P' : params.roofsData.roofLayout, - roofPitch: params.roofsData.roofPitch === null || params.roofsData.roofPitch === undefined || params.roofsData.roofPitch === '' ? 0 : params.roofsData.roofPitch, - roofAngle: params.roofsData.roofAngle === null || params.roofsData.roofAngle === undefined || params.roofsData.roofAngle === '' ? 0 : params.roofsData.roofAngle, + roofPitch: + params.roofsData.roofPitch === null || params.roofsData.roofPitch === undefined || params.roofsData.roofPitch === '' + ? 0 + : params.roofsData.roofPitch, + roofAngle: + params.roofsData.roofAngle === null || params.roofsData.roofAngle === undefined || params.roofsData.roofAngle === '' + ? 0 + : params.roofsData.roofAngle, }, ], } @@ -643,7 +649,11 @@ export function useCanvasSetting(executeEffect = true) { setDimensionLineSettings({ ...dimensionLineSettings, pixel: res.originPixel, color: res.originColor }) /** 도면크기 설정 */ - setPlanSizeSettingMode({ ...planSizeSettingMode, originHorizon: res.originHorizon, originVertical: res.originVertical }) + setPlanSizeSettingMode({ + ...planSizeSettingMode, + originHorizon: res.originHorizon, + originVertical: res.originVertical, + }) canvas.setWidth(res.originHorizon) canvas.setHeight(res.originVertical) canvas.renderAll() @@ -723,7 +733,7 @@ export function useCanvasSetting(executeEffect = true) { /** 조회된 글꼴 데이터가 없는 경우 (데이터 초기화) */ /** 흡착점 ON/OFF */ - setAdsorptionPointMode(false) + setAdsorptionPointMode(true) /** 치수선 설정 */ resetDimensionLineSettings() diff --git a/src/hooks/useCanvas.js b/src/hooks/useCanvas.js index 4945fdd7..679b17fa 100644 --- a/src/hooks/useCanvas.js +++ b/src/hooks/useCanvas.js @@ -26,6 +26,7 @@ export function useCanvas(id) { const isImageDisplay = useRecoilValue(imageDisplaySelector) const {} = useFont() const resetCanvasZoom = useResetRecoilState(canvasZoomState) + const zoom = useRecoilValue(canvasZoomState) /** * 처음 셋팅 @@ -50,8 +51,53 @@ export function useCanvas(id) { }, []) useEffect(() => { - // canvas 사이즈가 변경되면 다시 - }, [canvasSize]) + // zoom 상태가 변경될 때 tempGrid 라인들의 크기를 캔버스에 맞게 조정 + if (canvas) { + adjustTempGridLines() + } + }, [zoom]) + + const adjustTempGridLines = () => { + if (!canvas) return + + const canvasWidth = canvas.getWidth() + const canvasHeight = canvas.getHeight() + const currentZoom = canvas.getZoom() + const viewportTransform = canvas.viewportTransform + + // 실제 보이는 캔버스 영역 계산 (zoom과 pan 고려) + const visibleLeft = -viewportTransform[4] / currentZoom + const visibleTop = -viewportTransform[5] / currentZoom + const visibleRight = visibleLeft + canvasWidth / currentZoom + const visibleBottom = visibleTop + canvasHeight / currentZoom + + // tempGrid 라인들을 찾아서 크기 조정 + const tempGridLines = canvas.getObjects().filter((obj) => ['tempGrid', 'lineGrid', 'mouseLine'].includes(obj.name)) + + tempGridLines.forEach((line) => { + if (line.direction === 'vertical') { + // 세로 라인: y축을 캔버스 전체 높이로 설정 + line.set({ + x1: line.x1, + y1: visibleTop - 100, // 여유 공간 추가 + x2: line.x1, + y2: visibleBottom + 100, // 여유 공간 추가 + }) + } else if (line.direction === 'horizontal') { + // 가로 라인: x축을 캔버스 전체 너비로 설정 + line.set({ + x1: visibleLeft - 100, // 여유 공간 추가 + y1: line.y1, + x2: visibleRight + 100, // 여유 공간 추가 + y2: line.y1, + }) + } + + line.setCoords() + }) + + canvas.renderAll() + } useEffect(() => { canvas diff --git a/src/hooks/useEvent.js b/src/hooks/useEvent.js index 6499f2f3..748e680a 100644 --- a/src/hooks/useEvent.js +++ b/src/hooks/useEvent.js @@ -369,18 +369,39 @@ export function useEvent() { } const drawMouseLine = (pointer) => { - const horizontalLine = new fabric.Line([-2 * canvas.width, pointer.y, 2 * canvas.width, pointer.y], { + // 캔버스의 실제 보이는 영역 계산 (zoom과 pan 고려) + const canvasWidth = canvas.getWidth() + const canvasHeight = canvas.getHeight() + const currentZoom = canvas.getZoom() + const viewportTransform = canvas.viewportTransform + + const visibleLeft = -viewportTransform[4] / currentZoom + const visibleTop = -viewportTransform[5] / currentZoom + const visibleRight = visibleLeft + canvasWidth / currentZoom + const visibleBottom = visibleTop + canvasHeight / currentZoom + + // 여유 공간 추가 + const padding = 200 + const lineLeft = visibleLeft - padding + const lineTop = visibleTop - padding + const lineRight = visibleRight + padding + const lineBottom = visibleBottom + padding + + // 가로선 (수평선) + const horizontalLine = new fabric.Line([lineLeft, pointer.y, lineRight, pointer.y], { stroke: 'red', strokeWidth: 1, selectable: false, + direction: 'horizontal', name: 'mouseLine', }) - // 세로선 - const verticalLine = new fabric.Line([pointer.x, -2 * canvas.height, pointer.x, 2 * canvas.height], { + // 세로선 (수직선) + const verticalLine = new fabric.Line([pointer.x, lineTop, pointer.x, lineBottom], { stroke: 'red', strokeWidth: 1, selectable: false, + direction: 'vertical', name: 'mouseLine', })