Compare commits

..

No commits in common. "312ebfc50322bb2816b8d5d2f59659341bfd9ef2" and "116612ec2b5ab4584f695f6734496855ac4c430e" have entirely different histories.

7 changed files with 67 additions and 200 deletions

View File

@ -216,8 +216,6 @@ export const SAVE_KEY = [
'isMultipleOf45', 'isMultipleOf45',
'from', 'from',
'originColor', 'originColor',
'originWidth',
'originHeight',
] ]
export const OBJECT_PROTOTYPE = [fabric.Line.prototype, fabric.Polygon.prototype, fabric.Triangle.prototype, fabric.Group.prototype] export const OBJECT_PROTOTYPE = [fabric.Line.prototype, fabric.Polygon.prototype, fabric.Triangle.prototype, fabric.Group.prototype]

View File

@ -87,10 +87,6 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
this.initLines() this.initLines()
this.init() this.init()
this.setShape() this.setShape()
const originWidth = this.originWidth ?? this.width
const originHeight = this.originHeight ?? this.height
this.originWidth = this.angle === 90 || this.angle === 270 ? originHeight : originWidth
this.originHeight = this.angle === 90 || this.angle === 270 ? originWidth : originHeight
}, },
setShape() { setShape() {
@ -130,13 +126,11 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
this.on('moving', () => { this.on('moving', () => {
this.initLines() this.initLines()
this.addLengthText() this.addLengthText()
this.setCoords()
}) })
this.on('modified', (e) => { this.on('modified', (e) => {
this.initLines() this.initLines()
this.addLengthText() this.addLengthText()
this.setCoords()
}) })
this.on('selected', () => { this.on('selected', () => {

View File

@ -12,7 +12,7 @@ import { useSurfaceShapeBatch } from '@/hooks/surface/useSurfaceShapeBatch'
export default function SizeSetting(props) { export default function SizeSetting(props) {
const contextPopupPosition = useRecoilValue(contextPopupPositionState) const contextPopupPosition = useRecoilValue(contextPopupPositionState)
const [settingTarget, setSettingTarget] = useState(props.side || 1) const [settingTarget, setSettingTarget] = useState(1)
const { id, pos = contextPopupPosition, target } = props const { id, pos = contextPopupPosition, target } = props
const { getMessage } = useMessage() const { getMessage } = useMessage()
const { closePopup } = usePopup() const { closePopup } = usePopup()
@ -47,11 +47,11 @@ export default function SizeSetting(props) {
<div className="size-option-top"> <div className="size-option-top">
<div className="size-option-wrap"> <div className="size-option-wrap">
<div className="size-option mb5"> <div className="size-option mb5">
<input type="text" className="input-origin mr5" value={(target?.originWidth * 10).toFixed(0)} readOnly={true} /> <input type="text" className="input-origin mr5" value={target?.width.toFixed(0) * 10} readOnly={true} />
<span className="normal-font">mm</span> <span className="normal-font">mm</span>
</div> </div>
<div className="size-option"> <div className="size-option">
<input type="text" className="input-origin mr5" defaultValue={(target?.originWidth * 10).toFixed(0)} ref={widthRef} /> <input type="text" className="input-origin mr5" defaultValue={target?.width.toFixed(0) * 10} ref={widthRef} />
<span className="normal-font">mm</span> <span className="normal-font">mm</span>
</div> </div>
</div> </div>
@ -60,11 +60,11 @@ export default function SizeSetting(props) {
<div className="size-option-side"> <div className="size-option-side">
<div className="size-option-wrap"> <div className="size-option-wrap">
<div className="size-option mb5"> <div className="size-option mb5">
<input type="text" className="input-origin mr5" value={(target?.originHeight * 10).toFixed(0)} readOnly={true} /> <input type="text" className="input-origin mr5" value={target?.height.toFixed(0) * 10} readOnly={true} />
<span className="normal-font">mm</span> <span className="normal-font">mm</span>
</div> </div>
<div className="size-option"> <div className="size-option">
<input type="text" className="input-origin mr5" defaultValue={(target?.originHeight * 10).toFixed(0)} ref={heightRef} /> <input type="text" className="input-origin mr5" defaultValue={target?.height.toFixed(0) * 10} ref={heightRef} />
<span className="normal-font">mm</span> <span className="normal-font">mm</span>
</div> </div>
</div> </div>

View File

@ -1,9 +1,9 @@
'use client' 'use client'
import { useRecoilValue, useResetRecoilState } from 'recoil' import { useRecoilValue, useResetRecoilState } from 'recoil'
import { canvasSettingState, canvasState, currentCanvasPlanState, currentObjectState, globalPitchState } from '@/store/canvasAtom' import { canvasSettingState, canvasState, currentCanvasPlanState, globalPitchState } from '@/store/canvasAtom'
import { LINE_TYPE, MENU, POLYGON_TYPE } from '@/common/common' import { LINE_TYPE, MENU, POLYGON_TYPE } from '@/common/common'
import { getIntersectionPoint } from '@/util/canvas-util' import { getIntersectionPoint, toFixedWithoutRounding } from '@/util/canvas-util'
import { degreesToRadians } from '@turf/turf' import { degreesToRadians } from '@turf/turf'
import { QPolygon } from '@/components/fabric/QPolygon' import { QPolygon } from '@/components/fabric/QPolygon'
import { useSwal } from '@/hooks/useSwal' import { useSwal } from '@/hooks/useSwal'
@ -21,13 +21,10 @@ import { placementShapeDrawingPointsState } from '@/store/placementShapeDrawingA
import { getBackGroundImage } from '@/lib/imageActions' import { getBackGroundImage } from '@/lib/imageActions'
import { useCanvasSetting } from '@/hooks/option/useCanvasSetting' import { useCanvasSetting } from '@/hooks/option/useCanvasSetting'
import { useText } from '@/hooks/useText' import { useText } from '@/hooks/useText'
import SizeSetting from '@/components/floor-plan/modal/object/SizeSetting'
import { v4 as uuidv4 } from 'uuid'
import { useState } from 'react'
export function useSurfaceShapeBatch({ isHidden, setIsHidden }) { export function useSurfaceShapeBatch({ isHidden, setIsHidden }) {
const { getMessage } = useMessage() const { getMessage } = useMessage()
const { drawDirectionArrow, addPolygon, addLengthText, setPolygonLinesActualSize } = usePolygon() const { drawDirectionArrow, addPolygon, addLengthText } = usePolygon()
const lengthTextFont = useRecoilValue(fontSelector('lengthText')) const lengthTextFont = useRecoilValue(fontSelector('lengthText'))
const resetOuterLinePoints = useResetRecoilState(outerLinePointsState) const resetOuterLinePoints = useResetRecoilState(outerLinePointsState)
const resetPlacementShapeDrawingPoints = useResetRecoilState(placementShapeDrawingPointsState) const resetPlacementShapeDrawingPoints = useResetRecoilState(placementShapeDrawingPointsState)
@ -39,13 +36,11 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) {
const { swalFire } = useSwal() const { swalFire } = useSwal()
const { addCanvasMouseEventListener, initEvent } = useEvent() const { addCanvasMouseEventListener, initEvent } = useEvent()
// const { addCanvasMouseEventListener, initEvent } = useContext(EventContext) // const { addCanvasMouseEventListener, initEvent } = useContext(EventContext)
const { addPopup, closePopup, closeAll } = usePopup() const { addPopup, closePopup } = usePopup()
const { setSurfaceShapePattern } = useRoofFn() const { setSurfaceShapePattern } = useRoofFn()
const { changeCorridorDimensionText } = useText() const { changeCorridorDimensionText } = useText()
const currentCanvasPlan = useRecoilValue(currentCanvasPlanState) const currentCanvasPlan = useRecoilValue(currentCanvasPlanState)
const { fetchSettings } = useCanvasSetting(false) const { fetchSettings } = useCanvasSetting(false)
const currentObject = useRecoilValue(currentObjectState)
const [popupId, setPopupId] = useState(uuidv4())
const applySurfaceShape = (surfaceRefs, selectedType, id) => { const applySurfaceShape = (surfaceRefs, selectedType, id) => {
let length1, length2, length3, length4, length5 let length1, length2, length3, length4, length5
@ -884,7 +879,6 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) {
drawDirectionArrow(roof) drawDirectionArrow(roof)
changeCorridorDimensionText() changeCorridorDimensionText()
addLengthText(roof) addLengthText(roof)
roof.setCoords()
initEvent() initEvent()
canvas.renderAll() canvas.renderAll()
}) })
@ -922,138 +916,71 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) {
} }
const resizeSurfaceShapeBatch = (side, target, width, height) => { const resizeSurfaceShapeBatch = (side, target, width, height) => {
if (!target || target.type !== 'QPolygon') return const originTarget = { ...target }
width = width / 10 const objectWidth = target.width
height = height / 10 const objectHeight = target.height
const changeWidth = width / 10 / objectWidth
const changeHeight = height / 10 / objectHeight
let sideX = 'left'
let sideY = 'top'
// 현재 QPolygon의 점들 가져오기 (변형 적용된 실제 좌표) //그룹 중심점 변경
const currentPoints = target.getCurrentPoints() || [] if (side === 2) {
const angle = target.angle % 360 sideX = 'right'
if (currentPoints.length === 0) return sideY = 'top'
} else if (side === 3) {
// 현재 바운딩 박스 계산 sideX = 'left'
let minX = Math.min(...currentPoints.map((p) => p.x)) sideY = 'bottom'
let maxX = Math.max(...currentPoints.map((p) => p.x)) } else if (side === 4) {
let minY = Math.min(...currentPoints.map((p) => p.y)) sideX = 'right'
let maxY = Math.max(...currentPoints.map((p) => p.y)) sideY = 'bottom'
let currentWidth = maxX - minX
let currentHeight = maxY - minY
// 회전에 관계없이 단순한 앵커 포인트 계산
let anchorX, anchorY
switch (side) {
case 1: // left-top
anchorX = minX
anchorY = minY
break
case 2: // right-top
anchorX = maxX
anchorY = minY
break
case 3: // left-bottom
anchorX = minX
anchorY = maxY
break
case 4: // right-bottom
anchorX = maxX
anchorY = maxY
break
default:
return
} }
// 목표 크기 (회전에 관계없이 동일하게 적용) //변경 전 좌표
let targetWidth = width const newCoords = target.getPointByOrigin(sideX, sideY)
let targetHeight = height
// 새로운 점들 계산 - 앵커 포인트는 고정, 나머지는 비례적으로 확장 target.set({
// 각도와 side에 따라 확장 방향 결정 originX: sideX,
const newPoints = currentPoints.map((point) => { originY: sideY,
// 앵커 포인트 기준으로 새로운 위치 계산 left: newCoords.x,
// side와 각도에 관계없이 일관된 방식으로 처리 top: newCoords.y,
// 앵커 포인트에서 각 점까지의 절대 거리
const deltaX = point.x - anchorX
const deltaY = point.y - anchorY
// 새로운 크기에 맞춰 비례적으로 확장
const newDeltaX = (deltaX / currentWidth) * targetWidth
const newDeltaY = (deltaY / currentHeight) * targetHeight
const newX = anchorX + newDeltaX
const newY = anchorY + newDeltaY
return {
x: newX, // 소수점 1자리로 반올림
y: newY,
}
}) })
// 기존 객체의 속성들을 복사 (scale은 1로 고정) target.scaleX = changeWidth
const originalOptions = { target.scaleY = changeHeight
stroke: target.stroke,
strokeWidth: target.strokeWidth, const currentPoints = target.getCurrentPoints()
fill: target.fill,
opacity: target.opacity, target.set({
visible: target.visible,
selectable: target.selectable,
evented: target.evented,
hoverCursor: target.hoverCursor,
moveCursor: target.moveCursor,
lockMovementX: target.lockMovementX,
lockMovementY: target.lockMovementY,
lockRotation: target.lockRotation,
lockScalingX: target.lockScalingX,
lockScalingY: target.lockScalingY,
lockUniScaling: target.lockUniScaling,
name: target.name,
uuid: target.uuid,
roofType: target.roofType,
roofMaterial: target.roofMaterial,
azimuth: target.azimuth,
// tilt: target.tilt,
// angle: target.angle,
// scale은 항상 1로 고정
scaleX: 1, scaleX: 1,
scaleY: 1, scaleY: 1,
lines: target.lines, width: toFixedWithoutRounding(width / 10, 1),
// 기타 모든 사용자 정의 속성들 height: toFixedWithoutRounding(height / 10, 1),
...Object.fromEntries(
Object.entries(target).filter(
([key, value]) =>
!['type', 'left', 'top', 'width', 'height', 'scaleX', 'scaleY', 'points', 'lines', 'texts', 'canvas', 'angle', 'tilt'].includes(key) &&
typeof value !== 'function',
),
),
}
// 기존 QPolygon 제거
canvas.remove(target)
// 새로운 QPolygon 생성 (scale은 1로 고정됨)
const newPolygon = new QPolygon(newPoints, originalOptions, canvas)
newPolygon.set({
originWidth: width,
originHeight: height,
}) })
//크기 변경후 좌표를 재 적용
const changedCoords = target.getPointByOrigin(originTarget.originX, originTarget.originY)
// 캔버스에 추가 target.set({
canvas.add(newPolygon) originX: originTarget.originX,
originY: originTarget.originY,
left: changedCoords.x,
top: changedCoords.y,
})
canvas.renderAll()
// 선택 상태 유지 //면형상 리사이즈시에만
canvas.setActiveObject(newPolygon) target.fire('polygonMoved')
target.points = currentPoints
target.fire('modified')
newPolygon.fire('modified') setSurfaceShapePattern(target, roofDisplay.column, false, target.roofMaterial, true)
setSurfaceShapePattern(newPolygon, null, null, newPolygon.roofMaterial)
drawDirectionArrow(newPolygon) if (target.direction) {
newPolygon.setCoords() drawDirectionArrow(target)
changeSurfaceLineType(newPolygon) }
canvas?.renderAll() target.setCoords()
closeAll() canvas.renderAll()
addPopup(popupId, 1, <SizeSetting id={popupId} side={side} target={newPolygon} />)
} }
const changeSurfaceLinePropertyEvent = () => { const changeSurfaceLinePropertyEvent = () => {
@ -1437,50 +1364,6 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) {
return orderedPoints return orderedPoints
} }
const rotateSurfaceShapeBatch = () => {
if (currentObject) {
// 기존 관련 객체들 제거
const relatedObjects = canvas
.getObjects()
.filter(
(obj) =>
obj.parentId === currentObject.id ||
(obj.name === 'lengthText' && obj.parentId === currentObject.id) ||
(obj.name === 'arrow' && obj.parentId === currentObject.id),
)
relatedObjects.forEach((obj) => canvas.remove(obj))
// 현재 회전값에 90도 추가
const currentAngle = currentObject.angle || 0
const newAngle = (currentAngle + 90) % 360
const originWidth = currentObject.originWidth
const originHeight = currentObject.originHeight
// 회전 적용 (width/height 교체 제거로 도형 깨짐 방지)
currentObject.rotate(newAngle)
// QPolygon 내부 구조 재구성 (선이 깨지는 문제 해결)
if (currentObject.type === 'QPolygon' && currentObject.lines) {
currentObject.initLines()
}
currentObject.set({
originWidth: originHeight,
originHeight: originWidth,
})
currentObject.setCoords()
currentObject.fire('modified')
// 화살표와 선 다시 그리기
drawDirectionArrow(currentObject)
setTimeout(() => {
setPolygonLinesActualSize(currentObject)
changeSurfaceLineType(currentObject)
}, 200)
canvas.renderAll()
}
}
return { return {
applySurfaceShape, applySurfaceShape,
deleteAllSurfacesAndObjects, deleteAllSurfacesAndObjects,
@ -1490,6 +1373,5 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) {
changeSurfaceLineProperty, changeSurfaceLineProperty,
changeSurfaceLinePropertyReset, changeSurfaceLinePropertyReset,
changeSurfaceLineType, changeSurfaceLineType,
rotateSurfaceShapeBatch,
} }
} }

View File

@ -1,6 +1,7 @@
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil' import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'
import { canvasSettingState, canvasState, currentMenuState, currentObjectState } from '@/store/canvasAtom' import { canvasSettingState, canvasState, currentMenuState, currentObjectState } from '@/store/canvasAtom'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { MENU, POLYGON_TYPE } from '@/common/common'
import AuxiliarySize from '@/components/floor-plan/modal/auxiliary/AuxiliarySize' import AuxiliarySize from '@/components/floor-plan/modal/auxiliary/AuxiliarySize'
import { usePopup } from '@/hooks/usePopup' import { usePopup } from '@/hooks/usePopup'
import { v4 as uuidv4 } from 'uuid' import { v4 as uuidv4 } from 'uuid'
@ -11,9 +12,11 @@ import { gridColorState } from '@/store/gridAtom'
import { contextPopupPositionState, contextPopupState } from '@/store/popupAtom' import { contextPopupPositionState, contextPopupState } from '@/store/popupAtom'
import AuxiliaryEdit from '@/components/floor-plan/modal/auxiliary/AuxiliaryEdit' import AuxiliaryEdit from '@/components/floor-plan/modal/auxiliary/AuxiliaryEdit'
import SizeSetting from '@/components/floor-plan/modal/object/SizeSetting' import SizeSetting from '@/components/floor-plan/modal/object/SizeSetting'
import RoofMaterialSetting from '@/components/floor-plan/modal/object/RoofMaterialSetting'
import DormerOffset from '@/components/floor-plan/modal/object/DormerOffset' import DormerOffset from '@/components/floor-plan/modal/object/DormerOffset'
import FontSetting from '@/components/common/font/FontSetting' import FontSetting from '@/components/common/font/FontSetting'
import RoofAllocationSetting from '@/components/floor-plan/modal/roofAllocation/RoofAllocationSetting' import RoofAllocationSetting from '@/components/floor-plan/modal/roofAllocation/RoofAllocationSetting'
import LinePropertySetting from '@/components/floor-plan/modal/lineProperty/LinePropertySetting'
import FlowDirectionSetting from '@/components/floor-plan/modal/flowDirection/FlowDirectionSetting' import FlowDirectionSetting from '@/components/floor-plan/modal/flowDirection/FlowDirectionSetting'
import { useCommonUtils } from './common/useCommonUtils' import { useCommonUtils } from './common/useCommonUtils'
@ -26,6 +29,7 @@ import ColumnRemove from '@/components/floor-plan/modal/module/column/ColumnRemo
import ColumnInsert from '@/components/floor-plan/modal/module/column/ColumnInsert' import ColumnInsert from '@/components/floor-plan/modal/module/column/ColumnInsert'
import RowRemove from '@/components/floor-plan/modal/module/row/RowRemove' import RowRemove from '@/components/floor-plan/modal/module/row/RowRemove'
import RowInsert from '@/components/floor-plan/modal/module/row/RowInsert' import RowInsert from '@/components/floor-plan/modal/module/row/RowInsert'
import CircuitNumberEdit from '@/components/floor-plan/modal/module/CircuitNumberEdit'
import { useObjectBatch } from '@/hooks/object/useObjectBatch' import { useObjectBatch } from '@/hooks/object/useObjectBatch'
import { useSurfaceShapeBatch } from '@/hooks/surface/useSurfaceShapeBatch' import { useSurfaceShapeBatch } from '@/hooks/surface/useSurfaceShapeBatch'
import { fontSelector, globalFontAtom } from '@/store/fontAtom' import { fontSelector, globalFontAtom } from '@/store/fontAtom'
@ -41,8 +45,6 @@ import PlacementSurfaceLineProperty from '@/components/floor-plan/modal/placemen
import { selectedMenuState } from '@/store/menuAtom' import { selectedMenuState } from '@/store/menuAtom'
import { useTrestle } from './module/useTrestle' import { useTrestle } from './module/useTrestle'
import { useCircuitTrestle } from './useCirCuitTrestle' import { useCircuitTrestle } from './useCirCuitTrestle'
import { usePolygon } from '@/hooks/usePolygon'
import { useText } from '@/hooks/useText'
export function useContextMenu() { export function useContextMenu() {
const canvas = useRecoilValue(canvasState) const canvas = useRecoilValue(canvasState)
@ -62,7 +64,7 @@ export function useContextMenu() {
const [column, setColumn] = useState(null) const [column, setColumn] = useState(null)
const { handleZoomClear } = useCanvasEvent() const { handleZoomClear } = useCanvasEvent()
const { moveObjectBatch, copyObjectBatch } = useObjectBatch({}) const { moveObjectBatch, copyObjectBatch } = useObjectBatch({})
const { moveSurfaceShapeBatch, rotateSurfaceShapeBatch } = useSurfaceShapeBatch({}) const { moveSurfaceShapeBatch } = useSurfaceShapeBatch({})
const [globalFont, setGlobalFont] = useRecoilState(globalFontAtom) const [globalFont, setGlobalFont] = useRecoilState(globalFontAtom)
const { addLine, removeLine } = useLine() const { addLine, removeLine } = useLine()
const { removeGrid } = useGrid() const { removeGrid } = useGrid()
@ -71,12 +73,10 @@ export function useContextMenu() {
const { settingsData, setSettingsDataSave } = useCanvasSetting(false) const { settingsData, setSettingsDataSave } = useCanvasSetting(false)
const { swalFire } = useSwal() const { swalFire } = useSwal()
const { alignModule, modulesRemove, moduleRoofRemove } = useModule() const { alignModule, modulesRemove, moduleRoofRemove } = useModule()
const { removeRoofMaterial, removeAllRoofMaterial, moveRoofMaterial, removeOuterLines, setSurfaceShapePattern } = useRoofFn() const { removeRoofMaterial, removeAllRoofMaterial, moveRoofMaterial, removeOuterLines } = useRoofFn()
const selectedMenu = useRecoilValue(selectedMenuState) const selectedMenu = useRecoilValue(selectedMenuState)
const { isAllComplete, clear: resetModule } = useTrestle() const { isAllComplete, clear: resetModule } = useTrestle()
const { isExistCircuit } = useCircuitTrestle() const { isExistCircuit } = useCircuitTrestle()
const { changeCorridorDimensionText } = useText()
const { setPolygonLinesActualSize, drawDirectionArrow } = usePolygon()
const currentMenuSetting = () => { const currentMenuSetting = () => {
switch (selectedMenu) { switch (selectedMenu) {
case 'outline': case 'outline':
@ -170,11 +170,6 @@ export function useContextMenu() {
name: getMessage('contextmenu.size.edit'), name: getMessage('contextmenu.size.edit'),
component: <SizeSetting id={popupId} target={currentObject} />, component: <SizeSetting id={popupId} target={currentObject} />,
}, },
{
id: 'rotate',
name: `${getMessage('contextmenu.rotate')}`,
fn: () => rotateSurfaceShapeBatch(),
},
{ {
id: 'roofMaterialRemove', id: 'roofMaterialRemove',
shortcut: ['d', 'D'], shortcut: ['d', 'D'],

View File

@ -446,7 +446,6 @@
"contextmenu.remove": "削除", "contextmenu.remove": "削除",
"contextmenu.remove.all": "完全削除", "contextmenu.remove.all": "完全削除",
"contextmenu.move": "移動", "contextmenu.move": "移動",
"contextmenu.rotate": "回転",
"contextmenu.copy": "コピー", "contextmenu.copy": "コピー",
"contextmenu.edit": "編集", "contextmenu.edit": "編集",
"contextmenu.module.vertical.align": "モジュールの垂直方向の中央揃え", "contextmenu.module.vertical.align": "モジュールの垂直方向の中央揃え",

View File

@ -446,7 +446,6 @@
"contextmenu.remove": "삭제", "contextmenu.remove": "삭제",
"contextmenu.remove.all": "전체 삭제", "contextmenu.remove.all": "전체 삭제",
"contextmenu.move": "이동", "contextmenu.move": "이동",
"contextmenu.rotate": "회전",
"contextmenu.copy": "복사", "contextmenu.copy": "복사",
"contextmenu.edit": "편집", "contextmenu.edit": "편집",
"contextmenu.module.vertical.align": "모듈 세로 가운데 정렬", "contextmenu.module.vertical.align": "모듈 세로 가운데 정렬",