setOpenSelect(!openSelect)}>
{selected}
diff --git a/src/components/ui/SurfaceShape.jsx b/src/components/ui/SurfaceShape.jsx
index 706729a3..01c557ff 100644
--- a/src/components/ui/SurfaceShape.jsx
+++ b/src/components/ui/SurfaceShape.jsx
@@ -620,6 +620,22 @@ export const SurfaceShapeModal = ({ canvas }) => {
canvas?.add(obj)
obj.set({ direction: direction })
+
+ //각도 추가
+ let originAngle = 0 //기본 남쪽
+
+ if (direction === 'west') {
+ //서
+ originAngle = 90
+ } else if (direction === 'east') {
+ //동
+ originAngle = 270
+ } else if (direction === 'north') {
+ //북
+ originAngle = 180
+ }
+ obj.set({ originAngle: originAngle })
+
setCurrentPattern(obj)
canvas?.renderAll()
})
diff --git a/src/hooks/useCanvas.js b/src/hooks/useCanvas.js
index 052605b6..37ffdc5f 100644
--- a/src/hooks/useCanvas.js
+++ b/src/hooks/useCanvas.js
@@ -38,7 +38,8 @@ export function useCanvas(id) {
setCanvasForEvent(c)
return () => {
- c.dispose()
+ // c.dispose()
+ c.clear()
}
}, [])
diff --git a/src/hooks/useMode.js b/src/hooks/useMode.js
index bdd02fcf..978c342b 100644
--- a/src/hooks/useMode.js
+++ b/src/hooks/useMode.js
@@ -37,6 +37,7 @@ import offsetPolygon from '@/util/qpolygon-utils'
import { isObjectNotEmpty } from '@/util/common-utils'
import * as turf from '@turf/turf'
import { INPUT_TYPE, Mode } from '@/common/common'
+import { m } from 'framer-motion'
export function useMode() {
const [mode, setMode] = useRecoilState(modeState)
@@ -953,6 +954,7 @@ export function useMode() {
fill: 'white',
stroke: 'black',
transparentCorners: false,
+ name: 'dormer',
})
canvas.add(rect)
@@ -1316,6 +1318,8 @@ export function useMode() {
// 새로운 다각형 객체를 캔버스에 추가합니다.
canvas.add(polygon)
+ console.log('polygon', polygon)
+
changeMode(canvas, Mode.DEFAULT)
return polygon
}
@@ -4854,6 +4858,9 @@ export function useMode() {
canvas?.off('mouse:move')
canvas?.off('mouse:out')
const roofs = canvas?.getObjects().filter((obj) => obj.name === 'roof')
+ const dormers = canvas?.getObjects().filter((obj) => obj.name === 'dormer')
+
+ console.log('roofs', roofs)
roofs.forEach((roof, index) => {
// const offsetPolygonPoint = offsetPolygon(roof.points(), -20) //이동되서 찍을라고 바꿈
@@ -4875,18 +4882,147 @@ export function useMode() {
lockScalingY: true, // Y 축 크기 조정 잠금
idx: index,
parentId: roof.id,
+ direction: roof.direction,
})
canvas?.add(trestlePoly)
trestlePoly.setViewLengthText(false)
})
+
+ //도머가 있으면 외곽으로 선을 낸다
+ dormers.forEach((dormer) => {
+ const offsetDormerPoint = offsetPolygon(rectToPolygon(dormer), 20)
+ const dormerPoly = new QPolygon(offsetDormerPoint, {
+ fill: 'transparent',
+ stroke: 'blue',
+ strokeDashArray: [5, 5],
+ strokeWidth: 1,
+ fontSize: fontSize,
+ name: 'dormerTrestle',
+ lockMovementX: true, // X 축 이동 잠금
+ lockMovementY: true, // Y축 이동 잠금
+ lockRotation: true, // 회전 잠금
+ lockScalingX: true, // X축 크기 조정 잠금
+ lockScalingY: true, // Y축 크기 조정 잠금
+ })
+ canvas?.add(dormerPoly)
+ dormerPoly.setViewLengthText(false)
+ })
+
removeHelpPointAndHelpLine()
}
+ const rectToPolygon = (rect) => {
+ const points = []
+ const left = rect.left
+ const top = rect.top
+ const width = rect.width * rect.scaleX // 스케일 적용
+ const height = rect.height * rect.scaleY // 스케일 적용
+
+ // 네 개의 꼭짓점 좌표 계산
+ points.push({ x: left, y: top }) // 좌상단
+ points.push({ x: left + width, y: top }) // 우상단
+ points.push({ x: left + width, y: top + height }) // 우하단
+ points.push({ x: left, y: top + height }) // 좌하단
+
+ return points
+ }
+
+ const polygonToTurfPolygon = (polygon) => {
+ const coordinates = polygon.points.map((point) => [point.x, point.y])
+ coordinates.push(coordinates[0])
+ return turf.polygon(
+ [coordinates],
+ {},
+ {
+ parentId: polygon.parentId,
+ },
+ )
+ }
+
+ /**
+ * trestle에서 영역을 가져와 mouse:move 이벤트로 해당 영역에 진입했을때 booleanPointInPolygon 로 진입여부를 확인
+ * 확인 후 셀을 이동시킴
+ */
+ const drawCellManualInTrestle = () => {
+ const trestlePolygons = canvas?.getObjects().filter((obj) => obj.name === 'trestle') //가대를 가져옴
+
+ if (trestlePolygons.length !== 0) {
+ let lastPointPosition = { x: 0, y: 0 }
+
+ canvas.off()
+ canvas.on('mouse:move', (e) => {
+ //마우스 이벤트 삭제 후 재추가
+ const mousePoint = canvas.getPointer(e.e)
+ const turfPoint = turf.point([mousePoint.x, mousePoint.y])
+
+ let inside = false
+
+ for (let i = 0; i < trestlePolygons.length; i++) {
+ const turfPolygon = polygonToTurfPolygon(trestlePolygons[i])
+ if (turf.booleanPointInPolygon(turfPoint, turfPolygon)) {
+ //turf에 보면 폴리곤안에 포인트가 있는지 함수가 있다
+ const direction = trestlePolygons[i].direction //도형의 방향
+ let width = direction === 'south' || direction === 'north' ? 172.2 : 113.4
+ let height = direction === 'south' || direction === 'north' ? 113.4 : 172.2
+ if (Math.abs(mousePoint.x - lastPointPosition.x) >= 4 || Math.abs(mousePoint.y - lastPointPosition.y) >= 4) {
+ let isDrawing = false
+ let fabricPolygon
+ if (isDrawing) return
+ canvas?.remove(...canvas?.getObjects().filter((obj) => obj.name === 'tmpCell')) //움직일때 일단 지워가면서 움직임
+
+ const points = [
+ { x: mousePoint.x - width / 2, y: mousePoint.y - height / 2 },
+ { x: mousePoint.x + width / 2, y: mousePoint.y - height / 2 },
+ { x: mousePoint.x + width / 2, y: mousePoint.y + height / 2 },
+ { x: mousePoint.x - width / 2, y: mousePoint.y + height / 2 },
+ ]
+
+ fabricPolygon = new QPolygon(points, {
+ fill: '#BFFD9F',
+ stroke: 'black',
+ selectable: true, // 선택 가능하게 설정
+ lockMovementX: true, // X 축 이동 잠금
+ lockMovementY: true, // Y 축 이동 잠금
+ lockRotation: true, // 회전 잠금
+ lockScalingX: true, // X 축 크기 조정 잠금
+ lockScalingY: true, // Y 축 크기 조정 잠금
+ opacity: 0.8,
+ parentId: trestlePolygons[i].parentId,
+ name: 'tmpCell',
+ })
+
+ canvas?.add(fabricPolygon) //움직여가면서 추가됨
+ lastPointPosition = { x: mousePoint.x, y: mousePoint.y }
+
+ canvas?.on('mouse:up', (e) => {
+ //마우스 클릭시 set으로 해당 위치에 셀을 넣음
+ fabricPolygon.set({ name: 'cell' })
+ fabricPolygon.setCoords()
+ })
+ }
+
+ canvas?.renderAll()
+ inside = true
+ break
+ }
+ }
+
+ if (!inside) {
+ canvas?.remove(...canvas?.getObjects().filter((obj) => obj.name === 'tmpCell'))
+ canvas?.renderAll()
+ }
+ })
+ }
+ }
+
//배터리 셀 넣기
const drawCellInTrestle = () => {
const trestlePolygons = canvas?.getObjects().filter((obj) => obj.name === 'trestle' && obj.selected)
const notSelectedTrestlePolygons = canvas?.getObjects().filter((obj) => obj.name === 'trestle' && !obj.selected)
+
+ const dormerTrestlePolygons = canvas?.getObjects().filter((obj) => obj.name === 'dormerTrestle') //도머 객체
+
if (trestlePolygons.length === 0) {
alert('가대가 없습니다.')
return
@@ -4910,18 +5046,209 @@ export function useMode() {
return acc.length > cur.length ? acc : cur
})
- let drawRoofCells
- if (maxLengthLine.direction === 'right' || maxLengthLine.direction === 'left') {
- drawRoofCells = trestle.fillCell({ width: 113.4, height: 172.2, padding: 0 })
- trestle.direction = 'south'
- } else {
- drawRoofCells = trestle.fillCell({ width: 172.2, height: 113.4, padding: 0 })
- trestle.direction = 'east'
+ const turfTrestlePolygon = polygonToTurfPolygon(trestle) //폴리곤을 turf 객체로 변환
+
+ const containsDormerTrestlePolygons = dormerTrestlePolygons.filter((dormerTrestle) => {
+ // 폴리곤 안에 도머 폴리곤이 포함되어있는지 확인해서 반환하는 로직
+ return (
+ turf.booleanContains(turfTrestlePolygon, polygonToTurfPolygon(dormerTrestle)) ||
+ turf.booleanWithin(polygonToTurfPolygon(dormerTrestle), turfTrestlePolygon)
+ )
+ })
+
+ let difference = turfTrestlePolygon //기본 객체(면형상)
+
+ if (containsDormerTrestlePolygons.length > 0) {
+ //turf로 도머를 제외시키는 로직
+ for (let i = 0; i < containsDormerTrestlePolygons.length; i++) {
+ if (i === 0) {
+ difference = turf.difference(turf.featureCollection([turfTrestlePolygon, polygonToTurfPolygon(containsDormerTrestlePolygons[i])])) //한 면에 도머가 1개일때
+ } else {
+ if (difference) {
+ difference = turf.difference(turf.featureCollection([difference, polygonToTurfPolygon(containsDormerTrestlePolygons[i])])) //한면에 도머가 여러개일때 계속 제외시킴
+ }
+ }
+ }
}
- drawRoofCells.forEach((cell) => {
- drawCellsArray.push(cell)
- })
+ const bbox = turf.bbox(difference)
+ let width = maxLengthLine.direction === 'right' || maxLengthLine.direction === 'left' ? 172.2 : 113.4
+ let height = maxLengthLine.direction === 'right' || maxLengthLine.direction === 'left' ? 113.4 : 172.2
+
+ //배치면때는 방향쪽으로 패널이 넓게 누워져야함
+ if (trestle.direction !== undefined) {
+ width = trestle.direction === 'south' || trestle.direction === 'north' ? 172.2 : 113.4
+ height = trestle.direction === 'south' || trestle.direction === 'north' ? 113.4 : 172.2
+ }
+ const cols = Math.floor((bbox[2] - bbox[0]) / width)
+ const rows = Math.floor((bbox[3] - bbox[1]) / height)
+
+ // console.log('bbox', bbox)
+
+ const boxes = []
+
+ for (let x = bbox[0]; x < bbox[2]; x += width) {
+ for (let y = bbox[1]; y < bbox[3]; y += height) {
+ const box = turf.polygon([
+ [
+ [x, y],
+ [x + width, y],
+ [x + width, y + height],
+ [x, y + height],
+ [x, y],
+ ],
+ ])
+
+ if (turf.booleanWithin(box, turfTrestlePolygon)) {
+ boxes.push(box)
+ }
+ }
+ }
+
+ for (let col = 0; col <= cols; col++) {
+ for (let row = 0; row <= rows; row++) {
+ let x = 0,
+ y = 0,
+ square = [],
+ margin = 0
+
+ if (trestle.direction !== undefined) {
+ //배치면 처림 방향이 정해져있는 경우
+
+ if (trestle.direction === 'south' || trestle.direction === 'north') {
+ //남,북
+ margin = (bbox[2] - bbox[0] - cols * width) / 2 //박스 끝에서 박스 시작값을 빼고 width와 계산된 cols를 곱한값을 뺀뒤 나누기 2 하면 가운데 배치됨
+ if (trestle.direction === 'south') {
+ //남쪽
+ x = col === 0 ? trestle.left + margin : bbox[0] + col * width + margin //상하 위치 기준이면 좌우 가운데 정렬한다
+ y = bbox[3] - row * height
+ } else {
+ //북쪽
+ x = col === 0 ? trestle.left + margin : bbox[0] + col * width + margin
+ y = bbox[1] + row * height
+ }
+ } else if (trestle.direction === 'east' || trestle.direction === 'west') {
+ //동쪽
+ margin = (bbox[3] - bbox[1] - rows * height) / 2
+ if (trestle.direction === 'east') {
+ x = bbox[2] - col * width
+ y = rows === 0 ? trestle.top + margin : bbox[1] + row * height + margin //좌우 위치 기준이면 상하 가운데 정렬한다
+ } else {
+ x = bbox[0] + col * width
+ y = rows === 0 ? trestle.top + margin : bbox[1] + row * height + margin
+ }
+ }
+ } else {
+ //방향이 없는 경우 ex) 템플릿
+ x = bbox[0] + col * width
+ y = bbox[1] + row * height
+ }
+
+ square = [
+ [x, y],
+ [x + width, y],
+ [x + width, y + height],
+ [x, y + height],
+ [x, y],
+ ]
+
+ const squarePolygon = turf.polygon([square])
+
+ // console.log('turfTrestlePolygon', turfTrestlePolygon)
+ // console.log('squarePolygon', squarePolygon)
+
+ const areaSize = turf.area(turfTrestlePolygon)
+
+ // console.log('areaSize', areaSize)
+ const objSize = turf.area(squarePolygon)
+
+ // console.log('objSize', objSize)
+
+ const maxObject = Math.floor(areaSize / objSize)
+
+ // console.log('maxObjectSize', maxObject)
+
+ const disjointFromTrestle = turf.booleanContains(turfTrestlePolygon, squarePolygon) || turf.booleanWithin(squarePolygon, turfTrestlePolygon)
+
+ if (disjointFromTrestle) {
+ let turfCoordnates = squarePolygon.geometry.coordinates[0].slice(0, -1)
+ const points = turfCoordnates.map((coord) => ({ x: coord[0], y: coord[1] }))
+
+ if (containsDormerTrestlePolygons.length > 0) {
+ //도머가 있으면 적용되는 로직
+ const isDisjoint = containsDormerTrestlePolygons.some((dormerTrestle) => {
+ return turf.booleanDisjoint(squarePolygon, polygonToTurfPolygon(dormerTrestle)) //도머가 여러개일수있으므로 겹치는게 있다면...
+ })
+ if (isDisjoint) {
+ const fabricPolygon = new QPolygon(points, {
+ fill: '#BFFD9F',
+ stroke: 'black',
+ selectable: true, // 선택 가능하게 설정
+ lockMovementX: false, // X 축 이동 잠금
+ lockMovementY: false, // Y 축 이동 잠금
+ lockRotation: false, // 회전 잠금
+ lockScalingX: false, // X 축 크기 조정 잠금
+ lockScalingY: false, // Y 축 크기 조정 잠금
+ opacity: 0.8,
+ parentId: trestle.parentId,
+ })
+ canvas?.add(fabricPolygon)
+ }
+ } else {
+ //도머가 없을땐 그냥 그림
+ const fabricPolygon = new QPolygon(points, {
+ fill: '#BFFD9F',
+ stroke: 'black',
+ selectable: true, // 선택 가능하게 설정
+ lockMovementX: true, // X 축 이동 잠금
+ lockMovementY: true, // Y 축 이동 잠금
+ lockRotation: true, // 회전 잠금
+ lockScalingX: true, // X 축 크기 조정 잠금
+ lockScalingY: true, // Y 축 크기 조정 잠금
+ opacity: 0.8,
+ parentId: trestle.parentId,
+ })
+ canvas?.add(fabricPolygon)
+ }
+ }
+ }
+ }
+
+ // let drawRoofCells
+ // if (maxLengthLine.direction === 'right' || maxLengthLine.direction === 'left') {
+ // drawRoofCells = trestle.fillCell({ width: 113.4, height: 172.2, padding: 0 })
+ // trestle.direction = 'south'
+ // } else {
+ // drawRoofCells = trestle.fillCell({ width: 172.2, height: 113.4, padding: 0 })
+ // trestle.direction = 'east'
+ // }
+
+ // drawRoofCells.forEach((cell) => {
+ // drawCellsArray.push(cell)
+ // })
+
+ /**
+ * 추후에 가대까지 완료하면 그룹시켜버림
+ */
+ // const groupCellObj = canvas
+ // ?.getObjects()
+ // .filter(
+ // (obj) =>
+ // obj?.parentId === trestle.parentId ||
+ // obj?.id === trestle.parentId ||
+ // (obj?.name === 'arrow' && obj?.parent.id === trestle.parentId) ||
+ // (obj?.name === 'directionText' && obj?.parent.parent.id === trestle.parentId),
+ // )
+
+ // console.log('groupCellObj', groupCellObj)
+
+ // canvas?.add(
+ // new fabric.Group(groupCellObj, {
+ // name: 'cellGroup',
+ // originX: 'center',
+ // originY: 'center',
+ // }),
+ // )
})
setDrewRoofCells(drawCellsArray)
@@ -5162,6 +5489,7 @@ export function useMode() {
createRoofRack,
drawRoofPolygon,
drawCellInTrestle,
+ drawCellManualInTrestle,
setDirectionTrestles,
cutHelpLines,
}