Merge pull request 'dev' (#693) from dev into prd-deploy

Reviewed-on: #693
This commit is contained in:
ysCha 2026-03-09 13:38:14 +09:00
commit d30d92df0b
5 changed files with 155 additions and 147 deletions

View File

@ -84,12 +84,12 @@ export default function AuxiliarySize(props) {
//1 2 , 2 1 .
if (checkedRadio === 1) {
const newX2 = Big(x1).plus(dx.times(scaleFactor))
const newY2 = Big(y1).plus(dy.times(scaleFactor))
const newX2 = Big(x1).plus(dx.times(scaleFactor)).toNumber()
const newY2 = Big(y1).plus(dy.times(scaleFactor)).toNumber()
currentObject.set({ x2: newX2, y2: newY2 })
} else if (checkedRadio === 2) {
const newX1 = Big(x2).minus(dx.times(scaleFactor))
const newY1 = Big(y2).minus(dy.times(scaleFactor))
const newX1 = Big(x2).minus(dx.times(scaleFactor)).toNumber()
const newY1 = Big(y2).minus(dy.times(scaleFactor)).toNumber()
currentObject.set({ x1: newX1, y1: newY1 })
}
//planeSize actualSize .

View File

@ -1,4 +1,5 @@
import { BATCH_TYPE, POLYGON_TYPE } from '@/common/common'
import { QPolygon } from '@/components/fabric/QPolygon'
import { canvasState } from '@/store/canvasAtom'
import { polygonToTurfPolygon } from '@/util/canvas-util'
import { useRecoilValue } from 'recoil'
@ -44,6 +45,42 @@ export function useModule() {
const { clear: removeTrestleMaterials } = useTrestle()
const { checkModuleDisjointSurface } = useTurf()
// clone() 대신 직접 새 QPolygon을 생성하여 Maximum call stack 방지
const createModuleCopy = (module, newLeft, newTop, overrides = {}) => {
const deltaX = newLeft - module.left
const deltaY = newTop - module.top
const newPoints = module.points.map((p) => ({
x: p.x + deltaX,
y: p.y + deltaY,
}))
return new QPolygon(newPoints, {
fill: module.fill,
stroke: module.stroke,
strokeWidth: module.strokeWidth,
opacity: module.opacity,
selectable: module.selectable,
lockMovementX: module.lockMovementX,
lockMovementY: module.lockMovementY,
lockRotation: module.lockRotation,
lockScalingX: module.lockScalingX,
lockScalingY: module.lockScalingY,
parentId: module.parentId,
initOptions: module.initOptions,
direction: module.direction,
arrow: module.arrow,
name: module.name,
surfaceId: module.surfaceId,
moduleInfo: module.moduleInfo,
left: newLeft,
top: newTop,
width: module.width,
height: module.height,
toFixed: 2,
sort: false,
...overrides,
})
}
const moduleMove = (length, direction) => {
const selectedObj = canvas.getActiveObjects() //선택된 객체들을 가져옴
const selectedIds = selectedObj.map((obj) => obj.id) // selectedObj의 ID 추출
@ -256,27 +293,10 @@ export function useModule() {
modules.forEach((module) => {
const { top, left } = getPosotion(module, direction, Number(length) + Number(moduleLength) * 10, false)
module.clone((obj) => {
obj.set({
parentId: module.parentId,
initOptions: module.initOptions,
direction: module.direction,
arrow: module.arrow,
name: module.name,
type: module.type,
length: module.length,
points: module.points,
surfaceId: module.surfaceId,
moduleInfo: module.moduleInfo,
left,
top,
id: uuidv4(),
})
copyModule = obj
canvas.add(obj)
copyModules.push(obj)
obj.setCoords()
})
copyModule = createModuleCopy(module, left, top)
canvas.add(copyModule)
copyModules.push(copyModule)
copyModule.setCoords()
if (isOverlapObjects(copyModule, objects) || isOutsideSurface(copyModule, surface)) {
isWarning = true
copyModule.set({ fill: 'red' })
@ -318,27 +338,10 @@ export function useModule() {
canvas.discardActiveObject() //선택해제
modules.forEach((module) => {
const { top, left } = getPosotion(module, direction, length, true)
module.clone((obj) => {
obj.set({
parentId: module.parentId,
initOptions: module.initOptions,
direction: module.direction,
arrow: module.arrow,
name: module.name,
type: module.type,
length: module.length,
points: module.points,
surfaceId: module.surfaceId,
moduleInfo: module.moduleInfo,
left,
top,
id: uuidv4(),
})
copyModules.push(obj)
copyModule = obj
canvas.add(obj)
canvas.renderAll()
})
copyModule = createModuleCopy(module, left, top)
copyModules.push(copyModule)
canvas.add(copyModule)
canvas.renderAll()
if (
isOverlapOtherModules(copyModule, otherModules) ||
@ -412,29 +415,10 @@ export function useModule() {
modules.forEach((module) => {
const { top, left } = getPosotion(module, direction, Number(length), true)
module.clone((obj) => {
obj.set({
parentId: module.parentId,
initOptions: module.initOptions,
stroke: 'black',
direction: module.direction,
arrow: module.arrow,
name: module.name,
type: module.type,
length: module.length,
points: module.points,
moduleInfo: module.moduleInfo,
surfaceId: module.surfaceId,
left,
top,
id: uuidv4(),
})
copyModule = obj
canvas.add(obj)
copyModules.push(obj)
obj.setCoords()
console.log(obj)
})
copyModule = createModuleCopy(module, left, top, { stroke: 'black' })
canvas.add(copyModule)
copyModules.push(copyModule)
copyModule.setCoords()
if (
isOverlapOtherModules(copyModule, otherModules) ||
isOverlapObjects(copyModule, objects) ||
@ -770,28 +754,10 @@ export function useModule() {
otherModules = getOtherModules(columnModules)
columnModules.forEach((module) => {
const { top, left } = getPosotion(module, type, moduleIntvlHor, true)
let copyModule = null
module.clone((obj) => {
obj.set({
parentId: module.parentId,
initOptions: module.initOptions,
direction: module.direction,
arrow: module.arrow,
name: module.name,
type: module.type,
length: module.length,
points: module.points,
moduleInfo: module.moduleInfo,
surfaceId: module.surfaceId,
left,
top,
id: uuidv4(),
})
copyModule = obj
canvas.add(obj)
copyModules.push(obj)
obj.setCoords()
})
const copyModule = createModuleCopy(module, left, top)
canvas.add(copyModule)
copyModules.push(copyModule)
copyModule.setCoords()
canvas.renderAll()
if (
@ -879,29 +845,10 @@ export function useModule() {
otherModules = getOtherModules(rowModules)
rowModules.forEach((module) => {
const { top, left } = getPosotion(module, type, moduleIntvlVer, true)
let copyModule = null
module.clone((obj) => {
obj.set({
parentId: module.parentId,
initOptions: module.initOptions,
direction: module.direction,
arrow: module.arrow,
name: module.name,
type: module.type,
length: module.length,
points: module.points,
surfaceId: module.surfaceId,
moduleInfo: module.moduleInfo,
fill: module.fill,
left,
top,
id: uuidv4(),
})
copyModule = obj
canvas.add(obj)
copyModules.push(obj)
obj.setCoords()
})
const copyModule = createModuleCopy(module, left, top)
canvas.add(copyModule)
copyModules.push(copyModule)
copyModule.setCoords()
canvas.renderAll()
if (

View File

@ -184,14 +184,14 @@ export function useModuleBasicSetting(tabNum) {
// 방향에 따른 좌표 설정
const directionConfig = {
south: { coord1: 'y1', coord2: 'y2', findExtreme: Math.max },
north: { coord1: 'y1', coord2: 'y2', findExtreme: Math.min },
east: { coord1: 'x1', coord2: 'x2', findExtreme: Math.max },
west: { coord1: 'x1', coord2: 'x2', findExtreme: Math.min },
south: { coord1: 'y1', coord2: 'y2', findExtreme: Math.max, offsetX: 0, offsetY: 1 },
north: { coord1: 'y1', coord2: 'y2', findExtreme: Math.min, offsetX: 0, offsetY: -1 },
east: { coord1: 'x1', coord2: 'x2', findExtreme: Math.max, offsetX: 1, offsetY: 0 },
west: { coord1: 'x1', coord2: 'x2', findExtreme: Math.min, offsetX: -1, offsetY: 0 },
}
const config = directionConfig[direction] || directionConfig.south
const { coord1, coord2, findExtreme } = config
const { coord1, coord2, offsetX, offsetY } = config
// 2. 직선만 필터링 (대각선 제외)
// 남/북: y1 === y2 인 경우 수평 직선
@ -200,22 +200,29 @@ export function useModuleBasicSetting(tabNum) {
if (straightLines.length === 0) return
// 3. 가장 끝에 있는 직선 찾기
// 남쪽: 가장 하단 (y값이 가장 큰), 북쪽: 가장 상단 (y값이 가장 작은)
// 동쪽: 가장 오른쪽 (x값이 가장 큰), 서쪽: 가장 왼쪽 (x값이 가장 작은)
const extremeValue = findExtreme(...straightLines.map((line) => line[coord1]))
const eavesLines = straightLines.filter((line) => line[coord1] === extremeValue)
// 3. 폴리곤의 turf polygon 생성 (처마 방향 판별용)
const points = polygon.getCurrentPoints()
const turfCoords = points.map((p) => [p.x, p.y])
turfCoords.push(turfCoords[0]) // 폴리곤 닫기
const turfPolygon = turf.polygon([turfCoords])
// 4. 직선에 대해 타입 설정
// 4. 각 직선에 대해 처마/용마루 판별
// 라인 중점에서 처마 방향으로 약간 이동한 점이 폴리곤 외부이면 처마(eaves)
// 폴리곤 내부이면 용마루(ridge)
straightLines.forEach((line) => {
if (eavesLines.includes(line)) {
// 가장 끝에 있는 직선은 eaves
const midX = (line.x1 + line.x2) / 2
const midY = (line.y1 + line.y2) / 2
// 처마 방향으로 약간 이동한 테스트 포인트
const testPoint = turf.point([midX + offsetX * 0.5, midY + offsetY * 0.5])
if (!turf.booleanPointInPolygon(testPoint, turfPolygon)) {
// 테스트 포인트가 폴리곤 외부 → 처마 방향을 향함
line.attributes = {
...line.attributes,
type: LINE_TYPE.WALLLINE.EAVES,
}
} else {
// 나머지 직선은 ridge
// 테스트 포인트가 폴리곤 내부 → 용마루 방향을 향함
line.attributes = {
...line.attributes,
type: LINE_TYPE.SUBLINE.RIDGE,

View File

@ -208,6 +208,13 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) {
isXInversion: xInversion,
isYInversion: yInversion,
})
// 최초 생성 시 planeSize를 계산된 길이로 저장 (회전 후 좌표 반올림에 의한 오차 방지)
batchSurface.lines.forEach((line) => {
if (!line.attributes.planeSize || line.attributes.planeSize === 0) {
line.attributes.planeSize = line.getLength()
}
})
canvas.setActiveObject(batchSurface)
setSurfaceShapePattern(batchSurface, roofDisplay.column)
drawDirectionArrow(batchSurface)
@ -375,9 +382,9 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) {
}
points = [
{ x: pointer.x, y: pointer.y - parseInt(newLength2) / 2 },
{ x: pointer.x - parseInt(length1) / 2, y: pointer.y + parseInt(newLength2) / 2 },
{ x: pointer.x + parseInt(length1) / 2, y: pointer.y + parseInt(newLength2) / 2 },
{ x: pointer.x, y: pointer.y - newLength2 / 2 },
{ x: pointer.x - length1 / 2, y: pointer.y + newLength2 / 2 },
{ x: pointer.x + length1 / 2, y: pointer.y + newLength2 / 2 },
]
break

View File

@ -1023,6 +1023,9 @@ export const usePolygon = () => {
const line = divideLines[i]
const { intersections, startPoint, endPoint } = line
// 원본 라인의 기하학적 길이 (비율 계산용)
const originalGeomLength = Math.round(Math.hypot(line.x2 - line.x1, line.y2 - line.y1)) * 10
if (intersections.length === 1) {
const newLinePoint1 = [line.x1, line.y1, intersections[0].x, intersections[0].y]
const newLinePoint2 = [intersections[0].x, intersections[0].y, line.x2, line.y2]
@ -1042,25 +1045,43 @@ export const usePolygon = () => {
name: 'newLine',
})
// 두 라인 중 큰 길이로 통일
// 분할된 각 세그먼트의 기하학적 길이
const length1 = Math.round(Math.hypot(newLine1.x1 - newLine1.x2, newLine1.y1 - newLine1.y2)) * 10
const length2 = Math.round(Math.hypot(newLine2.x1 - newLine2.x2, newLine2.y1 - newLine2.y2)) * 10
const maxLength = Math.max(length1, length2)
const unifiedPlaneSize = line.attributes.planeSize ?? maxLength
const unifiedActualSize = line.attributes.actualSize ?? maxLength
// 원본에 planeSize/actualSize가 있으면 비율로 분배, 없으면 기하학적 길이 사용
let planeSize1, planeSize2, actualSize1, actualSize2
if (line.attributes.planeSize && originalGeomLength > 0) {
const ratio1 = length1 / originalGeomLength
const ratio2 = length2 / originalGeomLength
planeSize1 = Math.round(line.attributes.planeSize * ratio1)
planeSize2 = Math.round(line.attributes.planeSize * ratio2)
} else {
planeSize1 = length1
planeSize2 = length2
}
if (line.attributes.actualSize && originalGeomLength > 0) {
const ratio1 = length1 / originalGeomLength
const ratio2 = length2 / originalGeomLength
actualSize1 = Math.round(line.attributes.actualSize * ratio1)
actualSize2 = Math.round(line.attributes.actualSize * ratio2)
} else {
actualSize1 = length1
actualSize2 = length2
}
newLine1.attributes = {
...line.attributes,
planeSize: unifiedPlaneSize,
actualSize: unifiedActualSize,
planeSize: planeSize1,
actualSize: actualSize1,
}
newLine1.length = maxLength
newLine1.length = length1
newLine2.attributes = {
...line.attributes,
planeSize: unifiedPlaneSize,
actualSize: unifiedActualSize,
planeSize: planeSize2,
actualSize: actualSize2,
}
newLine2.length = maxLength
newLine2.length = length2
newLines.push(newLine1, newLine2)
divideLines.splice(i, 1) // 기존 line 제거
@ -1080,12 +1101,25 @@ export const usePolygon = () => {
})
const calcLength = Math.round(Math.hypot(newLine.x1 - newLine.x2, newLine.y1 - newLine.y2)) * 10
let segPlaneSize, segActualSize
if (line.attributes.planeSize && originalGeomLength > 0) {
segPlaneSize = Math.round(line.attributes.planeSize * (calcLength / originalGeomLength))
} else {
segPlaneSize = calcLength
}
if (line.attributes.actualSize && originalGeomLength > 0) {
segActualSize = Math.round(line.attributes.actualSize * (calcLength / originalGeomLength))
} else {
segActualSize = calcLength
}
newLine.attributes = {
...line.attributes,
planeSize: line.attributes.planeSize ?? calcLength,
actualSize: line.attributes.actualSize ?? calcLength,
planeSize: segPlaneSize,
actualSize: segActualSize,
}
newLine.length = line.attributes.planeSize ?? calcLength
newLine.length = calcLength
newLines.push(newLine)
currentPoint = minDistancePoint
@ -1100,12 +1134,25 @@ export const usePolygon = () => {
name: 'newLine',
})
const lastCalcLength = Math.round(Math.hypot(newLine.x1 - newLine.x2, newLine.y1 - newLine.y2)) * 10
let lastPlaneSize, lastActualSize
if (line.attributes.planeSize && originalGeomLength > 0) {
lastPlaneSize = Math.round(line.attributes.planeSize * (lastCalcLength / originalGeomLength))
} else {
lastPlaneSize = lastCalcLength
}
if (line.attributes.actualSize && originalGeomLength > 0) {
lastActualSize = Math.round(line.attributes.actualSize * (lastCalcLength / originalGeomLength))
} else {
lastActualSize = lastCalcLength
}
newLine.attributes = {
...line.attributes,
planeSize: line.attributes.planeSize ?? lastCalcLength,
actualSize: line.attributes.actualSize ?? lastCalcLength,
planeSize: lastPlaneSize,
actualSize: lastActualSize,
}
newLine.length = line.attributes.planeSize ?? lastCalcLength
newLine.length = lastCalcLength
newLines.push(newLine)
divideLines.splice(i, 1) // 기존 line 제거