From b3af3cd0e003dc267baa7a0f445195fa5f23e334 Mon Sep 17 00:00:00 2001 From: Jaeyoung Lee Date: Tue, 25 Feb 2025 13:46:10 +0900 Subject: [PATCH 001/109] =?UTF-8?q?=EC=99=B8=EB=B2=BD=EC=84=A0=20=ED=8E=B8?= =?UTF-8?q?=EC=A7=91,=20=EC=98=A4=ED=94=84=EC=85=8B=20=EA=B8=B0=EB=8A=A5?= =?UTF-8?q?=20=EC=A0=81=EC=9A=A9=20=EB=B0=8F=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../modal/wallLineOffset/type/Offset.jsx | 2 +- .../modal/wallLineOffset/type/WallLine.jsx | 4 +- src/hooks/roofcover/useOuterLineWall.js | 10 - src/hooks/roofcover/useRoofShapeSetting.js | 16 +- .../roofcover/useWallLineOffsetSetting.js | 346 +++++++++--------- src/hooks/useMode.js | 74 +++- src/hooks/usePolygon.js | 4 +- src/util/qpolygon-utils.js | 17 +- 8 files changed, 251 insertions(+), 222 deletions(-) diff --git a/src/components/floor-plan/modal/wallLineOffset/type/Offset.jsx b/src/components/floor-plan/modal/wallLineOffset/type/Offset.jsx index 418a9419..4e7545cb 100644 --- a/src/components/floor-plan/modal/wallLineOffset/type/Offset.jsx +++ b/src/components/floor-plan/modal/wallLineOffset/type/Offset.jsx @@ -74,7 +74,7 @@ export default function Offset({ length1Ref, arrow1Ref, currentWallLineRef }) {
- +
mm
diff --git a/src/components/floor-plan/modal/wallLineOffset/type/WallLine.jsx b/src/components/floor-plan/modal/wallLineOffset/type/WallLine.jsx index 581f57f3..92f6a10b 100644 --- a/src/components/floor-plan/modal/wallLineOffset/type/WallLine.jsx +++ b/src/components/floor-plan/modal/wallLineOffset/type/WallLine.jsx @@ -46,7 +46,7 @@ export default forwardRef(function WallLine({ length1Ref, length2Ref, arrow1Ref,
- +
mm
@@ -80,7 +80,7 @@ export default forwardRef(function WallLine({ length1Ref, length2Ref, arrow1Ref,
- +
mm
diff --git a/src/hooks/roofcover/useOuterLineWall.js b/src/hooks/roofcover/useOuterLineWall.js index 7e94f074..a1292ccf 100644 --- a/src/hooks/roofcover/useOuterLineWall.js +++ b/src/hooks/roofcover/useOuterLineWall.js @@ -143,7 +143,6 @@ export function useOuterLineWall(id, propertiesId) { const mouseDown = (e) => { let pointer = getIntersectMousePoint(e) pointer = { x: Big(pointer.x).round(1).toNumber(), y: Big(pointer.y).round(1).toNumber() } - console.log('mouseDown', pointer, points) if (points.length === 0) { setPoints((prev) => [...prev, pointer]) @@ -151,14 +150,11 @@ export function useOuterLineWall(id, propertiesId) { const lastPoint = points[points.length - 1] let newPoint = { x: pointer.x, y: pointer.y } const length = distanceBetweenPoints(lastPoint, newPoint) - console.log('length', length) if (verticalHorizontalMode) { const vector = { x: Big(pointer.x).minus(Big(points[points.length - 1].x)), y: Big(pointer.y).minus(Big(points[points.length - 1].y)), } - // const slope = Math.abs(vector.y / vector.x) // 기울기 계산 - console.log('vector', vector.x.toNumber(), vector.y.toNumber(), Math.abs(vector.y.toNumber() / vector.x.toNumber()) >= 1) const slope = vector.x.eq(0) ? Big(1) : vector.y.div(vector.x).abs() // 기울기 계산 let scaledVector @@ -167,13 +163,11 @@ export function useOuterLineWall(id, propertiesId) { // 기울기가 1 이상이면 x축 방향으로 그림 scaledVector = { x: 0, - // y: vector.y >= 0 ? Number(length) : -Number(length), y: vector.y.gte(0) ? Big(length).toNumber() : Big(length).neg().toNumber(), } } else { // 기울기가 1 미만이면 y축 방향으로 그림 scaledVector = { - // x: vector.x >= 0 ? Number(length) : -Number(length), x: vector.x.gte(0) ? Big(length).toNumber() : Big(length).neg().toNumber(), y: 0, } @@ -182,8 +176,6 @@ export function useOuterLineWall(id, propertiesId) { const verticalLength = scaledVector.y const horizontalLength = scaledVector.x - console.log('verticalLength', verticalLength, 'horizontalLength', horizontalLength) - newPoint = { x: Big(lastPoint.x).plus(horizontalLength).toNumber(), y: Big(lastPoint.y).plus(verticalLength).toNumber(), @@ -876,8 +868,6 @@ export function useOuterLineWall(id, propertiesId) { const firstPoint = points[0] - console.log('points 좌표 : ', points) - points.forEach((point, idx) => { if (idx === 0 || !isAllRightAngle) { return diff --git a/src/hooks/roofcover/useRoofShapeSetting.js b/src/hooks/roofcover/useRoofShapeSetting.js index e9b7bafe..ea21aecb 100644 --- a/src/hooks/roofcover/useRoofShapeSetting.js +++ b/src/hooks/roofcover/useRoofShapeSetting.js @@ -63,13 +63,6 @@ export function useRoofShapeSetting(id) { }, [jerkinHeadPitch]) useEffect(() => { - const outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine') - // if (!outerLineFix || outerLines.length === 0) { - // swalFire({ text: '외벽선이 없습니다.' }) - // // setShowRoofShapeSettingModal(false) - // closePopup(id) - // } - return () => { if (!isFixRef.current) { return @@ -77,6 +70,7 @@ export function useRoofShapeSetting(id) { const outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine') const pitchTexts = canvas.getObjects().filter((obj) => obj.name === 'pitchText') + canvas.remove(...pitchTexts) outerLines.forEach((line) => { let stroke, strokeWidth @@ -115,14 +109,6 @@ export function useRoofShapeSetting(id) { return } - /*const outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine') - outerLines.forEach((line) => { - line.set({ - stroke: '#000000', - strokeWidth: 4, - }) - })*/ - currentObject.set({ stroke: '#EA10AC', strokeWidth: 4, diff --git a/src/hooks/roofcover/useWallLineOffsetSetting.js b/src/hooks/roofcover/useWallLineOffsetSetting.js index 22af0f49..548b509c 100644 --- a/src/hooks/roofcover/useWallLineOffsetSetting.js +++ b/src/hooks/roofcover/useWallLineOffsetSetting.js @@ -6,6 +6,8 @@ import { useEvent } from '@/hooks/useEvent' import { useLine } from '@/hooks/useLine' import { useSwal } from '@/hooks/useSwal' import { usePopup } from '@/hooks/usePopup' +import Big from 'big.js' +import { outerLineFixState } from '@/store/outerLineAtom' // 외벽선 편집 및 오프셋 export function useWallLineOffsetSetting(id) { @@ -28,6 +30,8 @@ export function useWallLineOffsetSetting(id) { const [isLoading, setIsLoading] = useState(false) + const outerLineFix = useRecoilValue(outerLineFixState) + const drawLine = (point1, point2, idx, direction = currentWallLineRef.current.direction) => { const line = addLine([point1.x, point1.y, point2.x, point2.y], { stroke: 'black', @@ -59,7 +63,7 @@ export function useWallLineOffsetSetting(id) { useEffect(() => { const outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine') - if (outerLines.length === 0) { + if (!outerLineFix || outerLines.length === 0) { swalFire({ text: '외벽선이 없습니다.' }) closePopup(id) return @@ -277,7 +281,7 @@ export function useWallLineOffsetSetting(id) { } } - rearrangeOuterLine(currentIdx + 1) + reArrangeOuterLine(currentIdx + 1) drawLine(point1, point2, currentIdx) drawLine(point2, point3, currentIdx + 1) @@ -286,229 +290,217 @@ export function useWallLineOffsetSetting(id) { canvas.remove(currentWallLineRef.current) currentWallLineRef.current = null canvas.renderAll() + + canvas + .getObjects() + .filter((obj) => obj.name === 'outerLine') + .forEach((obj) => obj.fire('modified')) } - const rearrangeOuterLine = (idxParam) => { + /** + * outreLine의 index를 조절한다. + * @param idxParam + * @param isNegative + */ + const reArrangeOuterLine = (idxParam, isNegative = false) => { const outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine') outerLines.forEach((outerLine) => { if (outerLine.idx >= idxParam) { - outerLine.idx = outerLine.idx + 1 + outerLine.idx = isNegative ? outerLine.idx - 1 : outerLine.idx + 1 } }) } + /** + * offset 저장 + */ const handleOffsetSave = () => { - const direction = currentWallLineRef.current.direction - let canDirections = direction === 'left' || direction === 'right' ? ['up', 'down'] : ['left', 'right'] - const currentIdx = currentWallLineRef.current.idx + if (!currentObject) return + const currentLine = currentObject + const currentVector = currentLine.y1 === currentLine.y2 ? 'horizontal' : 'vertical' + const canDirections = currentVector === 'horizontal' ? ['up', 'down'] : ['left', 'right'] if (!canDirections.includes(arrow1Ref.current)) { alert('방향을 다시 선택하세요') return } - const outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine') + outerLines.sort((a, b) => a.idx - b.idx) - const idx = currentWallLineRef.current.idx - const prevIdx = idx - 1 <= 0 ? outerLines.length : idx - 1 - const nextIdx = idx + 1 > outerLines.length ? 1 : idx + 1 + const currentIdx = currentLine.idx + const prevIdx = currentIdx - 1 <= 0 ? outerLines.length : currentIdx - 1 + const nextIdx = currentLine.idx + 1 > outerLines.length ? 1 : currentIdx + 1 - const currentLine = currentWallLineRef.current const prevLine = outerLines.find((line) => line.idx === prevIdx) const nextLine = outerLines.find((line) => line.idx === nextIdx) + const prevVector = prevLine.y1 === prevLine.y2 ? 'horizontal' : 'vertical' + const nextVector = nextLine.y1 === nextLine.y2 ? 'horizontal' : 'vertical' - const length = length1Ref.current.value / 10 - const currentLineX = Math.floor(Math.max(currentLine.x1, currentLine.x2)) - const currentLineY = Math.floor(Math.max(currentLine.y1, currentLine.y2)) - switch (arrow1Ref.current) { - case 'up': { - if (prevLine.direction === currentLine.direction) { - const newX = - currentLine.direction === 'left' - ? Math.floor(Math.max(currentLine.x1, currentLine.x2)) - : Math.floor(Math.min(currentLine.x1, currentLine.x2)) + const offsetLength = Big(Number(length1Ref.current.value)).div(10) + if (offsetLength.eq(0)) return - const newPoint1 = { x: newX, y: currentLineY - length } - const newPoint2 = { x: prevLine.x2, y: prevLine.y2 } - rearrangeOuterLine(currentIdx) - drawLine(newPoint1, newPoint2, currentIdx, 'top') + const currentLineMinX = Big(Math.max(currentLine.x1, currentLine.x2)) + const currentLineMaxX = Big(Math.max(currentLine.x1, currentLine.x2)) + const currentLineMinY = Big(Math.max(currentLine.y1, currentLine.y2)) + const currentLineMaxY = Big(Math.max(currentLine.y1, currentLine.y2)) - if (Math.abs(currentLineY - nextLine.y1) < 2) { - nextLine.set({ y1: currentLineY - length }) - } else { - nextLine.set({ y2: currentLineY - length }) - } - } else if (nextLine.direction === currentLine.direction) { - const newX = - currentLine.direction === 'left' - ? Math.floor(Math.min(currentLine.x1, currentLine.x2)) - : Math.floor(Math.max(currentLine.x1, currentLine.x2)) - - const newPoint1 = { x: newX, y: currentLineY - length } - const newPoint2 = { x: nextLine.x1, y: nextLine.y1 } - rearrangeOuterLine(currentIdx + 1) - drawLine(newPoint1, newPoint2, currentIdx + 1, 'top') - - if (Math.abs(currentLineY - prevLine.y1) < 2) { - prevLine.set({ y1: currentLineY - length }) - } else { - prevLine.set({ y2: currentLineY - length }) - } + if (currentVector === 'horizontal') { + const prevX = currentLine.x1 < currentLine.x2 ? Math.min(currentLine.x1, currentLine.x2) : Math.max(currentLine.x1, currentLine.x2) + const nextX = currentLine.x1 < currentLine.x2 ? Math.max(currentLine.x1, currentLine.x2) : Math.min(currentLine.x1, currentLine.x2) + if (arrow1Ref.current === 'up') { + currentLine.set({ y1: currentLineMaxY.minus(offsetLength).toNumber(), y2: currentLineMaxY.minus(offsetLength).toNumber() }) + if (prevVector === currentVector) { + const point1 = { x: prevX, y: prevLine.y2 } + const point2 = { x: prevX, y: currentLine.y1 } + reArrangeOuterLine(currentIdx) + drawLine(point1, point2, currentIdx, arrow1Ref.current) } else { - if (Math.abs(currentLineY - prevLine.y1) < 2) { - prevLine.set({ y1: prevLine.y1 - length }) + if (Big(prevLine.y1).minus(currentLineMinY).abs().lte(Big(prevLine.y2).minus(currentLineMinY).abs())) { + prevLine.set({ y1: currentLine.y1 }) } else { - prevLine.set({ y2: prevLine.y2 - length }) + prevLine.set({ y2: currentLine.y1 }) } - if (Math.abs(currentLineY - nextLine.y1) < 2) { - nextLine.set({ y1: nextLine.y1 - length }) - } else { - nextLine.set({ y2: nextLine.y2 - length }) + if (Big(prevLine.y1).minus(Big(prevLine.y2)).eq(0)) { + reArrangeOuterLine(currentIdx - 1, true) + canvas.remove(prevLine) + } + } + if (nextVector === currentVector) { + const point1 = { x: nextX, y: nextLine.y2 } + const point2 = { x: nextX, y: currentLine.y1 } + reArrangeOuterLine(currentIdx + 1) + drawLine(point1, point2, currentIdx + 1, arrow1Ref.current) + } else { + if (Big(nextLine.y1).minus(currentLineMaxY).abs().lte(Big(nextLine.y2).minus(currentLineMaxY).abs())) { + nextLine.set({ y1: currentLine.y1 }) + } else { + nextLine.set({ y2: currentLine.y1 }) + } + if (Big(nextLine.y1).minus(Big(nextLine.y2)).eq(0)) { + reArrangeOuterLine(currentIdx + 1, true) + canvas.remove(nextLine) } } - - currentLine.set({ y1: currentLine.y1 - length, y2: currentLine.y2 - length }) - - break } - case 'down': { - if (prevLine.direction === currentLine.direction) { - const newX = - currentLine.direction === 'left' - ? Math.floor(Math.max(currentLine.x1, currentLine.x2)) - : Math.floor(Math.min(currentLine.x1, currentLine.x2)) - const newPoint1 = { x: newX, y: currentLineY + length } - const newPoint2 = { x: prevLine.x2, y: prevLine.y2 } - rearrangeOuterLine(currentIdx) - drawLine(newPoint1, newPoint2, currentIdx, 'bottom') - if (Math.abs(currentLineY - nextLine.y1) < 2) { - nextLine.set({ y1: currentLineY + length }) - } else { - nextLine.set({ y2: currentLineY + length }) - } - } else if (nextLine.direction === currentLine.direction) { - const newX = - currentLine.direction === 'left' - ? Math.floor(Math.min(currentLine.x1, currentLine.x2)) - : Math.floor(Math.max(currentLine.x1, currentLine.x2)) - const newPoint1 = { x: newX, y: currentLineY + length } - const newPoint2 = { x: nextLine.x1, y: nextLine.y1 } - rearrangeOuterLine(currentIdx + 1) - drawLine(newPoint1, newPoint2, currentIdx + 1, 'bottom') - if (Math.abs(currentLineY - prevLine.y1) < 2) { - prevLine.set({ y1: currentLineY + length }) - } else { - prevLine.set({ y2: currentLineY + length }) - } + if (arrow1Ref.current === 'down') { + currentLine.set({ y1: currentLineMaxY.plus(offsetLength).toNumber(), y2: currentLineMaxY.plus(offsetLength).toNumber() }) + if (prevVector === currentVector) { + const point1 = { x: prevX, y: prevLine.y2 } + const point2 = { x: prevX, y: currentLine.y1 } + reArrangeOuterLine(currentIdx) + drawLine(point1, point2, currentIdx, arrow1Ref.current) } else { - if (Math.abs(currentLineY - prevLine.y1) < 2) { - prevLine.set({ y1: prevLine.y1 + length }) + if (Big(prevLine.y1).minus(currentLineMinY).abs().lte(Big(prevLine.y2).minus(currentLineMinY).abs())) { + prevLine.set({ y1: currentLine.y1 }) } else { - prevLine.set({ y2: prevLine.y2 + length }) + prevLine.set({ y2: currentLine.y1 }) } - if (Math.abs(currentLineY - nextLine.y1) < 2) { - nextLine.set({ y1: nextLine.y1 + length }) - } else { - nextLine.set({ y2: nextLine.y2 + length }) + if (Big(prevLine.y1).minus(Big(prevLine.y2)).eq(0)) { + reArrangeOuterLine(currentIdx - 1, true) + canvas.remove(prevLine) + } + } + if (nextVector === currentVector) { + const point1 = { x: nextX, y: nextLine.y2 } + const point2 = { x: nextX, y: currentLine.y1 } + reArrangeOuterLine(currentIdx + 1) + drawLine(point1, point2, currentIdx + 1, arrow1Ref.current) + } else { + if (Big(nextLine.y1).minus(currentLineMaxY).abs().lte(Big(nextLine.y2).minus(currentLineMaxY).abs())) { + nextLine.set({ y1: currentLine.y1 }) + } else { + nextLine.set({ y2: currentLine.y1 }) + } + if (Big(nextLine.y1).minus(Big(nextLine.y2)).eq(0)) { + reArrangeOuterLine(currentIdx + 1, true) + canvas.remove(nextLine) } } - - currentLine.set({ y1: currentLine.y1 + length, y2: currentLine.y2 + length }) - break } - case 'left': { - if (prevLine.direction === currentLine.direction) { - const newY = - currentLine.direction === 'top' - ? Math.floor(Math.max(currentLine.y1, currentLine.y2)) - : Math.floor(Math.min(currentLine.y1, currentLine.y2)) - const newPoint1 = { x: currentLineX - length, y: newY } - const newPoint2 = { x: prevLine.x2, y: prevLine.y2 } - rearrangeOuterLine(currentIdx) - drawLine(newPoint1, newPoint2, currentIdx, 'left') - if (Math.abs(currentLineX - nextLine.x1) < 2) { - nextLine.set({ x1: currentLineX - length }) - } else { - nextLine.set({ x2: currentLineX - length }) - } - } else if (nextLine.direction === currentLine.direction) { - const newY = - currentLine.direction === 'top' - ? Math.floor(Math.min(currentLine.y1, currentLine.y2)) - : Math.floor(Math.max(currentLine.y1, currentLine.y2)) - const newPoint1 = { x: currentLineX - length, y: newY } - const newPoint2 = { x: nextLine.x1, y: nextLine.y1 } - rearrangeOuterLine(currentIdx + 1) - drawLine(newPoint1, newPoint2, currentIdx + 1, 'left') - if (Math.abs(currentLineX - prevLine.x1) < 2) { - prevLine.set({ x1: currentLineX - length }) - } else { - prevLine.set({ x2: currentLineX - length }) - } - } else { - if (Math.abs(currentLineX - prevLine.x1) < 2) { - prevLine.set({ x1: prevLine.x1 - length }) - } else { - prevLine.set({ x2: prevLine.x2 - length }) - } + } else { + const prevY = currentLine.y1 < currentLine.y2 ? Math.min(currentLine.y1, currentLine.y2) : Math.max(currentLine.y1, currentLine.y2) + const nextY = currentLine.y1 < currentLine.y2 ? Math.max(currentLine.y1, currentLine.y2) : Math.min(currentLine.y1, currentLine.y2) + if (arrow1Ref.current === 'left') { + currentLine.set({ x1: currentLineMaxX.minus(offsetLength).toNumber(), x2: currentLineMaxX.minus(offsetLength).toNumber() }) - if (Math.abs(currentLineX - nextLine.x1) < 2) { - nextLine.set({ x1: nextLine.x1 - length }) + if (prevVector === currentVector) { + const point1 = { x: prevLine.x2, y: prevY } + const point2 = { x: currentLine.x1, y: prevY } + reArrangeOuterLine(currentIdx) + drawLine(point1, point2, currentIdx, arrow1Ref.current) + } else { + if (Big(prevLine.x1).minus(currentLineMinX).abs().lte(Big(prevLine.x2).minus(currentLineMinX).abs())) { + prevLine.set({ x1: currentLine.x1 }) } else { - nextLine.set({ x2: nextLine.x2 - length }) + prevLine.set({ x2: currentLine.x1 }) + } + if (Big(prevLine.x1).minus(Big(prevLine.x2)).eq(0)) { + reArrangeOuterLine(currentIdx - 1, true) + canvas.remove(prevLine) + } + } + if (nextVector === currentVector) { + const point1 = { x: currentLine.x2, y: nextY } + const point2 = { x: nextLine.x1, y: nextY } + reArrangeOuterLine(currentIdx + 1) + drawLine(point1, point2, currentIdx + 1, arrow1Ref.current) + } else { + if (Big(nextLine.x1).minus(currentLineMaxX).abs().lte(Big(nextLine.x2).minus(currentLineMaxX).abs())) { + nextLine.set({ x1: currentLine.x2 }) + } else { + nextLine.set({ x2: currentLine.x2 }) + } + if (Big(nextLine.x1).minus(Big(nextLine.x2)).eq(0)) { + reArrangeOuterLine(currentIdx + 1, true) + canvas.remove(nextLine) } } - - currentLine.set({ x1: currentLine.x1 - length, x2: currentLine.x2 - length }) - break } - case 'right': { - if (prevLine.direction === currentLine.direction) { - const newY = - currentLine.direction === 'top' - ? Math.floor(Math.max(currentLine.y1, currentLine.y2)) - : Math.floor(Math.min(currentLine.y1, currentLine.y2)) - const newPoint1 = { x: currentLineX + length, y: newY } - const newPoint2 = { x: prevLine.x2, y: prevLine.y2 } - rearrangeOuterLine(currentIdx) - drawLine(newPoint1, newPoint2, currentIdx, 'right') - if (Math.abs(currentLineX - nextLine.x1) < 2) { - nextLine.set({ x1: currentLineX + length }) - } else { - nextLine.set({ x2: currentLineX + length }) - } - } else if (nextLine.direction === currentLine.direction) { - const newY = - currentLine.direction === 'top' - ? Math.floor(Math.min(currentLine.y1, currentLine.y2)) - : Math.floor(Math.max(currentLine.y1, currentLine.y2)) - const newPoint1 = { x: currentLineX + length, y: newY } - const newPoint2 = { x: nextLine.x1, y: nextLine.y1 } - rearrangeOuterLine(currentIdx + 1) - drawLine(newPoint1, newPoint2, currentIdx + 1, 'right') + if (arrow1Ref.current === 'right') { + currentLine.set({ x1: currentLineMaxX.plus(offsetLength).toNumber(), x2: currentLineMaxX.plus(offsetLength).toNumber() }) - if (Math.abs(currentLineX - prevLine.x1) < 2) { - prevLine.set({ x1: currentLineX + length }) - } else { - prevLine.set({ x2: currentLineX + length }) - } + if (prevVector === currentVector) { + const point1 = { x: prevLine.x2, y: prevY } + const point2 = { x: currentLine.x1, y: prevY } + reArrangeOuterLine(currentIdx) + drawLine(point1, point2, currentIdx, arrow1Ref.current) } else { - if (Math.abs(currentLineX - prevLine.x1) < 2) { - prevLine.set({ x1: prevLine.x1 + length }) + if (Big(prevLine.x1).minus(currentLineMinX).abs().lte(Big(prevLine.x2).minus(currentLineMinX).abs())) { + prevLine.set({ x1: currentLine.x1 }) } else { - prevLine.set({ x2: prevLine.x2 + length }) + prevLine.set({ x2: currentLine.x1 }) } - if (Math.abs(currentLineX - nextLine.x1) < 2) { - nextLine.set({ x1: nextLine.x1 + length }) - } else { - nextLine.set({ x2: nextLine.x2 + length }) + + if (Big(prevLine.x1).minus(Big(prevLine.x2)).eq(0)) { + reArrangeOuterLine(currentIdx - 1, true) + canvas.remove(prevLine) } } + if (nextVector === currentVector) { + const point1 = { x: currentLine.x2, y: nextY } + const point2 = { x: nextLine.x1, y: nextY } + reArrangeOuterLine(currentIdx + 1) + drawLine(point1, point2, currentIdx + 1, arrow1Ref.current) + } else { + if (Big(nextLine.x1).minus(currentLineMaxX).abs().lte(Big(nextLine.x2).minus(currentLineMaxX).abs())) { + nextLine.set({ x1: currentLine.x2 }) + } else { + nextLine.set({ x2: currentLine.x2 }) + } - currentLine.set({ x1: currentLine.x1 + length, x2: currentLine.x2 + length }) - - break + if (Big(nextLine.x1).minus(Big(nextLine.x2)).eq(0)) { + reArrangeOuterLine(currentIdx + 1, true) + canvas.remove(nextLine) + } + } } } + + const newOuterLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine') + newOuterLines.sort((a, b) => a.idx - b.idx) + newOuterLines.forEach((line, idx) => { + line.fire('modified') + }) + canvas.renderAll() } diff --git a/src/hooks/useMode.js b/src/hooks/useMode.js index 06f1428a..19d43c5e 100644 --- a/src/hooks/useMode.js +++ b/src/hooks/useMode.js @@ -33,7 +33,7 @@ import { import { QLine } from '@/components/fabric/QLine' import { fabric } from 'fabric' import { QPolygon } from '@/components/fabric/QPolygon' -import offsetPolygon from '@/util/qpolygon-utils' +import offsetPolygon, { calculateAngle } from '@/util/qpolygon-utils' import { isObjectNotEmpty } from '@/util/common-utils' import * as turf from '@turf/turf' import { INPUT_TYPE, LINE_TYPE, Mode, POLYGON_TYPE } from '@/common/common' @@ -1771,6 +1771,16 @@ export function useMode() { wall.lines = afterLine.concat(beforeLine) //외벽선을 기준으로 polygon을 생성한다. 지붕선의 기준이 됨. + const divWallLines = [] + wall.lines.forEach((currentWall, index) => { + const nextWall = wall.lines[(index + 1) % wall.lines.length] + const currentAngle = calculateAngle(currentWall.startPoint, currentWall.endPoint) + const nextAngle = calculateAngle(nextWall.startPoint, nextWall.endPoint) + if (currentAngle === nextAngle) { + divWallLines.push({ currentWall: currentWall, nextWall: nextWall, index: index }) + } + }) + const polygon = createRoofPolygon(wall.points) const originPolygon = new QPolygon(wall.points, { fontSize: 0 }) originPolygon.setViewLengthText(false) @@ -1787,6 +1797,45 @@ export function useMode() { offsetPolygon = createPaddingPolygon(polygon, wall.lines).vertices } + if (divWallLines.length > 0) { + /** + * 외벽선을 분기한 횟수를 저장한다. 외벽선은 offset이 같지 않을때 분기한다. + */ + let addPoint = 0 + + divWallLines.forEach((line) => { + const currentWall = line.currentWall + const nextWall = line.nextWall + const index = line.index + addPoint + const xDiff = Big(currentWall.x1).minus(Big(nextWall.x1)) + const yDiff = Big(currentWall.y1).minus(Big(nextWall.y1)) + const offsetCurrentPoint = offsetPolygon[index] + let offsetNextPoint = offsetPolygon[(index + 1) % offsetPolygon.length] + line.index = index + + if (currentWall.attributes.offset !== nextWall.attributes.offset) { + const offsetPoint1 = { + x: xDiff.eq(0) ? offsetCurrentPoint.x : nextWall.x1, + y: yDiff.eq(0) ? offsetCurrentPoint.y : nextWall.y1, + } + const diffOffset = Big(nextWall.attributes.offset).minus(Big(currentWall.attributes.offset)) + const offsetPoint2 = { + x: yDiff.eq(0) ? offsetPoint1.x : Big(offsetPoint1.x).plus(diffOffset).toNumber(), + y: xDiff.eq(0) ? offsetPoint1.y : Big(offsetPoint1.y).plus(diffOffset).toNumber(), + } + const offsetPoint3 = { + x: yDiff.eq(0) ? offsetNextPoint.x : Big(offsetNextPoint.x).plus(diffOffset).toNumber(), + y: xDiff.eq(0) ? offsetNextPoint.y : Big(offsetNextPoint.y).plus(diffOffset).toNumber(), + } + offsetPolygon.splice(index + 1, 0, offsetPoint1, offsetPoint2) + offsetNextPoint = offsetPoint3 + addPoint++ + } else { + addPoint-- + } + }) + } + const roof = makePolygon( offsetPolygon.map((point) => { return { x1: point.x, y1: point.y } @@ -1806,6 +1855,8 @@ export function useMode() { roof.name = POLYGON_TYPE.ROOF roof.setWall(wall) + let roofWallIndex = 0 + roof.lines.forEach((line, index) => { const x1 = Big(line.x1) const x2 = Big(line.x2) @@ -1816,18 +1867,21 @@ export function useMode() { roofId: roof.id, planeSize: lineLength, actualSize: lineLength, - wallLine: wall.lines[index].id, - type: wall.lines[index].attributes.type, - offset: wall.lines[index].attributes.offset, - width: wall.lines[index].attributes.width, - pitch: wall.lines[index].attributes.pitch, - sleeve: wall.lines[index].attributes.sleeve || false, + wallLine: wall.lines[roofWallIndex].id, + type: wall.lines[roofWallIndex].attributes.type, + offset: wall.lines[roofWallIndex].attributes.offset, + width: wall.lines[roofWallIndex].attributes.width, + pitch: wall.lines[roofWallIndex].attributes.pitch, + sleeve: wall.lines[roofWallIndex].attributes.sleeve || false, + } + + const isDivLine = divWallLines.some((divLine) => divLine.index === index) + if (!isDivLine) { + roofWallIndex++ } }) wall.set({ - // originX: 'center', - // originY: 'center', attributes: { roofId: roof.id, }, @@ -1846,7 +1900,7 @@ export function useMode() { const y2 = Big(line.y2) const lineLength = x1.minus(x2).abs().pow(2).plus(y1.minus(y2).abs().pow(2)).sqrt().times(10).round().toNumber() line.attributes.roofId = roof.id - line.attributes.currentRoofId = roof.lines[index].id + // line.attributes.currentRoofId = roof.lines[index].id line.attributes.planeSize = lineLength line.attributes.actualSize = lineLength diff --git a/src/hooks/usePolygon.js b/src/hooks/usePolygon.js index 99845d46..50470060 100644 --- a/src/hooks/usePolygon.js +++ b/src/hooks/usePolygon.js @@ -1,7 +1,7 @@ -import { ANGLE_TYPE, canvasState, currentAngleTypeSelector, fontFamilyState, fontSizeState, pitchTextSelector } from '@/store/canvasAtom' +import { ANGLE_TYPE, canvasState, currentAngleTypeSelector, pitchTextSelector } from '@/store/canvasAtom' import { useRecoilValue } from 'recoil' import { fabric } from 'fabric' -import { findAndRemoveClosestPoint, getDegreeByChon, getDegreeInOrientation, getDirectionByPoint, isPointOnLine } from '@/util/canvas-util' +import { findAndRemoveClosestPoint, getDegreeByChon, getDegreeInOrientation, isPointOnLine } from '@/util/canvas-util' import { QPolygon } from '@/components/fabric/QPolygon' import { isSamePoint, removeDuplicatePolygons } from '@/util/qpolygon-utils' import { flowDisplaySelector } from '@/store/settingAtom' diff --git a/src/util/qpolygon-utils.js b/src/util/qpolygon-utils.js index 0444c93d..74e4654f 100644 --- a/src/util/qpolygon-utils.js +++ b/src/util/qpolygon-utils.js @@ -23,11 +23,11 @@ export const defineQPloygon = () => { * @returns {number} */ export const calculateAngle = (point1, point2) => { - const deltaX = Big(point2?.x !== undefined ? point2.x : 0) - .minus(point1?.x !== undefined ? point1.x : 0) + const deltaX = Big(point2.x ?? 0) + .minus(point1.x ?? 0) .toNumber() - const deltaY = Big(point2?.y !== undefined ? point2.y : 0) - .minus(point1?.y !== undefined ? point1.y : 0) + const deltaY = Big(point2.y ?? 0) + .minus(point1.y ?? 0) .toNumber() const angleInRadians = Math.atan2(deltaY, deltaX) return angleInRadians * (180 / Math.PI) @@ -3525,7 +3525,14 @@ export const calcLinePlaneSize = (points) => { * @returns number */ export const calcLineActualSize = (points, degree) => { + const { x1, y1, x2, y2 } = points const planeSize = calcLinePlaneSize(points) - const height = Big(Math.tan(Big(degree).times(Math.PI / 180))).times(planeSize) + let height = Big(Math.tan(Big(degree).times(Math.PI / 180))).times(planeSize) + /** + * 대각선일 경우 높이 계산 변경 + */ + if (x1 !== x2 && y1 !== y2) { + height = Big(Math.tan(Big(degree).times(Math.PI / 180))).times(Big(x1).minus(x2).times(10).round()) + } return Big(planeSize).pow(2).plus(height.pow(2)).sqrt().abs().round().toNumber() } -- 2.47.2 From 99a584f8cf9c6ae370d27092704832e62b13f551 Mon Sep 17 00:00:00 2001 From: Jaeyoung Lee Date: Mon, 17 Mar 2025 17:35:43 +0900 Subject: [PATCH 002/109] =?UTF-8?q?=EC=A7=80=EB=B6=95=EB=8D=AE=EA=B0=9C=20?= =?UTF-8?q?=EA=B7=B8=EB=A6=AC=EA=B8=B0=20=EB=A1=9C=EC=A7=81=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EC=A4=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useMode.js | 14 +- src/util/qpolygon-utils.js | 2335 +++++++++++++++++++++++++++++++++++- 2 files changed, 2340 insertions(+), 9 deletions(-) diff --git a/src/hooks/useMode.js b/src/hooks/useMode.js index 19d43c5e..4dcbd6b7 100644 --- a/src/hooks/useMode.js +++ b/src/hooks/useMode.js @@ -1679,10 +1679,11 @@ export function useMode() { const offsetEdges = [] polygon.edges.forEach((edge, i) => { - const offset = + /* const offset = lines[i % lines.length].attributes.offset === undefined || lines[i % lines.length].attributes.offset === 0 ? 0.1 - : lines[i % lines.length].attributes.offset + : lines[i % lines.length].attributes.offset*/ + const offset = lines[i % lines.length].attributes.offset const dx = edge.outwardNormal.x * offset const dy = edge.outwardNormal.y * offset offsetEdges.push(createOffsetEdge(edge, dx, dy)) @@ -1717,10 +1718,12 @@ export function useMode() { const offsetEdges = [] polygon.edges.forEach((edge, i) => { - const offset = + /*const offset = lines[i % lines.length].attributes.offset === undefined || lines[i % lines.length].attributes.offset === 0 ? 0.1 - : lines[i % lines.length].attributes.offset + : lines[i % lines.length].attributes.offset*/ + const offset = lines[i % lines.length].attributes.offset + const dx = edge.inwardNormal.x * offset const dy = edge.inwardNormal.y * offset offsetEdges.push(createOffsetEdge(edge, dx, dy)) @@ -1769,6 +1772,8 @@ export function useMode() { }) wall.lines = afterLine.concat(beforeLine) + wall.baseLines = wall.lines + wall.colorLines = [] //외벽선을 기준으로 polygon을 생성한다. 지붕선의 기준이 됨. const divWallLines = [] @@ -1947,6 +1952,7 @@ export function useMode() { strokeWidth: wallStrokeWidth, selectable: false, }) + wall.colorLines.push(wallLine) canvas.add(wallLine) }) diff --git a/src/util/qpolygon-utils.js b/src/util/qpolygon-utils.js index 74e4654f..5b5c1bab 100644 --- a/src/util/qpolygon-utils.js +++ b/src/util/qpolygon-utils.js @@ -30,7 +30,9 @@ export const calculateAngle = (point1, point2) => { .minus(point1.y ?? 0) .toNumber() const angleInRadians = Math.atan2(deltaY, deltaX) - return angleInRadians * (180 / Math.PI) + return Big(angleInRadians * (180 / Math.PI)) + .round() + .toNumber() } function inwardEdgeNormal(vertex1, vertex2) { @@ -739,15 +741,2338 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { //Math.abs(line.x1 - line.x2) > 1 && Math.abs(line.y1 - line.y2) > 1 const hasNonParallelLines = roof.lines.filter((line) => Big(line.x1).minus(Big(line.x2)).gt(1) && Big(line.y1).minus(Big(line.y2)).gt(1)) if (hasNonParallelLines.length > 0) { - alert('대각선이 존재합니다.') + // alert('대각선이 존재합니다.') return } - drawRidge(roof, canvas, textMode) + const eavesType = [LINE_TYPE.WALLLINE.EAVES, LINE_TYPE.WALLLINE.HIPANDGABLE] + const gableType = [LINE_TYPE.WALLLINE.GABLE, LINE_TYPE.WALLLINE.JERKINHEAD] + // + // const eaves = roof.lines.filter((line) => eavesType.includes(line.attributes?.type)) + // const gables = roof.lines.filter((line) => gableType.includes(line.attributes?.type)) + + /** + * 외벽선 + */ + const baseLines = roof.wall.baseLines + const baseLinePoints = baseLines.map((line) => ({ x: line.x1, y: line.y1 })) + + console.log('지붕 마루 갯수 확인 : ', getMaxRidge(baseLines.length)) + + /** + * baseLine을 기준으로 확인용 polygon 작성 + */ + const checkWallPolygon = new QPolygon(baseLinePoints, {}) + + /** + * 외벽선이 시계방향인지 시계반대 방향인지 확인 + * @type {boolean} + */ + let counterClockwise = true + let signedArea = 0 + + baseLinePoints.forEach((point, index) => { + const nextPoint = baseLinePoints[(index + 1) % baseLinePoints.length] + signedArea += point.x * nextPoint.y - point.y * nextPoint.x + }) + + if (signedArea > 0) { + counterClockwise = false + } + + const drawEavesFirstLines = [] + const drawEavesSecondLines = [] + /** + * 모양을 판단하여 그린다. + */ + baseLines.forEach((currentLine, index) => { + /** + * 현재 라인이 처마유형일 경우 + */ + if (eavesType.includes(currentLine.attributes?.type)) { + const nextLine = baseLines[(index + 1) % baseLines.length] + const prevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length] + /** + * 현재 라인이 처마인 경우 + */ + if (eavesType.includes(nextLine.attributes?.type)) { + const prevAngle = calculateAngle(prevLine.startPoint, prevLine.endPoint) + const nextAngle = calculateAngle(nextLine.startPoint, nextLine.endPoint) + /** + * 이전, 다음 라인이 처마일때 라인의 방향이 반대면 ㄷ 모양으로 판단한다. + */ + if ( + eavesType.includes(prevLine.attributes?.type) && + eavesType.includes(nextLine.attributes?.type) && + Big(prevAngle).minus(Big(nextAngle)).abs().eq(180) + ) { + const checkScale = Big(10) + const xVector = Big(nextLine.x2).minus(Big(nextLine.x1)) + const yVector = Big(nextLine.y2).minus(Big(nextLine.y1)) + const currentMidX = Big(currentLine.x1).plus(Big(currentLine.x2)).div(2) + const currentMidY = Big(currentLine.y1).plus(Big(currentLine.y2)).div(2) + + /** + * 다음라인 방향에 포인트를 확인해서 역방향 ㄷ 모양인지 판단한다. + * @type {{x: *, y: *}} + */ + const checkPoints = { + x: currentMidX.plus(checkScale.times(Math.sign(xVector.toNumber()))).toNumber(), + y: currentMidY.plus(checkScale.times(Math.sign(yVector.toNumber()))).toNumber(), + } + + // const checkMidLine = new fabric.Line([currentMidX, currentMidY, checkPoints.x, checkPoints.y], { + // stroke: 'blue', + // strokeWidth: 2, + // }) + // canvas.add(checkMidLine) + // canvas.renderAll() + + if (checkWallPolygon.inPolygon(checkPoints)) { + drawEavesFirstLines.push({ currentLine, prevLine, nextLine, size: currentLine.attributes.planeSize }) + } else { + drawEavesSecondLines.push({ currentLine, prevLine, nextLine, size: currentLine.attributes.planeSize }) + } + } else { + drawEavesSecondLines.push({ currentLine, prevLine, nextLine, size: currentLine.attributes.planeSize }) + } + } + } + }) + + drawEavesFirstLines.sort((a, b) => a.size - b.size) + + /** 추녀마루 */ + let baseHipLines = [] + /** 용마루 */ + let baseRidgeLines = [] + /** ⨆ 모양 처마에 추녀마루를 그린다. */ + drawEavesFirstLines.forEach((current) => { + const { currentLine, prevLine, nextLine } = current + let { x1, x2, y1, y2 } = currentLine + let beforePrevLine, afterNextLine + + /** 이전 라인의 경사 */ + const prevDegree = prevLine.attributes.pitch > 0 ? getDegreeByChon(prevLine.attributes.pitch) : prevLine.attributes.degree + /** 다음 라인의 경사 */ + const currentDegree = currentLine.attributes.pitch > 0 ? getDegreeByChon(currentLine.attributes.pitch) : currentLine.attributes.degree + + /** 이전 라인의 전라인, 다음 라인의 다음라인을 찾는다 */ + baseLines.forEach((line, index) => { + if (line === prevLine) { + beforePrevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length] + } + if (line === nextLine) { + afterNextLine = baseLines[(index + 1) % baseLines.length] + } + }) + + /** 이전, 다음라인의 사잇각의 vector를 구한다. */ + let prevVector = getHalfAngleVector(prevLine, currentLine) + let nextVector = getHalfAngleVector(currentLine, nextLine) + + let prevHipVector = { x: Big(prevVector.x), y: Big(prevVector.y) } + let nextHipVector = { x: Big(nextVector.x), y: Big(nextVector.y) } + + /** 각 라인의 흐름 방향을 확인한다. */ + const currentAngle = calculateAngle(currentLine.startPoint, currentLine.endPoint) + const prevAngle = calculateAngle(prevLine.startPoint, prevLine.endPoint) + const nextAngle = calculateAngle(nextLine.startPoint, nextLine.endPoint) + const beforePrevAngle = calculateAngle(beforePrevLine.startPoint, beforePrevLine.endPoint) + const afterNextAngle = calculateAngle(afterNextLine.startPoint, afterNextLine.endPoint) + + /** 현재 라인의 길이 추녀마루 길이의 기준이 된다. */ + let currentSize = Big(x2) + .minus(Big(x1)) + .plus(Big(y2).minus(Big(y1))) + .abs() + + // console.log('currentLine : ', currentLine.attributes.planeSize) + + /** 이전 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/ + const prevCheckPoint = { + x: Big(x1).plus(Big(prevHipVector.x).times(10)), + y: Big(y1).plus(Big(prevHipVector.y).times(10)), + } + if (!checkWallPolygon.inPolygon(prevCheckPoint)) { + prevHipVector = { x: Big(prevHipVector.x).neg().toNumber(), y: Big(prevHipVector.y).neg().toNumber() } + } + + /** 다음 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/ + const nextCheckPoint = { + x: Big(x2).plus(Big(nextHipVector.x).times(10)), + y: Big(y2).plus(Big(nextHipVector.y).times(10)), + } + if (!checkWallPolygon.inPolygon(nextCheckPoint)) { + nextHipVector = { x: Big(nextHipVector.x).neg().toNumber(), y: Big(nextHipVector.y).neg().toNumber() } + } + + /** 현재 라인의 길이를 기준으로 추녀 마루의 길이를 삼각함수를 사용하여 판단한다.*/ + let hipLength = currentSize.div(2).pow(2).plus(currentSize.div(2).pow(2)).sqrt() + + /** + * 현재 라인에서 2번째 전 라인과 2번째 후 라인의 각도가 같을때 -_- 와 같은 형태로 판단하고 + * 맞은 편 외벽선까지의 거리를 확인후 currentSize 를 조정한다. + */ + if (currentAngle === beforePrevAngle && currentAngle === afterNextAngle) { + const xVector = Big(nextLine.x2).minus(Big(nextLine.x1)) + const yVector = Big(nextLine.y2).minus(Big(nextLine.y1)) + const currentMidX = Big(x1).plus(Big(x2)).div(2) + const currentMidY = Big(y1).plus(Big(y2)).div(2) + + const midLineEdge = { + vertex1: { x: currentMidX.toNumber(), y: currentMidY.toNumber() }, + vertex2: { + x: currentMidX.plus(currentSize.times(Math.sign(xVector))).toNumber(), + y: currentMidY.plus(currentSize.times(Math.sign(yVector))).toNumber(), + }, + } + + /*const checkMidLine = new fabric.Line([midLineEdge.vertex1.x, midLineEdge.vertex1.y, midLineEdge.vertex2.x, midLineEdge.vertex2.y], { + stroke: 'blue', + strokeWidth: 2, + }) + canvas.add(checkMidLine) + canvas.renderAll()*/ + + /** 현재 라인의 중심 지점에서 현재라인의 길이만큼 다음라인의 방향만큼 거리를 확인한다*/ + baseLines + .filter((line) => line !== currentLine) + .forEach((line) => { + const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } + const intersection = edgesIntersection(midLineEdge, lineEdge) + /** 현재라인의 길이만큼 거리가 모자라면 해당 길이 만큼을 현재라인의 길이로 판단하고 나머지 계산을 진행한다.*/ + if (intersection && !intersection.isIntersectionOutside) { + const intersectionSize = Big(intersection.x) + .minus(Big(currentMidX)) + .plus(Big(intersection.y).minus(Big(currentMidY))) + if (intersectionSize.lt(currentSize)) { + currentSize = intersectionSize + } + } + }) + + hipLength = currentSize.div(2).pow(2).plus(currentSize.div(2).pow(2)).sqrt() + } else { + if (currentAngle !== beforePrevAngle && currentAngle !== afterNextAngle && beforePrevLine !== afterNextLine) { + console.log('4각 아님') + // console.log('currentLine : ', currentLine.attributes.planeSize) + const beforePrevX1 = beforePrevLine.x1, + beforePrevY1 = beforePrevLine.y1, + beforePrevX2 = beforePrevLine.x2, + beforePrevY2 = beforePrevLine.y2 + const afterNextX1 = afterNextLine.x1, + afterNextY1 = afterNextLine.y1, + afterNextX2 = afterNextLine.x2, + afterNextY2 = afterNextLine.y2 + + /** beforePrevLine 과 afterNextLine 을 연결하는 사각형의 경우 6각으로 판단 */ + const connectBAPoint = { x1: afterNextX2, y1: afterNextY2, x2: beforePrevX1, y2: beforePrevY1 } + const isConnect = + baseLines.filter( + (line) => + line.x1 === connectBAPoint.x1 && line.y1 === connectBAPoint.y1 && line.x2 === connectBAPoint.x2 && line.y2 === connectBAPoint.y2, + ).length > 0 + + /** 6각 */ + // console.log('isConnect : ', isConnect) + if (isConnect) { + const checkScale = currentSize.pow(2).plus(currentSize.pow(2)).sqrt() + const intersectBaseLine = [] + if (baseHipLines.filter((line) => line.x1 === x1 && line.y1 === y1).length === 0) { + const prevEndPoint = { + x: Big(x1).plus(Big(prevHipVector.x).times(checkScale)), + y: Big(y1).plus(Big(prevHipVector.y).times(checkScale)), + } + baseLines + .filter((line) => line !== currentLine && line !== prevLine && line !== nextLine) + .forEach((line) => { + const intersection = edgesIntersection( + { vertex1: { x: x1, y: y1 }, vertex2: prevEndPoint }, + { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }, + ) + if (intersection && !intersection.isIntersectionOutside) { + /*const intersectCircle = new fabric.Circle({ + left: intersection.x - 2, + top: intersection.y - 2, + radius: 4, + fill: 'red', + parentId: roof.id, + }) + canvas.add(intersectCircle) + canvas.renderAll()*/ + intersectBaseLine.push({ + intersection, + distance: Big(intersection.x) + .minus(Big(x1)) + .pow(2) + .plus(Big(intersection.y).minus(Big(y1)).pow(2)) + .sqrt(), + }) + } + }) + } + if (baseHipLines.filter((line) => line.x1 === x2 && line.y1 === y2).length === 0) { + const nextEndPoint = { + x: Big(x2).plus(Big(nextHipVector.x).times(checkScale)), + y: Big(y2).plus(Big(nextHipVector.y).times(checkScale)), + } + baseLines + .filter((line) => line !== currentLine && line !== prevLine && line !== nextLine) + .forEach((line) => { + const intersection = edgesIntersection( + { vertex1: { x: x2, y: y2 }, vertex2: nextEndPoint }, + { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }, + ) + if (intersection && !intersection.isIntersectionOutside) { + /*const intersectCircle = new fabric.Circle({ + left: intersection.x - 2, + top: intersection.y - 2, + radius: 4, + fill: 'orange', + parentId: roof.id, + }) + canvas.add(intersectCircle) + canvas.renderAll()*/ + intersectBaseLine.push({ + intersection, + distance: Big(intersection.x) + .minus(Big(x2)) + .pow(2) + .plus(Big(intersection.y).minus(Big(y2)).pow(2)) + .sqrt(), + }) + } + }) + } + const intersection = intersectBaseLine.reduce((prev, current) => (prev.distance < current.distance ? prev : current), intersectBaseLine[0]) + if (intersection) { + /*const intersectCircle = new fabric.Circle({ + left: intersection.intersection.x - 2, + top: intersection.intersection.y - 2, + radius: 4, + fill: 'green', + parentId: roof.id, + }) + canvas.add(intersectCircle) + canvas.renderAll()*/ + + hipLength = intersection.distance + } + } else { + //라인 확인용 + /*const checkCurrentLine = new fabric.Line([x1, y1, x2, y2], { + stroke: 'yellow', + strokeWidth: 4, + parentId: roof.id, + }) + canvas.add(checkCurrentLine) + canvas.renderAll()*/ + + const rightAngleLine = baseLines + .filter( + (line) => + line !== prevLine && + line !== nextLine && + (prevAngle === calculateAngle(line.startPoint, line.endPoint) || nextAngle === calculateAngle(line.startPoint, line.endPoint)), + ) + .filter((line) => { + const index = baseLines.indexOf(line) + const prevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length] + const nextLine = baseLines[(index + 1) % baseLines.length] + const prevAngle = calculateAngle(prevLine.startPoint, prevLine.endPoint) + const nextAngle = calculateAngle(nextLine.startPoint, nextLine.endPoint) + switch (prevAngle) { + case 90: + return nextAngle === -90 + case -90: + return nextAngle === 90 + case 0: + return nextAngle === 180 + case 180: + return nextAngle === 0 + } + }) + + const oppositeCurrentLine = baseLines + .filter((line) => { + const angle = calculateAngle(line.startPoint, line.endPoint) + switch (currentAngle) { + case 90: + return angle === -90 + case -90: + return angle === 90 + case 0: + return angle === 180 + case 180: + return angle === 0 + } + }) + .filter((line) => { + const index = baseLines.indexOf(line) + const prevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length] + const nextLine = baseLines[(index + 1) % baseLines.length] + const prevAngle = calculateAngle(prevLine.startPoint, prevLine.endPoint) + const nextAngle = calculateAngle(nextLine.startPoint, nextLine.endPoint) + switch (prevAngle) { + case 90: + return nextAngle === -90 + case -90: + return nextAngle === 90 + case 0: + return nextAngle === 180 + case 180: + return nextAngle === 0 + } + }) + + let checkHipPoints + if (baseHipLines.filter((line) => line.x1 === x1 && line.y1 === y1).length === 0) { + checkHipPoints = [ + x1, + y1, + Big(x1).plus(Big(prevHipVector.x).times(hipLength)).round(2), + Big(y1).plus(Big(prevHipVector.y).times(hipLength)).round(2), + ] + } + + if (baseHipLines.filter((line) => line.x1 === x2 && line.y1 === y2).length === 0) { + checkHipPoints = [ + x2, + y2, + Big(x2).plus(Big(nextHipVector.x).times(hipLength)).round(2), + Big(y2).plus(Big(nextHipVector.y).times(hipLength)).round(2), + ] + } + + /* const checkHipLine = new fabric.Line(checkHipPoints, { + stroke: 'red', + strokeWidth: 2, + parentId: roof.id, + }) + canvas.add(checkHipLine) + canvas.renderAll()*/ + + if (checkHipPoints) { + const intersectPoints = [] + rightAngleLine.forEach((line) => { + /*const checkRightAngle = new fabric.Line([line.x1, line.y1, line.x2, line.y2], { + stroke: 'red', + strokeWidth: 4, + parentId: roof.id, + }) + canvas.add(checkRightAngle) + canvas.renderAll()*/ + + const intersection = edgesIntersection( + { vertex1: { x: checkHipPoints[0], y: checkHipPoints[1] }, vertex2: { x: checkHipPoints[2], y: checkHipPoints[3] } }, + { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }, + ) + if (intersection) { + /*const checkRightAngleCircle = new fabric.Circle({ + left: intersection.x - 2, + top: intersection.y - 2, + radius: 4, + fill: 'red', + parentId: roof.id, + }) + canvas.add(checkRightAngleCircle) + canvas.renderAll()*/ + intersectPoints.push({ + intersection, + distance: Big(intersection.x) + .minus(Big(checkHipPoints[0])) + .pow(2) + .plus(Big(intersection.y).minus(Big(checkHipPoints[1])).pow(2)) + .sqrt(), + }) + } + }) + + oppositeCurrentLine.forEach((line) => { + /*const checkOpposite = new fabric.Line([line.x1, line.y1, line.x2, line.y2], { + stroke: 'blue', + strokeWidth: 4, + parentId: roof.id, + }) + canvas.add(checkOpposite) + canvas.renderAll() +*/ + const intersection = edgesIntersection( + { vertex1: { x: checkHipPoints[0], y: checkHipPoints[1] }, vertex2: { x: checkHipPoints[2], y: checkHipPoints[3] } }, + { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }, + ) + if (intersection) { + /*const checkOppositeCircle = new fabric.Circle({ + left: intersection.x - 2, + top: intersection.y - 2, + radius: 4, + fill: 'blue', + parentId: roof.id, + }) + canvas.add(checkOppositeCircle) + canvas.renderAll()*/ + intersectPoints.push({ + intersection, + distance: Big(intersection.x) + .minus(Big(checkHipPoints[0])) + .pow(2) + .plus(Big(intersection.y).minus(Big(checkHipPoints[1])).pow(2)) + .sqrt(), + }) + } + }) + + // console.log('intersectPoints : ', intersectPoints) + + const intersection = intersectPoints.reduce((prev, current) => (prev.distance.lt(current.distance) ? prev : current), intersectPoints[0]) + if (intersection) { + /*const checkHipCircle = new fabric.Circle({ + left: intersection.intersection.x - 2, + top: intersection.intersection.y - 2, + radius: 4, + fill: 'green', + parentId: roof.id, + }) + canvas.add(checkHipCircle) + canvas.renderAll()*/ + hipLength = intersection.distance.div(2) + } + } + } + } + } + + let prevHipLine, nextHipLine + /** 이전라인과의 연결지점에 추녀마루를 그린다. */ + if (baseHipLines.filter((line) => line.x1 === x1 && line.y1 === y1).length === 0) { + const prevEndPoint = { + x: Big(x1).plus(Big(prevHipVector.x).times(hipLength)).round(1), + y: Big(y1).plus(Big(prevHipVector.y).times(hipLength)).round(1), + } + + const intersectRidgeLine = [] + baseRidgeLines.forEach((line) => { + const intersection = edgesIntersection( + { vertex1: { x: x1, y: y1 }, vertex2: prevEndPoint }, + { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }, + ) + // console.log('intersection : ', intersection, 'prevEndPoint : ', prevEndPoint.x.toNumber(), prevEndPoint.y.toNumber()) + if (intersection && !intersection.isIntersectionOutside) { + intersectRidgeLine.push({ + intersection, + distance: Big(intersection.x) + .minus(Big(x1)) + .pow(2) + .plus(Big(intersection.y).minus(Big(y1)).pow(2)) + .sqrt(), + }) + } + }) + const intersectRidge = intersectRidgeLine.reduce((prev, current) => (prev.distance < current.distance ? prev : current), intersectRidgeLine[0]) + // console.log('intersectRidge : ', intersectRidge) + + if (intersectRidge) { + prevEndPoint.x = Big(intersectRidge.intersection.x).round(1) + prevEndPoint.y = Big(intersectRidge.intersection.y).round(1) + } + + const xVector = Math.sign(Big(prevEndPoint.x).minus(Big(x1)).neg().toNumber()) + const yVector = Math.sign(Big(prevEndPoint.y).minus(Big(y1)).neg().toNumber()) + /** 지붕 선까지의 곂침에 따른 길이를 파악하기 위한 scale*/ + let scale = Big(currentLine.attributes.offset).pow(2).plus(Big(prevLine.attributes.offset).pow(2)).sqrt() + scale = scale.eq(0) ? Big(1) : scale + + /** scale 만큼 추녀마루를 늘려서 겹치는 포인트를 확인한다. */ + const hipEdge = { + vertex1: { x: Big(x1).plus(scale.times(xVector)).toNumber(), y: Big(y1).plus(scale.times(yVector)).toNumber() }, + vertex2: prevEndPoint, + } + + /*const checkLine = new fabric.Line([hipEdge.vertex1.x, hipEdge.vertex1.y, hipEdge.vertex2.x, hipEdge.vertex2.y], { + stroke: 'yellow', + strokeWidth: 2, + parentId: roof.id, + }) + canvas.add(checkLine) + canvas.renderAll()*/ + + let intersectPoints = [] + /** 외벽선에서 추녀 마루가 겹치는 경우에 대한 확인*/ + roof.lines.forEach((line) => { + const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } + const intersection = edgesIntersection(hipEdge, lineEdge) + if ( + intersection && + !intersection.isIntersectionOutside && + Math.sign(prevEndPoint.x - x1) === Math.sign(prevEndPoint.x - intersection.x) && + Math.sign(prevEndPoint.y - y1) === Math.sign(prevEndPoint.y - intersection.y) + ) { + /* const intersectPoint = new fabric.Circle({ + left: intersection.x - 2, + top: intersection.y - 2, + radius: 4, + fill: 'red', + parentId: roof.id, + }) + canvas.add(intersectPoint) + canvas.renderAll()*/ + + const intersectSize = prevEndPoint.x + .minus(Big(intersection.x)) + .pow(2) + .plus(prevEndPoint.y.minus(Big(intersection.y)).pow(2)) + .abs() + .sqrt() + .toNumber() + + intersectPoints.push({ + intersection, + size: intersectSize, + }) + } + }) + + intersectPoints = intersectPoints.reduce((prev, current) => { + return prev.size < current.size ? prev : current + }, intersectPoints[0]) + + // console.log('intersectPoints', intersectPoints) + + /** 겹치는 외벽선이 있을때 추녀마루를 외벽선 까지 늘려서 수정*/ + if (intersectPoints && intersectPoints.intersection) { + prevHipLine = drawHipLine( + [intersectPoints.intersection.x, intersectPoints.intersection.y, prevEndPoint.x.toNumber(), prevEndPoint.y.toNumber()], + canvas, + roof, + textMode, + null, + prevDegree, + currentDegree, + ) + baseHipLines.push({ x1, y1, x2: prevEndPoint.x.toNumber(), y2: prevEndPoint.y.toNumber(), line: prevHipLine }) + } + } + if (baseHipLines.filter((line) => line.x1 === x2 && line.y1 === y2).length === 0) { + const nextEndPoint = { + x: Big(x2).plus(Big(nextHipVector.x).times(hipLength)).round(1), + y: Big(y2).plus(Big(nextHipVector.y).times(hipLength)).round(1), + } + + const intersectRidgeLine = [] + baseRidgeLines.forEach((line) => { + // console.log('nextEndPoint : ', nextEndPoint.x.toNumber(), nextEndPoint.y.toNumber()) + // console.log('intersection : ', 'x : ', line.x1, 'y : ', line.y1, 'x : ', line.x2, 'y : ', line.y2) + const intersection = edgesIntersection( + { vertex1: { x: x2, y: y2 }, vertex2: nextEndPoint }, + { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }, + ) + // console.log('intersection : ', intersection, 'nextEndPoint : ', nextEndPoint.x.toNumber(), nextEndPoint.y.toNumber()) + if (intersection && !intersection.isIntersectionOutside) { + intersectRidgeLine.push({ + intersection, + distance: Big(intersection.x) + .minus(Big(x1)) + .pow(2) + .plus(Big(intersection.y).minus(Big(y1)).pow(2)) + .sqrt(), + }) + } + }) + const intersectRidge = intersectRidgeLine.reduce((prev, current) => (prev.distance < current.distance ? prev : current), intersectRidgeLine[0]) + // console.log('intersectRidge : ', intersectRidge) + + if (intersectRidge) { + nextEndPoint.x = Big(intersectRidge.intersection.x).round(1) + nextEndPoint.y = Big(intersectRidge.intersection.y).round(1) + } + + const xVector = Math.sign(Big(nextEndPoint.x).minus(Big(x2)).neg().toNumber()) + const yVector = Math.sign(Big(nextEndPoint.y).minus(Big(y2)).neg().toNumber()) + let scale = Big(currentLine.attributes.offset).pow(2).plus(Big(nextLine.attributes.offset).pow(2)).sqrt() + scale = scale.eq(0) ? Big(1) : scale + + const hipEdge = { + vertex1: { x: Big(x2).plus(scale.times(xVector)).toNumber(), y: Big(y2).plus(scale.times(yVector)).toNumber() }, + vertex2: nextEndPoint, + } + + /* const checkLine = new fabric.Line([hipEdge.vertex1.x, hipEdge.vertex1.y, hipEdge.vertex2.x, hipEdge.vertex2.y], { + stroke: 'yellow', + strokeWidth: 2, + parentId: roof.id, + }) + canvas.add(checkLine) + canvas.renderAll()*/ + + let intersectPoints = [] + roof.lines.forEach((line) => { + const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } + const intersection = edgesIntersection(hipEdge, lineEdge) + // console.log('intersection', intersection) + if ( + intersection && + !intersection.isIntersectionOutside && + Math.sign(nextEndPoint.x - x2) === Math.sign(nextEndPoint.x - intersection.x) && + Math.sign(nextEndPoint.y - y2) === Math.sign(nextEndPoint.y - intersection.y) + ) { + /*const intersectPoint = new fabric.Circle({ + left: intersection.x - 2, + top: intersection.y - 2, + radius: 4, + fill: 'blue', + parentId: roof.id, + }) + canvas.add(intersectPoint) + canvas.renderAll()*/ + + // console.log('nextEndPoint', nextEndPoint.x.toNumber(), nextEndPoint.y.toNumber()) + // console.log('intersection', intersection.x, intersection.y) + + const intersectSize = nextEndPoint.x + .minus(Big(intersection.x)) + .pow(2) + .plus(nextEndPoint.y.minus(Big(intersection.y)).pow(2)) + .abs() + .sqrt() + .toNumber() + + intersectPoints.push({ + intersection, + size: intersectSize, + }) + } + }) + + intersectPoints = intersectPoints.reduce((prev, current) => { + return prev.size < current.size ? prev : current + }, intersectPoints[0]) + + // console.log('intersectPoints : ', intersectPoints) + + if (intersectPoints && intersectPoints.intersection) { + nextHipLine = drawHipLine( + [intersectPoints.intersection.x, intersectPoints.intersection.y, nextEndPoint.x.toNumber(), nextEndPoint.y.toNumber()], + canvas, + roof, + textMode, + null, + prevDegree, + currentDegree, + ) + baseHipLines.push({ x1: x2, y1: y2, x2: nextEndPoint.x.toNumber(), y2: nextEndPoint.y.toNumber(), line: nextHipLine }) + } + } + + // console.log('prevHipLine : ', prevHipLine) + // console.log('nextHipLine : ', nextHipLine) + /** 두 추녀마루가 한점에서 만나는 경우 해당 점을 기점으로 마루를 작성한다.*/ + if ( + prevHipLine !== undefined && + nextHipLine !== undefined && + Big(prevHipLine.x2).minus(Big(nextHipLine.x2)).abs().lte(1) && + Big(prevHipLine.y2).minus(Big(nextHipLine.y2)).abs().lte(1) + ) { + console.log('마루 작성') + const startPoint = { x: prevHipLine.x2, y: prevHipLine.y2 } + + // baseLines에서 가장 작은 x1과 가장 큰 x1, 가장 작은 y1과 가장 큰 y1을 계산 + let minX = Infinity + let maxX = -Infinity + let minY = Infinity + let maxY = -Infinity + + baseLines.forEach((line) => { + if (line.x1 < minX) { + minX = line.x1 + } + if (line.x1 > maxX) { + maxX = line.x1 + } + if (line.y1 < minY) { + minY = line.y1 + } + if (line.y1 > maxY) { + maxY = line.y1 + } + }) + const checkLength = Big(maxX) + .minus(Big(minX)) + .pow(2) + .plus(Big(maxY).minus(Big(minY)).pow(2)) + .sqrt() + + const currentMidX = Big(currentLine.x2).plus(Big(currentLine.x1)).div(2) + const currentMidY = Big(currentLine.y2).plus(Big(currentLine.y1)).div(2) + + const xVector = Big(currentMidX).minus(Big(startPoint.x)).round(0, Big.roundDown) + const yVector = Big(currentMidY).minus(Big(startPoint.y)).round(0, Big.roundDown) + + const checkEdges = { + vertex1: { x: startPoint.x, y: startPoint.y }, + vertex2: { + x: Big(startPoint.x).minus(checkLength.times(Math.sign(xVector))), + y: Big(startPoint.y).minus(checkLength.times(Math.sign(yVector))), + }, + } + + /*const checkLine = new fabric.Line([checkEdges.vertex1.x, checkEdges.vertex1.y, checkEdges.vertex2.x, checkEdges.vertex2.y], { + stroke: 'purple', + strokeWidth: 2, + parentId: roof.id, + }) + canvas.add(checkLine) + canvas.renderAll()*/ + + /** 맞은편 벽 까지의 길이 판단을 위한 교차되는 line*/ + const intersectBaseLine = [] + baseLines + .filter((line) => { + /** currentAngle 의 반대 각도인 라인 */ + const angle = calculateAngle(line.startPoint, line.endPoint) + switch (currentAngle) { + case 90: + return angle === -90 + case -90: + return angle === 90 + case 0: + return angle === 180 + case 180: + return angle === 0 + } + }) + .filter((line) => { + const currentMinX = Math.min(x1, x2) + const currentMaxX = Math.max(x1, x2) + const currentMinY = Math.min(y1, y2) + const currentMaxY = Math.max(y1, y2) + const lineMinX = Math.min(line.x1, line.x2) + const lineMaxX = Math.max(line.x1, line.x2) + const lineMinY = Math.min(line.y1, line.y2) + const lineMaxY = Math.max(line.y1, line.y2) + + /** currentLine 의 안쪽에 있거나 currentLine이 line의 안쪽에 있는 라인 */ + if (Big(currentLine.y1).minus(Big(currentLine.y2)).abs().lte(1)) { + return ( + (currentMinX <= lineMinX && lineMinX <= currentMaxX) || + (currentMinX <= lineMaxX && lineMaxX <= currentMaxX) || + (lineMinX <= currentMinX && currentMinX <= lineMaxX) || + (lineMinX <= currentMaxX && currentMaxX <= lineMaxX) + ) + } else { + return ( + (currentMinY <= lineMinY && lineMinY <= currentMaxY) || + (currentMinY <= lineMaxY && lineMaxY <= currentMaxY) || + (lineMinY <= currentMinY && currentMinY <= lineMaxY) || + (lineMinY <= currentMaxY && currentMaxY <= lineMaxY) + ) + } + }) + .forEach((line, index) => { + const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } + const intersection = edgesIntersection(checkEdges, lineEdge) + if (intersection) { + /*const intersectPoint = new fabric.Circle({ + left: intersection.x - 2, + top: intersection.y - 2, + radius: 4, + fill: 'green', + parentId: roof.id, + }) + canvas.add(intersectPoint) + canvas.renderAll()*/ + intersectBaseLine.push({ intersection, line }) + } + }) + + // console.log('intersectBaseLine : ', intersectBaseLine) + + /** 맞은편 라인 */ + const oppositeLine = intersectBaseLine.reduce((prev, current) => { + const prevDistance = Big(prev.intersection.x) + .minus(Big(startPoint.x)) + .pow(2) + .plus(Big(prev.intersection.y).minus(Big(startPoint.y)).pow(2)) + .sqrt() + const currentDistance = Big(current.intersection.x) + .minus(Big(startPoint.x)) + .pow(2) + .plus(Big(current.intersection.y).minus(Big(startPoint.y)).pow(2)) + .sqrt() + return prevDistance < currentDistance ? prev : current + }, intersectBaseLine[0]) + + // console.log('oppositeLine : ', oppositeLine) + + /** 맞은편 라인까지의 길이 = 전체 길이 - 현재라인의 길이 */ + const oppositeSize = oppositeLine + ? Big(oppositeLine.intersection.x) + .minus(Big(startPoint.x)) + .pow(2) + .plus(Big(oppositeLine.intersection.y).minus(Big(startPoint.y)).pow(2)) + .sqrt() + .minus(currentSize.div(2)) + .round(1) + : Infinity + + // console.log('startPoint : ', startPoint, 'currentSize : ', currentSize.toNumber()) + + /** 이전, 다음 라인중 길이가 짧은 길이*/ + const lineMinSize = + prevLine.attributes.planeSize < nextLine.attributes.planeSize + ? Big(prevLine.attributes.planeSize).div(10) + : Big(nextLine.attributes.planeSize).div(10) + + /** 마루의 길이는 이전 다음 라인중 짧은것의 길이와 현재라인부터 맞은편 라인까지의 길이에서 현재 라인의 길이를 뺀 것중 짧은 길이 */ + const ridgeSize = Big(Math.min(oppositeSize, lineMinSize)) + + // console.log('oppositeSize : ', oppositeSize, 'lineMinSize : ', lineMinSize.toNumber(), 'ridgeSize : ', ridgeSize.toNumber()) + + if (ridgeSize.gt(0)) { + const points = [ + startPoint.x, + startPoint.y, + Big(startPoint.x).minus(ridgeSize.times(Math.sign(xVector))), + Big(startPoint.y).minus(ridgeSize.times(Math.sign(yVector))), + ] + + /** 동일 라인이 있는지 확인. */ + if ( + baseRidgeLines.filter((line) => { + const ridgeMinX = Math.min(points[0], points[2]) + const ridgeMaxX = Math.max(points[0], points[2]) + const ridgeMinY = Math.min(points[1], points[3]) + const ridgeMaxY = Math.max(points[1], points[3]) + const lineMinX = Math.min(line.x1, line.x2) + const lineMaxX = Math.max(line.x1, line.x2) + const lineMinY = Math.min(line.y1, line.y2) + const lineMaxY = Math.max(line.y1, line.y2) + + return ridgeMinX === lineMinX && ridgeMaxX === lineMaxX && ridgeMinY === lineMinY && ridgeMaxY === lineMaxY + }).length > 0 + ) { + return + } + + /* const checkMidLine = new fabric.Line(points, { + stroke: 'blue', + strokeWidth: 2, + parentId: roof.id, + }) + canvas.add(checkMidLine) + canvas.renderAll()*/ + // console.log('points : ', points) + + /** 마루 생성 */ + const ridgeLine = drawRidgeLine(points, canvas, roof, textMode) + baseRidgeLines.push(ridgeLine) + + /** 포인트 조정*/ + baseHipLines + .filter((line) => line.line === prevHipLine || line.line === nextHipLine) + .forEach((line) => { + line.x2 = points[0] + line.y2 = points[1] + }) + prevHipLine.x2 = points[0] + prevHipLine.y2 = points[1] + nextHipLine.x2 = points[0] + nextHipLine.y2 = points[1] + prevHipLine.fire('modified') + nextHipLine.fire('modified') + } + } + }) + + /** 중복제거 */ + baseRidgeLines.forEach((ridge) => { + baseRidgeLines + .filter((ridge2) => ridge !== ridge2) + .forEach((ridge2) => { + let overlap = segmentsOverlap(ridge, ridge2) + if (overlap) { + roof.ridges = roof.ridges.filter((r) => r !== ridge && r !== ridge2) + roof.innerLines = roof.innerLines.filter((l) => l !== ridge && l !== ridge2) + roof.canvas.remove(ridge) + roof.canvas.remove(ridge2) + baseRidgeLines = baseRidgeLines.filter((r) => r !== ridge && r !== ridge2) + + let x1 = Math.min(ridge.x1, ridge2.x1, ridge.x2, ridge2.x2) + let x2 = Math.max(ridge.x1, ridge2.x1, ridge.x2, ridge2.x2) + let y1 = Math.min(ridge.y1, ridge2.y1, ridge.y2, ridge2.y2) + let y2 = Math.max(ridge.y1, ridge2.y1, ridge.y2, ridge2.y2) + + const newRidge = drawRidgeLine([x1, y1, x2, y2], canvas, roof, textMode) + roof.ridges.push(newRidge) + roof.innerLines.push(newRidge) + baseRidgeLines.push(newRidge) + } + }) + }) + + /** ㄴ 모양 처마에 추녀마루를 그린다. */ + drawEavesSecondLines.forEach((current) => { + const { currentLine, prevLine, nextLine } = current + let { x1, x2, y1, y2 } = currentLine + let beforePrevLine, afterNextLine + + /** 이전 라인의 경사 */ + const prevDegree = prevLine.attributes.pitch > 0 ? getDegreeByChon(prevLine.attributes.pitch) : prevLine.attributes.degree + /** 다음 라인의 경사 */ + const currentDegree = currentLine.attributes.pitch > 0 ? getDegreeByChon(currentLine.attributes.pitch) : currentLine.attributes.degree + + //라인 확인용 + /*const checkCurrentLine = new fabric.Line([x1, y1, x2, y2], { + stroke: 'red', + strokeWidth: 4, + parentId: roof.id, + }) + canvas.add(checkCurrentLine) + canvas.renderAll()*/ + // console.log('currentLine : ', currentLine.attributes.planeSize) + + /** 이전, 다음라인의 사잇각의 vector를 구한다. */ + let prevVector = getHalfAngleVector(prevLine, currentLine) + let nextVector = getHalfAngleVector(currentLine, nextLine) + + let prevHipVector = { x: Big(prevVector.x), y: Big(prevVector.y) } + let nextHipVector = { x: Big(nextVector.x), y: Big(nextVector.y) } + + /** 각 라인의 흐름 방향을 확인한다. */ + const currentAngle = calculateAngle(currentLine.startPoint, currentLine.endPoint) + /*const beforePrevAngle = calculateAngle(beforePrevLine.startPoint, beforePrevLine.endPoint) + const afterNextAngle = calculateAngle(afterNextLine.startPoint, afterNextLine.endPoint)*/ + + /** 현재 라인의 길이 추녀마루 길이의 기준이 된다. */ + let hipLength = Big(x2) + .minus(Big(x1)) + .plus(Big(y2).minus(Big(y1))) + .abs() + + // console.log('currentLine : ', currentLine.attributes.planeSize) + + /** 이전 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/ + const prevCheckPoint = { + x: Big(x1).plus(Big(prevHipVector.x).times(10)), + y: Big(y1).plus(Big(prevHipVector.y).times(10)), + } + if (!checkWallPolygon.inPolygon(prevCheckPoint)) { + prevHipVector = { x: Big(prevHipVector.x).neg(), y: Big(prevHipVector.y).neg() } + } + + /** 다음 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/ + const nextCheckPoint = { + x: Big(x2).plus(Big(nextHipVector.x).times(10)), + y: Big(y2).plus(Big(nextHipVector.y).times(10)), + } + if (!checkWallPolygon.inPolygon(nextCheckPoint)) { + nextHipVector = { x: Big(nextHipVector.x).neg(), y: Big(nextHipVector.y).neg() } + } + + let prevHipLine, nextHipLine + /** 이전라인과의 연결지점에 추녀마루를 그린다. */ + // console.log('이전라인 : ', baseHipLines.filter((line) => line.x1 === x1 && line.y1 === y1).length === 0) + if (baseHipLines.filter((line) => line.x1 === x1 && line.y1 === y1).length === 0) { + let prevEndPoint = { + x: Big(x1).plus(Big(prevHipVector.x).times(hipLength)).round(2), + y: Big(y1).plus(Big(prevHipVector.y).times(hipLength)).round(2), + } + + const prevEndEdge = { vertex1: { x: x1, y: y1 }, vertex2: prevEndPoint } + + // console.log('prevHipVector : ', prevHipVector.x.toNumber(), prevHipVector.x.s, prevHipVector.y.toNumber(), prevHipVector.y.s) + + const intersectBaseLine = [] + baseLines + .filter((line) => line !== currentLine && line !== prevLine && line !== nextLine) + .filter((line) => { + if (currentAngle === 0 || currentAngle === 180) { + return Big(line.y1).minus(Big(y1)).s === nextHipVector.y.s || Big(line.y2).minus(Big(y1)).s === nextHipVector.y.s + } else { + return Big(line.x1).minus(Big(x1)).s === nextHipVector.x.s || Big(line.x2).minus(Big(x1)).s === nextHipVector.x.s + } + }) + .forEach((line) => { + // console.log('line : ', line.attributes.planeSize) + const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } + const intersection = edgesIntersection(prevEndEdge, lineEdge) + + if (intersection && Big(intersection.x - x1).s === nextHipVector.x.s && Big(intersection.y - y1).s === nextHipVector.y.s) { + /* const circle = new fabric.Circle({ + left: intersection.x - 2, + top: intersection.y - 2, + radius: 4, + fill: 'red', + parentId: roof.id, + }) + canvas.add(circle) + canvas.renderAll()*/ + const size = Big(intersection.x - x1) + .abs() + .pow(2) + .plus( + Big(intersection.y - y1) + .pow(2) + .abs(), + ) + .sqrt() + if (size.gt(0)) { + intersectBaseLine.push({ + intersection, + size, + }) + } + } + }) + + const intersectBase = intersectBaseLine.reduce((prev, current) => { + return prev.size < current.size ? prev : current + }, intersectBaseLine[0]) + + if (intersectBase) { + prevEndPoint = { + x: Big(x1) + .plus(Big(nextHipVector.x).times(intersectBase.size.div(2))) + .round(2), + y: Big(y1) + .plus(Big(nextHipVector.y).times(intersectBase.size.div(2))) + .round(2), + } + } + + const intersectRidgeLine = [] + baseRidgeLines.forEach((line) => { + const intersection = edgesIntersection( + { vertex1: { x: x1, y: y1 }, vertex2: prevEndPoint }, + { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }, + ) + /*const checkLine = new fabric.Line([x1, y1, prevEndPoint.x, prevEndPoint.y], { + stroke: 'red', + strokeWidth: 2, + parentId: roof.id, + }) + canvas.add(checkLine) + canvas.renderAll()*/ + + /* if (intersection) { + const intersectCircle = new fabric.Circle({ + left: intersection.x - 2, + top: intersection.y - 2, + radius: 4, + fill: 'red', + parentId: roof.id, + }) + canvas.add(intersectCircle) + canvas.renderAll() + }*/ + if (intersection && !intersection.isIntersectionOutside) { + intersectRidgeLine.push({ + intersection, + distance: Big(intersection.x) + .minus(Big(x1)) + .abs() + .pow(2) + .plus(Big(intersection.y).minus(Big(y1)).pow(2).abs()) + .sqrt(), + }) + } + }) + + const intersectRidge = intersectRidgeLine.reduce((prev, current) => (prev.distance < current.distance ? prev : current), intersectRidgeLine[0]) + + if (intersectRidge) { + prevEndPoint.x = Big(intersectRidge.intersection.x).round(1) + prevEndPoint.y = Big(intersectRidge.intersection.y).round(1) + } + + const xVector = Math.sign(Big(prevEndPoint.x).minus(Big(x1)).neg().toNumber()) + const yVector = Math.sign(Big(prevEndPoint.y).minus(Big(y1)).neg().toNumber()) + /** 지붕 선까지의 곂침에 따른 길이를 파악하기 위한 scale*/ + let scale = Big(currentLine.attributes.offset).pow(2).plus(Big(prevLine.attributes.offset).pow(2)).sqrt() + scale = scale.eq(0) ? Big(1) : scale + + /** scale 만큼 추녀마루를 늘려서 겹치는 포인트를 확인한다. */ + const hipEdge = { + vertex1: { x: Big(x1).plus(scale.times(xVector)).toNumber(), y: Big(y1).plus(scale.times(yVector)).toNumber() }, + vertex2: prevEndPoint, + } + + /*const checkLine = new fabric.Line([hipEdge.vertex1.x, hipEdge.vertex1.y, hipEdge.vertex2.x, hipEdge.vertex2.y], { + stroke: 'yellow', + strokeWidth: 2, + parentId: roof.id, + }) + canvas.add(checkLine) + canvas.renderAll()*/ + + let intersectPoints = [] + /** 외벽선에서 추녀 마루가 겹치는 경우에 대한 확인*/ + roof.lines.forEach((line) => { + const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } + const intersection = edgesIntersection(hipEdge, lineEdge) + if ( + intersection && + !intersection.isIntersectionOutside && + Math.sign(prevEndPoint.x - x1) === Math.sign(prevEndPoint.x - intersection.x) && + Math.sign(prevEndPoint.y - y1) === Math.sign(prevEndPoint.y - intersection.y) + ) { + /*const intersectPoint = new fabric.Circle({ + left: intersection.x - 2, + top: intersection.y - 2, + radius: 4, + fill: 'cyan', + parentId: roof.id, + }) + canvas.add(intersectPoint) + canvas.renderAll()*/ + + const intersectSize = prevEndPoint.x + .minus(Big(intersection.x)) + .abs() + .pow(2) + .plus(prevEndPoint.y.minus(Big(intersection.y)).pow(2).abs()) + .sqrt() + .toNumber() + + intersectPoints.push({ + intersection, + size: intersectSize, + }) + } + }) + + intersectPoints = intersectPoints.reduce((prev, current) => { + return prev.size < current.size ? prev : current + }, intersectPoints[0]) + + // console.log('intersectPoints', intersectPoints) + + /** 겹치는 외벽선이 있을때 추녀마루를 외벽선 까지 늘려서 수정*/ + if (intersectPoints && intersectPoints.intersection) { + prevHipLine = drawHipLine( + [intersectPoints.intersection.x, intersectPoints.intersection.y, prevEndPoint.x.toNumber(), prevEndPoint.y.toNumber()], + canvas, + roof, + textMode, + null, + prevDegree, + currentDegree, + ) + baseHipLines.push({ x1, y1, x2: prevEndPoint.x.toNumber(), y2: prevEndPoint.y.toNumber(), line: prevHipLine }) + } + } + /** 다음라인과의 연결지점에 추녀마루를 그린다. */ + // console.log('다음라인 : ', baseHipLines.filter((line) => line.x1 === x2 && line.y1 === y2).length === 0) + if (baseHipLines.filter((line) => line.x1 === x2 && line.y1 === y2).length === 0) { + let nextEndPoint = { + x: Big(x2).plus(Big(nextHipVector.x).times(hipLength)).round(2), + y: Big(y2).plus(Big(nextHipVector.y).times(hipLength)).round(2), + } + + /*const nextEndLine = new fabric.Line([x2, y2, nextEndPoint.x, nextEndPoint.y], { + stroke: 'red', + strokeWidth: 2, + parentId: roof.id, + }) + canvas.add(nextEndLine) + canvas.renderAll()*/ + + const nextEndEdge = { + vertex1: { x: x2, y: y2 }, + vertex2: nextEndPoint, + } + + const intersectBaseLine = [] + baseLines + .filter((line) => line !== currentLine && line !== prevLine && line !== nextLine) + .filter((line) => { + if (currentAngle === 0 || currentAngle === 180) { + return Big(line.y1).minus(Big(y1)).s === nextHipVector.y.s || Big(line.y2).minus(Big(y1)).s === nextHipVector.y.s + } else { + return Big(line.x1).minus(Big(x1)).s === nextHipVector.x.s || Big(line.x2).minus(Big(x1)).s === nextHipVector.x.s + } + }) + .forEach((line) => { + // console.log('line : ', line.attributes.planeSize) + const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } + const intersection = edgesIntersection(nextEndEdge, lineEdge) + + if (intersection && Big(intersection.x - x2).s === nextHipVector.x.s && Big(intersection.y - y2).s === nextHipVector.y.s) { + /*console.log('next intersection ============') + const circle = new fabric.Circle({ + left: intersection.x - 2, + top: intersection.y - 2, + radius: 4, + fill: 'red', + parentId: roof.id, + }) + canvas.add(circle) + canvas.renderAll()*/ + const size = Big(intersection.x - x2) + .abs() + .pow(2) + .plus( + Big(intersection.y - y2) + .abs() + .pow(2), + ) + .sqrt() + if (size.gt(0)) { + intersectBaseLine.push({ + intersection, + size, + }) + } + } + }) + + const intersectBase = intersectBaseLine.reduce((prev, current) => { + return prev.size.lt(current.size) ? prev : current + }, intersectBaseLine[0]) + + if (intersectBase) { + nextEndPoint = { + x: Big(x2) + .plus(Big(nextHipVector.x).times(intersectBase.size.div(2))) + .round(2), + y: Big(y2) + .plus(Big(nextHipVector.y).times(intersectBase.size.div(2))) + .round(2), + } + } + + const intersectRidgeLine = [] + baseRidgeLines.forEach((line) => { + // console.log('nextEndPoint : ', nextEndPoint.x.toNumber(), nextEndPoint.y.toNumber()) + // console.log('intersection : ', 'x : ', line.x1, 'y : ', line.y1, 'x : ', line.x2, 'y : ', line.y2) + const intersection = edgesIntersection( + { vertex1: { x: x2, y: y2 }, vertex2: nextEndPoint }, + { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }, + ) + + if (intersection && !intersection.isIntersectionOutside) { + intersectRidgeLine.push({ + intersection, + distance: Big(intersection.x) + .minus(Big(x1)) + .pow(2) + .plus(Big(intersection.y).minus(Big(y1)).pow(2)) + .sqrt(), + }) + } + }) + const intersectRidge = intersectRidgeLine.reduce((prev, current) => (prev.distance < current.distance ? prev : current), intersectRidgeLine[0]) + // console.log('intersectRidge : ', intersectRidge) + + if (intersectRidge) { + nextEndPoint.x = Big(intersectRidge.intersection.x).round(1) + nextEndPoint.y = Big(intersectRidge.intersection.y).round(1) + } + + const xVector = Math.sign(Big(nextEndPoint.x).minus(Big(x2)).neg().toNumber()) + const yVector = Math.sign(Big(nextEndPoint.y).minus(Big(y2)).neg().toNumber()) + let scale = Big(currentLine.attributes.offset).pow(2).plus(Big(nextLine.attributes.offset).pow(2)).sqrt() + scale = scale.eq(0) ? Big(1) : scale + + const hipEdge = { + vertex1: { x: Big(x2).plus(scale.times(xVector)).toNumber(), y: Big(y2).plus(scale.times(yVector)).toNumber() }, + vertex2: nextEndPoint, + } + + /* const checkLine = new fabric.Line([hipEdge.vertex1.x, hipEdge.vertex1.y, hipEdge.vertex2.x, hipEdge.vertex2.y], { + stroke: 'yellow', + strokeWidth: 2, + parentId: roof.id, + }) + canvas.add(checkLine) + canvas.renderAll()*/ + + let intersectPoints = [] + roof.lines.forEach((line) => { + const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } + const intersection = edgesIntersection(hipEdge, lineEdge) + // console.log('intersection', intersection) + if ( + intersection && + !intersection.isIntersectionOutside && + Math.sign(nextEndPoint.x - x2) === Math.sign(nextEndPoint.x - intersection.x) && + Math.sign(nextEndPoint.y - y2) === Math.sign(nextEndPoint.y - intersection.y) + ) { + /*const intersectPoint = new fabric.Circle({ + left: intersection.x - 2, + top: intersection.y - 2, + radius: 4, + fill: 'blue', + parentId: roof.id, + }) + canvas.add(intersectPoint) + canvas.renderAll()*/ + + // console.log('nextEndPoint', nextEndPoint.x.toNumber(), nextEndPoint.y.toNumber()) + // console.log('intersection', intersection.x, intersection.y) + + const intersectSize = nextEndPoint.x + .minus(Big(intersection.x)) + .pow(2) + .plus(nextEndPoint.y.minus(Big(intersection.y)).pow(2)) + .abs() + .sqrt() + .toNumber() + + intersectPoints.push({ + intersection, + size: intersectSize, + }) + } + }) + + intersectPoints = intersectPoints.reduce((prev, current) => { + return prev.size < current.size ? prev : current + }, intersectPoints[0]) + + // console.log('intersectPoints : ', intersectPoints) + + if (intersectPoints && intersectPoints.intersection) { + nextHipLine = drawHipLine( + [intersectPoints.intersection.x, intersectPoints.intersection.y, nextEndPoint.x.toNumber(), nextEndPoint.y.toNumber()], + canvas, + roof, + textMode, + null, + prevDegree, + currentDegree, + ) + baseHipLines.push({ x1: x2, y1: y2, x2: nextEndPoint.x.toNumber(), y2: nextEndPoint.y.toNumber(), line: nextHipLine }) + } + } + }) + + // console.log('baseHipLines : ', baseHipLines) + // console.log('baseRidgeLines : ', baseRidgeLines) + /** 지붕선에 맞닫지 않은 부분을 확인하여 처리 한다.*/ + /** 1. 그려진 마루 중 추녀마루가 부재하는 경우를 판단. 부재는 연결된 라인이 홀수인 경우로 판단한다.*/ + let unFinishedRidge = [] + baseRidgeLines.forEach((current) => { + /*const checkLine = new fabric.Line([current.x1, current.y1, current.x2, current.y2], { + stroke: 'red', + strokeWidth: 2, + parentId: roof.id, + }) + canvas.add(checkLine) + canvas.renderAll()*/ + + let checkPoint = [ + { x: current.x1, y: current.y1, line: current, cnt: 0 }, + { x: current.x2, y: current.y2, line: current, cnt: 0 }, + ] + baseHipLines.forEach((line) => { + /*const checkPoint1 = new fabric.Circle({ + left: line.x1, + top: line.y1, + radius: 4, + fill: 'red', + parentId: roof.id, + }) + const checkPoint2 = new fabric.Circle({ + left: line.x2, + top: line.y2, + radius: 4, + fill: 'blue', + }) + canvas.add(checkPoint1) + canvas.add(checkPoint2) + canvas.renderAll()*/ + // console.log('current : ', current.x1, current.y1, current.x2, current.y2) + // console.log('line : ', line.x1, line.y1, line.x2, line.y2) + + if ((line.x1 === current.x1 && line.y1 === current.y1) || (line.x2 === current.x1 && line.y2 === current.y1)) { + checkPoint[0].cnt = checkPoint[0].cnt + 1 + } + if ((line.x1 === current.x2 && line.y1 === current.y2) || (line.x2 === current.x2 && line.y2 === current.y2)) { + checkPoint[1].cnt = checkPoint[1].cnt + 1 + } + }) + // console.log('checkPoint : ', checkPoint) + if (checkPoint[0].cnt === 0 || checkPoint[0].cnt % 2 !== 0) { + unFinishedRidge.push(checkPoint[0]) + } + if (checkPoint[1].cnt === 0 || checkPoint[1].cnt % 2 !== 0) { + unFinishedRidge.push(checkPoint[1]) + } + }) + // console.log('unFinishedRidge : ', unFinishedRidge) + //포인트 확인 + /*unFinishedRidge.forEach((current) => { + const checkCircle = new fabric.Circle({ + left: current.x, + top: current.y, + radius: 4, + fill: 'green', + parentId: roof.id, + }) + canvas.add(checkCircle) + canvas.renderAll() + })*/ + + /** 2. 그려진 추녀마루 중 완성되지 않은 것을 찾는다. 완성되지 않았다는 것은 연결된 포인트가 홀수인 경우로 판단한다.*/ + const findUnFinishedPoints = (baseHipLines) => { + let unFinishedPoint = [] + baseHipLines.forEach((current) => { + let checkPoint = [ + { x: current.x1, y: current.y1, checked: true, line: current.line }, + { x: current.x2, y: current.y2, checked: true, line: current.line }, + ] + baseLines.forEach((line) => { + if ((line.x1 === current.x1 && line.y1 === current.y1) || (line.x2 === current.x1 && line.y2 === current.y1)) { + checkPoint[0].checked = false + } + if ((line.x1 === current.x2 && line.y1 === current.y2) || (line.x2 === current.x2 && line.y2 === current.y2)) { + checkPoint[1].checked = false + } + }) + + const samePoints = [] + checkPoint + .filter((point) => point.checked) + .forEach((point) => { + baseHipLines.forEach((line) => { + if (line.x1 === point.x && line.y1 === point.y) { + samePoints.push({ x: point.x, y: point.y, line: line }) + } + if (line.x2 === point.x && line.y2 === point.y) { + samePoints.push({ x: point.x, y: point.y, line: line }) + } + }) + }) + if (samePoints.length > 0 && samePoints.length % 2 !== 0) { + unFinishedPoint.push(samePoints[0]) + } + }) + return unFinishedPoint + } + + let unFinishedPoint = findUnFinishedPoints(baseHipLines) + // console.log('unFinishedPoint : ', unFinishedPoint) + + /**3. 라인이 부재인 마루의 모자란 라인을 찾는다. 라인은 그려진 추녀마루 중에 완성되지 않은 추녀마루와 확인한다.*/ + /**3-1 라인을 그릴때 각도가 필요하기 때문에 각도를 구한다. 각도는 전체 지부의 각도가 같다면 하나로 처리 */ + let degreeAllLine = [] + baseLines.forEach((line) => { + const pitch = line.attributes.pitch + const degree = line.attributes.degree + // console.log('pitch : ', pitch, 'degree : ', degree) + degreeAllLine.push(pitch > 0 ? getDegreeByChon(pitch) : degree) + }) + + let currentDegree, prevDegree + degreeAllLine = [...new Set(degreeAllLine)] + // console.log('degreeAllLine : ', degreeAllLine) + currentDegree = degreeAllLine[0] + if (degreeAllLine.length === 1) { + prevDegree = currentDegree + } + + /*unFinishedPoint.forEach((current) => { + const circle = new fabric.Circle({ + left: current.x, + top: current.y, + radius: 4, + fill: 'red', + parentId: roof.id, + }) + canvas.add(circle) + canvas.renderAll() + })*/ + + /** 라인이 부재한 마루에 라인을 찾아 그린다.*/ + unFinishedRidge.forEach((current) => { + /*const checkLine = new fabric.Line([current.line.x1, current.line.y1, current.line.x2, current.line.y2], { + stroke: 'red', + strokeWidth: 2, + parentId: roof.id, + }) + canvas.add(checkLine) + canvas.renderAll()*/ + + /*const checkCircle = new fabric.Circle({ + left: current.x, + top: current.y, + radius: 4, + fill: 'green', + parentId: roof.id, + }) + canvas.add(checkCircle) + canvas.renderAll()*/ + + let checkPoints = [] + + unFinishedPoint + .filter( + (point) => + point.x !== current.x && + point.y !== current.y && + Big(point.x) + .minus(Big(current.x)) + .abs() + .minus(Big(point.y).minus(Big(current.y)).abs()) + .abs() + .lt(1), + ) + .forEach((point) => { + /*console.log('point : ', point.x, point.y) + const circle = new fabric.Circle({ + left: point.x, + top: point.y, + radius: 4, + fill: 'red', + parentId: roof.id, + }) + canvas.add(circle) + canvas.renderAll() + + const checkLine = new fabric.Line([current.x, current.y, point.x, point.y], { + stroke: 'green', + strokeWidth: 2, + parentId: roof.id, + }) + canvas.add(checkLine) + canvas.renderAll()*/ + + const pointEdge = { vertex1: { x: point.x, y: point.y }, vertex2: { x: current.x, y: current.y } } + let isIntersection = false + baseLines.forEach((line) => { + const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } + const intersection = edgesIntersection(pointEdge, lineEdge) + if (intersection && !intersection.isIntersectionOutside) { + isIntersection = true + } + }) + if (!isIntersection) { + checkPoints.push({ + point, + size: Big(point.x) + .minus(Big(current.x)) + .abs() + .pow(2) + .plus(Big(point.y).minus(Big(current.y)).abs().pow(2)) + .sqrt(), + }) + } + }) + // console.log('checkPoints : ', checkPoints) + // console.log('current ridge: ', current) + /*const checkCurrent = new fabric.Circle({ + left: current.x, + top: current.y, + radius: 4, + fill: 'blue', + parentId: roof.id, + }) + canvas.add(checkCurrent) + canvas.renderAll()*/ + if (checkPoints.length > 0) { + /*checkPoints.forEach((point) => { + const checkPoints = new fabric.Circle({ + left: point.point.x, + top: point.point.y, + radius: 4, + fill: 'red', + parentId: roof.id, + }) + canvas.add(checkPoints) + canvas.renderAll() + })*/ + + checkPoints.sort((a, b) => a.size - b.size) + // console.log('Big(2).minus(Big(current.cnt) : ', Big(2).minus(Big(current.cnt)).toNumber(), current.cnt) + const maxCnt = Big(2).minus(Big(current.cnt)).toNumber() + for (let i = 0; i < maxCnt; i++) { + const checkPoint = checkPoints[i] + // console.log('current : ', current.line.attributes.planeSize) + // console.log('1. checkPoint : ', checkPoint) + if (checkPoint) { + let point = [checkPoint.point.x, checkPoint.point.y, current.x, current.y] + let hipBasePoint + + baseHipLines.forEach((line) => { + const checkAngel1 = calculateAngle({ x: point[0], y: point[1] }, { x: point[2], y: point[3] }) + const checkAngel2 = calculateAngle({ x: line.line.x1, y: line.line.y1 }, { x: line.line.x2, y: line.line.y2 }) + + const isConnectLine = + ((line.line.x1 === point[0] && line.line.y1 === point[1]) || (line.line.x2 === point[0] && line.line.y2 === point[1])) && + checkAngel1 === checkAngel2 + const isOverlap = segmentsOverlap(line.line, { x1: point[0], y1: point[1], x2: point[2], y2: point[3] }) + const isSameLine = + (point[0] === line.x2 && point[1] === line.y2 && point[2] === line.x1 && point[3] === line.y1) || + (point[0] === line.x1 && point[1] === line.y1 && point[2] === line.x2 && point[3] === line.y2) + + if (isConnectLine || isOverlap || isSameLine) { + /** 겹치는 추녀마루와 하나의 선으로 변경*/ + const mergePoint = [ + { x: point[0], y: point[1] }, + { x: point[2], y: point[3] }, + { x: line.line.x1, y: line.line.y1 }, + { x: line.line.x2, y: line.line.y2 }, + ] + /** baseHipLines도 조정*/ + /*const mergeBasePoint = [ + { x: point[0], y: point[1] }, + { x: point[2], y: point[3] }, + { x: line.x1, y: line.y1 }, + { x: line.x2, y: line.y2 }, + ]*/ + mergePoint.sort((a, b) => a.x - b.x) + // mergeBasePoint.sort((a, b) => a.x - b.x) + /*const checkLine = new fabric.Line([mergeBasePoint[0].x, mergeBasePoint[0].y, mergeBasePoint[3].x, mergeBasePoint[3].y], { + stroke: 'red', + strokeWidth: 4, + parentId: roof.id, + }) + canvas.add(checkLine) + canvas.renderAll()*/ + + hipBasePoint = { x1: line.x1, y1: line.y1, x2: line.x2, y2: line.y2 } + // line.x1 = mergeBasePoint[0].x + // line.y1 = mergeBasePoint[0].y + // line.x2 = mergeBasePoint[3].x + // line.y2 = mergeBasePoint[3].y + point = [mergePoint[0].x, mergePoint[0].y, mergePoint[3].x, mergePoint[3].y] + canvas.remove(line.line) + baseHipLines = baseHipLines.filter((baseLine) => baseLine.line !== line.line) + } + }) + + const hipLine = drawHipLine(point, canvas, roof, textMode, null, prevDegree, currentDegree) + if (hipBasePoint) { + baseHipLines.push({ x1: hipBasePoint.x1, y1: hipBasePoint.y1, x2: point[2], y2: point[3], line: hipLine }) + } else { + baseHipLines.push({ x1: point[0], y1: point[1], x2: point[2], y2: point[3], line: hipLine }) + } + current.cnt = current.cnt + 1 + } + } + } + + /** 라인이 다 그려지지 않은 경우 */ + if (current.cnt % 2 !== 0) { + // console.log('no checkPoints :', current) + + let basePoints = baseLinePoints + .filter((point) => + Big(point.x) + .minus(Big(current.x)) + .abs() + .minus(Big(point.y).minus(Big(current.y)).abs()) + .abs() + .lt(1), + ) + .filter((point) => { + const pointEdge = { vertex1: { x: current.x, y: current.y }, vertex2: { x: point.x, y: point.y } } + + const intersectPoints = [] + baseLines.forEach((line) => { + const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } + const intersection = edgesIntersection(pointEdge, lineEdge) + if (intersection && !intersection.isIntersectionOutside) { + intersectPoints.push(intersection) + } + }) + return ( + !intersectPoints.filter( + (intersect) => !(Big(intersect.x).minus(Big(point.x)).abs().lt(1) && Big(intersect.y).minus(Big(point.y)).abs().lt(1)), + ).length > 0 + ) + }) + + /** hip을 그리기 위한 기본 길이*/ + let hipSize = 0 + if (basePoints.length > 0) { + basePoints.sort((a, b) => { + const aSize = Big(a.x) + .minus(Big(current.x)) + .abs() + .pow(2) + .plus(Big(a.y).minus(Big(current.y)).abs().pow(2)) + .sqrt() + const bSize = Big(b.x) + .minus(Big(current.x)) + .abs() + .pow(2) + .plus(Big(b.y).minus(Big(current.y)).abs().pow(2)) + .sqrt() + return aSize - bSize + }) + const baseHips = baseHipLines.filter((line) => line.x1 === basePoints[0].x && line.y1 === basePoints[0].y) + if (baseHips.length > 0) { + hipSize = Big(baseHips[0].line.attributes.planeSize).div(10) + } + } + + if (hipSize.eq(0)) { + const ridge = current.line + basePoints = baseHipLines.filter((line) => (line.x2 === ridge.x1 && line.y2 === ridge.y1) || (line.x2 === ridge.x2 && line.y2 === ridge.y2)) + basePoints.sort((a, b) => a.line.attributes.planeSize - b.line.attributes.planeSize) + hipSize = Big(basePoints[0].line.attributes.planeSize).div(10) + } + + hipSize = hipSize.times(hipSize).div(2).sqrt().round().toNumber() + + /** 현재 라인을 기준으로 45, 135, 225, 315 방향을 확인하기 위한 좌표, hip은 45도 방향으로만 그린다. */ + const checkEdge45 = { vertex1: { x: current.x, y: current.y }, vertex2: { x: current.x + hipSize, y: current.y - hipSize } } + const checkEdge135 = { vertex1: { x: current.x, y: current.y }, vertex2: { x: current.x + hipSize, y: current.y + hipSize } } + const checkEdge225 = { vertex1: { x: current.x, y: current.y }, vertex2: { x: current.x - hipSize, y: current.y + hipSize } } + const checkEdge315 = { vertex1: { x: current.x, y: current.y }, vertex2: { x: current.x - hipSize, y: current.y - hipSize } } + + let intersectPoints = [] + let notIntersect45 = true, + notIntersect135 = true, + notIntersect225 = true, + notIntersect315 = true + baseLines.forEach((line) => { + const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } + const intersection45 = edgesIntersection(checkEdge45, lineEdge) + const intersection135 = edgesIntersection(checkEdge135, lineEdge) + const intersection225 = edgesIntersection(checkEdge225, lineEdge) + const intersection315 = edgesIntersection(checkEdge315, lineEdge) + + if (intersection45 && !intersection45.isIntersectionOutside) { + intersectPoints.push(intersection45) + notIntersect45 = false + } + if (intersection135 && !intersection135.isIntersectionOutside) { + intersectPoints.push(intersection135) + notIntersect135 = false + } + if (intersection225 && !intersection225.isIntersectionOutside) { + intersectPoints.push(intersection225) + notIntersect225 = false + } + if (intersection315 && !intersection315.isIntersectionOutside) { + intersectPoints.push(intersection315) + notIntersect315 = false + } + }) + /** baseLine의 각 좌표와 교차하는 경우로 한정*/ + intersectPoints = intersectPoints.filter((is) => baseLinePoints.filter((point) => point.x === is.x && point.y === is.y).length > 0) + /** baseLine과 교차하는 좌표의 경우 이미 그려진 추녀마루가 존재한다면 제외한다. */ + intersectPoints = intersectPoints.filter((point) => baseHipLines.filter((hip) => hip.x1 === point.x && hip.y1 === point.y).length === 0) + /** baseLine과 교차하지 않는 포인트를 추가한다.*/ + if (notIntersect45) { + intersectPoints.push(checkEdge45.vertex2) + } + if (notIntersect135) { + intersectPoints.push(checkEdge135.vertex2) + } + if (notIntersect225) { + intersectPoints.push(checkEdge225.vertex2) + } + if (notIntersect315) { + intersectPoints.push(checkEdge315.vertex2) + } + intersectPoints.forEach((is) => { + const points = [current.x, current.y, is.x, is.y] + const hipLine = drawHipLine(points, canvas, roof, textMode, null, prevDegree, currentDegree) + baseHipLines.push({ x1: points[0], y1: points[1], x2: points[2], y2: points[3], line: hipLine }) + current.cnt = current.cnt + 1 + }) + } + }) + + /** hip이 짝수개가 맞다아있는데 마루와 연결되지 않는 포인트를 찾는다. 그려지지 않은 마루를 찾기 위함.*/ + let noRidgeHipPoints = baseHipLines + .filter((current) => { + const lines = baseHipLines + .filter((line) => line !== current) + .filter((line) => (line.x1 === current.x2 && line.y1 === current.y2) || (line.x2 === current.x2 && line.y2 === current.y2)) + + return lines.length !== 0 && lines.length % 2 !== 0 + }) + .filter( + (current) => + baseRidgeLines.filter((line) => (line.x1 === current.x2 && line.y1 === current.y2) || (line.x2 === current.x2 && line.y2 === current.y2)) + .length === 0, + ) + + noRidgeHipPoints.forEach((current) => { + const orthogonalPoints = noRidgeHipPoints.filter( + (point) => point !== current && ((current.x2 === point.x2 && current.y2 !== point.y2) || (current.x2 !== point.x2 && current.y2 === point.y2)), + ) + + /** 직교 하는 포인트가 존재 할때 마루를 그린다. */ + if (orthogonalPoints.length > 0) { + const points = [current.x2, current.y2, orthogonalPoints[0].x2, orthogonalPoints[0].y2] + const ridgeLine = drawRidgeLine(points, canvas, roof, textMode) + baseRidgeLines.push(ridgeLine) + /** array에서 object 삭제*/ + noRidgeHipPoints = noRidgeHipPoints.filter((point) => point !== current && point !== orthogonalPoints[0]) + } + }) + + /** 직교 하는 포인트가 없는 경우 남은 포인트 처리 */ + const checkEdgeLines = [] + noRidgeHipPoints.forEach((current) => { + /*const checkCircle = new fabric.Circle({ + left: current.x2, + top: current.y2, + radius: 4, + fill: 'red', + parentId: roof.id, + }) + canvas.add(checkCircle) + canvas.renderAll()*/ + noRidgeHipPoints.forEach((current) => { + noRidgeHipPoints + .filter((point) => point !== current) + .forEach((point) => { + checkEdgeLines.push( + { vertex1: { x: current.x2, y: current.y2 }, vertex2: { x: current.x2, y: point.y2 } }, + { vertex1: { x: current.x2, y: point.y2 }, vertex2: { x: point.x2, y: point.y2 } }, + { vertex1: { x: point.x2, y: point.y2 }, vertex2: { x: point.x2, y: current.y2 } }, + { vertex1: { x: point.x2, y: current.y2 }, vertex2: { x: current.x2, y: current.y2 } }, + ) + }) + }) + }) + // 라인 확인용 + /*checkEdgeLines.forEach((edge) => { + const checkLine = new fabric.Line([edge.vertex1.x, edge.vertex1.y, edge.vertex2.x, edge.vertex2.y], { + stroke: 'yellow', + strokeWidth: 2, + parentId: roof.id, + }) + canvas.add(checkLine) + canvas.renderAll() + })*/ + + /** 연결되지 않은 포인트를 찾아서 해당 포인트를 가지고 있는 라인을 찾는다. */ + let unFinishedPoints = [] + let unFinishedLines = [] + let intersectPoints = [] + baseHipLines.forEach((line) => { + if (baseLinePoints.filter((point) => point.x === line.x1 && point.y === line.y1).length === 0) { + unFinishedPoints.push({ x: line.x1, y: line.y1 }) + } + if (baseLinePoints.filter((point) => point.x === line.x2 && point.y === line.y2).length === 0) { + unFinishedPoints.push({ x: line.x2, y: line.y2 }) + } + }) + unFinishedPoints + .filter((point) => unFinishedPoints.filter((p) => p !== point && p.x === point.x && p.y === point.y).length === 0) + .forEach((point) => { + baseHipLines + .filter((line) => (line.x1 === point.x && line.y1 === point.y) || (line.x2 === point.x && line.y2 === point.y)) + .forEach((line) => unFinishedLines.push(line)) + }) + unFinishedLines.forEach((line) => { + /*const checkLine = new fabric.Line([line.x1, line.y1, line.x2, line.y2], { + stroke: 'red', + strokeWidth: 2, + parentId: roof.id, + }) + canvas.add(checkLine) + canvas.renderAll()*/ + + const xVector = Math.sign(Big(line.x2).minus(Big(line.x1))) + const yVector = Math.sign(Big(line.y2).minus(Big(line.y1))) + let lineIntersectPoints = [] + checkEdgeLines.forEach((edge) => { + const intersectEdge = edgesIntersection(edge, { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }) + const intersection = [] + if (intersectEdge) { + const isXVector = Math.sign(Big(intersectEdge.x).minus(Big(line.x1))) === xVector + const isYVector = Math.sign(Big(intersectEdge.y).minus(Big(line.y1))) === yVector + if (isXVector && isYVector) { + lineIntersectPoints.push({ + intersection: intersectEdge, + size: Big(intersectEdge.x) + .minus(Big(line.x1)) + .abs() + .pow(2) + .plus(Big(intersectEdge.y).minus(Big(line.y1)).abs().pow(2)) + .sqrt() + .round(1) + .toNumber(), + }) + } + } + }) + + lineIntersectPoints = lineIntersectPoints.filter( + (point, index, self) => index === self.findIndex((p) => p.intersection.x === point.intersection.x && p.intersection.y === point.intersection.y), + ) + lineIntersectPoints.sort((a, b) => a.size - b.size) + intersectPoints.push({ intersection: lineIntersectPoints[0].intersection, line }) + }) + + intersectPoints.forEach((intersection) => { + const intersectPoint = intersection.intersection + noRidgeHipPoints.forEach((hipPoint) => { + const angle = calculateAngle({ x: intersectPoint.x, y: intersectPoint.y }, { x: hipPoint.x2, y: hipPoint.y2 }) + if (angle === 0 || angle === 180 || angle === 90 || angle === -90) { + const ridgeLine = drawRidgeLine([intersectPoint.x, intersectPoint.y, hipPoint.x2, hipPoint.y2], canvas, roof, textMode) + baseRidgeLines.push(ridgeLine) + let baseHipLine = baseHipLines.filter((line) => line === intersection.line)[0] + baseHipLine.x2 = intersectPoint.x + baseHipLines.y2 = intersectPoint.y + intersection.line.x2 = intersectPoint.x + intersection.line.y2 = intersectPoint.y + /** 보조선 라인 조정*/ + const hipLine = intersection.line.line + /** 평면길이 */ + const planeSize = calcLinePlaneSize({ + x1: hipLine.x1, + y1: hipLine.y1, + x2: intersectPoint.x, + y2: intersectPoint.y, + }) + /** 실제길이 */ + const actualSize = + prevDegree === currentDegree + ? calcLineActualSize( + { + x1: hipLine.x1, + y1: hipLine.y1, + x2: intersectPoint.x, + y2: intersectPoint.y, + }, + currentDegree, + ) + : 0 + hipLine.set({ x2: intersectPoint.x, y2: intersectPoint.y, attributes: { roofId: roof.id, planeSize, actualSize } }) + hipLine.fire('modified') + intersectPoints = intersectPoints.filter((isp) => isp !== intersection) + noRidgeHipPoints = noRidgeHipPoints.filter((hip) => hip !== hipPoint) + } + }) + }) + + console.log('baseRidgeLines :', baseRidgeLines) + + const ridgeAllPoints = [] + baseRidgeLines.forEach((line) => ridgeAllPoints.push({ x: line.x1, y: line.y1 }, { x: line.x2, y: line.y2 })) + + console.log('ridgeAllPoints :', ridgeAllPoints) + + console.log('지붕 마루 갯수 확인 : ', baseRidgeLines.length, getMaxRidge(baseLines.length)) + + ridgeAllPoints.forEach((current) => { + console.log('current :', current) + + const ridgeLines = [], + hipLines = [] + + ridgeAllPoints + .filter((point) => point !== current) + .forEach((point) => { + /*canvas + .getObjects() + .filter((obj) => obj.name === 'checkPoint' || obj.name === 'checkLine') + .forEach((obj) => canvas.remove(obj)) + canvas.renderAll() + const checkPoint = new fabric.Circle({ + left: current.x, + top: current.y, + radius: 4, + fill: 'blue', + parentId: roof.id, + name: 'checkPoint', + }) + canvas.add(checkPoint) + canvas.renderAll()*/ + let checkRidgeLine, checkHipLine + /** 직선인 경우 마루 확인*/ + if ( + baseRidgeLines.length < getMaxRidge(baseLines.length) && + ((point.x === current.x && point.y !== current.y) || (point.x !== current.x && point.y === current.y)) + ) { + checkRidgeLine = { x1: current.x, y1: current.y, x2: point.x, y2: point.y } + } + /** 대각선인 경우 hip확인*/ + if ( + Big(point.x) + .minus(Big(current.x)) + .abs() + .eq(Big(point.y).minus(Big(current.y)).abs()) + ) { + checkHipLine = { x1: current.x, y1: current.y, x2: point.x, y2: point.y } + } + + if (checkRidgeLine) { + const ridgePoints = [checkRidgeLine.x1, checkRidgeLine.y1, checkRidgeLine.x2, checkRidgeLine.y2] + + let baseIntersection = false + const ridgeInterSection = [] + const ridgeEdge = { vertex1: { x: ridgePoints[0], y: ridgePoints[1] }, vertex2: { x: ridgePoints[2], y: ridgePoints[3] } } + baseLines.forEach((line) => { + const intersection = edgesIntersection(ridgeEdge, { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }) + if (intersection && !intersection.isIntersectionOutside) { + ridgeInterSection.push(intersection) + } + }) + baseRidgeLines.forEach((line) => { + const intersection = edgesIntersection(ridgeEdge, { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }) + if (intersection && !intersection.isIntersectionOutside) { + ridgeInterSection.push(intersection) + } + }) + baseHipLines.forEach((line) => { + const intersection = edgesIntersection(ridgeEdge, { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }) + if (intersection && !intersection.isIntersectionOutside) { + ridgeInterSection.push(intersection) + } + }) + const otherRidgeInterSection = ridgeInterSection.filter( + (intersection) => + !( + (intersection.x === ridgePoints[0] && intersection.y === ridgePoints[1]) || + (intersection.x === ridgePoints[2] && intersection.y === ridgePoints[3]) + ), + ) + const alreadyRidges = baseRidgeLines.filter( + (line) => + (line.x1 === ridgePoints[0] && line.y1 === ridgePoints[1] && line.x2 === ridgePoints[2] && line.y2 === ridgePoints[3]) || + (line.x1 === ridgePoints[2] && line.y1 === ridgePoints[3] && line.x2 === ridgePoints[0] && line.y2 === ridgePoints[1]), + ) + + if (!baseIntersection && alreadyRidges.length === 0 && otherRidgeInterSection.length === 0) { + const ridgeLine = drawRidgeLine(ridgePoints, canvas, roof, textMode) + baseRidgeLines.push(ridgeLine) + } + + // checkLine1 = { x1: checkRidgeLine.x1, y1: checkRidgeLine.y1, x2: checkRidgeLine.x2, y2: checkRidgeLine.y2 } + // checkLine2 = { x1: checkRidgeLine.x2, y1: checkRidgeLine.y2, x2: checkRidgeLine.x1, y2: checkRidgeLine.y1 } + } + if (checkHipLine) { + const hipPoints = [checkHipLine.x1, checkHipLine.y1, checkHipLine.x2, checkHipLine.y2] + + let baseIntersection = false + let hipInterSection = [] + const hipEdge = { vertex1: { x: hipPoints[0], y: hipPoints[1] }, vertex2: { x: hipPoints[2], y: hipPoints[3] } } + baseLines.forEach((line) => { + const intersection = edgesIntersection(hipEdge, { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }) + if (intersection && !intersection.isIntersectionOutside) { + baseIntersection = true + } + }) + baseRidgeLines.forEach((line) => { + const intersection = edgesIntersection(hipEdge, { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }) + if (intersection && !intersection.isIntersectionOutside) { + hipInterSection.push(intersection) + } + }) + baseHipLines.forEach((line) => { + const intersection = edgesIntersection(hipEdge, { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }) + if (intersection && !intersection.isIntersectionOutside) { + hipInterSection.push(intersection) + } + }) + const otherHipInterSection = hipInterSection.filter( + (intersection) => + !( + (intersection.x === hipPoints[0] && intersection.y === hipPoints[1]) || + (intersection.x === hipPoints[2] && intersection.y === hipPoints[3]) + ), + ) + const alreadyHips = baseHipLines.filter( + (line) => + (line.x1 === hipPoints[0] && line.y1 === hipPoints[1] && line.x2 === hipPoints[2] && line.y2 === hipPoints[3]) || + (line.x1 === hipPoints[2] && line.y1 === hipPoints[3] && line.x2 === hipPoints[0] && line.y2 === hipPoints[1]), + ) + + if (!baseIntersection && alreadyHips.length === 0 && otherHipInterSection.length === 0) { + const hipLine = drawHipLine(hipPoints, canvas, roof, textMode, null, prevDegree, currentDegree) + baseHipLines.push({ x1: hipPoints[0], y1: hipPoints[1], x2: hipPoints[2], y2: hipPoints[3], line: hipLine }) + } + } + }) + }) + + /** 연결 되지 않은 마루 갯수*/ + /*const missingCount = Big(baseRidgeLines.length) + .minus(connectRidgePoint.length + 1) + .toNumber() + + console.log('missingCount :', missingCount)*/ + /*baseRidgeLines.forEach((current) => { + baseRidgeLines + .filter((ridge) => ridge !== current) + .forEach((ridge) => { + /!** 직선인 경우 확인 *!/ + let checkLineEdge + if (current.x1 === ridge.x1 || current.y1 === ridge.y1) { + checkLineEdge = { vertex1: { x: current.x1, y: current.y1 }, vertex2: { x: ridge.x1, y: ridge.y1 } } + } else if (current.x2 === ridge.x1 || current.y2 === ridge.y1) { + checkLineEdge = { vertex1: { x: current.x2, y: current.y2 }, vertex2: { x: ridge.x1, y: ridge.y1 } } + } else if (current.x1 === ridge.x2 || current.y1 === ridge.y2) { + checkLineEdge = { vertex1: { x: current.x1, y: current.y1 }, vertex2: { x: ridge.x2, y: ridge.y2 } } + } else if (current.x2 === ridge.x2 || current.y2 === ridge.y2) { + checkLineEdge = { vertex1: { x: current.x2, y: current.y2 }, vertex2: { x: ridge.x2, y: ridge.y2 } } + } + + console.log('checkLineEdge :', checkLineEdge) + if (checkLineEdge) { + let hasIntersectLine = false + /!** 외벽선을 통과하는지 확인*!/ + baseLines.forEach((line) => { + const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } + const intersection = edgesIntersection(checkLineEdge, lineEdge) + if (intersection && !intersection.isIntersectionOutside) { + hasIntersectLine = true + } + }) + + /!** hipLines를 통과하는지 확인*!/ + baseHipLines.forEach((line) => { + const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } + const intersection = edgesIntersection(checkLineEdge, lineEdge) + if (intersection && !intersection.isIntersectionOutside) { + hasIntersectLine = true + } + }) + + // if (!hasIntersectLine) { + const checkLine = new fabric.Line([checkLineEdge.vertex1.x, checkLineEdge.vertex1.y, checkLineEdge.vertex2.x, checkLineEdge.vertex2.y], { + stroke: 'red', + strokeWidth: 2, + parentId: roof.id, + }) + canvas.add(checkLine) + canvas.renderAll() + // } + } + }) + })*/ + + /*drawRidge(roof, canvas, textMode) drawHips(roof, canvas, textMode) connectLinePoint(roof, canvas, textMode) modifyRidge(roof, canvas, textMode) - drawCenterLine(roof, canvas, textMode) + drawCenterLine(roof, canvas, textMode)*/ +} + +/** + * 추녀 마루를 그린다. + * @param points + * @param canvas + * @param roof + * @param textMode + * @param currentRoof + * @param prevDegree + * @param currentDegree + */ +const drawHipLine = (points, canvas, roof, textMode, currentRoof, prevDegree, currentDegree) => { + // console.log('drawHipLine : ', points) + const hip = new QLine(points, { + parentId: roof.id, + fontSize: roof.fontSize, + stroke: '#1083E3', + strokeWidth: 2, + name: LINE_TYPE.SUBLINE.HIP, + textMode: textMode, + attributes: { + roofId: roof.id, + // currentRoofId: currentRoof.id, + planeSize: calcLinePlaneSize({ + x1: points[0], + y1: points[1], + x2: points[2], + y2: points[3], + }), + actualSize: + prevDegree === currentDegree + ? calcLineActualSize( + { + x1: points[0], + y1: points[1], + x2: points[2], + y2: points[3], + }, + currentDegree, + ) + : 0, + }, + }) + + canvas.add(hip) + canvas.renderAll() + return hip +} + +/** + * 마루를 그린다. + * @param points + * @param canvas + * @param roof + * @param textMode + * @returns {*} + */ +const drawRidgeLine = (points, canvas, roof, textMode) => { + const ridge = new QLine(points, { + parentId: roof.id, + fontSize: roof.fontSize, + stroke: '#1083E3', + strokeWidth: 2, + name: LINE_TYPE.SUBLINE.RIDGE, + textMode: textMode, + attributes: { + roofId: roof.id, + planeSize: calcLinePlaneSize({ + x1: points[0], + y1: points[1], + x2: points[2], + y2: points[3], + }), + actualSize: calcLinePlaneSize({ + x1: points[0], + y1: points[1], + x2: points[2], + y2: points[3], + }), + }, + }) + canvas.add(ridge) + canvas.renderAll() + + return ridge +} + +/** + * 벡터를 정규화(Normalization)하는 함수 + * @param v + * @returns {{x: *, y: *}|{x: number, y: number}} + */ +const normalizeVector = (v) => { + /** 벡터의 크기(길이)*/ + const magnitude = Big(v.x).pow(2).plus(Big(v.y).pow(2)).sqrt() + if (magnitude.eq(0)) return { x: 0, y: 0 } // 크기가 0일 경우 (예외 처리) + return { x: Big(v.x).div(magnitude).toNumber(), y: Big(v.y).div(magnitude).toNumber() } +} + +/** + * 사잇각의 절반 방향 벡터 계산 함수 + * @param line1 + * @param line2 + * @returns {{x: *, y: *}|{x: number, y: number}} + */ +const getHalfAngleVector = (line1, line2) => { + const v1 = { x: Big(line1.x2).minus(Big(line1.x1)).toNumber(), y: Big(line1.y1).minus(Big(line1.y2)).toNumber() } + const v2 = { x: Big(line2.x2).minus(Big(line2.x1)).toNumber(), y: Big(line2.y1).minus(Big(line2.y2)).toNumber() } + + /** + * 벡터 정규화 + * @type {{x: *, y: *}|{x: number, y: number}} + */ + const unitV1 = normalizeVector(v1) // 첫 번째 벡터를 정규화 + const unitV2 = normalizeVector(v2) // 두 번째 벡터를 정규화 + + /** + * 두 벡터를 더합니다 + * @type {{x: *, y: *}} + */ + const summedVector = { x: Big(unitV1.x).plus(Big(unitV2.x)).toNumber(), y: Big(unitV1.y).plus(Big(unitV2.y)).toNumber() } + + /** 결과 벡터를 정규화하여 사잇각 벡터를 반환합니다 */ + return normalizeVector(summedVector) } /** @@ -757,7 +3082,7 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { * @param canvas * @param textMode */ -const drawRidge = (roof, canvas, textMode) => { +const drawRidges = (roof, canvas, textMode) => { console.log('roof.lines : ', roof.lines) const wallLines = canvas?.getObjects().find((object) => object.name === POLYGON_TYPE.WALL && object.attributes.roofId === roof.id).lines // 외벽의 라인 const roofLines = roof.lines // 지붕의 라인 -- 2.47.2 From 10c2668a675471e2e178d9511eaf471ca0a67c8d Mon Sep 17 00:00:00 2001 From: Jaeyoung Lee Date: Mon, 17 Mar 2025 19:05:56 +0900 Subject: [PATCH 003/109] =?UTF-8?q?=EB=A7=88=EB=A3=A8=20=ED=98=95=EC=83=81?= =?UTF-8?q?=20=EB=B2=84=EA=B7=B8=20=EC=88=98=EC=A0=95=20=EB=B0=8F=20innerL?= =?UTF-8?q?ines=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/util/qpolygon-utils.js | 46 ++++++++++++++++++++++++++++++-------- 1 file changed, 37 insertions(+), 9 deletions(-) diff --git a/src/util/qpolygon-utils.js b/src/util/qpolygon-utils.js index f6916e55..070fb096 100644 --- a/src/util/qpolygon-utils.js +++ b/src/util/qpolygon-utils.js @@ -846,6 +846,9 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { let baseHipLines = [] /** 용마루 */ let baseRidgeLines = [] + /** 용마루의 갯수*/ + let baseRidgeCount = 0 + /** ⨆ 모양 처마에 추녀마루를 그린다. */ drawEavesFirstLines.forEach((current) => { const { currentLine, prevLine, nextLine } = current @@ -1629,6 +1632,8 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { // console.log('oppositeSize : ', oppositeSize, 'lineMinSize : ', lineMinSize.toNumber(), 'ridgeSize : ', ridgeSize.toNumber()) if (ridgeSize.gt(0)) { + baseRidgeCount = baseRidgeCount + 1 + console.log('baseRidgeCount : ', baseRidgeCount) const points = [ startPoint.x, startPoint.y, @@ -1691,21 +1696,20 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { .forEach((ridge2) => { let overlap = segmentsOverlap(ridge, ridge2) if (overlap) { - roof.ridges = roof.ridges.filter((r) => r !== ridge && r !== ridge2) - roof.innerLines = roof.innerLines.filter((l) => l !== ridge && l !== ridge2) roof.canvas.remove(ridge) roof.canvas.remove(ridge2) baseRidgeLines = baseRidgeLines.filter((r) => r !== ridge && r !== ridge2) + baseRidgeCount = baseRidgeCount - 2 + let x1 = Math.min(ridge.x1, ridge2.x1, ridge.x2, ridge2.x2) let x2 = Math.max(ridge.x1, ridge2.x1, ridge.x2, ridge2.x2) let y1 = Math.min(ridge.y1, ridge2.y1, ridge.y2, ridge2.y2) let y2 = Math.max(ridge.y1, ridge2.y1, ridge.y2, ridge2.y2) const newRidge = drawRidgeLine([x1, y1, x2, y2], canvas, roof, textMode) - roof.ridges.push(newRidge) - roof.innerLines.push(newRidge) baseRidgeLines.push(newRidge) + baseRidgeCount = baseRidgeCount + 1 } }) }) @@ -2600,11 +2604,32 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { /** 직교 하는 포인트가 존재 할때 마루를 그린다. */ if (orthogonalPoints.length > 0) { + baseRidgeCount = baseRidgeCount + 1 const points = [current.x2, current.y2, orthogonalPoints[0].x2, orthogonalPoints[0].y2] const ridgeLine = drawRidgeLine(points, canvas, roof, textMode) baseRidgeLines.push(ridgeLine) /** array에서 object 삭제*/ - noRidgeHipPoints = noRidgeHipPoints.filter((point) => point !== current && point !== orthogonalPoints[0]) + // noRidgeHipPoints = noRidgeHipPoints.filter((point) => point !== current && point !== orthogonalPoints[0]) + } + }) + + /** 중복제거*/ + baseRidgeLines.forEach((current) => { + const sameRidge = baseRidgeLines.filter( + (line) => + line !== current && + ((line.x1 === current.x1 && line.y1 === current.y1 && line.x2 === current.x2 && line.y2 === current.y2) || + (line.x1 === current.x2 && line.y1 === current.y2 && line.x2 === current.x1 && line.y2 === current.y1)), + ) + + console.log('sameRidge : ', sameRidge) + if (sameRidge.length > 0) { + sameRidge.forEach((duplicateLine) => { + const index = baseRidgeLines.indexOf(duplicateLine) + if (index !== -1) { + baseRidgeLines.splice(index, 1) + } + }) } }) @@ -2709,6 +2734,7 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { noRidgeHipPoints.forEach((hipPoint) => { const angle = calculateAngle({ x: intersectPoint.x, y: intersectPoint.y }, { x: hipPoint.x2, y: hipPoint.y2 }) if (angle === 0 || angle === 180 || angle === 90 || angle === -90) { + baseRidgeCount = baseRidgeCount + 1 const ridgeLine = drawRidgeLine([intersectPoint.x, intersectPoint.y, hipPoint.x2, hipPoint.y2], canvas, roof, textMode) baseRidgeLines.push(ridgeLine) let baseHipLine = baseHipLines.filter((line) => line === intersection.line)[0] @@ -2753,11 +2779,9 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { console.log('ridgeAllPoints :', ridgeAllPoints) - console.log('지붕 마루 갯수 확인 : ', baseRidgeLines.length, getMaxRidge(baseLines.length)) + console.log('지붕 마루 갯수 확인 : ', baseRidgeLines.length, baseRidgeCount, getMaxRidge(baseLines.length)) ridgeAllPoints.forEach((current) => { - console.log('current :', current) - const ridgeLines = [], hipLines = [] @@ -2781,8 +2805,9 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { canvas.renderAll()*/ let checkRidgeLine, checkHipLine /** 직선인 경우 마루 확인*/ + console.log('baseRidgeCount :', baseRidgeCount, baseRidgeLines.length) if ( - baseRidgeLines.length < getMaxRidge(baseLines.length) && + baseRidgeCount < getMaxRidge(baseLines.length) && ((point.x === current.x && point.y !== current.y) || (point.x !== current.x && point.y === current.y)) ) { checkRidgeLine = { x1: current.x, y1: current.y, x2: point.x, y2: point.y } @@ -2835,6 +2860,7 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { ) if (!baseIntersection && alreadyRidges.length === 0 && otherRidgeInterSection.length === 0) { + baseRidgeCount = baseRidgeCount + 1 const ridgeLine = drawRidgeLine(ridgePoints, canvas, roof, textMode) baseRidgeLines.push(ridgeLine) } @@ -2887,6 +2913,8 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { }) }) + roof.innerLines = [...baseRidgeLines, ...baseHipLines.map((line) => line.line)] + /** 연결 되지 않은 마루 갯수*/ /*const missingCount = Big(baseRidgeLines.length) .minus(connectRidgePoint.length + 1) -- 2.47.2 From 2537240bb0eac4d4ba08a583814037409722dcf8 Mon Sep 17 00:00:00 2001 From: Jaeyoung Lee Date: Tue, 18 Mar 2025 09:36:48 +0900 Subject: [PATCH 004/109] =?UTF-8?q?=EC=A4=91=EB=B3=B5=EB=90=9C=20=EB=A7=88?= =?UTF-8?q?=EB=A3=A8=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/util/qpolygon-utils.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/util/qpolygon-utils.js b/src/util/qpolygon-utils.js index 070fb096..77bc8cae 100644 --- a/src/util/qpolygon-utils.js +++ b/src/util/qpolygon-utils.js @@ -2629,6 +2629,7 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { if (index !== -1) { baseRidgeLines.splice(index, 1) } + canvas.remove(duplicateLine) }) } }) -- 2.47.2 From 3b4195207032276be8dd9ecef4aa348a83d9b805 Mon Sep 17 00:00:00 2001 From: Jaeyoung Lee Date: Tue, 18 Mar 2025 16:23:38 +0900 Subject: [PATCH 005/109] =?UTF-8?q?=EC=A7=80=EB=B6=95=20=EB=8D=AE=EA=B0=9C?= =?UTF-8?q?=20=EA=B3=84=EC=82=B0=20=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/util/qpolygon-utils.js | 490 +++++++------------------------------ 1 file changed, 82 insertions(+), 408 deletions(-) diff --git a/src/util/qpolygon-utils.js b/src/util/qpolygon-utils.js index ab828fb4..52551cdd 100644 --- a/src/util/qpolygon-utils.js +++ b/src/util/qpolygon-utils.js @@ -854,6 +854,13 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { /** ⨆ 모양 처마에 추녀마루를 그린다. */ drawEavesFirstLines.forEach((current) => { + // 확인용 라인, 포인트 제거 + /*canvas + .getObjects() + .filter((obj) => obj.name === 'checkPoint' || obj.name === 'checkLine') + .forEach((obj) => canvas.remove(obj)) + canvas.renderAll() +*/ const { currentLine, prevLine, nextLine } = current let { x1, x2, y1, y2 } = currentLine let beforePrevLine, afterNextLine @@ -934,13 +941,6 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { }, } - /*const checkMidLine = new fabric.Line([midLineEdge.vertex1.x, midLineEdge.vertex1.y, midLineEdge.vertex2.x, midLineEdge.vertex2.y], { - stroke: 'blue', - strokeWidth: 2, - }) - canvas.add(checkMidLine) - canvas.renderAll()*/ - /** 현재 라인의 중심 지점에서 현재라인의 길이만큼 다음라인의 방향만큼 거리를 확인한다*/ baseLines .filter((line) => line !== currentLine) @@ -981,7 +981,6 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { ).length > 0 /** 6각 */ - // console.log('isConnect : ', isConnect) if (isConnect) { const checkScale = currentSize.pow(2).plus(currentSize.pow(2)).sqrt() const intersectBaseLine = [] @@ -998,15 +997,6 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }, ) if (intersection && !intersection.isIntersectionOutside) { - /*const intersectCircle = new fabric.Circle({ - left: intersection.x - 2, - top: intersection.y - 2, - radius: 4, - fill: 'red', - parentId: roof.id, - }) - canvas.add(intersectCircle) - canvas.renderAll()*/ intersectBaseLine.push({ intersection, distance: Big(intersection.x) @@ -1031,15 +1021,6 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }, ) if (intersection && !intersection.isIntersectionOutside) { - /*const intersectCircle = new fabric.Circle({ - left: intersection.x - 2, - top: intersection.y - 2, - radius: 4, - fill: 'orange', - parentId: roof.id, - }) - canvas.add(intersectCircle) - canvas.renderAll()*/ intersectBaseLine.push({ intersection, distance: Big(intersection.x) @@ -1053,28 +1034,9 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { } const intersection = intersectBaseLine.reduce((prev, current) => (prev.distance < current.distance ? prev : current), intersectBaseLine[0]) if (intersection) { - /*const intersectCircle = new fabric.Circle({ - left: intersection.intersection.x - 2, - top: intersection.intersection.y - 2, - radius: 4, - fill: 'green', - parentId: roof.id, - }) - canvas.add(intersectCircle) - canvas.renderAll()*/ - hipLength = intersection.distance } } else { - //라인 확인용 - /*const checkCurrentLine = new fabric.Line([x1, y1, x2, y2], { - stroke: 'yellow', - strokeWidth: 4, - parentId: roof.id, - }) - canvas.add(checkCurrentLine) - canvas.renderAll()*/ - const rightAngleLine = baseLines .filter( (line) => @@ -1151,39 +1113,14 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { ] } - /* const checkHipLine = new fabric.Line(checkHipPoints, { - stroke: 'red', - strokeWidth: 2, - parentId: roof.id, - }) - canvas.add(checkHipLine) - canvas.renderAll()*/ - if (checkHipPoints) { const intersectPoints = [] rightAngleLine.forEach((line) => { - /*const checkRightAngle = new fabric.Line([line.x1, line.y1, line.x2, line.y2], { - stroke: 'red', - strokeWidth: 4, - parentId: roof.id, - }) - canvas.add(checkRightAngle) - canvas.renderAll()*/ - const intersection = edgesIntersection( { vertex1: { x: checkHipPoints[0], y: checkHipPoints[1] }, vertex2: { x: checkHipPoints[2], y: checkHipPoints[3] } }, { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }, ) if (intersection) { - /*const checkRightAngleCircle = new fabric.Circle({ - left: intersection.x - 2, - top: intersection.y - 2, - radius: 4, - fill: 'red', - parentId: roof.id, - }) - canvas.add(checkRightAngleCircle) - canvas.renderAll()*/ intersectPoints.push({ intersection, distance: Big(intersection.x) @@ -1196,28 +1133,11 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { }) oppositeCurrentLine.forEach((line) => { - /*const checkOpposite = new fabric.Line([line.x1, line.y1, line.x2, line.y2], { - stroke: 'blue', - strokeWidth: 4, - parentId: roof.id, - }) - canvas.add(checkOpposite) - canvas.renderAll() -*/ const intersection = edgesIntersection( { vertex1: { x: checkHipPoints[0], y: checkHipPoints[1] }, vertex2: { x: checkHipPoints[2], y: checkHipPoints[3] } }, { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }, ) if (intersection) { - /*const checkOppositeCircle = new fabric.Circle({ - left: intersection.x - 2, - top: intersection.y - 2, - radius: 4, - fill: 'blue', - parentId: roof.id, - }) - canvas.add(checkOppositeCircle) - canvas.renderAll()*/ intersectPoints.push({ intersection, distance: Big(intersection.x) @@ -1229,19 +1149,8 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { } }) - // console.log('intersectPoints : ', intersectPoints) - const intersection = intersectPoints.reduce((prev, current) => (prev.distance.lt(current.distance) ? prev : current), intersectPoints[0]) if (intersection) { - /*const checkHipCircle = new fabric.Circle({ - left: intersection.intersection.x - 2, - top: intersection.intersection.y - 2, - radius: 4, - fill: 'green', - parentId: roof.id, - }) - canvas.add(checkHipCircle) - canvas.renderAll()*/ hipLength = intersection.distance.div(2) } } @@ -1263,7 +1172,6 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { { vertex1: { x: x1, y: y1 }, vertex2: prevEndPoint }, { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }, ) - // console.log('intersection : ', intersection, 'prevEndPoint : ', prevEndPoint.x.toNumber(), prevEndPoint.y.toNumber()) if (intersection && !intersection.isIntersectionOutside) { intersectRidgeLine.push({ intersection, @@ -1276,7 +1184,6 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { } }) const intersectRidge = intersectRidgeLine.reduce((prev, current) => (prev.distance < current.distance ? prev : current), intersectRidgeLine[0]) - // console.log('intersectRidge : ', intersectRidge) if (intersectRidge) { prevEndPoint.x = Big(intersectRidge.intersection.x).round(1) @@ -1295,14 +1202,6 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { vertex2: prevEndPoint, } - /*const checkLine = new fabric.Line([hipEdge.vertex1.x, hipEdge.vertex1.y, hipEdge.vertex2.x, hipEdge.vertex2.y], { - stroke: 'yellow', - strokeWidth: 2, - parentId: roof.id, - }) - canvas.add(checkLine) - canvas.renderAll()*/ - let intersectPoints = [] /** 외벽선에서 추녀 마루가 겹치는 경우에 대한 확인*/ roof.lines.forEach((line) => { @@ -1314,16 +1213,6 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { Math.sign(prevEndPoint.x - x1) === Math.sign(prevEndPoint.x - intersection.x) && Math.sign(prevEndPoint.y - y1) === Math.sign(prevEndPoint.y - intersection.y) ) { - /* const intersectPoint = new fabric.Circle({ - left: intersection.x - 2, - top: intersection.y - 2, - radius: 4, - fill: 'red', - parentId: roof.id, - }) - canvas.add(intersectPoint) - canvas.renderAll()*/ - const intersectSize = prevEndPoint.x .minus(Big(intersection.x)) .pow(2) @@ -1367,13 +1256,11 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { const intersectRidgeLine = [] baseRidgeLines.forEach((line) => { - // console.log('nextEndPoint : ', nextEndPoint.x.toNumber(), nextEndPoint.y.toNumber()) - // console.log('intersection : ', 'x : ', line.x1, 'y : ', line.y1, 'x : ', line.x2, 'y : ', line.y2) const intersection = edgesIntersection( { vertex1: { x: x2, y: y2 }, vertex2: nextEndPoint }, { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }, ) - // console.log('intersection : ', intersection, 'nextEndPoint : ', nextEndPoint.x.toNumber(), nextEndPoint.y.toNumber()) + if (intersection && !intersection.isIntersectionOutside) { intersectRidgeLine.push({ intersection, @@ -1386,7 +1273,6 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { } }) const intersectRidge = intersectRidgeLine.reduce((prev, current) => (prev.distance < current.distance ? prev : current), intersectRidgeLine[0]) - // console.log('intersectRidge : ', intersectRidge) if (intersectRidge) { nextEndPoint.x = Big(intersectRidge.intersection.x).round(1) @@ -1403,38 +1289,16 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { vertex2: nextEndPoint, } - /* const checkLine = new fabric.Line([hipEdge.vertex1.x, hipEdge.vertex1.y, hipEdge.vertex2.x, hipEdge.vertex2.y], { - stroke: 'yellow', - strokeWidth: 2, - parentId: roof.id, - }) - canvas.add(checkLine) - canvas.renderAll()*/ - let intersectPoints = [] roof.lines.forEach((line) => { const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } const intersection = edgesIntersection(hipEdge, lineEdge) - // console.log('intersection', intersection) if ( intersection && !intersection.isIntersectionOutside && Math.sign(nextEndPoint.x - x2) === Math.sign(nextEndPoint.x - intersection.x) && Math.sign(nextEndPoint.y - y2) === Math.sign(nextEndPoint.y - intersection.y) ) { - /*const intersectPoint = new fabric.Circle({ - left: intersection.x - 2, - top: intersection.y - 2, - radius: 4, - fill: 'blue', - parentId: roof.id, - }) - canvas.add(intersectPoint) - canvas.renderAll()*/ - - // console.log('nextEndPoint', nextEndPoint.x.toNumber(), nextEndPoint.y.toNumber()) - // console.log('intersection', intersection.x, intersection.y) - const intersectSize = nextEndPoint.x .minus(Big(intersection.x)) .pow(2) @@ -1454,8 +1318,6 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { return prev.size < current.size ? prev : current }, intersectPoints[0]) - // console.log('intersectPoints : ', intersectPoints) - if (intersectPoints && intersectPoints.intersection) { nextHipLine = drawHipLine( [intersectPoints.intersection.x, intersectPoints.intersection.y, nextEndPoint.x.toNumber(), nextEndPoint.y.toNumber()], @@ -2154,44 +2016,80 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { } }) + /** baseHipLine 이 ridge에 붙지 않은 경우 확인 */ + baseHipLines.forEach((hipLine) => { + const ridgeCount = baseRidgeLines.filter( + (ridgeLine) => (hipLine.x2 === ridgeLine.x1 && hipLine.y2 === ridgeLine.y1) || (hipLine.x2 === ridgeLine.x2 && hipLine.y2 === ridgeLine.y2), + ).length + if (ridgeCount === 0) { + const hipXVector = Big(hipLine.x2).minus(hipLine.x1) + const hipYVector = Big(hipLine.y2).minus(hipLine.y1) + const hipSize = hipXVector.abs().pow(2).plus(hipYVector.abs().pow(2)).sqrt() + + const intersectRidgePoints = [] + + const hipLineEdge = { vertex1: { x: hipLine.x1, y: hipLine.y1 }, vertex2: { x: hipLine.x2, y: hipLine.y2 } } + baseRidgeLines.forEach((ridgeLine) => { + const ridgeLineEdge = { vertex1: { x: ridgeLine.x1, y: ridgeLine.y1 }, vertex2: { x: ridgeLine.x2, y: ridgeLine.y2 } } + const intersection = edgesIntersection(hipLineEdge, ridgeLineEdge) + + if (intersection) { + const intersectXVector = Big(intersection.x).minus(Big(hipLine.x1)) + const intersectYVector = Big(intersection.y).minus(Big(hipLine.y1)) + const intersectSize = intersectXVector.pow(2).plus(intersectYVector.pow(2)).sqrt() + + if (!intersection.isIntersectionOutside) { + intersectRidgePoints.push({ + x: intersection.x, + y: intersection.y, + size: intersectSize, + }) + } else if ( + ((intersection.x === ridgeLine.x1 && intersection.y === ridgeLine.y1) || + (intersection.x === ridgeLine.x2 && intersection.y === ridgeLine.y2)) && + Math.sign(hipXVector.toNumber()) === Math.sign(intersectXVector.toNumber()) && + Math.sign(hipYVector.toNumber()) === Math.sign(intersectYVector.toNumber()) && + intersectSize.gt(0) && + intersectSize.lt(hipSize) + ) { + intersectRidgePoints.push({ + x: intersection.x, + y: intersection.y, + size: intersectSize, + }) + } + } + }) + + intersectRidgePoints.sort((prev, current) => prev.size.minus(current.size).toNumber()) + if (intersectRidgePoints.length > 0) { + const oldPlaneSize = hipLine.line.attributes.planeSize + const oldActualSize = hipLine.line.attributes.actualSize + const theta = Big(Math.acos(Big(oldPlaneSize).div(oldActualSize))) + .times(180) + .div(Math.PI) + const planeSize = calcLinePlaneSize({ x1: hipLine.line.x1, y1: hipLine.line.y1, x2: hipLine.line.x2, y2: hipLine.line.y2 }) + hipLine.x2 = intersectRidgePoints[0].x + hipLine.y2 = intersectRidgePoints[0].y + hipLine.line.set({ x2: intersectRidgePoints[0].x, y2: intersectRidgePoints[0].y }) + hipLine.line.attributes.planeSize = planeSize + hipLine.line.attributes.actualSize = planeSize === oldActualSize ? 0 : Big(planeSize).div(theta).round(1).toNumber() + hipLine.line.fire('modified') + } + } + }) + // console.log('baseHipLines : ', baseHipLines) // console.log('baseRidgeLines : ', baseRidgeLines) /** 지붕선에 맞닫지 않은 부분을 확인하여 처리 한다.*/ /** 1. 그려진 마루 중 추녀마루가 부재하는 경우를 판단. 부재는 연결된 라인이 홀수인 경우로 판단한다.*/ let unFinishedRidge = [] baseRidgeLines.forEach((current) => { - /*const checkLine = new fabric.Line([current.x1, current.y1, current.x2, current.y2], { - stroke: 'red', - strokeWidth: 2, - parentId: roof.id, - }) - canvas.add(checkLine) - canvas.renderAll()*/ - let checkPoint = [ { x: current.x1, y: current.y1, line: current, cnt: 0 }, { x: current.x2, y: current.y2, line: current, cnt: 0 }, ] baseHipLines.forEach((line) => { - /*const checkPoint1 = new fabric.Circle({ - left: line.x1, - top: line.y1, - radius: 4, - fill: 'red', - parentId: roof.id, - }) - const checkPoint2 = new fabric.Circle({ - left: line.x2, - top: line.y2, - radius: 4, - fill: 'blue', - }) - canvas.add(checkPoint1) - canvas.add(checkPoint2) - canvas.renderAll()*/ - // console.log('current : ', current.x1, current.y1, current.x2, current.y2) - // console.log('line : ', line.x1, line.y1, line.x2, line.y2) - if ((line.x1 === current.x1 && line.y1 === current.y1) || (line.x2 === current.x1 && line.y2 === current.y1)) { checkPoint[0].cnt = checkPoint[0].cnt + 1 } @@ -2207,19 +2105,6 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { unFinishedRidge.push(checkPoint[1]) } }) - // console.log('unFinishedRidge : ', unFinishedRidge) - //포인트 확인 - /*unFinishedRidge.forEach((current) => { - const checkCircle = new fabric.Circle({ - left: current.x, - top: current.y, - radius: 4, - fill: 'green', - parentId: roof.id, - }) - canvas.add(checkCircle) - canvas.renderAll() - })*/ /** 2. 그려진 추녀마루 중 완성되지 않은 것을 찾는다. 완성되지 않았다는 것은 연결된 포인트가 홀수인 경우로 판단한다.*/ const findUnFinishedPoints = (baseHipLines) => { @@ -2259,7 +2144,6 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { } let unFinishedPoint = findUnFinishedPoints(baseHipLines) - // console.log('unFinishedPoint : ', unFinishedPoint) /**3. 라인이 부재인 마루의 모자란 라인을 찾는다. 라인은 그려진 추녀마루 중에 완성되지 않은 추녀마루와 확인한다.*/ /**3-1 라인을 그릴때 각도가 필요하기 때문에 각도를 구한다. 각도는 전체 지부의 각도가 같다면 하나로 처리 */ @@ -2267,50 +2151,18 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { baseLines.forEach((line) => { const pitch = line.attributes.pitch const degree = line.attributes.degree - // console.log('pitch : ', pitch, 'degree : ', degree) degreeAllLine.push(pitch > 0 ? getDegreeByChon(pitch) : degree) }) let currentDegree, prevDegree degreeAllLine = [...new Set(degreeAllLine)] - // console.log('degreeAllLine : ', degreeAllLine) currentDegree = degreeAllLine[0] if (degreeAllLine.length === 1) { prevDegree = currentDegree } - /*unFinishedPoint.forEach((current) => { - const circle = new fabric.Circle({ - left: current.x, - top: current.y, - radius: 4, - fill: 'red', - parentId: roof.id, - }) - canvas.add(circle) - canvas.renderAll() - })*/ - /** 라인이 부재한 마루에 라인을 찾아 그린다.*/ unFinishedRidge.forEach((current) => { - /*const checkLine = new fabric.Line([current.line.x1, current.line.y1, current.line.x2, current.line.y2], { - stroke: 'red', - strokeWidth: 2, - parentId: roof.id, - }) - canvas.add(checkLine) - canvas.renderAll()*/ - - /*const checkCircle = new fabric.Circle({ - left: current.x, - top: current.y, - radius: 4, - fill: 'green', - parentId: roof.id, - }) - canvas.add(checkCircle) - canvas.renderAll()*/ - let checkPoints = [] unFinishedPoint @@ -2326,25 +2178,6 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { .lt(1), ) .forEach((point) => { - /*console.log('point : ', point.x, point.y) - const circle = new fabric.Circle({ - left: point.x, - top: point.y, - radius: 4, - fill: 'red', - parentId: roof.id, - }) - canvas.add(circle) - canvas.renderAll() - - const checkLine = new fabric.Line([current.x, current.y, point.x, point.y], { - stroke: 'green', - strokeWidth: 2, - parentId: roof.id, - }) - canvas.add(checkLine) - canvas.renderAll()*/ - const pointEdge = { vertex1: { x: point.x, y: point.y }, vertex2: { x: current.x, y: current.y } } let isIntersection = false baseLines.forEach((line) => { @@ -2366,37 +2199,11 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { }) } }) - // console.log('checkPoints : ', checkPoints) - // console.log('current ridge: ', current) - /*const checkCurrent = new fabric.Circle({ - left: current.x, - top: current.y, - radius: 4, - fill: 'blue', - parentId: roof.id, - }) - canvas.add(checkCurrent) - canvas.renderAll()*/ if (checkPoints.length > 0) { - /*checkPoints.forEach((point) => { - const checkPoints = new fabric.Circle({ - left: point.point.x, - top: point.point.y, - radius: 4, - fill: 'red', - parentId: roof.id, - }) - canvas.add(checkPoints) - canvas.renderAll() - })*/ - checkPoints.sort((a, b) => a.size - b.size) - // console.log('Big(2).minus(Big(current.cnt) : ', Big(2).minus(Big(current.cnt)).toNumber(), current.cnt) const maxCnt = Big(2).minus(Big(current.cnt)).toNumber() for (let i = 0; i < maxCnt; i++) { const checkPoint = checkPoints[i] - // console.log('current : ', current.line.attributes.planeSize) - // console.log('1. checkPoint : ', checkPoint) if (checkPoint) { let point = [checkPoint.point.x, checkPoint.point.y, current.x, current.y] let hipBasePoint @@ -2422,27 +2229,9 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { { x: line.line.x2, y: line.line.y2 }, ] /** baseHipLines도 조정*/ - /*const mergeBasePoint = [ - { x: point[0], y: point[1] }, - { x: point[2], y: point[3] }, - { x: line.x1, y: line.y1 }, - { x: line.x2, y: line.y2 }, - ]*/ mergePoint.sort((a, b) => a.x - b.x) - // mergeBasePoint.sort((a, b) => a.x - b.x) - /*const checkLine = new fabric.Line([mergeBasePoint[0].x, mergeBasePoint[0].y, mergeBasePoint[3].x, mergeBasePoint[3].y], { - stroke: 'red', - strokeWidth: 4, - parentId: roof.id, - }) - canvas.add(checkLine) - canvas.renderAll()*/ hipBasePoint = { x1: line.x1, y1: line.y1, x2: line.x2, y2: line.y2 } - // line.x1 = mergeBasePoint[0].x - // line.y1 = mergeBasePoint[0].y - // line.x2 = mergeBasePoint[3].x - // line.y2 = mergeBasePoint[3].y point = [mergePoint[0].x, mergePoint[0].y, mergePoint[3].x, mergePoint[3].y] canvas.remove(line.line) baseHipLines = baseHipLines.filter((baseLine) => baseLine.line !== line.line) @@ -2462,8 +2251,6 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { /** 라인이 다 그려지지 않은 경우 */ if (current.cnt % 2 !== 0) { - // console.log('no checkPoints :', current) - let basePoints = baseLinePoints .filter((point) => Big(point.x) @@ -2624,8 +2411,6 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { ((line.x1 === current.x1 && line.y1 === current.y1 && line.x2 === current.x2 && line.y2 === current.y2) || (line.x1 === current.x2 && line.y1 === current.y2 && line.x2 === current.x1 && line.y2 === current.y1)), ) - - console.log('sameRidge : ', sameRidge) if (sameRidge.length > 0) { sameRidge.forEach((duplicateLine) => { const index = baseRidgeLines.indexOf(duplicateLine) @@ -2640,15 +2425,6 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { /** 직교 하는 포인트가 없는 경우 남은 포인트 처리 */ const checkEdgeLines = [] noRidgeHipPoints.forEach((current) => { - /*const checkCircle = new fabric.Circle({ - left: current.x2, - top: current.y2, - radius: 4, - fill: 'red', - parentId: roof.id, - }) - canvas.add(checkCircle) - canvas.renderAll()*/ noRidgeHipPoints.forEach((current) => { noRidgeHipPoints .filter((point) => point !== current) @@ -2662,16 +2438,6 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { }) }) }) - // 라인 확인용 - /*checkEdgeLines.forEach((edge) => { - const checkLine = new fabric.Line([edge.vertex1.x, edge.vertex1.y, edge.vertex2.x, edge.vertex2.y], { - stroke: 'yellow', - strokeWidth: 2, - parentId: roof.id, - }) - canvas.add(checkLine) - canvas.renderAll() - })*/ /** 연결되지 않은 포인트를 찾아서 해당 포인트를 가지고 있는 라인을 찾는다. */ let unFinishedPoints = [] @@ -2693,20 +2459,11 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { .forEach((line) => unFinishedLines.push(line)) }) unFinishedLines.forEach((line) => { - /*const checkLine = new fabric.Line([line.x1, line.y1, line.x2, line.y2], { - stroke: 'red', - strokeWidth: 2, - parentId: roof.id, - }) - canvas.add(checkLine) - canvas.renderAll()*/ - const xVector = Math.sign(Big(line.x2).minus(Big(line.x1))) const yVector = Math.sign(Big(line.y2).minus(Big(line.y1))) let lineIntersectPoints = [] checkEdgeLines.forEach((edge) => { const intersectEdge = edgesIntersection(edge, { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }) - const intersection = [] if (intersectEdge) { const isXVector = Math.sign(Big(intersectEdge.x).minus(Big(line.x1))) === xVector const isYVector = Math.sign(Big(intersectEdge.y).minus(Big(line.y1))) === yVector @@ -2730,7 +2487,9 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { (point, index, self) => index === self.findIndex((p) => p.intersection.x === point.intersection.x && p.intersection.y === point.intersection.y), ) lineIntersectPoints.sort((a, b) => a.size - b.size) - intersectPoints.push({ intersection: lineIntersectPoints[0].intersection, line }) + if (lineIntersectPoints.length > 0) { + intersectPoints.push({ intersection: lineIntersectPoints[0].intersection, line }) + } }) intersectPoints.forEach((intersection) => { @@ -2776,40 +2535,15 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { }) }) - console.log('baseRidgeLines :', baseRidgeLines) - const ridgeAllPoints = [] baseRidgeLines.forEach((line) => ridgeAllPoints.push({ x: line.x1, y: line.y1 }, { x: line.x2, y: line.y2 })) - console.log('ridgeAllPoints :', ridgeAllPoints) - - console.log('지붕 마루 갯수 확인 : ', baseRidgeLines.length, baseRidgeCount, getMaxRidge(baseLines.length)) - ridgeAllPoints.forEach((current) => { - const ridgeLines = [], - hipLines = [] - ridgeAllPoints .filter((point) => point !== current) .forEach((point) => { - /*canvas - .getObjects() - .filter((obj) => obj.name === 'checkPoint' || obj.name === 'checkLine') - .forEach((obj) => canvas.remove(obj)) - canvas.renderAll() - const checkPoint = new fabric.Circle({ - left: current.x, - top: current.y, - radius: 4, - fill: 'blue', - parentId: roof.id, - name: 'checkPoint', - }) - canvas.add(checkPoint) - canvas.renderAll()*/ let checkRidgeLine, checkHipLine /** 직선인 경우 마루 확인*/ - console.log('baseRidgeCount :', baseRidgeCount, baseRidgeLines.length) if ( baseRidgeCount < getMaxRidge(baseLines.length) && ((point.x === current.x && point.y !== current.y) || (point.x !== current.x && point.y === current.y)) @@ -2868,9 +2602,6 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { const ridgeLine = drawRidgeLine(ridgePoints, canvas, roof, textMode) baseRidgeLines.push(ridgeLine) } - - // checkLine1 = { x1: checkRidgeLine.x1, y1: checkRidgeLine.y1, x2: checkRidgeLine.x2, y2: checkRidgeLine.y2 } - // checkLine2 = { x1: checkRidgeLine.x2, y1: checkRidgeLine.y2, x2: checkRidgeLine.x1, y2: checkRidgeLine.y1 } } if (checkHipLine) { const hipPoints = [checkHipLine.x1, checkHipLine.y1, checkHipLine.x2, checkHipLine.y2] @@ -2919,61 +2650,12 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { roof.innerLines = [...baseRidgeLines, ...baseHipLines.map((line) => line.line)] - /** 연결 되지 않은 마루 갯수*/ - /*const missingCount = Big(baseRidgeLines.length) - .minus(connectRidgePoint.length + 1) - .toNumber() - - console.log('missingCount :', missingCount)*/ - /*baseRidgeLines.forEach((current) => { - baseRidgeLines - .filter((ridge) => ridge !== current) - .forEach((ridge) => { - /!** 직선인 경우 확인 *!/ - let checkLineEdge - if (current.x1 === ridge.x1 || current.y1 === ridge.y1) { - checkLineEdge = { vertex1: { x: current.x1, y: current.y1 }, vertex2: { x: ridge.x1, y: ridge.y1 } } - } else if (current.x2 === ridge.x1 || current.y2 === ridge.y1) { - checkLineEdge = { vertex1: { x: current.x2, y: current.y2 }, vertex2: { x: ridge.x1, y: ridge.y1 } } - } else if (current.x1 === ridge.x2 || current.y1 === ridge.y2) { - checkLineEdge = { vertex1: { x: current.x1, y: current.y1 }, vertex2: { x: ridge.x2, y: ridge.y2 } } - } else if (current.x2 === ridge.x2 || current.y2 === ridge.y2) { - checkLineEdge = { vertex1: { x: current.x2, y: current.y2 }, vertex2: { x: ridge.x2, y: ridge.y2 } } - } - - console.log('checkLineEdge :', checkLineEdge) - if (checkLineEdge) { - let hasIntersectLine = false - /!** 외벽선을 통과하는지 확인*!/ - baseLines.forEach((line) => { - const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } - const intersection = edgesIntersection(checkLineEdge, lineEdge) - if (intersection && !intersection.isIntersectionOutside) { - hasIntersectLine = true - } - }) - - /!** hipLines를 통과하는지 확인*!/ - baseHipLines.forEach((line) => { - const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } - const intersection = edgesIntersection(checkLineEdge, lineEdge) - if (intersection && !intersection.isIntersectionOutside) { - hasIntersectLine = true - } - }) - - // if (!hasIntersectLine) { - const checkLine = new fabric.Line([checkLineEdge.vertex1.x, checkLineEdge.vertex1.y, checkLineEdge.vertex2.x, checkLineEdge.vertex2.y], { - stroke: 'red', - strokeWidth: 2, - parentId: roof.id, - }) - canvas.add(checkLine) - canvas.renderAll() - // } - } - }) - })*/ + /** 확인용 라인, 포인트 제거 */ + canvas + .getObjects() + .filter((obj) => obj.name === 'checkPoint' || obj.name === 'checkLine') + .forEach((obj) => canvas.remove(obj)) + canvas.renderAll() /*drawRidge(roof, canvas, textMode) drawHips(roof, canvas, textMode) @@ -2993,7 +2675,6 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { * @param currentDegree */ const drawHipLine = (points, canvas, roof, textMode, currentRoof, prevDegree, currentDegree) => { - // console.log('drawHipLine : ', points) const hip = new QLine(points, { parentId: roof.id, fontSize: roof.fontSize, @@ -5882,16 +5563,9 @@ export const calcLinePlaneSize = (points) => { * @returns number */ export const calcLineActualSize = (points, degree) => { - const { x1, y1, x2, y2 } = points const planeSize = calcLinePlaneSize(points) - let height = Big(Math.tan(Big(degree).times(Math.PI / 180))).times(planeSize) - /** - * 대각선일 경우 높이 계산 변경 - */ - if (x1 !== x2 && y1 !== y2) { - height = Big(Math.tan(Big(degree).times(Math.PI / 180))).times(Big(x1).minus(x2).times(10).round()) - } - return Big(planeSize).pow(2).plus(height.pow(2)).sqrt().abs().round().toNumber() + const theta = Big(Math.cos(Big(degree).times(Math.PI).div(180))) + return Big(planeSize).div(theta).round(1).toNumber() } export const createLinesFromPolygon = (points) => { -- 2.47.2 From b6e70f6eb0a628db9297ad4fbf58ab48bb6f8b6c Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Wed, 19 Mar 2025 09:52:35 +0900 Subject: [PATCH 006/109] =?UTF-8?q?innerLines=20=EC=B4=88=EA=B8=B0=20?= =?UTF-8?q?=EC=85=8B=ED=8C=85=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/common/useCanvasConfigInitialize.js | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/hooks/common/useCanvasConfigInitialize.js b/src/hooks/common/useCanvasConfigInitialize.js index 91d65edb..46b71588 100644 --- a/src/hooks/common/useCanvasConfigInitialize.js +++ b/src/hooks/common/useCanvasConfigInitialize.js @@ -9,7 +9,7 @@ import { globalFontAtom } from '@/store/fontAtom' import { useRoof } from '@/hooks/common/useRoof' import { usePolygon } from '@/hooks/usePolygon' import { useRoofFn } from '@/hooks/common/useRoofFn' -import { POLYGON_TYPE } from '@/common/common' +import { LINE_TYPE, POLYGON_TYPE } from '@/common/common' export function useCanvasConfigInitialize() { const canvas = useRecoilValue(canvasState) @@ -64,6 +64,7 @@ export function useCanvasConfigInitialize() { groupDimensionInit() reGroupInit() //그룹 객체 재그룹 moduleInit() + innerLinesInit() // innerLines 세팅 추가 } const gridInit = () => { @@ -228,5 +229,23 @@ export function useCanvasConfigInitialize() { }) } + const innerLinesInit = () => { + const roofs = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF) + // innerLine이 세팅이 안되어있는경우 찾아서 세팅한다. + let innerLineTypes = Object.keys(LINE_TYPE.SUBLINE).map((key, value) => LINE_TYPE.SUBLINE[key]) + + roofs.forEach((roof) => { + roof.innerLines = canvas + .getObjects() + .filter( + (obj) => + obj.type === 'QLine' && + obj.attributes?.type !== 'pitchSizeLine' && + obj.attributes?.roofId === roof.id && + innerLineTypes.includes(obj.name), + ) + }) + } + return { canvasLoadInit, gridInit } } -- 2.47.2 From d8341385a4828212ff212744950e96a4dea6ce5e Mon Sep 17 00:00:00 2001 From: yoosangwook Date: Fri, 21 Mar 2025 16:44:01 +0900 Subject: [PATCH 007/109] =?UTF-8?q?chore:=20=ED=99=98=EA=B2=BD=EB=B3=80?= =?UTF-8?q?=EC=88=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.development | 8 +++++++- .env.production | 8 +++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/.env.development b/.env.development index cfc50e71..bd024a3b 100644 --- a/.env.development +++ b/.env.development @@ -8,4 +8,10 @@ SESSION_SECRET="i3iHH1yp2/2SpQSIySQ4bpyc4g0D+zCF9FAn5xUG0+Y=" NEXT_PUBLIC_CONVERTER_API_URL="https://v2.convertapi.com/convert/dwg/to/png?Secret=secret_yAS4QDalL9jgQ7vS" NEXT_PUBLIC_Q_ORDER_AUTO_LOGIN_URL="http://q-order-stg.q-cells.jp:8120/eos/login/autoLogin" -NEXT_PUBLIC_Q_MUSUBI_AUTO_LOGIN_URL="http://q-musubi-stg.q-cells.jp:8120/qm/login/autoLogin" \ No newline at end of file +NEXT_PUBLIC_Q_MUSUBI_AUTO_LOGIN_URL="http://q-musubi-stg.q-cells.jp:8120/qm/login/autoLogin" + +AWS_REGION="ap-northeast-2" +AMPLIFY_BUCKET="interplug" +AWS_ACCESS_KEY_ID="AKIAVWMWJCUXFHEAZ4FR" +AWS_SECRET_ACCESS_KEY="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" +AWS_S3_BASE_URL="https://interplug.s3.ap-northeast-2.amazonaws.com" diff --git a/.env.production b/.env.production index 09bb27d3..28c4dd86 100644 --- a/.env.production +++ b/.env.production @@ -10,4 +10,10 @@ NEXT_PUBLIC_CONVERTER_API_URL="https://v2.convertapi.com/convert/dwg/to/png?Secr # NEXT_PUBLIC_Q_ORDER_AUTO_LOGIN_URL="https://q-order.q-cells.jp/eos/login/autoLogin" # NEXT_PUBLIC_Q_MUSUBI_AUTO_LOGIN_URL="https://q-musubi.q-cells.jp/qm/login/autoLogin" NEXT_PUBLIC_Q_ORDER_AUTO_LOGIN_URL="http://q-order-stg.q-cells.jp:8120/eos/login/autoLogin" -NEXT_PUBLIC_Q_MUSUBI_AUTO_LOGIN_URL="http://q-musubi-stg.q-cells.jp:8120/qm/login/autoLogin" \ No newline at end of file +NEXT_PUBLIC_Q_MUSUBI_AUTO_LOGIN_URL="http://q-musubi-stg.q-cells.jp:8120/qm/login/autoLogin" + +AWS_REGION="ap-northeast-2" +AMPLIFY_BUCKET="interplug" +AWS_ACCESS_KEY_ID="AKIAVWMWJCUXFHEAZ4FR" +AWS_SECRET_ACCESS_KEY="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" +AWS_S3_BASE_URL="https://interplug.s3.ap-northeast-2.amazonaws.com" \ No newline at end of file -- 2.47.2 From 20cef812ae305d12b88e0b42cec36be401e5ea4b Mon Sep 17 00:00:00 2001 From: yoosangwook Date: Fri, 21 Mar 2025 16:47:14 +0900 Subject: [PATCH 008/109] =?UTF-8?q?chore:=20=EB=B8=8C=EB=9D=BC=EC=9A=B0?= =?UTF-8?q?=EC=A0=80=EC=9A=A9=20=ED=99=98=EA=B2=BD=EB=B3=80=EC=88=98=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.development | 2 +- .env.production | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.env.development b/.env.development index bd024a3b..4f613c70 100644 --- a/.env.development +++ b/.env.development @@ -14,4 +14,4 @@ AWS_REGION="ap-northeast-2" AMPLIFY_BUCKET="interplug" AWS_ACCESS_KEY_ID="AKIAVWMWJCUXFHEAZ4FR" AWS_SECRET_ACCESS_KEY="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" -AWS_S3_BASE_URL="https://interplug.s3.ap-northeast-2.amazonaws.com" +NEXT_PUBLIC_AWS_S3_BASE_URL="https://interplug.s3.ap-northeast-2.amazonaws.com" diff --git a/.env.production b/.env.production index 28c4dd86..f0951f7a 100644 --- a/.env.production +++ b/.env.production @@ -16,4 +16,4 @@ AWS_REGION="ap-northeast-2" AMPLIFY_BUCKET="interplug" AWS_ACCESS_KEY_ID="AKIAVWMWJCUXFHEAZ4FR" AWS_SECRET_ACCESS_KEY="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" -AWS_S3_BASE_URL="https://interplug.s3.ap-northeast-2.amazonaws.com" \ No newline at end of file +NEXT_PUBLIC_AWS_S3_BASE_URL="https://interplug.s3.ap-northeast-2.amazonaws.com" \ No newline at end of file -- 2.47.2 From 25240bc3c266886e6e04042ca436e342dd4e15ee Mon Sep 17 00:00:00 2001 From: Jaeyoung Lee Date: Mon, 24 Mar 2025 13:38:46 +0900 Subject: [PATCH 009/109] =?UTF-8?q?=EB=8F=99=EC=9D=B4=EB=8F=99,=20?= =?UTF-8?q?=ED=98=95=EC=9D=B4=EB=8F=99=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80.=20=EC=9D=B4=EC=97=90=20=EB=94=B0=EB=A5=B8=20?= =?UTF-8?q?=EB=B3=B4=EC=A1=B0=EC=84=A0=20=EC=9E=91=EC=84=B1=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EC=A4=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/common/common.js | 2 + src/components/fabric/QPolygon.js | 9 +- .../modal/movement/type/FlowLine.jsx | 11 +- .../floor-plan/modal/movement/type/Updown.jsx | 3 +- src/hooks/roofcover/useMovementSetting.js | 1190 +++++------------ src/hooks/roofcover/useRoofShapeSetting.js | 1 + src/hooks/useMode.js | 57 +- src/util/qpolygon-utils.js | 351 ++++- 8 files changed, 646 insertions(+), 978 deletions(-) diff --git a/src/common/common.js b/src/common/common.js index d82d43f0..d336244f 100644 --- a/src/common/common.js +++ b/src/common/common.js @@ -203,6 +203,8 @@ export const SAVE_KEY = [ 'fontWeight', 'dormerAttributes', 'toFixed', + 'startPoint', + 'endPoint', ] export const OBJECT_PROTOTYPE = [fabric.Line.prototype, fabric.Polygon.prototype, fabric.Triangle.prototype, fabric.Group.prototype] diff --git a/src/components/fabric/QPolygon.js b/src/components/fabric/QPolygon.js index f19687aa..65fe9d11 100644 --- a/src/components/fabric/QPolygon.js +++ b/src/components/fabric/QPolygon.js @@ -29,12 +29,13 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, { this.texts = [] this.hips = [] this.ridges = [] - this.connectRidges = [] this.cells = [] this.innerLines = [] this.children = [] this.separatePolygon = [] this.toFixed = options.toFixed ?? 1 + this.baseLines = [] + // this.colorLines = [] // 소수점 전부 제거 points.forEach((point) => { @@ -211,6 +212,12 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, { * @param settingModalFirstOptions */ drawHelpLine(settingModalFirstOptions) { + /* innerLines 초기화 */ + this.innerLines.forEach((line) => { + this.canvas.remove(line) + }) + this.canvas.renderAll() + let textMode = 'plane' const dimensionDisplay = settingModalFirstOptions?.dimensionDisplay.find((opt) => opt.selected).id diff --git a/src/components/floor-plan/modal/movement/type/FlowLine.jsx b/src/components/floor-plan/modal/movement/type/FlowLine.jsx index 565a9ff8..3c23a30a 100644 --- a/src/components/floor-plan/modal/movement/type/FlowLine.jsx +++ b/src/components/floor-plan/modal/movement/type/FlowLine.jsx @@ -15,13 +15,18 @@ export default function FlowLine({ FLOW_LINE_REF }) { const currentObject = useRecoilValue(currentObjectState) const handleFocus = () => { if (currentObject === null) { + FLOW_LINE_REF.POINTER_INPUT_REF.current.value = '' FLOW_LINE_REF.FILLED_INPUT_REF.current.value = '' FLOW_LINE_REF.FILLED_INPUT_REF.current.blur() } } const handleInput = (e) => { - const value = e.target.value.replace(/^0+/, '') - setFilledInput(value.replace(/[^0-9]/g, '')) + const regex = /^-?\d*$/ + let value = e.target.value + if (!regex.test(value) && value !== '') return + if (value.startsWith('0') || value === '-0') return + + setFilledInput(value.replace(/[^0-9-]/g, '')) } return ( <> @@ -47,7 +52,7 @@ export default function FlowLine({ FLOW_LINE_REF }) {
- {/**/} + {}
diff --git a/src/components/floor-plan/modal/movement/type/Updown.jsx b/src/components/floor-plan/modal/movement/type/Updown.jsx index bcc45590..786c7017 100644 --- a/src/components/floor-plan/modal/movement/type/Updown.jsx +++ b/src/components/floor-plan/modal/movement/type/Updown.jsx @@ -15,6 +15,7 @@ export default function Updown({ UP_DOWN_REF }) { const currentObject = useRecoilValue(currentObjectState) const handleFocus = () => { if (currentObject === null) { + UP_DOWN_REF.POINTER_INPUT_REF.current.value = '' UP_DOWN_REF.FILLED_INPUT_REF.current.value = '' UP_DOWN_REF.FILLED_INPUT_REF.current.blur() } @@ -48,7 +49,7 @@ export default function Updown({ UP_DOWN_REF }) {
- {/**/} + {}
diff --git a/src/hooks/roofcover/useMovementSetting.js b/src/hooks/roofcover/useMovementSetting.js index 1da5dcc8..558113ff 100644 --- a/src/hooks/roofcover/useMovementSetting.js +++ b/src/hooks/roofcover/useMovementSetting.js @@ -4,10 +4,9 @@ import { usePopup } from '@/hooks/usePopup' import { useMessage } from '@/hooks/useMessage' import { useEffect, useRef, useState } from 'react' import { useEvent } from '@/hooks/useEvent' -import { LINE_TYPE, POLYGON_TYPE } from '@/common/common' import { useSwal } from '@/hooks/useSwal' -import { QPolygon } from '@/components/fabric/QPolygon' -import { getDegreeByChon } from '@/util/canvas-util' +import { LINE_TYPE, POLYGON_TYPE } from '@/common/common' +import Big from 'big.js' //동선이동 형 올림 내림 export function useMovementSetting(id) { @@ -17,7 +16,6 @@ export function useMovementSetting(id) { } const canvas = useRecoilValue(canvasState) const { initEvent, addCanvasMouseEventListener } = useEvent() - // const { initEvent, addCanvasMouseEventListener } = useContext(EventContext) const { closePopup } = usePopup() const { getMessage } = useMessage() const currentObject = useRecoilValue(currentObjectState) @@ -31,939 +29,407 @@ export function useMovementSetting(id) { const { swalFire } = useSwal() const FLOW_LINE_REF = { + POINTER_INPUT_REF: useRef(null), FILLED_INPUT_REF: useRef(null), DOWN_LEFT_RADIO_REF: useRef(null), UP_RIGHT_RADIO_REF: useRef(null), } const UP_DOWN_REF = { + POINTER_INPUT_REF: useRef(null), FILLED_INPUT_REF: useRef(null), UP_RADIO_REF: useRef(null), DOWN_RADIO_REF: useRef(null), } + const CONFIRM_LINE_REF = useRef(null) + const FOLLOW_LINE_REF = useRef(null) + + /** 동선이동, 형이동 선택시 속성 처리*/ useEffect(() => { typeRef.current = type + selectedObject.current = null + if (FOLLOW_LINE_REF.current != null) { + canvas.remove(FOLLOW_LINE_REF.current) + canvas.renderAll() + FOLLOW_LINE_REF.current = null + } + if (CONFIRM_LINE_REF.current != null) { + canvas.remove(CONFIRM_LINE_REF.current) + canvas.renderAll() + CONFIRM_LINE_REF.current = null + } + clearRef() + + canvas.discardActiveObject() + /** 전체 object 선택 불가 */ canvas.getObjects().forEach((obj) => { obj.set({ selectable: false }) }) - const roofs = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF) // 기존 wallLine의 visible false + + /** 지붕선 관련 속성 처리*/ + const roofs = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF) roofs.forEach((roof) => { - roof.set({ selectable: false }) - roof.set({ strokeWidth: 1 }) roof.set({ stroke: '#000000' }) roof.innerLines.forEach((line) => { - line.bringToFront() - line.set({ selectable: false }) - line.set({ strokeWidth: 1 }) - line.set({ stroke: '#000000' }) - }) - roof.separatePolygon?.forEach((polygon) => { - polygon.bringToFront() - polygon.set({ selectable: false }) - polygon.set({ strokeWidth: 1 }) - polygon.set({ stroke: '#000000' }) + if (type === TYPE.FLOW_LINE && line.name === LINE_TYPE.SUBLINE.RIDGE) { + line.set({ selectable: true, strokeWidth: 5, stroke: '#1083E3' }) + line.bringToFront() + } else { + line.set({ selectable: false, strokeWidth: 2, stroke: '#000000' }) + } }) }) - if (type === TYPE.FLOW_LINE) { - roofs.forEach((roof) => { - roof.innerLines.forEach((line) => { - if (line.name === LINE_TYPE.SUBLINE.RIDGE) { - line.bringToFront() - line.set({ visible: true }) - line.set({ selectable: true }) - line.set({ strokeWidth: 4 }) - line.set({ stroke: '#1083E3' }) - } - }) + /** 외벽선 관련 속성 처리*/ + const walls = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.WALL) + walls.forEach((wall) => { + if (wall.baseLines.length === 0) { + wall.baseLines = canvas.getObjects().filter((obj) => obj.name === 'baseLine' && obj.attributes.wallId === wall.id) + } + wall.baseLines.forEach((line) => { + if (type === TYPE.UP_DOWN) { + line.set({ selectable: true, visible: true, stroke: '#1083E3', strokeWidth: 5 }) + line.bringToFront() + } else { + line.set({ selectable: false, visible: false }) + } }) - } else if (type === TYPE.UP_DOWN) { - const wallLine = canvas.getObjects().filter((obj) => obj.name === 'wallLine') // 기존 outerLine의 selectable true - wallLine.forEach((line) => { - line.bringToFront() - line.set({ selectable: true }) - }) - } + }) + + /** outerLines 속성처리*/ + const outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine') + outerLines.forEach((line) => line.set({ visible: false })) canvas.renderAll() }, [type]) + /** 팝업창이 열릴때,닫힐때 속성들을 처리*/ useEffect(() => { - canvas.renderAll() addCanvasMouseEventListener('mouse:move', mouseMoveEvent) addCanvasMouseEventListener('mouse:down', mouseDownEvent) return () => { - initEvent() - - const wallLines = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.WALL) - wallLines.forEach((line) => { - line.set({ visible: true }) + if (FOLLOW_LINE_REF.current != null) { + canvas.remove(FOLLOW_LINE_REF.current) + FOLLOW_LINE_REF.current = null + } + if (CONFIRM_LINE_REF.current != null) { + canvas.remove(CONFIRM_LINE_REF.current) + CONFIRM_LINE_REF.current = null + } + const roofs = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF) + roofs.forEach((roof) => { + roof.set({ stroke: '#1083E3' }) + roof.innerLines.forEach((line) => line.set({ selectable: true, strokeWidth: 2, stroke: '#1083E3' })) }) - - const wallLine = canvas.getObjects().filter((obj) => obj.name === 'wallLine') // 기존 outerLine의 selectable true - wallLine.forEach((line) => { - let wallStroke, wallStrokeWidth - switch (line.attributes.type) { - case LINE_TYPE.WALLLINE.EAVES: - wallStroke = '#45CD7D' - wallStrokeWidth = 4 - break - case LINE_TYPE.WALLLINE.HIPANDGABLE: - wallStroke = '#45CD7D' - wallStrokeWidth = 4 - break - case LINE_TYPE.WALLLINE.GABLE: - wallStroke = '#3FBAE6' - wallStrokeWidth = 4 - break - case LINE_TYPE.WALLLINE.JERKINHEAD: - wallStroke = '#3FBAE6' - wallStrokeWidth = 4 - break - case LINE_TYPE.WALLLINE.SHED: - wallStroke = '#000000' - wallStrokeWidth = 4 - break - case LINE_TYPE.WALLLINE.WALL: - wallStroke = '#000000' - wallStrokeWidth = 4 - break + const walls = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.WALL) + walls.forEach((wall) => { + if (wall.baseLines.length === 0) { + wall.baseLines = canvas.getObjects().filter((obj) => obj.name === 'baseLine' && obj.attributes.wallId === wall.id) } - line.set({ selectable: false, stroke: wallStroke, strokeWidth: wallStrokeWidth }) + wall.baseLines.forEach((baseLine) => { + baseLine.set({ selectable: false, visible: false }) + }) }) - + const outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine') + outerLines.forEach((line) => line.set({ visible: true })) canvas.renderAll() } }, []) + /** object 선택이 변경될 때 처리*/ useEffect(() => { - console.log('selectedObject.current : ', selectedObject.current) - if (selectedObject.current != null) { - let wallStroke - switch (selectedObject.current.attributes.type) { - case LINE_TYPE.WALLLINE.EAVES: - wallStroke = '#45CD7D' - break - case LINE_TYPE.WALLLINE.HIPANDGABLE: - wallStroke = '#45CD7D' - break - case LINE_TYPE.WALLLINE.GABLE: - wallStroke = '#3FBAE6' - break - case LINE_TYPE.WALLLINE.JERKINHEAD: - wallStroke = '#3FBAE6' - break - case LINE_TYPE.WALLLINE.SHED: - wallStroke = '#000000' - break - case LINE_TYPE.WALLLINE.WALL: - wallStroke = '#000000' - break - } - selectedObject.current.set({ stroke: wallStroke }) - selectedObject.current.bringToFront() - } - selectedObject.current = null - if (!currentObject) { - return + if (FOLLOW_LINE_REF.current != null) { + canvas.remove(FOLLOW_LINE_REF.current) + canvas.renderAll() + FOLLOW_LINE_REF.current = null } - clearRef() - selectedObject.current = currentObject - if (currentObject.name === 'wallLine') { - currentObject.set({ stroke: '#EA10AC' }) - currentObject.bringToFront() + if (selectedObject.current != null) { + selectedObject.current.set({ stroke: '#1083E3' }) + selectedObject.current = null } + if (!currentObject) return + + clearRef() + + canvas + .getObjects() + .filter((obj) => obj.name === 'checkPoint' || obj.name === 'checkLine') + .forEach((obj) => canvas.remove(obj)) + canvas.renderAll() + + if (CONFIRM_LINE_REF.current != null) { + canvas.remove(CONFIRM_LINE_REF.current) + canvas.renderAll() + CONFIRM_LINE_REF.current = null + } + + currentObject.set({ stroke: '#EA10AC' }) + selectedObject.current = currentObject + + const followLine = new fabric.Line([currentObject.x1, currentObject.y1, currentObject.x2, currentObject.y2], { + stroke: '#000000', + strokeWidth: 4, + selectable: false, + name: 'followLine', + }) + canvas.add(followLine) + FOLLOW_LINE_REF.current = followLine + + canvas.on('mouse:move', (event) => { + const mousePos = canvas.getPointer(event.e) + if (followLine.x1 === followLine.x2) { + followLine.left = mousePos.x - 2 + } else { + followLine.top = mousePos.y - 2 + } + canvas.renderAll() + }) + canvas.renderAll() }, [currentObject]) const clearRef = () => { if (type === TYPE.FLOW_LINE) { + FLOW_LINE_REF.POINTER_INPUT_REF.current.value = '' FLOW_LINE_REF.FILLED_INPUT_REF.current.value = '' FLOW_LINE_REF.DOWN_LEFT_RADIO_REF.current.checked = true FLOW_LINE_REF.UP_RIGHT_RADIO_REF.current.checked = false } if (type === TYPE.UP_DOWN) { + UP_DOWN_REF.POINTER_INPUT_REF.current.value = '' UP_DOWN_REF.FILLED_INPUT_REF.current.value = '' UP_DOWN_REF.UP_RADIO_REF.current.checked = true UP_DOWN_REF.DOWN_RADIO_REF.current.checked = false } } - const mouseDownEvent = (e) => { - if (typeRef.current === TYPE.FLOW_LINE) { - saveFlowLine(e) - } else { - saveUpDownLine(e) - } - } - const mouseMoveEvent = (e) => { + const target = canvas.getActiveObject() + if (!target) return + + const { top: targetTop, left: targetLeft } = target + const currentX = Big(canvas.getPointer(e.e).x).round(0, Big.roundUp) + const currentY = Big(canvas.getPointer(e.e).y).round(0, Big.roundUp) + let value = '' + if (target.y1 === target.y2) { + value = Big(targetTop).minus(currentY).times(10) + } else { + value = Big(targetLeft).minus(currentX).times(10).neg() + } if (typeRef.current === TYPE.FLOW_LINE) { - flowLineMoveEvent(e) + FLOW_LINE_REF.POINTER_INPUT_REF.current.value = value.toNumber() } else { - updownMoveEvent(e) + UP_DOWN_REF.POINTER_INPUT_REF.current.value = value.abs().toNumber() + const midX = Big(target.x1).plus(target.x2).div(2) + const midY = Big(target.y1).plus(target.y2).div(2) + const wall = canvas.getObjects().find((obj) => obj.id === target.attributes.wallId) + let checkPoint + if (target.y1 === target.y2) { + checkPoint = { x: midX.toNumber(), y: midY.plus(10).toNumber() } + if (wall.inPolygon(checkPoint)) { + if (value.s === -1) { + UP_DOWN_REF.UP_RADIO_REF.current.checked = false + UP_DOWN_REF.DOWN_RADIO_REF.current.checked = true + } else { + UP_DOWN_REF.UP_RADIO_REF.current.checked = true + UP_DOWN_REF.DOWN_RADIO_REF.current.checked = false + } + } else { + if (value.s === 1) { + UP_DOWN_REF.UP_RADIO_REF.current.checked = false + UP_DOWN_REF.DOWN_RADIO_REF.current.checked = true + } else { + UP_DOWN_REF.UP_RADIO_REF.current.checked = true + UP_DOWN_REF.DOWN_RADIO_REF.current.checked = false + } + } + } else { + checkPoint = { x: midX.plus(10).toNumber(), y: midY.toNumber() } + if (wall.inPolygon(checkPoint)) { + if (value.s === 1) { + UP_DOWN_REF.UP_RADIO_REF.current.checked = false + UP_DOWN_REF.DOWN_RADIO_REF.current.checked = true + } else { + UP_DOWN_REF.UP_RADIO_REF.current.checked = true + UP_DOWN_REF.DOWN_RADIO_REF.current.checked = false + } + } else { + if (value.s === -1) { + UP_DOWN_REF.UP_RADIO_REF.current.checked = false + UP_DOWN_REF.DOWN_RADIO_REF.current.checked = true + } else { + UP_DOWN_REF.UP_RADIO_REF.current.checked = true + UP_DOWN_REF.DOWN_RADIO_REF.current.checked = false + } + } + } } } - function edgesIntersection(edgeA, edgeB) { - const den = - (edgeB.vertex2.y - edgeB.vertex1.y) * (edgeA.vertex2.x - edgeA.vertex1.x) - - (edgeB.vertex2.x - edgeB.vertex1.x) * (edgeA.vertex2.y - edgeA.vertex1.y) - - if (den === 0) { - return null // lines are parallel or coincident - } - - const ua = - ((edgeB.vertex2.x - edgeB.vertex1.x) * (edgeA.vertex1.y - edgeB.vertex1.y) - - (edgeB.vertex2.y - edgeB.vertex1.y) * (edgeA.vertex1.x - edgeB.vertex1.x)) / - den - - const ub = - ((edgeA.vertex2.x - edgeA.vertex1.x) * (edgeA.vertex1.y - edgeB.vertex1.y) - - (edgeA.vertex2.y - edgeA.vertex1.y) * (edgeA.vertex1.x - edgeB.vertex1.x)) / - den - - // Edges are not intersecting but the lines defined by them are - const isIntersectionOutside = ua < 0 || ub < 0 || ua > 1 || ub > 1 - - return { - x: edgeA.vertex1.x + ua * (edgeA.vertex2.x - edgeA.vertex1.x), - y: edgeA.vertex1.y + ua * (edgeA.vertex2.y - edgeA.vertex1.y), - isIntersectionOutside, - } - } - - //동선 이동 마우스 클릭 이벤트 - const saveFlowLine = (e) => { - const target = selectedObject.current - if (!target) { - return - } - - let newPoint = [] - if (Math.sign(target.x1 - target.x2) !== 0) { - const sign = FLOW_LINE_REF.UP_RIGHT_RADIO_REF.current.checked ? 1 : -1 - newPoint = [ - target.x1, - target.y1 - sign * Number(FLOW_LINE_REF.FILLED_INPUT_REF.current.value / 10), - target.x2, - target.y2 - sign * Number(FLOW_LINE_REF.FILLED_INPUT_REF.current.value / 10), - ] - } else { - const sign = FLOW_LINE_REF.UP_RIGHT_RADIO_REF.current.checked ? -1 : 1 - newPoint = [ - target.x1 - sign * Number(FLOW_LINE_REF.FILLED_INPUT_REF.current.value / 10), - target.y1, - target.x2 - sign * Number(FLOW_LINE_REF.FILLED_INPUT_REF.current.value / 10), - target.y2, - ] - } - newPoint = newPoint.map((point) => Math.round(point * 10) / 10) - const cloned = new fabric.Line(newPoint, {}) - let currentObject = selectedObject.current - const currentX1 = currentObject.x1, - currentY1 = currentObject.y1, - currentX2 = currentObject.x2, - currentY2 = currentObject.y2 - - const currentMidX = (currentX1 + currentX2) / 2 - const currentMidY = (currentY1 + currentY2) / 2 - const clonedMidX = (cloned.x1 + cloned.x2) / 2 - const clonedMidY = (cloned.y1 + cloned.y2) / 2 - - const roof = canvas.getObjects().find((obj) => obj.id === currentObject.attributes.roofId) - let isOutside = false - roof.lines.forEach((line) => { - const intersection = edgesIntersection( - { vertex1: { x: currentMidX, y: currentMidY }, vertex2: { x: clonedMidX, y: clonedMidY } }, - { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }, - ) - if (intersection && !intersection.isIntersectionOutside) { - isOutside = true - } - }) - if (isOutside) { - swalFire({ text: getMessage('modal.movement.flow.line.move.alert'), icon: 'error' }) - return - } - currentObject.set({ x1: cloned.x1, y1: cloned.y1, x2: cloned.x2, y2: cloned.y2 }) - currentObject.setCoords() - const otherRidges = roof.innerLines.filter((line) => line.name === LINE_TYPE.SUBLINE.RIDGE && line.id !== currentObject.id) - const overlapRidges = otherRidges.filter((line) => { - if (currentObject.x1 === currentObject.x2 && line.x1 === line.x2 && 0 < Math.abs(currentObject.x1 - line.x1) < 1) { - if ( - currentObject.y1 <= line.y1 <= currentObject.y2 || - currentObject.y1 >= line.y1 >= currentObject.y2 || - currentObject.y1 <= line.y2 <= currentObject.y2 || - currentObject.y1 >= line.y2 >= currentObject.y2 - ) { - return true - } - } - if (currentObject.y1 === currentObject.y2 && line.y1 === line.y2 && 0 < Math.abs(currentObject.y1 - line.y1) < 1) { - if ( - currentObject.x1 <= line.x1 <= currentObject.x2 || - currentObject.x1 >= line.x1 >= currentObject.x2 || - currentObject.x1 <= line.x2 <= currentObject.x2 || - currentObject.x1 >= line.x2 >= currentObject.x2 - ) { - return true - } - } - }) - if (overlapRidges.length > 0) { - const minX = Math.min(...overlapRidges.flatMap((line) => [line.x1, line.x2]), currentObject.x1, currentObject.x2) - const maxX = Math.max(...overlapRidges.flatMap((line) => [line.x1, line.x2]), currentObject.x1, currentObject.x2) - const minY = Math.min(...overlapRidges.flatMap((line) => [line.y1, line.y2]), currentObject.y1, currentObject.y2) - const maxY = Math.max(...overlapRidges.flatMap((line) => [line.y1, line.y2]), currentObject.y1, currentObject.y2) - const newRidge = new fabric.Line([minX, minY, maxX, maxY], { - name: LINE_TYPE.SUBLINE.RIDGE, - selectable: true, - stroke: '#1083E3', - strokeWidth: 4, - attributes: { roofId: roof.id, currentRoofId: currentObject.attributes.currentRoofId }, - }) - - overlapRidges.forEach((line) => - line.attributes.currentRoofId.forEach((id) => { - if (!newRidge.attributes.currentRoofId.includes(id)) { - newRidge.attributes.currentRoofId.push(id) - } - }), - ) - canvas.add(newRidge) - newRidge.bringToFront() - roof.innerLines.push(newRidge) - - canvas.remove(currentObject) - roof.innerLines.forEach((innerLine, index) => { - if (innerLine.id === currentObject.id) { - roof.innerLines.splice(index, 1) - } - }) - - overlapRidges.forEach((line) => { - canvas.remove(line) - roof.innerLines.forEach((innerLine, index) => { - if (innerLine.id === line.id) { - roof.innerLines.splice(index, 1) - } - }) - }) - } - - if (roof.separatePolygon.length > 0) { - redrawSeparatePolygon(roof) - } else { - roof.innerLines - .filter((line) => currentObject !== line) - .filter( - (line) => - (line.x1 === currentX1 && line.y1 === currentY1) || - (line.x2 === currentX1 && line.y2 === currentY1) || - (line.x1 === currentX2 && line.y1 === currentY2) || - (line.x2 === currentX2 && line.y2 === currentY2), - ) - .forEach((line) => { - const lineDegree = 90 - Math.asin(line.attributes.planeSize / line.attributes.actualSize) * (180 / Math.PI) - if (line.x1 === currentX1 && line.y1 === currentY1) { - line.set({ x1: newPoint[0], y1: newPoint[1] }) - } else if (line.x2 === currentX1 && line.y2 === currentY1) { - line.set({ x2: newPoint[0], y2: newPoint[1] }) - } else if (line.x1 === currentX2 && line.y1 === currentY2) { - line.set({ x1: newPoint[2], y1: newPoint[3] }) - } else if (line.x2 === currentX2 && line.y2 === currentY2) { - line.set({ x2: newPoint[2], y2: newPoint[3] }) - } - line.setCoords() - line.attributes.planeSize = Math.round(Math.sqrt(Math.pow(line.x2 - line.x1, 2) + Math.pow(line.y2 - line.y1, 2)) * 10) - line.attributes.actualSize = Math.round( - Math.sqrt(Math.pow(line.attributes.planeSize, 2) + Math.pow(line.attributes.planeSize * Math.tan(lineDegree * (Math.PI / 180)), 2)), - ) - }) - } - - currentObject.set({ x1: cloned.x1, y1: cloned.y1, x2: cloned.x2, y2: cloned.y2 }) - currentObject.setCoords() - + const mouseDownEvent = (e) => { + canvas + .getObjects() + .filter((obj) => obj.name === 'checkPoint' || obj.name === 'checkLine') + .forEach((obj) => canvas.remove(obj)) canvas.renderAll() - canvas.discardActiveObject() - FLOW_LINE_REF.FILLED_INPUT_REF.current.value = '' - } - - //형 올림내림 마우스 클릭 이벤트 - const saveUpDownLine = (e) => { const target = selectedObject.current - if (!target) { - return - } + if (!target) return - const roof = canvas.getObjects().find((obj) => obj.id === target.attributes.roofId) - const wallLines = roof.wall.lines - const roofLines = roof.lines - const currentWall = wallLines.find((line) => line.id === target.attributes.currentWall) - let prevWall, nextWall - let prevEave, nextEave + const roofId = target.attributes.roofId + const followLine = canvas.getObjects().find((obj) => obj.name === 'followLine') - wallLines.forEach((wallLine, index) => { - if (wallLine.id === currentWall.id) { - prevWall = index === 0 ? wallLines[wallLines.length - 1] : wallLines[index - 1] - nextWall = index === wallLines.length - 1 ? wallLines[0] : wallLines[index + 1] - } - }) - - const eaves = roofLines.filter((line) => line.attributes?.type === LINE_TYPE.WALLLINE.EAVES) - - wallLines - .filter((line) => line.attributes.type === LINE_TYPE.WALLLINE.EAVES) - .forEach((eave, index) => { - if (eave.id === currentWall.id) { - prevEave = index === 0 ? eaves[eaves.length - 1] : eaves[index - 1] - nextEave = index === eaves.length - 1 ? eaves[0] : eaves[index + 1] - } - }) - - const currentRoof = roofLines.find((line) => line.id === target.attributes.currentRoof) - const currentRidges = roof.innerLines.filter( - (line) => line.name === LINE_TYPE.SUBLINE.RIDGE && line.attributes.currentRoof.includes(currentRoof.id), - ) - /*const prevWallRidges = roof.innerLines.filter( - (line) => line.name === LINE_TYPE.SUBLINE.RIDGE && line.attributes.currentRoof.includes(prevEave.id), - ) - const nextWallRidges = roof.innerLines.filter( - (line) => line.name === LINE_TYPE.SUBLINE.RIDGE && line.attributes.currentRoof.includes(nextEave.id), - ) - //라인 확인 작업 - /!* const roofLine = new fabric.Line([currentRoof.x1, currentRoof.y1, currentRoof.x2, currentRoof.y2], { - stroke: 'red', - strokeWidth: 5, + const confirmLine = new fabric.Line([followLine.x1, followLine.y1, followLine.x2, followLine.y2], { + left: followLine.left, + top: followLine.top, + stroke: '#000000', + strokeWidth: 4, selectable: false, + parentId: roofId, + name: 'confirmLine', + target, }) - canvas.add(roofLine) + canvas.add(confirmLine) + canvas.renderAll() + CONFIRM_LINE_REF.current = confirmLine - const prevEaves = new fabric.Line([prevEave.x1, prevEave.y1, prevEave.x2, prevEave.y2], { - stroke: 'yellow', - strokeWidth: 5, - selectable: false, - }) - canvas.add(prevEaves) - currentRidges.forEach((ridge) => ridge.set({ stroke: 'purple', strokeWidth: 5 })) - prevWallRidges.forEach((ridge) => ridge.set({ stroke: 'yellow', strokeWidth: 5 })) - nextWallRidges.forEach((ridge) => ridge.set({ stroke: 'green', strokeWidth: 5 })) - canvas.renderAll()*!/*/ - - console.log( - 'UP/DOWN', - UP_DOWN_REF.UP_RADIO_REF.current.checked, - UP_DOWN_REF.DOWN_RADIO_REF.current.checked, - UP_DOWN_REF.FILLED_INPUT_REF.current.value, - ) - - let compareLine - if (Math.sign(currentRoof.x1 - currentRoof.x2) === 0) { - const currentDiff = currentRidges[0].x1 - currentRoof.x1 - const prevDiff = prevEave.x1 - currentRoof.x1 - const nextDiff = nextEave.x1 - currentRoof.x1 - - console.log('currentDiff', Math.sign(currentDiff), currentDiff) - - if (UP_DOWN_REF.UP_RADIO_REF.current.checked) { - if (Math.sign(currentDiff) === 1) { - } else { - if (Math.sign(prevDiff) === 1 && Math.sign(nextDiff) === 1) { - } else { - } - } - } else { - if (Math.sign(currentDiff) === -1) { - } else { - if (Math.sign(prevDiff) === -1 && Math.sign(nextDiff) === -1) { - } else { - } - } - } - } else { - const currentDiff = currentRidges[0].y1 - currentRoof.y1 - const prevDiff = prevEave.y1 - currentRoof.y1 - const nextDiff = nextEave.y1 - currentRoof.y1 - - if (UP_DOWN_REF.UP_RADIO_REF.current.checked) { - if (Math.sign(currentDiff) === 1) { - } else { - if (Math.sign(prevDiff) === 1 && Math.sign(nextDiff) === 1) { - } else { - } - } - } else { - if (Math.sign(currentDiff) === -1) { - } else { - if (Math.sign(prevDiff) === -1 && Math.sign(nextDiff) === -1) { - } else { - } - } - } - } - - //확인 - /*const compareRoofLine = new fabric.Line([compareRoof.x1, compareRoof.y1, compareRoof.x2, compareRoof.y2], { - stroke: 'red', - strokeWidth: 5, - selectable: false, - }) - canvas.add(compareRoofLine) - canvas.renderAll()*/ - } - - const redrawSeparatePolygon = (roof) => { - roof.separatePolygon.forEach((polygon) => canvas.remove(polygon)) - roof.separatePolygon = [] - const roofLines = roof.lines - const wallLines = roof.wall.lines - const eaves = [] - roofLines.forEach((currentRoof, index) => { - if (currentRoof.attributes?.type === LINE_TYPE.WALLLINE.EAVES) { - eaves.push({ index: index, roof: currentRoof, length: currentRoof.attributes.planeSize }) - } - }) - const ridges = roof.innerLines.filter((line) => line.name === LINE_TYPE.SUBLINE.RIDGE) - eaves.sort((a, b) => a.length - b.length) - - const ridgeCount = eaves.length - 1 - console.log('ridgeCount', ridgeCount, 'ridges', ridges.length) - - const duplicatedEaves = [] - ridges.forEach((ridge) => { - const positiveLines = [] - const negativeLines = [] - ridge.attributes.currentRoof.forEach((id) => { - console.log('id : ', id) - const eave = roofLines.find((obj) => obj.id === id) - console.log('roof : ', eave) - if (ridge.x1 === ridge.x2) { - if (eave.y1 < eave.y2) { - positiveLines.push(eave) - } else { - negativeLines.push(eave) - } - } else { - if (eave.x1 < eave.x2) { - positiveLines.push(eave) - } else { - negativeLines.push(eave) - } - } - }) - if (positiveLines.length > 1) { - duplicatedEaves.push(positiveLines) - } - if (negativeLines.length > 1) { - duplicatedEaves.push(negativeLines) - } - }) - console.log('duplicatedEaves', duplicatedEaves) - - duplicatedEaves.forEach((duplicatedEave) => { - duplicatedEave.forEach((eave) => { - const index = eaves.findIndex((item) => item.roof.id === eave.id) - eaves.splice(index, 1) - }) - }) - - // if (ridgeCount === ridges.length) { - eaves.forEach((eave, i) => { - const index = eave.index, - currentRoof = eave.roof - const currentWall = wallLines[index] - const currentRidges = ridges.filter((ridge) => ridge.attributes.currentRoof.includes(eave.roof.id)) - let points = [] - const signX = Math.sign(currentRoof.x1 - currentRoof.x2) - let currentX1 = currentRoof.x1, - currentY1 = currentRoof.y1, - currentX2 = currentRoof.x2, - currentY2 = currentRoof.y2 - let changeX1 = [Math.min(currentRoof.x1, currentRoof.x2), Math.min(currentRoof.x1, currentRoof.x2)], - changeY1 = [Math.min(currentRoof.y1, currentRoof.y2), Math.min(currentRoof.y1, currentRoof.y2)], - changeX2 = [Math.max(currentRoof.x2, currentRoof.x1), Math.max(currentRoof.x2, currentRoof.x1)], - changeY2 = [Math.max(currentRoof.y2, currentRoof.y1), Math.max(currentRoof.y2, currentRoof.y1)] - - if (signX === 0) { - currentY1 = Math.min(currentRoof.y1, currentRoof.y2, currentWall.y1, currentWall.y2) - changeY1[1] = currentY1 - currentY2 = Math.max(currentRoof.y1, currentRoof.y2, currentWall.y1, currentWall.y2) - changeY2[1] = currentY2 - } else { - currentX1 = Math.min(currentRoof.x1, currentRoof.x2, currentWall.x1, currentWall.x2) - changeX1[1] = currentX1 - currentX2 = Math.max(currentRoof.x1, currentRoof.x2, currentWall.x1, currentWall.x2) - changeX2[1] = currentX2 - } - points.push({ x: currentX1, y: currentY1 }, { x: currentX2, y: currentY2 }) - - currentRidges.forEach((ridge) => { - let ridgeX1 = ridge.x1, - ridgeY1 = ridge.y1, - ridgeX2 = ridge.x2, - ridgeY2 = ridge.y2 - if (signX === 0) { - ridgeY1 = Math.min(ridge.y1, ridge.y2) - ridgeY2 = Math.max(ridge.y1, ridge.y2) - } else { - ridgeX1 = Math.min(ridge.x1, ridge.x2) - ridgeX2 = Math.max(ridge.x1, ridge.x2) - } - points.push({ x: ridgeX1, y: ridgeY1 }, { x: ridgeX2, y: ridgeY2 }) - }) - - points.forEach((point) => { - if (point.x === changeX1[0] && changeX1[0] !== changeX1[1]) { - point.x = changeX1[1] - } - if (point.x === changeX2[0] && changeX2[0] !== changeX2[1]) { - point.x = changeX2[1] - } - if (point.y === changeY1[0] && changeY1[0] !== changeY1[1]) { - point.y = changeY1[1] - } - if (point.y === changeY2[0] && changeY2[0] !== changeY2[1]) { - point.y = changeY2[1] - } - }) - //중복된 point 제거 - points = points.filter((point, index, self) => index === self.findIndex((p) => p.x === point.x && p.y === point.y)) - //point 정렬 (가장 좌측, 최상단의 점을 기준으로 삼는다.) - const startPoint = points - .filter((point) => point.x === Math.min(...points.map((point) => point.x))) - .reduce((prev, current) => { - return prev.y < current.y ? prev : current - }) - - const sortedPoints = [] - sortedPoints.push(startPoint) - - points.forEach((p, index) => { - if (index === 0) { - //시작점 다음 점 찾기, y좌표가 startPoint.y 보다 큰 점 중 x좌표가 가까운 점 - const underStartPoint = points.filter((point) => point.y > startPoint.y) - const nextPoint = underStartPoint - .filter((point) => point.x === startPoint.x) - .reduce((prev, current) => { - if (prev === undefined) { - return current - } - return Math.abs(prev.y - startPoint.y) < Math.abs(current.y - startPoint.y) ? prev : current - }, undefined) - if (nextPoint) { - sortedPoints.push(nextPoint) - } else { - const nextPoint = underStartPoint.reduce((prev, current) => { - const prevHypos = Math.sqrt(Math.abs(Math.pow(prev.x - startPoint.x, 2)) + Math.abs(Math.pow(prev.y - startPoint.y, 2))) - const currentHypos = Math.sqrt(Math.abs(Math.pow(current.x - startPoint.x, 2)) + Math.abs(Math.pow(current.y - startPoint.y, 2))) - return prevHypos < currentHypos ? prev : current - }, undefined) - sortedPoints.push(nextPoint) - } - } else { - const lastPoint = sortedPoints[sortedPoints.length - 1] - console.log('lastPoint', lastPoint) - const prevPoint = sortedPoints[sortedPoints.length - 2] - const otherPoints = points.filter((point) => sortedPoints.includes(point) === false) - const nextPoint = otherPoints.reduce((prev, current) => { - if (prev === undefined) { - const height = Math.abs(Math.sqrt(Math.abs(Math.pow(prevPoint.x - lastPoint.x, 2)) + Math.abs(Math.pow(prevPoint.y - lastPoint.y, 2)))) - const adjacent = Math.abs(Math.sqrt(Math.abs(Math.pow(current.x - lastPoint.x, 2)) + Math.abs(Math.pow(current.y - lastPoint.y, 2)))) - const hypotenuse = Math.abs(Math.sqrt(Math.abs(Math.pow(current.x - prevPoint.x, 2)) + Math.abs(Math.pow(current.y - prevPoint.y, 2)))) - - const angle = Math.round( - Math.acos((Math.pow(adjacent, 2) + Math.pow(height, 2) - Math.pow(hypotenuse, 2)) / (2 * adjacent * height)) * (180 / Math.PI), - ) - if (angle === 90) { - return current - } - } else { - return prev - } - }, undefined) - if (nextPoint) { - sortedPoints.push(nextPoint) - } else { - const nextPoint = otherPoints.reduce((prev, current) => { - if (prev !== undefined) { - const height = Math.abs( - Math.sqrt(Math.abs(Math.pow(prevPoint.x - lastPoint.x, 2)) + Math.abs(Math.pow(prevPoint.y - lastPoint.y, 2))), - ) - const adjacentC = Math.abs(Math.sqrt(Math.abs(Math.pow(current.x - lastPoint.x, 2)) + Math.abs(Math.pow(current.y - lastPoint.y, 2)))) - const hypotenuseC = Math.abs( - Math.sqrt(Math.abs(Math.pow(current.x - prevPoint.x, 2)) + Math.abs(Math.pow(current.y - prevPoint.y, 2))), - ) - const angleC = Math.round( - Math.acos((Math.pow(adjacentC, 2) + Math.pow(height, 2) - Math.pow(hypotenuseC, 2)) / (2 * adjacentC * height)) * (180 / Math.PI), - ) - const adjacentP = Math.abs(Math.sqrt(Math.abs(Math.pow(prev.x - lastPoint.x, 2)) + Math.abs(Math.pow(prev.y - lastPoint.y, 2)))) - const hypotenuseP = Math.abs(Math.sqrt(Math.abs(Math.pow(prev.x - prevPoint.x, 2)) + Math.abs(Math.pow(prev.y - prevPoint.y, 2)))) - const angleP = Math.round( - Math.acos((Math.pow(adjacentP, 2) + Math.pow(height, 2) - Math.pow(hypotenuseP, 2)) / (2 * adjacentP * height)) * (180 / Math.PI), - ) - if (Math.abs(90 - angleC) < Math.abs(90 - angleP)) { - return current - } else { - return prev - } - } else { - return current - } - }, undefined) - if (nextPoint) { - sortedPoints.push(nextPoint) - } - } - } - }) - - if (sortedPoints.length > 0) { - const roofPolygon = new QPolygon(sortedPoints, { - fill: 'transparent', - stroke: '#000000', - strokeWidth: 1, - selectable: false, - fontSize: roof.fontSize, - name: 'roofPolygon', - attributes: { - roofId: roof.id, - currentRoofId: currentRoof.id, - pitch: currentRoof.attributes.pitch, - degree: currentRoof.attributes.degree, - direction: currentRoof.direction, - }, - }) - const currentDegree = currentRoof.attributes.pitch > 0 ? getDegreeByChon(currentRoof.attributes.pitch) : currentRoof.attributes.degree - //지붕 각도에 따른 실측치 조정 - roofPolygon.lines.forEach((line) => { - line.attributes.planeSize = Math.round(Math.sqrt(Math.pow(line.x2 - line.x1, 2) + Math.pow(line.y2 - line.y1, 2)) * 10) - const slope = (line) => (line.x2 - line.x1 === 0 ? Infinity : (line.y2 - line.y1) / (line.x2 - line.x1)) - - if (currentDegree > 0 && slope(line) !== slope(currentRoof)) { - const height = Math.tan(currentDegree * (Math.PI / 180)) * line.attributes.planeSize - line.attributes.actualSize = Math.round(Math.sqrt(Math.pow(line.attributes.planeSize, 2) + Math.pow(height, 2))) - } else { - line.attributes.actualSize = line.attributes.planeSize - } - }) - roof.separatePolygon.push(roofPolygon) - canvas.add(roofPolygon) - canvas.renderAll() - } - }) - - duplicatedEaves.forEach((duplicatedEave) => { - const currentRoof = duplicatedEave[0] - let points = [] - duplicatedEave.forEach((eave) => { - points.push({ x: eave.x1, y: eave.y1 }, { x: eave.x2, y: eave.y2 }) - const currentRidges = ridges.filter((ridge) => ridge.attributes.currentRoof.includes(eave.id)) - currentRidges.forEach((ridge) => { - points.push({ x: ridge.x1, y: ridge.y1 }, { x: ridge.x2, y: ridge.y2 }) - }) - }) - console.log('points', points) - points = points.filter((point, index, self) => index === self.findIndex((p) => p.x === point.x && p.y === point.y)) - //point 정렬 (가장 좌측, 최상단의 점을 기준으로 삼는다.) - - const startPoint = points - .filter((point) => point.x === Math.min(...points.map((point) => point.x))) - .reduce((prev, current) => { - return prev.y < current.y ? prev : current - }) - - const sortedPoints = [] - sortedPoints.push(startPoint) - - points.forEach((p, index) => { - if (index === 0) { - //시작점 다음 점 찾기, y좌표가 startPoint.y 보다 큰 점 중 x좌표가 가까운 점 - const underStartPoint = points.filter((point) => point.y > startPoint.y) - const nextPoint = underStartPoint - .filter((point) => point.x === startPoint.x) - .reduce((prev, current) => { - if (prev === undefined) { - return current - } - return Math.abs(prev.y - startPoint.y) < Math.abs(current.y - startPoint.y) ? prev : current - }, undefined) - if (nextPoint) { - sortedPoints.push(nextPoint) - } else { - const nextPoint = underStartPoint.reduce((prev, current) => { - const prevHypos = Math.sqrt(Math.abs(Math.pow(prev.x - startPoint.x, 2)) + Math.abs(Math.pow(prev.y - startPoint.y, 2))) - const currentHypos = Math.sqrt(Math.abs(Math.pow(current.x - startPoint.x, 2)) + Math.abs(Math.pow(current.y - startPoint.y, 2))) - return prevHypos < currentHypos ? prev : current - }, undefined) - sortedPoints.push(nextPoint) - } - } else { - const lastPoint = sortedPoints[sortedPoints.length - 1] - console.log('lastPoint', lastPoint) - const prevPoint = sortedPoints[sortedPoints.length - 2] - const otherPoints = points.filter((point) => sortedPoints.includes(point) === false) - const nextPoint = otherPoints.reduce((prev, current) => { - if (prev === undefined) { - const height = Math.abs(Math.sqrt(Math.abs(Math.pow(prevPoint.x - lastPoint.x, 2)) + Math.abs(Math.pow(prevPoint.y - lastPoint.y, 2)))) - const adjacent = Math.abs(Math.sqrt(Math.abs(Math.pow(current.x - lastPoint.x, 2)) + Math.abs(Math.pow(current.y - lastPoint.y, 2)))) - const hypotenuse = Math.abs(Math.sqrt(Math.abs(Math.pow(current.x - prevPoint.x, 2)) + Math.abs(Math.pow(current.y - prevPoint.y, 2)))) - - const angle = Math.round( - Math.acos((Math.pow(adjacent, 2) + Math.pow(height, 2) - Math.pow(hypotenuse, 2)) / (2 * adjacent * height)) * (180 / Math.PI), - ) - if (angle === 90) { - return current - } - } else { - return prev - } - }, undefined) - if (nextPoint) { - sortedPoints.push(nextPoint) - } else { - const nextPoint = otherPoints.reduce((prev, current) => { - if (prev !== undefined) { - const height = Math.abs( - Math.sqrt(Math.abs(Math.pow(prevPoint.x - lastPoint.x, 2)) + Math.abs(Math.pow(prevPoint.y - lastPoint.y, 2))), - ) - const adjacentC = Math.abs(Math.sqrt(Math.abs(Math.pow(current.x - lastPoint.x, 2)) + Math.abs(Math.pow(current.y - lastPoint.y, 2)))) - const hypotenuseC = Math.abs( - Math.sqrt(Math.abs(Math.pow(current.x - prevPoint.x, 2)) + Math.abs(Math.pow(current.y - prevPoint.y, 2))), - ) - const angleC = Math.round( - Math.acos((Math.pow(adjacentC, 2) + Math.pow(height, 2) - Math.pow(hypotenuseC, 2)) / (2 * adjacentC * height)) * (180 / Math.PI), - ) - const adjacentP = Math.abs(Math.sqrt(Math.abs(Math.pow(prev.x - lastPoint.x, 2)) + Math.abs(Math.pow(prev.y - lastPoint.y, 2)))) - const hypotenuseP = Math.abs(Math.sqrt(Math.abs(Math.pow(prev.x - prevPoint.x, 2)) + Math.abs(Math.pow(prev.y - prevPoint.y, 2)))) - const angleP = Math.round( - Math.acos((Math.pow(adjacentP, 2) + Math.pow(height, 2) - Math.pow(hypotenuseP, 2)) / (2 * adjacentP * height)) * (180 / Math.PI), - ) - if (Math.abs(90 - angleC) < Math.abs(90 - angleP)) { - return current - } else { - return prev - } - } else { - return current - } - }, undefined) - if (nextPoint) { - sortedPoints.push(nextPoint) - } - } - } - }) - - if (sortedPoints.length > 0) { - const roofPolygon = new QPolygon(sortedPoints, { - fill: 'transparent', - stroke: '#000000', - strokeWidth: 1, - selectable: false, - fontSize: roof.fontSize, - name: 'roofPolygon', - attributes: { - roofId: roof.id, - currentRoofId: currentRoof.id, - pitch: currentRoof.attributes.pitch, - degree: currentRoof.attributes.degree, - direction: currentRoof.direction, - }, - }) - const currentDegree = currentRoof.attributes.pitch > 0 ? getDegreeByChon(currentRoof.attributes.pitch) : currentRoof.attributes.degree - //지붕 각도에 따른 실측치 조정 - roofPolygon.lines.forEach((line) => { - line.attributes.planeSize = Math.round(Math.sqrt(Math.pow(line.x2 - line.x1, 2) + Math.pow(line.y2 - line.y1, 2)) * 10) - const slope = (line) => (line.x2 - line.x1 === 0 ? Infinity : (line.y2 - line.y1) / (line.x2 - line.x1)) - - if (currentDegree > 0 && slope(line) !== slope(currentRoof)) { - const height = Math.tan(currentDegree * (Math.PI / 180)) * line.attributes.planeSize - line.attributes.actualSize = Math.round(Math.sqrt(Math.pow(line.attributes.planeSize, 2) + Math.pow(height, 2))) - } else { - line.attributes.actualSize = line.attributes.planeSize - } - }) - roof.separatePolygon.push(roofPolygon) - canvas.add(roofPolygon) - canvas.renderAll() - } - }) - console.log('roof.separatePolygon : ', roof.separatePolygon) - - ridges.forEach((ridge) => ridge.bringToFront()) - console.log('ridges : ', ridges) - } - - const flowLineMoveEvent = (e) => { - const target = canvas.getActiveObject() - if (!target) { - return - } - const { top: targetTop, left: targetLeft } = target - const currentX = canvas.getPointer(e.e).x - const currentY = Math.floor(canvas.getPointer(e.e).y) - - if (Math.sign(target.x1 - target.x2) !== 0) { - if (targetTop > currentY) { - FLOW_LINE_REF.UP_RIGHT_RADIO_REF.current.checked = true - FLOW_LINE_REF.FILLED_INPUT_REF.current.value = Math.floor((Number(Math.abs(Math.round(targetTop - currentY))) / 10000).toFixed(5) * 100000) - } else { - FLOW_LINE_REF.DOWN_LEFT_RADIO_REF.current.checked = true - FLOW_LINE_REF.FILLED_INPUT_REF.current.value = Math.floor((Number(Math.abs(Math.round(targetTop - currentY))) / 10000).toFixed(5) * 100000) - } - } else { - if (targetLeft < currentX) { - FLOW_LINE_REF.UP_RIGHT_RADIO_REF.current.checked = true - FLOW_LINE_REF.FILLED_INPUT_REF.current.value = Math.floor((Number(Math.abs(Math.round(currentX - targetLeft))) / 10000).toFixed(5) * 100000) - } else { - FLOW_LINE_REF.DOWN_LEFT_RADIO_REF.current.checked = true - FLOW_LINE_REF.FILLED_INPUT_REF.current.value = Math.floor((Number(Math.abs(Math.round(currentX - targetLeft))) / 10000).toFixed(5) * 100000) - } - } - - canvas?.renderAll() - } - - const updownMoveEvent = (e) => { - const target = canvas.getActiveObject() - if (!target) { - return - } - const { top: targetTop, left: targetLeft } = target - const currentX = canvas.getPointer(e.e).x - const currentY = Math.floor(canvas.getPointer(e.e).y) - - if (Math.sign(target.x1 - target.x2) !== 0) { - if (targetTop > currentY) { - UP_DOWN_REF.DOWN_RADIO_REF.current.checked = true - UP_DOWN_REF.FILLED_INPUT_REF.current.value = Math.floor((Number(Math.abs(targetTop - currentY)) / 10000).toFixed(5) * 100000) - } else { - UP_DOWN_REF.UP_RADIO_REF.current.checked = true - UP_DOWN_REF.FILLED_INPUT_REF.current.value = Math.floor((Number(Math.abs(targetTop - currentY)) / 10000).toFixed(5) * 100000) - } - } else { - if (targetLeft > currentX) { - UP_DOWN_REF.DOWN_RADIO_REF.current.checked = true - UP_DOWN_REF.FILLED_INPUT_REF.current.value = Math.floor((Number(Math.abs(targetLeft - currentX)) / 10000).toFixed(5) * 100000) - } else { - UP_DOWN_REF.UP_RADIO_REF.current.checked = true - UP_DOWN_REF.FILLED_INPUT_REF.current.value = Math.floor((Number(Math.abs(targetLeft - currentX)) / 10000).toFixed(5) * 100000) - } - } - - canvas?.renderAll() + handleSave() } const handleSave = () => { - if (type === TYPE.FLOW_LINE) { - saveFlowLine() + const target = selectedObject.current !== null ? selectedObject.current : CONFIRM_LINE_REF.current?.target + if (!target) return + + const roofId = target.attributes.roofId + const roof = canvas.getObjects().find((obj) => obj.id === roofId) + const wall = canvas.getObjects().find((obj) => obj.name === POLYGON_TYPE.WALL && obj.attributes.roofId === roofId) + const baseLines = wall.baseLines + let targetBaseLines = [] + + if (typeRef.current === TYPE.FLOW_LINE) { + const lineVector = + target.y1 === target.y2 + ? FLOW_LINE_REF.UP_RIGHT_RADIO_REF.current.checked + ? 'up' + : 'down' + : FLOW_LINE_REF.UP_RIGHT_RADIO_REF.current.checked + ? 'right' + : 'left' + switch (lineVector) { + case 'up': + baseLines + .filter((line) => line.y1 === line.y2 && line.y1 < target.y1) + .forEach((line) => targetBaseLines.push({ line, distance: Big(target.y1).minus(line.y1).abs().toNumber() })) + break + case 'down': + baseLines + .filter((line) => line.y1 === line.y2 && line.y1 > target.y1) + .forEach((line) => targetBaseLines.push({ line, distance: Big(line.y1).minus(target.y1).abs().toNumber() })) + break + case 'right': + baseLines + .filter((line) => line.x1 === line.x2 && line.x1 > target.x1) + .forEach((line) => targetBaseLines.push({ line, distance: Big(target.x1).minus(line.x1).abs().toNumber() })) + + break + case 'left': + baseLines + .filter((line) => line.x1 === line.x2 && line.x1 < target.x1) + .forEach((line) => targetBaseLines.push({ line, distance: Big(line.x1).minus(target.x1).abs().toNumber() })) + break + } } else { - saveUpDownLine() + targetBaseLines.push({ line: target, distance: 0 }) } + + targetBaseLines.sort((a, b) => a.distance - b.distance) + targetBaseLines = targetBaseLines.filter((line) => line.distance === targetBaseLines[0].distance) + let value + if (typeRef.current === TYPE.FLOW_LINE) { + value = + FLOW_LINE_REF.FILLED_INPUT_REF.current.value !== '' + ? Big(FLOW_LINE_REF.FILLED_INPUT_REF.current.value).times(2) + : Big(FLOW_LINE_REF.POINTER_INPUT_REF.current.value).times(2) + if (target.y1 === target.y2) { + value = value.neg() + } + } else { + value = + UP_DOWN_REF.FILLED_INPUT_REF.current.value !== '' + ? Big(UP_DOWN_REF.FILLED_INPUT_REF.current.value) + : Big(UP_DOWN_REF.POINTER_INPUT_REF.current.value) + + const midX = Big(target.x1).plus(target.x2).div(2) + const midY = Big(target.y1).plus(target.y2).div(2) + const wall = canvas.getObjects().find((obj) => obj.id === target.attributes.wallId) + let checkPoint + if (target.y1 === target.y2) { + checkPoint = { x: midX.toNumber(), y: midY.plus(10).times(value.s).toNumber() } + } else { + checkPoint = { x: midX.plus(10).toNumber().times(value.s), y: midY.toNumber() } + } + + const inPolygon = wall.inPolygon(checkPoint) + + console.log('inPolygon', inPolygon) + if (UP_DOWN_REF.UP_RADIO_REF.current.checked && inPolygon) { + value = value.neg() + } else if (UP_DOWN_REF.DOWN_RADIO_REF.current.checked && !inPolygon) { + value = value.neg() + } + } + value = value.div(10) + targetBaseLines.forEach((target) => { + const currentLine = target.line + const index = baseLines.findIndex((line) => line === currentLine) + const nextLine = baseLines[(index + 1) % baseLines.length] + const prevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length] + let deltaX = 0 + let deltaY = 0 + if (currentLine.y1 === currentLine.y2) { + deltaY = value.toNumber() + } else { + deltaX = value.toNumber() + } + + currentLine.set({ + x1: currentLine.x1 + deltaX, + y1: currentLine.y1 + deltaY, + x2: currentLine.x2 + deltaX, + y2: currentLine.y2 + deltaY, + startPoint: { x: currentLine.x1 + deltaX, y: currentLine.y1 + deltaY }, + endPoint: { x: currentLine.x2 + deltaX, y: currentLine.y2 + deltaY }, + }) + + nextLine.set({ + x1: nextLine.x1 + deltaX, + y1: nextLine.y1 + deltaY, + startPoint: { x: nextLine.x1 + deltaX, y: nextLine.y1 + deltaY }, + }) + + prevLine.set({ + x2: prevLine.x2 + deltaX, + y2: prevLine.y2 + deltaY, + endPoint: { x: prevLine.x2 + deltaX, y: prevLine.y2 + deltaY }, + }) + + canvas.renderAll() + }) + + if (CONFIRM_LINE_REF.current !== null) { + canvas.remove(CONFIRM_LINE_REF.current) + CONFIRM_LINE_REF.current = null + canvas.renderAll() + } + if (FOLLOW_LINE_REF.current !== null) { + canvas.remove(FOLLOW_LINE_REF.current) + FOLLOW_LINE_REF.current = null + canvas.renderAll() + } + + roof.drawHelpLine() + initEvent() + closePopup(id) } return { diff --git a/src/hooks/roofcover/useRoofShapeSetting.js b/src/hooks/roofcover/useRoofShapeSetting.js index f1139366..1b4036c0 100644 --- a/src/hooks/roofcover/useRoofShapeSetting.js +++ b/src/hooks/roofcover/useRoofShapeSetting.js @@ -108,6 +108,7 @@ export function useRoofShapeSetting(id) { }) addPitchText(line) + line.bringToFront() } }) canvas.renderAll() diff --git a/src/hooks/useMode.js b/src/hooks/useMode.js index e4beea37..788f9d22 100644 --- a/src/hooks/useMode.js +++ b/src/hooks/useMode.js @@ -1770,10 +1770,7 @@ export function useMode() { afterLine.push(line) } }) - wall.lines = afterLine.concat(beforeLine) - wall.baseLines = wall.lines - wall.colorLines = [] //외벽선을 기준으로 polygon을 생성한다. 지붕선의 기준이 됨. const divWallLines = [] @@ -1905,55 +1902,21 @@ export function useMode() { const y2 = Big(line.y2) const lineLength = x1.minus(x2).abs().pow(2).plus(y1.minus(y2).abs().pow(2)).sqrt().times(10).round().toNumber() line.attributes.roofId = roof.id + line.attributes.wallId = wall.id // line.attributes.currentRoofId = roof.lines[index].id line.attributes.planeSize = lineLength line.attributes.actualSize = lineLength - let wallStroke, wallStrokeWidth - switch (line.attributes.type) { - case LINE_TYPE.WALLLINE.EAVES: - wallStroke = '#45CD7D' - wallStrokeWidth = 4 - break - case LINE_TYPE.WALLLINE.HIPANDGABLE: - wallStroke = '#45CD7D' - wallStrokeWidth = 4 - break - case LINE_TYPE.WALLLINE.GABLE: - wallStroke = '#3FBAE6' - wallStrokeWidth = 4 - break - case LINE_TYPE.WALLLINE.JERKINHEAD: - wallStroke = '#3FBAE6' - wallStrokeWidth = 4 - break - case LINE_TYPE.WALLLINE.SHED: - wallStroke = '#000000' - wallStrokeWidth = 4 - break - case LINE_TYPE.WALLLINE.WALL: - wallStroke = '#000000' - wallStrokeWidth = 4 - break - } - - //외벽선의 색깔 표시를 위해 라인을 추가한다. - const wallLine = new fabric.Line([line.x1, line.y1, line.x2, line.y2], { - parentId: wall.id, - name: 'wallLine', - attributes: { - wallId: wall.id, - roofId: roof.id, - type: line.attributes.type, - currentRoofId: line.attributes.currentRoofId, - currentWall: line.id, - }, - stroke: wallStroke, - strokeWidth: wallStrokeWidth, - selectable: false, + const baseLine = new fabric.Line([line.x1, line.y1, line.x2, line.y2], { + visible: false, + attributes: line.attributes, + startPoint: line.startPoint, + endPoint: line.endPoint, + parentId: roof.id, + name: 'baseLine', }) - wall.colorLines.push(wallLine) - canvas.add(wallLine) + roof.wall.baseLines.push(baseLine) + canvas.add(baseLine) }) setRoof(roof) diff --git a/src/util/qpolygon-utils.js b/src/util/qpolygon-utils.js index 52551cdd..17b97840 100644 --- a/src/util/qpolygon-utils.js +++ b/src/util/qpolygon-utils.js @@ -740,7 +740,9 @@ export const drawShedRoof = (roofId, canvas, textMode) => { */ export const drawRidgeRoof = (roofId, canvas, textMode) => { const roof = canvas?.getObjects().find((object) => object.id === roofId) + const wall = canvas.getObjects().find((object) => object.name === POLYGON_TYPE.WALL && object.attributes.roofId === roofId) + console.log('wall :', wall) //Math.abs(line.x1 - line.x2) > 1 && Math.abs(line.y1 - line.y2) > 1 const hasNonParallelLines = roof.lines.filter((line) => Big(line.x1).minus(Big(line.x2)).gt(1) && Big(line.y1).minus(Big(line.y2)).gt(1)) if (hasNonParallelLines.length > 0) { @@ -757,10 +759,13 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { /** * 외벽선 */ - const baseLines = roof.wall.baseLines + + const baseLines = wall.baseLines + const wallLines = wall.lines + console.log('wallLines :', wallLines) const baseLinePoints = baseLines.map((line) => ({ x: line.x1, y: line.y1 })) - console.log('지붕 마루 갯수 확인 : ', getMaxRidge(baseLines.length)) + console.log('지붕 마루 갯수 확인 : ', getMaxRidge(baseLines.length), baseLinePoints) /** * baseLine을 기준으로 확인용 polygon 작성 @@ -801,6 +806,8 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { if (eavesType.includes(nextLine.attributes?.type)) { const prevAngle = calculateAngle(prevLine.startPoint, prevLine.endPoint) const nextAngle = calculateAngle(nextLine.startPoint, nextLine.endPoint) + + console.log('prevAngle :', prevAngle, 'nextAngle :', nextAngle) /** * 이전, 다음 라인이 처마일때 라인의 방향이 반대면 ㄷ 모양으로 판단한다. */ @@ -1579,6 +1586,7 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { }) }) + console.log('drawEavesSecondLines : ', drawEavesSecondLines) /** ㄴ 모양 처마에 추녀마루를 그린다. */ drawEavesSecondLines.forEach((current) => { const { currentLine, prevLine, nextLine } = current @@ -1715,25 +1723,27 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { { vertex1: { x: x1, y: y1 }, vertex2: prevEndPoint }, { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }, ) - /*const checkLine = new fabric.Line([x1, y1, prevEndPoint.x, prevEndPoint.y], { + const checkLine = new fabric.Line([x1, y1, prevEndPoint.x, prevEndPoint.y], { stroke: 'red', strokeWidth: 2, parentId: roof.id, + name: 'checkLine', }) canvas.add(checkLine) - canvas.renderAll()*/ + canvas.renderAll() - /* if (intersection) { + if (intersection) { const intersectCircle = new fabric.Circle({ left: intersection.x - 2, top: intersection.y - 2, radius: 4, fill: 'red', parentId: roof.id, + name: 'checkPoint', }) canvas.add(intersectCircle) canvas.renderAll() - }*/ + } if (intersection && !intersection.isIntersectionOutside) { intersectRidgeLine.push({ intersection, @@ -1766,13 +1776,14 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { vertex2: prevEndPoint, } - /*const checkLine = new fabric.Line([hipEdge.vertex1.x, hipEdge.vertex1.y, hipEdge.vertex2.x, hipEdge.vertex2.y], { + const checkLine = new fabric.Line([hipEdge.vertex1.x, hipEdge.vertex1.y, hipEdge.vertex2.x, hipEdge.vertex2.y], { stroke: 'yellow', strokeWidth: 2, parentId: roof.id, + name: 'checkLine', }) canvas.add(checkLine) - canvas.renderAll()*/ + canvas.renderAll() let intersectPoints = [] /** 외벽선에서 추녀 마루가 겹치는 경우에 대한 확인*/ @@ -2279,7 +2290,7 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { }) /** hip을 그리기 위한 기본 길이*/ - let hipSize = 0 + let hipSize = Big(0) if (basePoints.length > 0) { basePoints.sort((a, b) => { const aSize = Big(a.x) @@ -2298,18 +2309,21 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { }) const baseHips = baseHipLines.filter((line) => line.x1 === basePoints[0].x && line.y1 === basePoints[0].y) if (baseHips.length > 0) { - hipSize = Big(baseHips[0].line.attributes.planeSize).div(10) + hipSize = Big(baseHips[0].line.attributes.planeSize) } } + console.log('hipSize : ', hipSize.toNumber()) if (hipSize.eq(0)) { const ridge = current.line - basePoints = baseHipLines.filter((line) => (line.x2 === ridge.x1 && line.y2 === ridge.y1) || (line.x2 === ridge.x2 && line.y2 === ridge.y2)) + basePoints = baseHipLines + .filter((line) => (line.x2 === ridge.x1 && line.y2 === ridge.y1) || (line.x2 === ridge.x2 && line.y2 === ridge.y2)) + .filter((line) => baseLines.filter((baseLine) => baseLine.x1 === line.x1 && baseLine.y1 === line.y1).length > 0) basePoints.sort((a, b) => a.line.attributes.planeSize - b.line.attributes.planeSize) - hipSize = Big(basePoints[0].line.attributes.planeSize).div(10) + hipSize = Big(basePoints[0].line.attributes.planeSize) } - hipSize = hipSize.times(hipSize).div(2).sqrt().round().toNumber() + hipSize = hipSize.pow(2).div(2).sqrt().round().div(10).toNumber() /** 현재 라인을 기준으로 45, 135, 225, 315 방향을 확인하기 위한 좌표, hip은 45도 방향으로만 그린다. */ const checkEdge45 = { vertex1: { x: current.x, y: current.y }, vertex2: { x: current.x + hipSize, y: current.y - hipSize } } @@ -2346,10 +2360,6 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { notIntersect315 = false } }) - /** baseLine의 각 좌표와 교차하는 경우로 한정*/ - intersectPoints = intersectPoints.filter((is) => baseLinePoints.filter((point) => point.x === is.x && point.y === is.y).length > 0) - /** baseLine과 교차하는 좌표의 경우 이미 그려진 추녀마루가 존재한다면 제외한다. */ - intersectPoints = intersectPoints.filter((point) => baseHipLines.filter((hip) => hip.x1 === point.x && hip.y1 === point.y).length === 0) /** baseLine과 교차하지 않는 포인트를 추가한다.*/ if (notIntersect45) { intersectPoints.push(checkEdge45.vertex2) @@ -2363,6 +2373,13 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { if (notIntersect315) { intersectPoints.push(checkEdge315.vertex2) } + /** baseLine의 각 좌표와 교차하는 경우로 한정*/ + intersectPoints = intersectPoints.filter((is) => baseLinePoints.filter((point) => point.x === is.x && point.y === is.y).length > 0) + /** baseLine과 교차하는 좌표의 경우 이미 그려진 추녀마루가 존재한다면 제외한다. */ + intersectPoints = intersectPoints.filter((point) => baseHipLines.filter((hip) => hip.x1 === point.x && hip.y1 === point.y).length === 0) + /** 중복제거 */ + intersectPoints = intersectPoints.filter((point, index, self) => index === self.findIndex((p) => p.x === point.x && p.y === point.y)) + intersectPoints.forEach((is) => { const points = [current.x, current.y, is.x, is.y] const hipLine = drawHipLine(points, canvas, roof, textMode, null, prevDegree, currentDegree) @@ -2388,9 +2405,46 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { ) noRidgeHipPoints.forEach((current) => { - const orthogonalPoints = noRidgeHipPoints.filter( - (point) => point !== current && ((current.x2 === point.x2 && current.y2 !== point.y2) || (current.x2 !== point.x2 && current.y2 === point.y2)), - ) + const checkPoint = new fabric.Circle({ + left: current.x2, + top: current.y2, + radius: 4, + fill: 'green', + parentId: roofId, + name: 'checkPoint', + }) + canvas.add(checkPoint) + canvas.renderAll() + console.log('noRidgeHipPoints current : ', current.x1, current.y1, current.x2, current.y2) + const orthogonalPoints = noRidgeHipPoints.filter((point) => { + const checkPoint1 = new fabric.Circle({ + left: point.x2, + top: point.y2, + radius: 4, + fill: 'blue', + parentId: roofId, + name: 'checkPoint', + }) + canvas.add(checkPoint1) + canvas.renderAll() + console.log(' orthogonalPoints : ', point.x1, point.y1, point.x2, point.y2) + console.log( + 'same point :', + point !== current, + 'y point 다름', + current.x2 === point.x2 && current.y2 !== point.y2, + 'x point 다름', + current.x2 !== point.x2 && current.y2 === point.y2, + ) + canvas.remove(checkPoint1) + canvas.renderAll() + if (point !== current && ((current.x2 === point.x2 && current.y2 !== point.y2) || (current.x2 !== point.x2 && current.y2 === point.y2))) { + return true + } + }) + + canvas.remove(checkPoint) + canvas.renderAll() /** 직교 하는 포인트가 존재 할때 마루를 그린다. */ if (orthogonalPoints.length > 0) { @@ -2458,6 +2512,7 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { .filter((line) => (line.x1 === point.x && line.y1 === point.y) || (line.x2 === point.x && line.y2 === point.y)) .forEach((line) => unFinishedLines.push(line)) }) + unFinishedLines.forEach((line) => { const xVector = Math.sign(Big(line.x2).minus(Big(line.x1))) const yVector = Math.sign(Big(line.y2).minus(Big(line.y1))) @@ -2492,52 +2547,223 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { } }) - intersectPoints.forEach((intersection) => { - const intersectPoint = intersection.intersection - noRidgeHipPoints.forEach((hipPoint) => { - const angle = calculateAngle({ x: intersectPoint.x, y: intersectPoint.y }, { x: hipPoint.x2, y: hipPoint.y2 }) - if (angle === 0 || angle === 180 || angle === 90 || angle === -90) { - baseRidgeCount = baseRidgeCount + 1 - const ridgeLine = drawRidgeLine([intersectPoint.x, intersectPoint.y, hipPoint.x2, hipPoint.y2], canvas, roof, textMode) - baseRidgeLines.push(ridgeLine) - let baseHipLine = baseHipLines.filter((line) => line === intersection.line)[0] - baseHipLine.x2 = intersectPoint.x - baseHipLines.y2 = intersectPoint.y - intersection.line.x2 = intersectPoint.x - intersection.line.y2 = intersectPoint.y - /** 보조선 라인 조정*/ - const hipLine = intersection.line.line - /** 평면길이 */ - const planeSize = calcLinePlaneSize({ - x1: hipLine.x1, - y1: hipLine.y1, - x2: intersectPoint.x, - y2: intersectPoint.y, + /** 마루를 그릴수 있는지 찾는다. */ + noRidgeHipPoints.forEach((hipPoint) => { + const ridgePoints = [] + intersectPoints + .filter( + (intersectPoint) => + (intersectPoint.intersection.x !== hipPoint.x2 && intersectPoint.intersection.y === hipPoint.y2) || + (intersectPoint.intersection.x === hipPoint.x2 && intersectPoint.intersection.y !== hipPoint.y2), + ) + .forEach((intersectPoint) => { + ridgePoints.push({ + intersection: intersectPoint, + distance: Big(intersectPoint.intersection.x) + .minus(Big(hipPoint.x2)) + .abs() + .pow(2) + .plus(Big(intersectPoint.intersection.y).minus(Big(hipPoint.y2)).abs().pow(2)) + .sqrt() + .round(1) + .toNumber(), }) - /** 실제길이 */ - const actualSize = - prevDegree === currentDegree - ? calcLineActualSize( - { - x1: hipLine.x1, - y1: hipLine.y1, - x2: intersectPoint.x, - y2: intersectPoint.y, - }, - currentDegree, - ) - : 0 - hipLine.set({ x2: intersectPoint.x, y2: intersectPoint.y, attributes: { roofId: roof.id, planeSize, actualSize } }) - hipLine.fire('modified') - intersectPoints = intersectPoints.filter((isp) => isp !== intersection) + }) + ridgePoints.sort((a, b) => a.distance - b.distance) + + if (ridgePoints.length > 0) { + const intersection = ridgePoints[0].intersection + const isPoint = intersection.intersection + const points = [hipPoint.x2, hipPoint.y2, isPoint.x, isPoint.y] + const ridgeLine = drawRidgeLine(points, canvas, roof, textMode) + baseRidgeCount = baseRidgeCount + 1 + baseRidgeLines.push(ridgeLine) + + let baseHipLine = baseHipLines.filter((line) => line === intersection.line)[0] + baseHipLine.x2 = isPoint.x + baseHipLines.y2 = isPoint.y + /** 보조선 라인 조정*/ + const hipLine = intersection.line.line + /** 평면길이 */ + const planeSize = calcLinePlaneSize({ + x1: hipLine.x1, + y1: hipLine.y1, + x2: isPoint.x, + y2: isPoint.y, + }) + /** 실제길이 */ + const actualSize = + prevDegree === currentDegree + ? calcLineActualSize( + { + x1: hipLine.x1, + y1: hipLine.y1, + x2: isPoint.x, + y2: isPoint.y, + }, + currentDegree, + ) + : 0 + hipLine.set({ + x2: isPoint.x, + y2: isPoint.y, + attributes: { roofId: roof.id, planeSize, actualSize }, + }) + hipLine.fire('modified') + intersectPoints = intersectPoints.filter((isp) => isp !== intersection) + noRidgeHipPoints = noRidgeHipPoints.filter((hip) => hip !== hipPoint) + } else { + const linePoints = [] + intersectPoints.forEach((intersectPoint) => { + const intersection = intersectPoint.intersection + const xVector = Math.sign(Big(intersection.x).minus(Big(hipPoint.x2))) + const yVector = Math.sign(Big(intersection.y).minus(Big(hipPoint.y2))) + + const checkEdge = { + vertex1: { x: intersection.x, y: intersection.y }, + vertex2: { x: Big(intersection.x).plus(Big(xVector).times(10)).toNumber(), y: Big(intersection.y).plus(Big(yVector).times(10)).toNumber() }, + } + const intersectX = edgesIntersection( + { + vertex1: { x: hipPoint.x2, y: hipPoint.y2 }, + vertex2: { x: Big(hipPoint.x2).plus(Big(xVector).neg().times(10)).toNumber(), y: hipPoint.y2 }, + }, + checkEdge, + ) + const intersectY = edgesIntersection( + { + vertex1: { x: hipPoint.x2, y: hipPoint.y2 }, + vertex2: { x: hipPoint.x2, y: Big(hipPoint.y2).plus(Big(yVector).neg().times(10)).toNumber() }, + }, + checkEdge, + ) + + let distanceX = Infinity, + distanceY = Infinity + + if (intersectX) { + distanceX = Big(intersectX.x) + .minus(Big(intersection.x)) + .abs() + .pow(2) + .plus(Big(intersectX.y).minus(Big(intersection.y)).abs().pow(2)) + .sqrt() + .round(1) + .toNumber() + } + if (intersectY) { + distanceY = Big(intersectY.x) + .minus(Big(intersection.x)) + .abs() + .pow(2) + .plus(Big(intersectY.y).minus(Big(intersection.y)).abs().pow(2)) + .sqrt() + .round(1) + .toNumber() + } + + if (distanceX < distanceY) { + linePoints.push({ intersection: intersectX, intersectPoint }) + } else { + linePoints.push({ intersection: intersectY, intersectPoint }) + } + }) + + const linePoint = linePoints.reduce((prev, current) => { + const prevDistance = Big(prev.intersection.x) + .minus(Big(hipPoint.x2)) + .abs() + .pow(2) + .plus(Big(prev.intersection.y).minus(Big(hipPoint.y2)).abs().pow(2)) + .sqrt() + const currentDistance = Big(current.intersection.x) + .minus(Big(hipPoint.x2)) + .abs() + .pow(2) + .plus(Big(current.intersection.y).minus(Big(hipPoint.y2)).abs().pow(2)) + .sqrt() + if (prevDistance < currentDistance) { + return prev + } else { + return current + } + }, linePoints[0]) + + if (!linePoint) return + const hipStartPoint = [hipPoint.x2, hipPoint.y2, linePoint.intersection.x, linePoint.intersection.y] + console.log('hipStartPoint : ', hipStartPoint) + /** 직선인 경우 마루를 그린다.*/ + if ( + (hipStartPoint[0] === hipStartPoint[2] && hipStartPoint[1] !== hipStartPoint[3]) || + (hipStartPoint[0] !== hipStartPoint[2] && hipStartPoint[1] === hipStartPoint[3]) + ) { + console.log('릿지1') + const ridgeLine = drawRidgeLine(hipStartPoint, canvas, roof, textMode) + baseRidgeCount = baseRidgeCount + 1 + baseRidgeLines.push(ridgeLine) noRidgeHipPoints = noRidgeHipPoints.filter((hip) => hip !== hipPoint) } - }) + console.log( + Big(hipStartPoint[0]).minus(Big(hipStartPoint[2])).abs().toNumber(), + Big(hipStartPoint[1]).minus(Big(hipStartPoint[3])).abs().toNumber(), + Big(hipStartPoint[0]) + .minus(Big(hipStartPoint[2])) + .abs() + .minus(Big(hipStartPoint[1]).minus(Big(hipStartPoint[3])).abs()) + .abs() + .lt(1), + ) + /** 대각선인경우 hip을 그린다. */ + if ( + Big(hipStartPoint[0]) + .minus(Big(hipStartPoint[2])) + .abs() + .minus(Big(hipStartPoint[1]).minus(Big(hipStartPoint[3])).abs()) + .abs() + .lt(1) + ) { + console.log('힙1') + const hipLine = drawHipLine(hipStartPoint, canvas, roof, textMode, null, prevDegree, currentDegree) + baseHipLines.push({ x1: hipStartPoint[0], y1: hipStartPoint[1], x2: hipStartPoint[2], y2: hipStartPoint[3], line: hipLine }) + noRidgeHipPoints = noRidgeHipPoints.filter((hip) => hip !== hipPoint) + } + + const isStartPoint = [ + linePoint.intersection.x, + linePoint.intersection.y, + linePoint.intersectPoint.intersection.x, + linePoint.intersectPoint.intersection.y, + ] + if ( + (isStartPoint[0] === isStartPoint[2] && isStartPoint[1] !== isStartPoint[3]) || + (isStartPoint[0] !== isStartPoint[2] && isStartPoint[1] === isStartPoint[3]) + ) { + const ridgeLine = drawRidgeLine(isStartPoint, canvas, roof, textMode) + baseRidgeCount = baseRidgeCount + 1 + baseRidgeLines.push(ridgeLine) + } + + console.log('isStartPoint : ', isStartPoint) + console.log(Big(isStartPoint[0]).minus(Big(isStartPoint[2])).toNumber()) + if ( + Big(isStartPoint[0]) + .minus(Big(isStartPoint[2])) + .abs() + .minus(Big(isStartPoint[1]).minus(Big(isStartPoint[3])).abs()) + .abs() + .lt(1) + ) { + const hipLine = drawHipLine(isStartPoint, canvas, roof, textMode, null, prevDegree, currentDegree) + baseHipLines.push({ x1: isStartPoint[0], y1: isStartPoint[1], x2: isStartPoint[2], y2: isStartPoint[3], line: hipLine }) + } + } }) const ridgeAllPoints = [] baseRidgeLines.forEach((line) => ridgeAllPoints.push({ x: line.x1, y: line.y1 }, { x: line.x2, y: line.y2 })) + console.log('ridge count', baseRidgeCount, getMaxRidge(baseLines.length)) + ridgeAllPoints.forEach((current) => { ridgeAllPoints .filter((point) => point !== current) @@ -2551,12 +2777,9 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { checkRidgeLine = { x1: current.x, y1: current.y, x2: point.x, y2: point.y } } /** 대각선인 경우 hip확인*/ - if ( - Big(point.x) - .minus(Big(current.x)) - .abs() - .eq(Big(point.y).minus(Big(current.y)).abs()) - ) { + const hipX = Big(current.x).minus(Big(point.x)).abs() + const hipY = Big(current.y).minus(Big(point.y)).abs() + if (hipX.eq(hipY) && hipX.gt(0) && hipY.gt(0)) { checkHipLine = { x1: current.x, y1: current.y, x2: point.x, y2: point.y } } -- 2.47.2 From a7b90621542218c6deb00210741e1ff56c45d880 Mon Sep 17 00:00:00 2001 From: yoosangwook Date: Mon, 24 Mar 2025 13:58:29 +0900 Subject: [PATCH 010/109] =?UTF-8?q?feat:=20s3=20=EC=97=85=EB=A1=9C?= =?UTF-8?q?=EB=93=9C=20=EA=B8=B0=EB=B3=B8=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 1 + qcast3.database.sqlite | Bin 16384 -> 16384 bytes src/app/api/image/upload/route.js | 70 ++++++++++++++++++++ src/components/floor-plan/modal/ImgLoad.jsx | 2 +- src/hooks/common/useRefFiles.js | 17 +++-- 5 files changed, 85 insertions(+), 5 deletions(-) create mode 100644 src/app/api/image/upload/route.js diff --git a/package.json b/package.json index 1f31b9d3..230e1b81 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "serve": "node server.js" }, "dependencies": { + "@aws-sdk/client-s3": "^3.772.0", "ag-grid-react": "^32.0.2", "axios": "^1.7.8", "big.js": "^6.2.2", diff --git a/qcast3.database.sqlite b/qcast3.database.sqlite index c0c43e6faa95884fffddcdf0dcf1413c2e23f368..66ac677f482169e439cd063c21efbc753f2a27c9 100644 GIT binary patch delta 122 zcmZo@U~Fh$oFL68J5k1&QFdd(e0^>{UIri#U|`^1y_w5kA%AEv9~*-xW23Bzp}7H9 zMoCFQv6a4lW?o5ZQ9({=x?ZudUSff6UVc$YMrvYliLQ}eVs2tpeqLgEv0ie1u6}7j YPJUvFer{!PVUBK2etN!MRzYey0Qtuyp8x;= delta 81 zcmZo@U~Fh$oFL68HBrWyQEFqte0^?SUIri#U|`^1xtYsgA^&85J6#RSloWGg%T!a{ jH1jlLT@#B`Bi$sUltkS`LvvGe( { + const Body = Buffer.from(await file.arrayBuffer()) + const Key = `upload/${file.name}` + const ContentType = file.ContentType + + await s3.send( + new PutObjectCommand({ + Bucket, + Key, + Body, + ContentType, + }), + ) + + return { + filePath: `https://${process.env.AMPLIFY_BUCKET}.s3.${process.env.AWS_REGION}.amazonaws.com/${Key}`, + fileName: Key, + } +} + +export async function POST(req) { + try { + const formData = await req.formData() + const file = formData.get('file') + + const result = await uploadImage(file) + result.message = '이미지 업로드 성공' + + return NextResponse.json(result) + } catch (error) { + console.error(error) + return NextResponse.json({ error: 'Failed to upload image' }, { status: 500 }) + } +} + +export async function DELETE(req) { + try { + const searchParams = req.nextUrl.searchParams + const Key = `upload/${searchParams.get('fileName')}` + console.log('🚀 ~ DELETE ~ Key:', Key) + + if (!Key) { + return NextResponse.json({ error: 'fileName parameter is required' }, { status: 400 }) + } + + await s3.send( + new DeleteObjectCommand({ + Bucket, + Key, + }), + ) + + return NextResponse.json({ message: '이미지 삭제 성공' }, { status: 200 }) + } catch (error) { + console.error('S3 Delete Error:', error) + return NextResponse.json({ error: 'Failed to delete image' }, { status: 500 }) + } +} diff --git a/src/components/floor-plan/modal/ImgLoad.jsx b/src/components/floor-plan/modal/ImgLoad.jsx index dda395a9..f0a86938 100644 --- a/src/components/floor-plan/modal/ImgLoad.jsx +++ b/src/components/floor-plan/modal/ImgLoad.jsx @@ -120,7 +120,7 @@ export default function ImgLoad() { value={refImage ? (refImage?.name ?? '') : (currentCanvasPlan?.bgImageName ?? '')} readOnly /> - {refImage && } + {currentCanvasPlan?.bgImageName && }
diff --git a/src/hooks/common/useRefFiles.js b/src/hooks/common/useRefFiles.js index b21cc954..1642daa9 100644 --- a/src/hooks/common/useRefFiles.js +++ b/src/hooks/common/useRefFiles.js @@ -28,7 +28,7 @@ export function useRefFiles() { const [settingModalFirstOptions, setSettingModalFirstOptions] = useRecoilState(settingModalFirstOptionsState) const { handleBackImageLoadToCanvas } = useCanvas() const { swalFire } = useSwal() - const { get, post } = useAxios() + const { get, post, del } = useAxios() useEffect(() => { if (refFileMethod === '1') { @@ -84,6 +84,9 @@ export function useRefFiles() { text: '삭제하시겠습니까?', type: 'confirm', confirmFn: async () => { + console.log('🚀 ~ handleFileDelete ~ handleFileDelete:', refImage) + console.log('🚀 ~ handleFileDelete ~ currentCanvasPlan.bgImageName:', currentCanvasPlan.bgImageName) + await del({ url: `http://localhost:3000/api/image/upload?fileName=${currentCanvasPlan.bgImageName}` }) setRefImage(null) setCurrentCanvasPlan((prev) => ({ ...prev, bgImageName: null })) await deleteBackGroundImage({ @@ -179,18 +182,24 @@ export function useRefFiles() { const formData = new FormData() formData.append('file', file) + // const res = await post({ + // url: `${process.env.NEXT_PUBLIC_HOST_URL}/image/upload`, + // data: formData, + // }) const res = await post({ - url: `${process.env.NEXT_PUBLIC_HOST_URL}/image/upload`, + url: `http://localhost:3000/api/image/upload`, data: formData, }) console.log('🚀 ~ handleUploadImageRefFile ~ res:', res) - setCurrentBgImage(`${process.env.NEXT_PUBLIC_HOST_URL}${res.filePath}`) + // setCurrentBgImage(`${process.env.NEXT_PUBLIC_HOST_URL}${res.filePath}`) + setCurrentBgImage(`${res.filePath}`) setRefImage(file) const params = { objectId: currentCanvasPlan.id, planNo: currentCanvasPlan.planNo, - imagePath: `${process.env.NEXT_PUBLIC_HOST_URL}${res.filePath}`, + // imagePath: `${process.env.NEXT_PUBLIC_HOST_URL}${res.filePath}`, + imagePath: `${res.filePath}`, } console.log('🚀 ~ handleUploadImageRefFile ~ params:', params) await setBackGroundImage(params) -- 2.47.2 From 2492b45a66159ad7392ff04a86c91ae9c5dcc5b9 Mon Sep 17 00:00:00 2001 From: yoosangwook Date: Mon, 24 Mar 2025 17:08:31 +0900 Subject: [PATCH 011/109] =?UTF-8?q?feat:=20=EA=B5=AC=EA=B8=80=20=EB=A7=B5?= =?UTF-8?q?=20=EC=BB=A8=EB=B2=84=ED=8A=B8=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/api/image/map/route.js | 68 +++++++++++++++++++++++++++++++++ src/hooks/common/useRefFiles.js | 14 +++++-- 2 files changed, 79 insertions(+), 3 deletions(-) create mode 100644 src/app/api/image/map/route.js diff --git a/src/app/api/image/map/route.js b/src/app/api/image/map/route.js new file mode 100644 index 00000000..3bcfb35a --- /dev/null +++ b/src/app/api/image/map/route.js @@ -0,0 +1,68 @@ +import { NextResponse } from 'next/server' + +export async function GET(req) { + try { + const searchParams = req.nextUrl.searchParams + const q = searchParams.get('q') + const fileNm = searchParams.get('fileNm') + const zoom = searchParams.get('zoom') + + /** 구글 맵을 이미지로 변경하기 위한 API */ + const API_KEY = 'AIzaSyDO7nVR1N_D2tKy60hgGFavpLaXkHpiHpc' + const targetUrl = `https://maps.googleapis.com/maps/api/staticmap?center=${q}&zoom=${zoom}&maptype=satellite&size=640x640&scale=1&key=${API_KEY}` + const decodeUrl = decodeURIComponent(targetUrl) + + /** 구글 맵을 이미지로 변경하기 위한 API 호출 */ + const response = await fetch(decodeUrl) + const data = await response.arrayBuffer() + // const buffer = Buffer.from(data) + + /** 변경된 이미지를 S3에 업로드 */ + const Body = Buffer.from(data) + const Key = `map/${file.name}` + const ContentType = 'image/png' + + await s3.send( + new PutObjectCommand({ + Bucket, + Key, + Body, + ContentType, + }), + ) + + const result = { + filePath: `https://${process.env.AMPLIFY_BUCKET}.s3.${process.env.AWS_REGION}.amazonaws.com/${Key}`, + fileName: Key, + } + + return NextResponse.json(result) + } catch (error) { + console.error(error) + return NextResponse.json({ error: 'Failed to upload image' }, { status: 500 }) + } +} + +export async function DELETE(req) { + try { + const searchParams = req.nextUrl.searchParams + const Key = `map/${searchParams.get('fileName')}` + console.log('🚀 ~ DELETE ~ Key:', Key) + + if (!Key) { + return NextResponse.json({ error: 'fileName parameter is required' }, { status: 400 }) + } + + await s3.send( + new DeleteObjectCommand({ + Bucket, + Key, + }), + ) + + return NextResponse.json({ message: '이미지 삭제 성공' }, { status: 200 }) + } catch (error) { + console.error('S3 Delete Error:', error) + return NextResponse.json({ error: 'Failed to delete image' }, { status: 500 }) + } +} diff --git a/src/hooks/common/useRefFiles.js b/src/hooks/common/useRefFiles.js index 1642daa9..254c4b12 100644 --- a/src/hooks/common/useRefFiles.js +++ b/src/hooks/common/useRefFiles.js @@ -105,6 +105,9 @@ export function useRefFiles() { text: '삭제하시겠습니까?', type: 'confirm', confirmFn: async () => { + console.log('🚀 ~ handleAddressDelete ~ handleAddressDelete:', refImage) + console.log('🚀 ~ handleAddressDelete ~ currentCanvasPlan.bgImageName:', currentCanvasPlan.bgImageName) + await del({ url: `http://localhost:3000/api/image/map?fileName=${currentCanvasPlan.bgImageName}` }) setMapPositionAddress('') setCurrentBgImage(null) setCurrentCanvasPlan((prev) => ({ ...prev, mapPositionAddress: null })) @@ -135,16 +138,21 @@ export function useRefFiles() { option1: newOption1, })) + // const res = await get({ + // url: `${process.env.NEXT_PUBLIC_HOST_URL}/map/convert?q=${queryRef.current.value}&fileNm=${currentCanvasPlan.id}&zoom=20`, + // }) const res = await get({ - url: `${process.env.NEXT_PUBLIC_HOST_URL}/map/convert?q=${queryRef.current.value}&fileNm=${currentCanvasPlan.id}&zoom=20`, + url: `http://localhost:3000/api/map/upload?q=${queryRef.current.value}&fileNm=${currentCanvasPlan.id}&zoom=20`, }) console.log('🚀 ~ handleMapImageDown ~ res:', res) - setCurrentBgImage(`${process.env.NEXT_PUBLIC_HOST_URL}${res.filePath}`) + // setCurrentBgImage(`${process.env.NEXT_PUBLIC_HOST_URL}${res.filePath}`) + setCurrentBgImage(`${res.filePath}`) await setBackGroundImage({ objectId: currentCanvasPlan.id, planNo: currentCanvasPlan.planNo, - imagePath: `${process.env.NEXT_PUBLIC_HOST_URL}${res.filePath}`, + // imagePath: `${process.env.NEXT_PUBLIC_HOST_URL}${res.filePath}`, + imagePath: `${res.filePath}`, }) } -- 2.47.2 From 9a9fa522f9e158d06c18a5bee97c3e749d4f0afe Mon Sep 17 00:00:00 2001 From: yoosangwook Date: Mon, 24 Mar 2025 17:54:32 +0900 Subject: [PATCH 012/109] =?UTF-8?q?feat:=20=EA=B5=AC=EA=B8=80=20=EB=A7=B5?= =?UTF-8?q?=20=EC=BB=A8=EB=B2=84=ED=8A=B8=20=EA=B8=B0=EB=8A=A5=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- qcast3.database.sqlite | Bin 16384 -> 16384 bytes src/app/api/image/map/route.js | 14 ++++++++++++-- src/hooks/common/useRefFiles.js | 29 +++++++++++++++++++---------- 3 files changed, 31 insertions(+), 12 deletions(-) diff --git a/qcast3.database.sqlite b/qcast3.database.sqlite index 66ac677f482169e439cd063c21efbc753f2a27c9..e6022fd79a14effb51bb9a253191dd42b5f6ec9e 100644 GIT binary patch delta 59 zcmZo@U~Fh$oFL7pHc`fzQEg+we0^?VUIqpRM*bZP{5v*t8LZ>ya1&-@5M^v+p1j%K PoHIAEpjh9;(A)q3y!Q^F delta 63 zcmZo@U~Fh$oFL68J5k1&QFdd(e0^>{UIri#U|`^1y_w5k9slH`_C{i*1v&YNDf+pU R#f3S#Ir-`NdRYaj=>Q$c63zes diff --git a/src/app/api/image/map/route.js b/src/app/api/image/map/route.js index 3bcfb35a..0cc76c02 100644 --- a/src/app/api/image/map/route.js +++ b/src/app/api/image/map/route.js @@ -1,4 +1,14 @@ import { NextResponse } from 'next/server' +import { S3Client, PutObjectCommand, DeleteObjectCommand } from '@aws-sdk/client-s3' + +const Bucket = process.env.AMPLIFY_BUCKET +const s3 = new S3Client({ + region: process.env.AWS_REGION, + credentials: { + accessKeyId: process.env.AWS_ACCESS_KEY_ID, + secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, + }, +}) export async function GET(req) { try { @@ -19,7 +29,7 @@ export async function GET(req) { /** 변경된 이미지를 S3에 업로드 */ const Body = Buffer.from(data) - const Key = `map/${file.name}` + const Key = `maps/${fileNm}` const ContentType = 'image/png' await s3.send( @@ -46,7 +56,7 @@ export async function GET(req) { export async function DELETE(req) { try { const searchParams = req.nextUrl.searchParams - const Key = `map/${searchParams.get('fileName')}` + const Key = `maps/${searchParams.get('fileName')}` console.log('🚀 ~ DELETE ~ Key:', Key) if (!Key) { diff --git a/src/hooks/common/useRefFiles.js b/src/hooks/common/useRefFiles.js index 254c4b12..3e726364 100644 --- a/src/hooks/common/useRefFiles.js +++ b/src/hooks/common/useRefFiles.js @@ -142,7 +142,7 @@ export function useRefFiles() { // url: `${process.env.NEXT_PUBLIC_HOST_URL}/map/convert?q=${queryRef.current.value}&fileNm=${currentCanvasPlan.id}&zoom=20`, // }) const res = await get({ - url: `http://localhost:3000/api/map/upload?q=${queryRef.current.value}&fileNm=${currentCanvasPlan.id}&zoom=20`, + url: `http://localhost:3000/api/image/map?q=${queryRef.current.value}&fileNm=${currentCanvasPlan.id}&zoom=20`, }) console.log('🚀 ~ handleMapImageDown ~ res:', res) // setCurrentBgImage(`${process.env.NEXT_PUBLIC_HOST_URL}${res.filePath}`) @@ -160,16 +160,25 @@ export function useRefFiles() { * 배경 이미지 로드를 위한 세팅 */ useEffect(() => { - if (!currentBgImage) { - return - } + // if (!currentBgImage) { + // return + // } console.log('🚀 ~ useEffect ~ currentBgImage:', currentBgImage) - handleBackImageLoadToCanvas(currentBgImage) - setCurrentCanvasPlan((prev) => ({ - ...prev, - bgImageName: refImage?.name ?? null, - mapPositionAddress: queryRef.current.value, - })) + if (currentBgImage) { + handleBackImageLoadToCanvas(currentBgImage) + setCurrentCanvasPlan((prev) => ({ + ...prev, + // bgImageName: refImage?.name ?? null, + bgImageName: currentBgImage.split('/').pop(), + mapPositionAddress: queryRef.current.value, + })) + } else { + setCurrentCanvasPlan((prev) => ({ + ...prev, + bgImageName: null, + mapPositionAddress: null, + })) + } }, [currentBgImage]) /** -- 2.47.2 From 11edbe19883e7034f58673dabc38def0179fd1a2 Mon Sep 17 00:00:00 2001 From: yoosangwook Date: Mon, 24 Mar 2025 19:01:09 +0900 Subject: [PATCH 013/109] =?UTF-8?q?feat:=20cad=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=BB=A8=EB=B2=84=ED=84=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/api/image/cad/route.js | 70 +++++++++++++++++++++++++++++++++ src/hooks/common/useRefFiles.js | 37 ++++++++++++++--- 2 files changed, 101 insertions(+), 6 deletions(-) create mode 100644 src/app/api/image/cad/route.js diff --git a/src/app/api/image/cad/route.js b/src/app/api/image/cad/route.js new file mode 100644 index 00000000..510feff2 --- /dev/null +++ b/src/app/api/image/cad/route.js @@ -0,0 +1,70 @@ +import { NextResponse } from 'next/server' +import { S3Client, PutObjectCommand, DeleteObjectCommand } from '@aws-sdk/client-s3' + +const Bucket = process.env.AMPLIFY_BUCKET +const s3 = new S3Client({ + region: process.env.AWS_REGION, + credentials: { + accessKeyId: process.env.AWS_ACCESS_KEY_ID, + secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, + }, +}) + +const uploadImage = async (file) => { + const Body = Buffer.from(await file.arrayBuffer()) + const Key = `cads/${file.name}` + const ContentType = file.ContentType + + await s3.send( + new PutObjectCommand({ + Bucket, + Key, + Body, + ContentType, + }), + ) + + return { + filePath: `https://${process.env.AMPLIFY_BUCKET}.s3.${process.env.AWS_REGION}.amazonaws.com/${Key}`, + fileName: Key, + } +} + +export async function POST(req) { + try { + const formData = await req.formData() + const file = formData.get('file') + + const result = await uploadImage(file) + result.message = '이미지 업로드 성공' + + return NextResponse.json(result) + } catch (error) { + console.error(error) + return NextResponse.json({ error: 'Failed to upload image' }, { status: 500 }) + } +} + +export async function DELETE(req) { + try { + const searchParams = req.nextUrl.searchParams + const Key = `cads/${searchParams.get('fileName')}` + console.log('🚀 ~ DELETE ~ Key:', Key) + + if (!Key) { + return NextResponse.json({ error: 'fileName parameter is required' }, { status: 400 }) + } + + await s3.send( + new DeleteObjectCommand({ + Bucket, + Key, + }), + ) + + return NextResponse.json({ message: '이미지 삭제 성공' }, { status: 200 }) + } catch (error) { + console.error('S3 Delete Error:', error) + return NextResponse.json({ error: 'Failed to delete image' }, { status: 500 }) + } +} diff --git a/src/hooks/common/useRefFiles.js b/src/hooks/common/useRefFiles.js index 3e726364..097d42ae 100644 --- a/src/hooks/common/useRefFiles.js +++ b/src/hooks/common/useRefFiles.js @@ -87,8 +87,9 @@ export function useRefFiles() { console.log('🚀 ~ handleFileDelete ~ handleFileDelete:', refImage) console.log('🚀 ~ handleFileDelete ~ currentCanvasPlan.bgImageName:', currentCanvasPlan.bgImageName) await del({ url: `http://localhost:3000/api/image/upload?fileName=${currentCanvasPlan.bgImageName}` }) - setRefImage(null) - setCurrentCanvasPlan((prev) => ({ ...prev, bgImageName: null })) + // setRefImage(null) + setCurrentBgImage(null) + // setCurrentCanvasPlan((prev) => ({ ...prev, bgImageName: null })) await deleteBackGroundImage({ objectId: currentCanvasPlan.id, planNo: currentCanvasPlan.planNo, @@ -110,7 +111,7 @@ export function useRefFiles() { await del({ url: `http://localhost:3000/api/image/map?fileName=${currentCanvasPlan.bgImageName}` }) setMapPositionAddress('') setCurrentBgImage(null) - setCurrentCanvasPlan((prev) => ({ ...prev, mapPositionAddress: null })) + // setCurrentCanvasPlan((prev) => ({ ...prev, mapPositionAddress: null })) await deleteBackGroundImage({ objectId: currentCanvasPlan.id, planNo: currentCanvasPlan.planNo, @@ -173,6 +174,7 @@ export function useRefFiles() { mapPositionAddress: queryRef.current.value, })) } else { + setRefImage(null) setCurrentCanvasPlan((prev) => ({ ...prev, bgImageName: null, @@ -230,15 +232,38 @@ export function useRefFiles() { const formData = new FormData() formData.append('file', file) + /** 캐드 도면 파일 변환 */ const res = await post({ url: converterUrl, data: formData }) console.log('🚀 ~ handleUploadConvertRefFile ~ res:', res) + + /** 캐드 도면 파일 업로드 */ const result = await post({ - url: `${process.env.NEXT_PUBLIC_HOST_URL}/cad/convert`, + url: `http://localhost:3000/api/image/cad`, data: res, }) console.log('🚀 ~ handleUploadConvertRefFile ~ result:', result) - setCurrentBgImage(`${process.env.NEXT_PUBLIC_HOST_URL}${result.filePath}`) - setRefImage(res.Files[0].FileData) + + setCurrentBgImage(`${result.filePath}`) + setRefImage(file) + + const params = { + objectId: currentCanvasPlan.id, + planNo: currentCanvasPlan.planNo, + // imagePath: `${process.env.NEXT_PUBLIC_HOST_URL}${res.filePath}`, + imagePath: `${result.filePath}`, + } + console.log('🚀 ~ handleUploadImageRefFile ~ params:', params) + await setBackGroundImage(params) + + // const res = await post({ url: converterUrl, data: formData }) + // console.log('🚀 ~ handleUploadConvertRefFile ~ res:', res) + // const result = await post({ + // url: `${process.env.NEXT_PUBLIC_HOST_URL}/cad/convert`, + // data: res, + // }) + // console.log('🚀 ~ handleUploadConvertRefFile ~ result:', result) + // setCurrentBgImage(`${process.env.NEXT_PUBLIC_HOST_URL}${result.filePath}`) + // setRefImage(res.Files[0].FileData) } /** -- 2.47.2 From 97389f714162a0872e8a7a9d9d3ed3a5d9735f20 Mon Sep 17 00:00:00 2001 From: yoosangwook Date: Tue, 25 Mar 2025 11:01:40 +0900 Subject: [PATCH 014/109] =?UTF-8?q?feat:=20=EC=BA=94=EB=B2=84=EC=8A=A4=20?= =?UTF-8?q?=EC=9D=B4=EB=AF=B8=EC=A7=80=20S3=EB=A1=9C=20=EC=A0=80=EC=9E=A5?= =?UTF-8?q?=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/api/image/canvas/route.js | 120 ++++++++++++++++++++++++++++ src/app/api/image/upload/route.js | 8 +- src/hooks/floorPlan/useImgLoader.js | 3 +- 3 files changed, 127 insertions(+), 4 deletions(-) create mode 100644 src/app/api/image/canvas/route.js diff --git a/src/app/api/image/canvas/route.js b/src/app/api/image/canvas/route.js new file mode 100644 index 00000000..f2e6df86 --- /dev/null +++ b/src/app/api/image/canvas/route.js @@ -0,0 +1,120 @@ +import { NextResponse } from 'next/server' +import { S3Client, PutObjectCommand, DeleteObjectCommand, GetObjectCommand } from '@aws-sdk/client-s3' +import sharp from 'sharp' +import { v4 as uuidv4 } from 'uuid' +const Bucket = process.env.AMPLIFY_BUCKET +const s3 = new S3Client({ + region: process.env.AWS_REGION, + credentials: { + accessKeyId: process.env.AWS_ACCESS_KEY_ID, + secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, + }, +}) + +const checkArea = (obj) => { + const { width, height, left, top } = obj + + if (left < 0 || top < 0 || width > 1600 || height > 1000) { + return false + } + + return true +} + +const cropImage = async (Key, width, height, left, top) => { + try { + const checkResult = checkArea({ width, height, left, top }) + + // Get the image from S3 + const { Body } = await s3.send( + new GetObjectCommand({ + Bucket, + Key, + }), + ) + + // Convert stream to buffer + const chunks = [] + for await (const chunk of Body) { + chunks.push(chunk) + } + const imageBuffer = Buffer.concat(chunks) + + let processedImage + if (!checkResult) { + processedImage = await sharp(imageBuffer).toBuffer() + } else { + processedImage = await sharp(imageBuffer) + .extract({ + width: parseInt(width), + height: parseInt(height), + left: parseInt(left), + top: parseInt(top), + }) + .png() + .toBuffer() + } + return processedImage + } catch (error) { + console.error('Error processing image:', error) + throw error + } +} + +export async function POST(req) { + try { + const formData = await req.formData() + const file = formData.get('file') + const objectNo = formData.get('objectNo') + const planNo = formData.get('planNo') + const type = formData.get('type') + const width = formData.get('width') + const height = formData.get('height') + const left = formData.get('left') + const top = formData.get('top') + + const OriginalKey = `Drawing/${uuidv4()}` + + // Upload original image + await s3.send( + new PutObjectCommand({ + Bucket, + Key: OriginalKey, + Body: Buffer.from(await file.arrayBuffer()), + ContentType: 'image/png', + }), + ) + + // Process the image + const bufferImage = await cropImage(OriginalKey, width, height, left, top) + + const Key = `Drawing/${objectNo}_${planNo}_${type}` + + // Upload processed image + await s3.send( + new PutObjectCommand({ + Bucket, + Key, + Body: bufferImage, + ContentType: 'image/png', + }), + ) + + await s3.send( + new DeleteObjectCommand({ + Bucket, + Key: OriginalKey, + }), + ) + + const result = { + filePath: `https://${process.env.AMPLIFY_BUCKET}.s3.${process.env.AWS_REGION}.amazonaws.com/${Key}`, + fileName: Key, + } + + return NextResponse.json(result) + } catch (error) { + console.error('Error in POST:', error) + return NextResponse.json({ error: 'Failed to process and upload image' }, { status: 500 }) + } +} diff --git a/src/app/api/image/upload/route.js b/src/app/api/image/upload/route.js index 99daa259..4d875257 100644 --- a/src/app/api/image/upload/route.js +++ b/src/app/api/image/upload/route.js @@ -48,13 +48,15 @@ export async function POST(req) { export async function DELETE(req) { try { const searchParams = req.nextUrl.searchParams - const Key = `upload/${searchParams.get('fileName')}` - console.log('🚀 ~ DELETE ~ Key:', Key) + const fileName = searchParams.get('fileName') - if (!Key) { + if (!fileName) { return NextResponse.json({ error: 'fileName parameter is required' }, { status: 400 }) } + const Key = `upload/${fileName}` + console.log('🚀 ~ DELETE ~ Key:', Key) + await s3.send( new DeleteObjectCommand({ Bucket, diff --git a/src/hooks/floorPlan/useImgLoader.js b/src/hooks/floorPlan/useImgLoader.js index e51a4a2b..2a5a09ab 100644 --- a/src/hooks/floorPlan/useImgLoader.js +++ b/src/hooks/floorPlan/useImgLoader.js @@ -79,7 +79,8 @@ export function useImgLoader() { /** 이미지 크롭 요청 */ const result = await post({ - url: `${process.env.NEXT_PUBLIC_HOST_URL}/image/canvas`, + // url: `${process.env.NEXT_PUBLIC_HOST_URL}/image/canvas`, + url: `http://localhost:3000/api/image/canvas`, data: formData, }) console.log('🚀 ~ handleCanvasToPng ~ result:', result) -- 2.47.2 From 553a259c1fc4f09e14bc4ab0569898f225d91b32 Mon Sep 17 00:00:00 2001 From: yoosangwook Date: Tue, 25 Mar 2025 11:02:08 +0900 Subject: [PATCH 015/109] =?UTF-8?q?chore:=20=EB=8F=84=EB=A9=B4=20=EC=9D=B4?= =?UTF-8?q?=EB=AF=B8=EC=A7=80=20=ED=81=AC=EB=A1=AD=EC=9D=84=20=EC=9C=84?= =?UTF-8?q?=ED=95=9C=20sharp=20lib=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 230e1b81..4c3b230b 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "react-responsive-modal": "^6.4.2", "react-select": "^5.8.1", "recoil": "^0.7.7", + "sharp": "^0.33.5", "sqlite": "^5.1.1", "sqlite3": "^5.1.7", "sweetalert2": "^11.14.1", -- 2.47.2 From 8d645d08dca2ba91b6aa71dcc542804acfdc84ff Mon Sep 17 00:00:00 2001 From: yoosangwook Date: Tue, 25 Mar 2025 13:33:36 +0900 Subject: [PATCH 016/109] =?UTF-8?q?refactor:=20API=20Router=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=9D=BC=EB=B6=80=20=EB=A6=AC=ED=8C=A9=ED=86=A0?= =?UTF-8?q?=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 패턴에 어긋나는 응답 포맷 수정 - 주석 작성 --- src/app/api/image/cad/route.js | 1 - src/app/api/image/canvas/route.js | 19 ++++++++++++++++--- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/app/api/image/cad/route.js b/src/app/api/image/cad/route.js index 510feff2..b7d64a50 100644 --- a/src/app/api/image/cad/route.js +++ b/src/app/api/image/cad/route.js @@ -36,7 +36,6 @@ export async function POST(req) { const file = formData.get('file') const result = await uploadImage(file) - result.message = '이미지 업로드 성공' return NextResponse.json(result) } catch (error) { diff --git a/src/app/api/image/canvas/route.js b/src/app/api/image/canvas/route.js index f2e6df86..6a15e9bf 100644 --- a/src/app/api/image/canvas/route.js +++ b/src/app/api/image/canvas/route.js @@ -75,7 +75,10 @@ export async function POST(req) { const OriginalKey = `Drawing/${uuidv4()}` - // Upload original image + /** + * 원본 이미지를 우선 저장한다. + * 이미지 이름이 겹지는 현상을 방지하기 위해 uuid 를 사용한다. + */ await s3.send( new PutObjectCommand({ Bucket, @@ -85,12 +88,19 @@ export async function POST(req) { }), ) - // Process the image + /** + * 저장된 원본 이미지를 기준으로 크롭여부를 결정하여 크롭 이미지를 저장한다. + */ const bufferImage = await cropImage(OriginalKey, width, height, left, top) + /** + * 크롭 이미지 이름을 결정한다. + */ const Key = `Drawing/${objectNo}_${planNo}_${type}` - // Upload processed image + /** + * 크롭이 완료된 이미지를 업로드한다. + */ await s3.send( new PutObjectCommand({ Bucket, @@ -100,6 +110,9 @@ export async function POST(req) { }), ) + /** + * 크롭이미지 저장이 완료되면 원본 이미지를 삭제한다. + */ await s3.send( new DeleteObjectCommand({ Bucket, -- 2.47.2 From c7de33b8b9021a794c1d7129e2931d7c76dc72cc Mon Sep 17 00:00:00 2001 From: yoosangwook Date: Tue, 25 Mar 2025 14:20:28 +0900 Subject: [PATCH 017/109] =?UTF-8?q?fix:=20popup=20spinner=20=EB=8F=99?= =?UTF-8?q?=EC=9E=91=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- qcast3.database.sqlite | Bin 16384 -> 16384 bytes src/hooks/common/useRefFiles.js | 8 +++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/qcast3.database.sqlite b/qcast3.database.sqlite index e6022fd79a14effb51bb9a253191dd42b5f6ec9e..166f2da26bfaf6d977a400c5ef8dd5a21ef49896 100644 GIT binary patch delta 82 zcmZo@U~Fh$oFL7pHBrWyQEOwue0^R~UIqpRM*bZP{5$wlHuD%n@bd(TvN4EqHp-fq mm|0FfYHuV~T9A{Un4+IsSzMT-o0FfOua{Mjnm&1ly$1kpWEZml delta 102 zcmZo@U~Fh$oFL7pHc`fzQEg+we0^SFUIqpRM*bZP{5yC(HuD%n@K27nlQmB^w=_vL zH8IjnPDx79HAyoz)lEt>veZpUGdD6yGBZd_O197|$O{YQ4d!EG5M^wXH8C_d;F`R{ G-U9$hz8XaU diff --git a/src/hooks/common/useRefFiles.js b/src/hooks/common/useRefFiles.js index 097d42ae..3be7fff2 100644 --- a/src/hooks/common/useRefFiles.js +++ b/src/hooks/common/useRefFiles.js @@ -1,5 +1,5 @@ import { useEffect, useRef, useState } from 'react' -import { useRecoilState } from 'recoil' +import { useRecoilState, useSetRecoilState } from 'recoil' import { useSwal } from '@/hooks/useSwal' import { useAxios } from '../useAxios' @@ -7,6 +7,7 @@ import { currentCanvasPlanState } from '@/store/canvasAtom' import { useCanvas } from '@/hooks/useCanvas' import { deleteBackGroundImage, setBackGroundImage } from '@/lib/imageActions' import { settingModalFirstOptionsState } from '@/store/settingAtom' +import { popSpinnerState } from '@/store/popupAtom' /** * 배경 이미지 관리 @@ -29,6 +30,7 @@ export function useRefFiles() { const { handleBackImageLoadToCanvas } = useCanvas() const { swalFire } = useSwal() const { get, post, del } = useAxios() + const setPopSpinnerStore = useSetRecoilState(popSpinnerState) useEffect(() => { if (refFileMethod === '1') { @@ -84,6 +86,7 @@ export function useRefFiles() { text: '삭제하시겠습니까?', type: 'confirm', confirmFn: async () => { + setPopSpinnerStore(true) console.log('🚀 ~ handleFileDelete ~ handleFileDelete:', refImage) console.log('🚀 ~ handleFileDelete ~ currentCanvasPlan.bgImageName:', currentCanvasPlan.bgImageName) await del({ url: `http://localhost:3000/api/image/upload?fileName=${currentCanvasPlan.bgImageName}` }) @@ -94,6 +97,7 @@ export function useRefFiles() { objectId: currentCanvasPlan.id, planNo: currentCanvasPlan.planNo, }) + setPopSpinnerStore(false) }, }) } @@ -188,6 +192,7 @@ export function useRefFiles() { * @param {*} file */ const handleUploadImageRefFile = async (file) => { + setPopSpinnerStore(true) const newOption1 = settingModalFirstOptions.option1.map((option) => ({ ...option, selected: option.column === 'imageDisplay' ? true : option.selected, @@ -222,6 +227,7 @@ export function useRefFiles() { } console.log('🚀 ~ handleUploadImageRefFile ~ params:', params) await setBackGroundImage(params) + setPopSpinnerStore(false) } /** -- 2.47.2 From 46f46d734ae861475081768e43c04c485ee7bca9 Mon Sep 17 00:00:00 2001 From: Jaeyoung Lee Date: Mon, 7 Apr 2025 10:05:55 +0900 Subject: [PATCH 018/109] =?UTF-8?q?=EC=A7=80=EB=B6=95=EB=8D=AE=EA=B0=9C=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1=20=EA=B4=80=EB=A0=A8=20=EC=9E=91=EC=97=85?= =?UTF-8?q?=EC=A4=91=20(=EB=A7=88=EB=A3=A8=EC=A7=80=EB=B6=95=201=EC=B0=A8?= =?UTF-8?q?=20=EC=A0=95=EB=A6=AC)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/roofcover/useMovementSetting.js | 15 +- src/hooks/useMode.js | 1 + src/util/qpolygon-utils.js | 468 +++++++++++++++++----- 3 files changed, 389 insertions(+), 95 deletions(-) diff --git a/src/hooks/roofcover/useMovementSetting.js b/src/hooks/roofcover/useMovementSetting.js index 558113ff..fc667ec3 100644 --- a/src/hooks/roofcover/useMovementSetting.js +++ b/src/hooks/roofcover/useMovementSetting.js @@ -7,6 +7,7 @@ import { useEvent } from '@/hooks/useEvent' import { useSwal } from '@/hooks/useSwal' import { LINE_TYPE, POLYGON_TYPE } from '@/common/common' import Big from 'big.js' +import { calcLinePlaneSize } from '@/util/qpolygon-utils' //동선이동 형 올림 내림 export function useMovementSetting(id) { @@ -107,6 +108,7 @@ export function useMovementSetting(id) { addCanvasMouseEventListener('mouse:move', mouseMoveEvent) addCanvasMouseEventListener('mouse:down', mouseDownEvent) return () => { + canvas.discardActiveObject() if (FOLLOW_LINE_REF.current != null) { canvas.remove(FOLLOW_LINE_REF.current) FOLLOW_LINE_REF.current = null @@ -366,7 +368,7 @@ export function useMovementSetting(id) { if (target.y1 === target.y2) { checkPoint = { x: midX.toNumber(), y: midY.plus(10).times(value.s).toNumber() } } else { - checkPoint = { x: midX.plus(10).toNumber().times(value.s), y: midY.toNumber() } + checkPoint = { x: midX.plus(10).times(value.s).toNumber(), y: midY.toNumber() } } const inPolygon = wall.inPolygon(checkPoint) @@ -400,18 +402,29 @@ export function useMovementSetting(id) { startPoint: { x: currentLine.x1 + deltaX, y: currentLine.y1 + deltaY }, endPoint: { x: currentLine.x2 + deltaX, y: currentLine.y2 + deltaY }, }) + const currentSize = calcLinePlaneSize({ x1: currentLine.x1, y1: currentLine.y1, x2: currentLine.x2, y2: currentLine.y2 }) + currentLine.attributes.planeSize = currentSize + currentLine.attributes.actualSize = currentSize + const nextOldActualSize = nextLine.attributes.planeSize nextLine.set({ x1: nextLine.x1 + deltaX, y1: nextLine.y1 + deltaY, startPoint: { x: nextLine.x1 + deltaX, y: nextLine.y1 + deltaY }, }) + const nextSize = calcLinePlaneSize({ x1: nextLine.x1, y1: nextLine.y1, x2: nextLine.x2, y2: nextLine.y2 }) + nextLine.attributes.planeSize = nextSize + nextLine.attributes.actualSize = nextSize + const prevOldActualSize = prevLine.attributes.planeSize prevLine.set({ x2: prevLine.x2 + deltaX, y2: prevLine.y2 + deltaY, endPoint: { x: prevLine.x2 + deltaX, y: prevLine.y2 + deltaY }, }) + const prevSize = calcLinePlaneSize({ x1: prevLine.x1, y1: prevLine.y1, x2: prevLine.x2, y2: prevLine.y2 }) + prevLine.attributes.planeSize = prevSize + prevLine.attributes.actualSize = prevSize canvas.renderAll() }) diff --git a/src/hooks/useMode.js b/src/hooks/useMode.js index 788f9d22..8615f7ca 100644 --- a/src/hooks/useMode.js +++ b/src/hooks/useMode.js @@ -1915,6 +1915,7 @@ export function useMode() { parentId: roof.id, name: 'baseLine', }) + baseLine.attributes.originPoint = { x1: line.x1, y1: line.y1, x2: line.x2, y2: line.y2 } roof.wall.baseLines.push(baseLine) canvas.add(baseLine) }) diff --git a/src/util/qpolygon-utils.js b/src/util/qpolygon-utils.js index 17b97840..07ca86e2 100644 --- a/src/util/qpolygon-utils.js +++ b/src/util/qpolygon-utils.js @@ -756,20 +756,43 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { // const eaves = roof.lines.filter((line) => eavesType.includes(line.attributes?.type)) // const gables = roof.lines.filter((line) => gableType.includes(line.attributes?.type)) - /** - * 외벽선 - */ - - const baseLines = wall.baseLines - const wallLines = wall.lines - console.log('wallLines :', wallLines) + /** 외벽선 */ + const baseLines = wall.baseLines.filter((line) => line.attributes.planeSize > 0) const baseLinePoints = baseLines.map((line) => ({ x: line.x1, y: line.y1 })) - console.log('지붕 마루 갯수 확인 : ', getMaxRidge(baseLines.length), baseLinePoints) - - /** - * baseLine을 기준으로 확인용 polygon 작성 + /** 모양 판단을 위한 라인 처리. + * 평행한 라인이 나누어져 있는 경우 하나의 선으로 판단 한다. */ + const drawBaseLines = [] + baseLines.forEach((currentLine, index) => { + let nextLine = baseLines[(index + 1) % baseLines.length] + let prevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length] + const currentAngle = calculateAngle(currentLine.startPoint, currentLine.endPoint) + const nextAngle = calculateAngle(nextLine.startPoint, nextLine.endPoint) + const prevAngle = calculateAngle(prevLine.startPoint, prevLine.endPoint) + + let { x1, y1, x2, y2 } = currentLine + + if (currentAngle !== prevAngle) { + if (currentAngle === nextAngle) { + let nextIndex = baseLines.findIndex((line) => line === nextLine) + while (nextIndex !== index) { + const checkNextLine = baseLines[(nextIndex + 1 + baseLines.length) % baseLines.length] + const checkAngle = calculateAngle(checkNextLine.startPoint, checkNextLine.endPoint) + if (currentAngle !== checkAngle) { + x2 = checkNextLine.x1 + y2 = checkNextLine.y1 + break + } else { + nextIndex = nextIndex + 1 + } + } + } + drawBaseLines.push({ x1, y1, x2, y2, line: currentLine, size: calcLinePlaneSize({ x1, y1, x2, y2 }) }) + } + }) + + /** baseLine을 기준으로 확인용 polygon 작성 */ const checkWallPolygon = new QPolygon(baseLinePoints, {}) /** @@ -790,23 +813,24 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { const drawEavesFirstLines = [] const drawEavesSecondLines = [] - /** - * 모양을 판단하여 그린다. - */ - baseLines.forEach((currentLine, index) => { - /** - * 현재 라인이 처마유형일 경우 - */ + /** 모양을 판단한다. */ + drawBaseLines.forEach((currentBaseLine, index) => { + let prevBaseLine = drawBaseLines[(index - 1 + drawBaseLines.length) % drawBaseLines.length] + let nextBaseLine = drawBaseLines[(index + 1) % drawBaseLines.length] + console.log('currentLine :', currentBaseLine) + console.log('prevLine :', prevBaseLine) + console.log('nextLine :', nextBaseLine) + const currentLine = currentBaseLine.line + const prevLine = prevBaseLine.line + const nextLine = nextBaseLine.line + /** 현재 라인이 처마유형일 경우 */ if (eavesType.includes(currentLine.attributes?.type)) { - const nextLine = baseLines[(index + 1) % baseLines.length] - const prevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length] - /** - * 현재 라인이 처마인 경우 - */ if (eavesType.includes(nextLine.attributes?.type)) { + /** + * 현재 라인이 처마인 경우 + */ const prevAngle = calculateAngle(prevLine.startPoint, prevLine.endPoint) const nextAngle = calculateAngle(nextLine.startPoint, nextLine.endPoint) - console.log('prevAngle :', prevAngle, 'nextAngle :', nextAngle) /** * 이전, 다음 라인이 처마일때 라인의 방향이 반대면 ㄷ 모양으로 판단한다. @@ -831,26 +855,24 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { y: currentMidY.plus(checkScale.times(Math.sign(yVector.toNumber()))).toNumber(), } - // const checkMidLine = new fabric.Line([currentMidX, currentMidY, checkPoints.x, checkPoints.y], { - // stroke: 'blue', - // strokeWidth: 2, - // }) - // canvas.add(checkMidLine) - // canvas.renderAll() - if (checkWallPolygon.inPolygon(checkPoints)) { - drawEavesFirstLines.push({ currentLine, prevLine, nextLine, size: currentLine.attributes.planeSize }) + drawEavesFirstLines.push({ currentBaseLine, prevBaseLine, nextBaseLine }) } else { - drawEavesSecondLines.push({ currentLine, prevLine, nextLine, size: currentLine.attributes.planeSize }) + drawEavesSecondLines.push({ currentBaseLine, prevBaseLine, nextBaseLine }) } } else { - drawEavesSecondLines.push({ currentLine, prevLine, nextLine, size: currentLine.attributes.planeSize }) + drawEavesSecondLines.push({ currentBaseLine, prevBaseLine, nextBaseLine }) } } } }) - drawEavesFirstLines.sort((a, b) => a.size - b.size) + drawEavesFirstLines.sort((a, b) => a.currentBaseLine.size - b.currentBaseLine.size) + console.log('drawEavesFirstLines :', drawEavesFirstLines) + console.log( + 'drawEavesFirstLines :', + drawEavesFirstLines.map((line) => line.currentBaseLine.size), + ) /** 추녀마루 */ let baseHipLines = [] @@ -862,14 +884,11 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { /** ⨆ 모양 처마에 추녀마루를 그린다. */ drawEavesFirstLines.forEach((current) => { // 확인용 라인, 포인트 제거 - /*canvas - .getObjects() - .filter((obj) => obj.name === 'checkPoint' || obj.name === 'checkLine') - .forEach((obj) => canvas.remove(obj)) - canvas.renderAll() -*/ - const { currentLine, prevLine, nextLine } = current - let { x1, x2, y1, y2 } = currentLine + const { currentBaseLine, prevBaseLine, nextBaseLine } = current + const currentLine = currentBaseLine.line + const prevLine = prevBaseLine.line + const nextLine = nextBaseLine.line + let { x1, x2, y1, y2, size } = currentBaseLine let beforePrevLine, afterNextLine /** 이전 라인의 경사 */ @@ -878,12 +897,12 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { const currentDegree = currentLine.attributes.pitch > 0 ? getDegreeByChon(currentLine.attributes.pitch) : currentLine.attributes.degree /** 이전 라인의 전라인, 다음 라인의 다음라인을 찾는다 */ - baseLines.forEach((line, index) => { - if (line === prevLine) { - beforePrevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length] + drawBaseLines.forEach((line, index) => { + if (line === prevBaseLine) { + beforePrevLine = drawBaseLines[(index - 1 + drawBaseLines.length) % drawBaseLines.length] } - if (line === nextLine) { - afterNextLine = baseLines[(index + 1) % baseLines.length] + if (line === nextBaseLine) { + afterNextLine = drawBaseLines[(index + 1) % drawBaseLines.length] } }) @@ -898,16 +917,11 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { const currentAngle = calculateAngle(currentLine.startPoint, currentLine.endPoint) const prevAngle = calculateAngle(prevLine.startPoint, prevLine.endPoint) const nextAngle = calculateAngle(nextLine.startPoint, nextLine.endPoint) - const beforePrevAngle = calculateAngle(beforePrevLine.startPoint, beforePrevLine.endPoint) - const afterNextAngle = calculateAngle(afterNextLine.startPoint, afterNextLine.endPoint) + const beforePrevAngle = calculateAngle(beforePrevLine.line.startPoint, beforePrevLine.line.endPoint) + const afterNextAngle = calculateAngle(afterNextLine.line.startPoint, afterNextLine.line.endPoint) /** 현재 라인의 길이 추녀마루 길이의 기준이 된다. */ - let currentSize = Big(x2) - .minus(Big(x1)) - .plus(Big(y2).minus(Big(y1))) - .abs() - - // console.log('currentLine : ', currentLine.attributes.planeSize) + let currentSize = Big(size).div(10) /** 이전 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/ const prevCheckPoint = { @@ -934,7 +948,7 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { * 현재 라인에서 2번째 전 라인과 2번째 후 라인의 각도가 같을때 -_- 와 같은 형태로 판단하고 * 맞은 편 외벽선까지의 거리를 확인후 currentSize 를 조정한다. */ - if (currentAngle === beforePrevAngle && currentAngle === afterNextAngle) { + if (beforePrevLine === afterNextLine || (currentAngle === beforePrevAngle && currentAngle === afterNextAngle)) { const xVector = Big(nextLine.x2).minus(Big(nextLine.x1)) const yVector = Big(nextLine.y2).minus(Big(nextLine.y1)) const currentMidX = Big(x1).plus(Big(x2)).div(2) @@ -970,6 +984,8 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { if (currentAngle !== beforePrevAngle && currentAngle !== afterNextAngle && beforePrevLine !== afterNextLine) { console.log('4각 아님') // console.log('currentLine : ', currentLine.attributes.planeSize) + // console.log('beforePrevLine : ', beforePrevLine) + // console.log('afterNextLine : ', afterNextLine) const beforePrevX1 = beforePrevLine.x1, beforePrevY1 = beforePrevLine.y1, beforePrevX2 = beforePrevLine.x2, @@ -988,6 +1004,7 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { ).length > 0 /** 6각 */ + // console.log('isConnect :', isConnect) if (isConnect) { const checkScale = currentSize.pow(2).plus(currentSize.pow(2)).sqrt() const intersectBaseLine = [] @@ -1493,10 +1510,11 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { // console.log('startPoint : ', startPoint, 'currentSize : ', currentSize.toNumber()) /** 이전, 다음 라인중 길이가 짧은 길이*/ - const lineMinSize = + /*const lineMinSize = prevLine.attributes.planeSize < nextLine.attributes.planeSize ? Big(prevLine.attributes.planeSize).div(10) - : Big(nextLine.attributes.planeSize).div(10) + : Big(nextLine.attributes.planeSize).div(10)*/ + const lineMinSize = prevBaseLine.size < nextBaseLine.size ? Big(prevBaseLine.size).div(10) : Big(nextBaseLine.size).div(10) /** 마루의 길이는 이전 다음 라인중 짧은것의 길이와 현재라인부터 맞은편 라인까지의 길이에서 현재 라인의 길이를 뺀 것중 짧은 길이 */ const ridgeSize = Big(Math.min(oppositeSize, lineMinSize)) @@ -1505,7 +1523,7 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { if (ridgeSize.gt(0)) { baseRidgeCount = baseRidgeCount + 1 - console.log('baseRidgeCount : ', baseRidgeCount) + // console.log('baseRidgeCount : ', baseRidgeCount) const points = [ startPoint.x, startPoint.y, @@ -1559,6 +1577,12 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { nextHipLine.fire('modified') } } + + canvas + .getObjects() + .filter((obj) => obj.name === 'checkPoint' || obj.name === 'checkLine') + .forEach((obj) => canvas.remove(obj)) + canvas.renderAll() }) /** 중복제거 */ @@ -1586,12 +1610,14 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { }) }) - console.log('drawEavesSecondLines : ', drawEavesSecondLines) + // console.log('drawEavesSecondLines : ', drawEavesSecondLines) /** ㄴ 모양 처마에 추녀마루를 그린다. */ drawEavesSecondLines.forEach((current) => { - const { currentLine, prevLine, nextLine } = current - let { x1, x2, y1, y2 } = currentLine - let beforePrevLine, afterNextLine + const { currentBaseLine, prevBaseLine, nextBaseLine } = current + const currentLine = currentBaseLine.line + const prevLine = prevBaseLine.line + const nextLine = nextBaseLine.line + let { x1, x2, y1, y2, size } = currentBaseLine /** 이전 라인의 경사 */ const prevDegree = prevLine.attributes.pitch > 0 ? getDegreeByChon(prevLine.attributes.pitch) : prevLine.attributes.degree @@ -1723,16 +1749,16 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { { vertex1: { x: x1, y: y1 }, vertex2: prevEndPoint }, { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }, ) - const checkLine = new fabric.Line([x1, y1, prevEndPoint.x, prevEndPoint.y], { + /*const checkLine = new fabric.Line([x1, y1, prevEndPoint.x, prevEndPoint.y], { stroke: 'red', strokeWidth: 2, parentId: roof.id, name: 'checkLine', }) canvas.add(checkLine) - canvas.renderAll() + canvas.renderAll()*/ - if (intersection) { + /* if (intersection) { const intersectCircle = new fabric.Circle({ left: intersection.x - 2, top: intersection.y - 2, @@ -1743,7 +1769,7 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { }) canvas.add(intersectCircle) canvas.renderAll() - } + }*/ if (intersection && !intersection.isIntersectionOutside) { intersectRidgeLine.push({ intersection, @@ -1776,14 +1802,14 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { vertex2: prevEndPoint, } - const checkLine = new fabric.Line([hipEdge.vertex1.x, hipEdge.vertex1.y, hipEdge.vertex2.x, hipEdge.vertex2.y], { + /* const checkLine = new fabric.Line([hipEdge.vertex1.x, hipEdge.vertex1.y, hipEdge.vertex2.x, hipEdge.vertex2.y], { stroke: 'yellow', strokeWidth: 2, parentId: roof.id, name: 'checkLine', }) canvas.add(checkLine) - canvas.renderAll() + canvas.renderAll()*/ let intersectPoints = [] /** 외벽선에서 추녀 마루가 겹치는 경우에 대한 확인*/ @@ -2313,7 +2339,6 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { } } - console.log('hipSize : ', hipSize.toNumber()) if (hipSize.eq(0)) { const ridge = current.line basePoints = baseHipLines @@ -2324,6 +2349,7 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { } hipSize = hipSize.pow(2).div(2).sqrt().round().div(10).toNumber() + // console.log('hipSize : ', hipSize.toNumber()) /** 현재 라인을 기준으로 45, 135, 225, 315 방향을 확인하기 위한 좌표, hip은 45도 방향으로만 그린다. */ const checkEdge45 = { vertex1: { x: current.x, y: current.y }, vertex2: { x: current.x + hipSize, y: current.y - hipSize } } @@ -2382,7 +2408,36 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { intersectPoints.forEach((is) => { const points = [current.x, current.y, is.x, is.y] - const hipLine = drawHipLine(points, canvas, roof, textMode, null, prevDegree, currentDegree) + const pointEdge = { vertex1: { x: points[0], y: points[1] }, vertex2: { x: points[2], y: points[3] } } + const vectorX = Math.sign(Big(points[2]).minus(Big(points[0])).toNumber()) + const vectorY = Math.sign(Big(points[3]).minus(Big(points[1])).toNumber()) + const roofIntersections = [] + roof.lines.forEach((line) => { + const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } + const intersection = edgesIntersection(pointEdge, lineEdge) + if (intersection) { + const vectorIntersectionX = Math.sign(Big(intersection.x).minus(Big(points[0])).toNumber()) + const vectorIntersectionY = Math.sign(Big(intersection.y).minus(Big(points[1])).toNumber()) + if (vectorIntersectionX === vectorX && vectorIntersectionY === vectorY) { + roofIntersections.push({ + x: intersection.x, + y: intersection.y, + size: calcLinePlaneSize({ x1: points[0], y1: points[1], x2: intersection.x, y2: intersection.y }), + }) + } + } + }) + roofIntersections.sort((a, b) => a.size - b.size) + + const hipLine = drawHipLine( + [points[0], points[1], roofIntersections[0].x, roofIntersections[0].y], + canvas, + roof, + textMode, + null, + prevDegree, + currentDegree, + ) baseHipLines.push({ x1: points[0], y1: points[1], x2: points[2], y2: points[3], line: hipLine }) current.cnt = current.cnt + 1 }) @@ -2405,7 +2460,7 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { ) noRidgeHipPoints.forEach((current) => { - const checkPoint = new fabric.Circle({ + /* const checkPoint = new fabric.Circle({ left: current.x2, top: current.y2, radius: 4, @@ -2415,9 +2470,9 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { }) canvas.add(checkPoint) canvas.renderAll() - console.log('noRidgeHipPoints current : ', current.x1, current.y1, current.x2, current.y2) + console.log('noRidgeHipPoints current : ', current.x1, current.y1, current.x2, current.y2)*/ const orthogonalPoints = noRidgeHipPoints.filter((point) => { - const checkPoint1 = new fabric.Circle({ + /*const checkPoint1 = new fabric.Circle({ left: point.x2, top: point.y2, radius: 4, @@ -2437,14 +2492,14 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { current.x2 !== point.x2 && current.y2 === point.y2, ) canvas.remove(checkPoint1) - canvas.renderAll() + canvas.renderAll()*/ if (point !== current && ((current.x2 === point.x2 && current.y2 !== point.y2) || (current.x2 !== point.x2 && current.y2 === point.y2))) { return true } }) - canvas.remove(checkPoint) - canvas.renderAll() + // canvas.remove(checkPoint) + // canvas.renderAll() /** 직교 하는 포인트가 존재 할때 마루를 그린다. */ if (orthogonalPoints.length > 0) { @@ -2691,28 +2746,28 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { if (!linePoint) return const hipStartPoint = [hipPoint.x2, hipPoint.y2, linePoint.intersection.x, linePoint.intersection.y] - console.log('hipStartPoint : ', hipStartPoint) + // console.log('hipStartPoint : ', hipStartPoint) /** 직선인 경우 마루를 그린다.*/ if ( (hipStartPoint[0] === hipStartPoint[2] && hipStartPoint[1] !== hipStartPoint[3]) || (hipStartPoint[0] !== hipStartPoint[2] && hipStartPoint[1] === hipStartPoint[3]) ) { - console.log('릿지1') + // console.log('릿지1') const ridgeLine = drawRidgeLine(hipStartPoint, canvas, roof, textMode) baseRidgeCount = baseRidgeCount + 1 baseRidgeLines.push(ridgeLine) noRidgeHipPoints = noRidgeHipPoints.filter((hip) => hip !== hipPoint) } - console.log( - Big(hipStartPoint[0]).minus(Big(hipStartPoint[2])).abs().toNumber(), - Big(hipStartPoint[1]).minus(Big(hipStartPoint[3])).abs().toNumber(), - Big(hipStartPoint[0]) - .minus(Big(hipStartPoint[2])) - .abs() - .minus(Big(hipStartPoint[1]).minus(Big(hipStartPoint[3])).abs()) - .abs() - .lt(1), - ) + // console.log( + // Big(hipStartPoint[0]).minus(Big(hipStartPoint[2])).abs().toNumber(), + // Big(hipStartPoint[1]).minus(Big(hipStartPoint[3])).abs().toNumber(), + // Big(hipStartPoint[0]) + // .minus(Big(hipStartPoint[2])) + // .abs() + // .minus(Big(hipStartPoint[1]).minus(Big(hipStartPoint[3])).abs()) + // .abs() + // .lt(1), + // ) /** 대각선인경우 hip을 그린다. */ if ( Big(hipStartPoint[0]) @@ -2722,7 +2777,7 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { .abs() .lt(1) ) { - console.log('힙1') + // console.log('힙1') const hipLine = drawHipLine(hipStartPoint, canvas, roof, textMode, null, prevDegree, currentDegree) baseHipLines.push({ x1: hipStartPoint[0], y1: hipStartPoint[1], x2: hipStartPoint[2], y2: hipStartPoint[3], line: hipLine }) noRidgeHipPoints = noRidgeHipPoints.filter((hip) => hip !== hipPoint) @@ -2743,8 +2798,8 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { baseRidgeLines.push(ridgeLine) } - console.log('isStartPoint : ', isStartPoint) - console.log(Big(isStartPoint[0]).minus(Big(isStartPoint[2])).toNumber()) + // console.log('isStartPoint : ', isStartPoint) + // console.log(Big(isStartPoint[0]).minus(Big(isStartPoint[2])).toNumber()) if ( Big(isStartPoint[0]) .minus(Big(isStartPoint[2])) @@ -2762,7 +2817,227 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { const ridgeAllPoints = [] baseRidgeLines.forEach((line) => ridgeAllPoints.push({ x: line.x1, y: line.y1 }, { x: line.x2, y: line.y2 })) - console.log('ridge count', baseRidgeCount, getMaxRidge(baseLines.length)) + // console.log('ridge count', baseRidgeCount, getMaxRidge(baseLines.length)) + + console.log('baseLinePoints : ', baseLinePoints) + /** hip 중에 지붕의 라인과 만나지 않은 선을 확인.*/ + baseHipLines + .filter( + (hip) => baseLinePoints.filter((point) => (point.x === hip.x1 && point.y === hip.y1) || (point.x === hip.x2 && point.y === hip.y2)).length > 0, + ) + .filter((hip) => { + const hipEdge = { vertex1: { x: hip.line.x1, y: hip.line.y1 }, vertex2: { x: hip.line.x2, y: hip.line.y2 } } + const hipVectorX = Math.sign(Big(hip.x1).minus(Big(hip.x2))) + const hipVectorY = Math.sign(Big(hip.y1).minus(Big(hip.y2))) + let isIntersect = false + roof.lines.forEach((line) => { + const edge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } + const intersection = edgesIntersection(edge, hipEdge) + if (intersection && !intersection.isIntersectionOutside) { + const isVectorX = Math.sign(Big(intersection.x).minus(Big(hip.x2))) + const isVectorY = Math.sign(Big(intersection.y).minus(Big(hip.y2))) + if (isVectorX === hipVectorX && isVectorY === hipVectorY) { + isIntersect = true + } + } + }) + return !isIntersect + }) + .forEach((hip) => { + console.log('hip : ', hip) + const checkLine = new fabric.Line([hip.line.x1, hip.line.y1, hip.line.x2, hip.line.y2], { + stroke: 'red', + strokeWidth: 4, + parentId: roofId, + name: 'checkLine', + }) + canvas.add(checkLine) + canvas.renderAll() + const hipLine = hip.line + if (hipLine) { + const hipVectorX = Big(hipLine.x2).plus(Big(hipLine.attributes.planeSize).times(Big(hipLine.x1).minus(Big(hipLine.x2)).neg().s)) + const hipVectorY = Big(hipLine.y2).plus(Big(hipLine.attributes.planeSize).times(Big(hipLine.y1).minus(Big(hipLine.y2)).neg().s)) + const overlapLineX = roof.lines + .filter((roofLine) => roofLine.x1 !== roofLine.x2 && roofLine.y1 === hipLine.y2 && roofLine.y2 === hipLine.y2) + .filter((roofLine) => { + const minX = Math.min(roofLine.x1, roofLine.x2, hipLine.x2) + const maxX = Math.max(roofLine.x1, roofLine.x2, hipLine.x2) + const checkLineEdge = { vertex1: { x: minX, y: hipLine.y2 }, vertex2: { x: maxX, y: hipLine.y2 } } + let isIntersect = false + baseHipLines.forEach((baseHipLine) => { + const edge = { vertex1: { x: baseHipLine.x1, y: baseHipLine.y1 }, vertex2: { x: baseHipLine.x2, y: baseHipLine.y2 } } + const intersection = edgesIntersection(edge, checkLineEdge) + if (intersection && !intersection.isIntersectionOutside) { + const isVectorX = Math.sign(Big(intersection.x).minus(Big(baseHipLine.x2))) + const isVectorY = Math.sign(Big(intersection.y).minus(Big(baseHipLine.y2))) + if (isVectorX === hipVectorX && isVectorY === hipVectorY) { + isIntersect = true + } + } + }) + baseRidgeLines.forEach((baseRidgeLine) => { + const edge = { vertex1: { x: baseRidgeLine.x1, y: baseRidgeLine.y1 }, vertex2: { x: baseRidgeLine.x2, y: baseRidgeLine.y2 } } + const intersection = edgesIntersection(edge, checkLineEdge) + if (intersection && !intersection.isIntersectionOutside) { + const isVectorX = Math.sign(Big(intersection.x).minus(Big(hipLine.x2))) + const isVectorY = Math.sign(Big(intersection.y).minus(Big(hipLine.y2))) + if (isVectorX === hipVectorX && isVectorY === hipVectorY) { + isIntersect = true + } + } + }) + return !isIntersect + }) + const overlapLineY = roof.lines + .filter((roofLine) => roofLine.y1 !== roofLine.y2 && roofLine.x1 === hipLine.x2 && roofLine.x2 === hipLine.x2) + .filter((roofLine) => { + const minY = Math.min(roofLine.y1, roofLine.y2, hipLine.y2) + const maxY = Math.max(roofLine.y1, roofLine.y2, hipLine.y2) + const checkLineEdge = { vertex1: { x: hipLine.x2, y: minY }, vertex2: { x: hipLine.x2, y: maxY } } + let isIntersect = false + baseHipLines.forEach((baseHipLine) => { + const edge = { vertex1: { x: baseHipLine.x1, y: baseHipLine.y1 }, vertex2: { x: baseHipLine.x2, y: baseHipLine.y2 } } + const intersection = edgesIntersection(edge, checkLineEdge) + if (intersection && !intersection.isIntersectionOutside) { + const isVectorX = Math.sign(Big(intersection.x).minus(Big(hipLine.x2))) + const isVectorY = Math.sign(Big(intersection.y).minus(Big(hipLine.y2))) + if (isVectorX === hipVectorX && isVectorY === hipVectorY) { + isIntersect = true + } + } + }) + baseRidgeLines.forEach((baseRidgeLine) => { + const edge = { vertex1: { x: baseRidgeLine.x1, y: baseRidgeLine.y1 }, vertex2: { x: baseRidgeLine.x2, y: baseRidgeLine.y2 } } + const intersection = edgesIntersection(edge, checkLineEdge) + if (intersection && !intersection.isIntersectionOutside) { + const isVectorX = Math.sign(Big(intersection.x).minus(Big(hipLine.x2))) + const isVectorY = Math.sign(Big(intersection.y).minus(Big(hipLine.y2))) + if (isVectorX === hipVectorX && isVectorY === hipVectorY) { + isIntersect = true + } + } + }) + return !isIntersect + }) + + overlapLineX.reduce((prev, current) => { + const prevDistance = Big(prev.x1) + .minus(Big(hipLine.x2)) + .abs() + .lt(Big(prev.x2).minus(Big(hipLine.x2)).abs()) + ? Big(prev.x1).minus(Big(hipLine.x2)).abs() + : Big(prev.x2).minus(Big(hipLine.x2)).abs() + const currentDistance = Big(current.x1) + .minus(Big(hipLine.x2)) + .abs() + .lt(Big(current.x2).minus(Big(hipLine.x2)).abs()) + ? Big(current.x1).minus(Big(hipLine.x2)).abs() + : Big(current.x2).minus(Big(hipLine.x2)).abs() + + return prevDistance.lt(currentDistance) ? prev : current + }, overlapLineX[0]) + + overlapLineY.reduce((prev, current) => { + const prevDistance = Big(prev.y1) + .minus(Big(hipLine.y2)) + .abs() + .lt(Big(prev.y2).minus(Big(hipLine.y2)).abs()) + ? Big(prev.y1).minus(Big(hipLine.y2)).abs() + : Big(prev.y2).minus(Big(hipLine.y2)).abs() + const currentDistance = Big(current.y1) + .minus(Big(hipLine.y2)) + .abs() + .lt(Big(current.y2).minus(Big(hipLine.y2)).abs()) + ? Big(current.y1).minus(Big(hipLine.y2)).abs() + : Big(current.y2).minus(Big(hipLine.y2)).abs() + return prevDistance.lt(currentDistance) ? prev : current + }, overlapLineY[0]) + + if (overlapLineX.length > 0) { + const overlapLine = overlapLineX[0] + const maxX = Math.max(overlapLine.x1, overlapLine.x2) + const point = [hipLine.x2, hipLine.y2, maxX, hipLine.y2] + const addLine = drawHipLine(point, canvas, roof, textMode, null, prevDegree, currentDegree) + baseHipLines.push({ x1: point[0], y1: point[1], x2: point[2], y2: point[3], line: addLine }) + } + if (overlapLineY.length > 0) { + const overlapLine = overlapLineY[0] + const maxY = Math.max(overlapLine.y1, overlapLine.y2) + const point = [hipLine.x2, hipLine.y2, hipLine.x2, maxY] + const addLine = drawHipLine(point, canvas, roof, textMode, null, prevDegree, currentDegree) + baseHipLines.push({ x1: point[0], y1: point[1], x2: point[2], y2: point[3], line: addLine }) + } + } + + const modifiedBaseLine = baseLines.filter((line) => (hip.x2 === line.x1 && hip.y2 === line.y1) || (hip.x2 === line.x2 && hip.y2 === line.y2)) + console.log('modifiedBaseLine : ', modifiedBaseLine) + if (modifiedBaseLine.length === 0) return + const verticalLine = modifiedBaseLine.find( + (line) => + (hip.x2 === line.attributes.originPoint.x1 && hip.x2 === line.attributes.originPoint.x2) || + (hip.y2 === line.attributes.originPoint.y1 && hip.y2 === line.attributes.originPoint.y2), + ) + const horizonLine = modifiedBaseLine.find((line) => line !== verticalLine) + + console.log('verticalLine', verticalLine) + console.log('horizonLine : ', horizonLine) + const horizonRoof = roof.lines.find((line) => { + const originPoint = horizonLine.attributes.originPoint + if (originPoint.y1 === originPoint.y2) { + return ( + line.y1 === line.y2 && + Math.sign(originPoint.x1 - originPoint.x2) === Math.sign(line.x1 - line.x2) && + Big(originPoint.y1).minus(Big(line.y1)).abs().minus(horizonLine.attributes.offset).abs().lt(1) + ) + } else { + return ( + line.x1 === line.x2 && + Math.sign(originPoint.y1 - originPoint.y2) === Math.sign(line.y1 - line.y2) && + Big(originPoint.x1).minus(Big(line.x1)).abs().minus(horizonLine.attributes.offset).abs().lt(1) + ) + } + }) + + if (horizonRoof) { + let horizonPoint + if (horizonRoof.y1 === horizonRoof.y2) { + const minX = Math.min(horizonRoof.x1, horizonRoof.x2, horizonLine.attributes.originPoint.x1, horizonLine.attributes.originPoint.x2) + const maxX = Math.max(horizonRoof.x1, horizonRoof.x2, horizonLine.attributes.originPoint.x1, horizonLine.attributes.originPoint.x2) + horizonPoint = [minX, horizonRoof.y1, maxX, horizonRoof.y1] + } else { + const minY = Math.min(horizonRoof.y1, horizonRoof.y2, horizonLine.attributes.originPoint.y1, horizonLine.attributes.originPoint.y2) + const maxY = Math.max(horizonRoof.y1, horizonRoof.y2, horizonLine.attributes.originPoint.y1, horizonLine.attributes.originPoint.y2) + horizonPoint = [horizonRoof.x1, minY, horizonRoof.x1, maxY] + } + let addLine + const alreadyHorizonLines = baseHipLines.find( + (hipLine) => + hipLine.x1 === horizonPoint[0] && hipLine.y1 === horizonPoint[1] && hipLine.x2 === horizonPoint[2] && hipLine.y2 === horizonPoint[3], + ) + console.log('alreadyHorizonLines : ', alreadyHorizonLines) + if (!alreadyHorizonLines) { + addLine = drawHipLine(horizonPoint, canvas, roof, textMode, null, prevDegree, currentDegree) + baseHipLines.push({ x1: horizonPoint[0], y1: horizonPoint[1], x2: horizonPoint[2], y2: horizonPoint[3], line: addLine }) + } else { + addLine = alreadyHorizonLines + } + + let verticalPoint + if (addLine.y1 === addLine.y2) { + verticalPoint = [hip.x2, hip.y2, hip.x2, addLine.y1] + } else { + verticalPoint = [hip.x2, hip.y2, addLine.x1, hip.y2] + } + const alreadyVerticalLine = baseHipLines.find( + (hipLine) => + hipLine.x1 === verticalPoint[0] && hipLine.y1 === verticalPoint[1] && hipLine.x2 === verticalPoint[2] && hipLine.y2 === verticalPoint[3], + ) + if (!alreadyVerticalLine) { + addLine = drawHipLine(verticalPoint, canvas, roof, textMode, null, prevDegree, currentDegree) + baseHipLines.push({ x1: verticalPoint[0], y1: verticalPoint[1], x2: verticalPoint[2], y2: verticalPoint[3], line: addLine }) + } + } + }) ridgeAllPoints.forEach((current) => { ridgeAllPoints @@ -2871,6 +3146,11 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { }) }) + /** 중복 제거 */ + baseHipLines.forEach((hipLine) => { + baseHipLines.filter((hipLine2) => hipLine !== hipLine2).forEach((hipLine2) => {}) + }) + roof.innerLines = [...baseRidgeLines, ...baseHipLines.map((line) => line.line)] /** 확인용 라인, 포인트 제거 */ -- 2.47.2 From c6b96bec23a054ed5dcff169a5c14719f54d7ccc Mon Sep 17 00:00:00 2001 From: yoosangwook Date: Wed, 23 Apr 2025 10:03:25 +0900 Subject: [PATCH 019/109] chore: update environment variables to use protocol-relative URLs for host --- .env.development | 2 +- .env.production | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.env.development b/.env.development index 4f613c70..ceac1712 100644 --- a/.env.development +++ b/.env.development @@ -1,6 +1,6 @@ NEXT_PUBLIC_API_SERVER_PATH="http://1.248.227.176:38080" -NEXT_PUBLIC_HOST_URL="http://1.248.227.176:4000" +NEXT_PUBLIC_HOST_URL="//1.248.227.176:4000" SESSION_SECRET="i3iHH1yp2/2SpQSIySQ4bpyc4g0D+zCF9FAn5xUG0+Y=" diff --git a/.env.production b/.env.production index f0951f7a..767c3e4b 100644 --- a/.env.production +++ b/.env.production @@ -1,6 +1,6 @@ NEXT_PUBLIC_API_SERVER_PATH="https://api.hanasys.jp/" -NEXT_PUBLIC_HOST_URL="http://1.248.227.176:4000" +NEXT_PUBLIC_HOST_URL="//1.248.227.176:4000" SESSION_SECRET="i3iHH1yp2/2SpQSIySQ4bpyc4g0D+zCF9FAn5xUG0+Y=" -- 2.47.2 From 8de8416160b822ae4b142dab68e2cb3b2a380158 Mon Sep 17 00:00:00 2001 From: yjnoh Date: Wed, 23 Apr 2025 10:10:57 +0900 Subject: [PATCH 020/109] =?UTF-8?q?managementStateLoaded=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../modal/circuitTrestle/CircuitTrestleSetting.jsx | 3 --- .../step/type/PassivityCircuitAllocation.jsx | 4 +--- src/hooks/useEstimate.js | 14 ++++++++------ 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/components/floor-plan/modal/circuitTrestle/CircuitTrestleSetting.jsx b/src/components/floor-plan/modal/circuitTrestle/CircuitTrestleSetting.jsx index a836044e..501302e3 100644 --- a/src/components/floor-plan/modal/circuitTrestle/CircuitTrestleSetting.jsx +++ b/src/components/floor-plan/modal/circuitTrestle/CircuitTrestleSetting.jsx @@ -83,9 +83,6 @@ export default function CircuitTrestleSetting({ id }) { } = useCircuitTrestle() // const { trigger: moduleSelectedDataTrigger } = useCanvasPopupStatusController(2) useEffect(() => { - if (!managementState) { - setManagementState(managementStateLoaded) - } // setCircuitData({ // makers, // selectedMaker, diff --git a/src/components/floor-plan/modal/circuitTrestle/step/type/PassivityCircuitAllocation.jsx b/src/components/floor-plan/modal/circuitTrestle/step/type/PassivityCircuitAllocation.jsx index e009a864..3c53b94f 100644 --- a/src/components/floor-plan/modal/circuitTrestle/step/type/PassivityCircuitAllocation.jsx +++ b/src/components/floor-plan/modal/circuitTrestle/step/type/PassivityCircuitAllocation.jsx @@ -37,9 +37,7 @@ export default function PassivityCircuitAllocation(props) { const circuitNumberText = useRecoilValue(fontSelector('circuitNumberText')) useEffect(() => { setModuleStatisticsData() - if (!managementState) { - setManagementState(managementStateLoaded) - } + canvas .getObjects() .filter((obj) => obj.name === POLYGON_TYPE.MODULE) diff --git a/src/hooks/useEstimate.js b/src/hooks/useEstimate.js index fae0b558..3536fda9 100644 --- a/src/hooks/useEstimate.js +++ b/src/hooks/useEstimate.js @@ -13,7 +13,7 @@ import { useTrestle } from '@/hooks/module/useTrestle' import { usePlan } from '@/hooks/usePlan' export function useEstimate() { - const { managementStateLoaded } = useContext(GlobalDataContext) + const { managementState } = useContext(GlobalDataContext) const { setIsGlobalLoading } = useContext(QcastContext) const router = useRouter() const loginUserState = useRecoilValue(loginUserStore) @@ -31,16 +31,18 @@ export function useEstimate() { * @param {Object} estimateParam - 견적서 저장 데이터 */ const saveEstimate = async (estimateParam) => { + console.log('managementState', managementState) + const userId = loginUserState.userId - const saleStoreId = managementStateLoaded.saleStoreId + const saleStoreId = managementState.saleStoreId const objectNo = currentCanvasPlan.objectNo const planNo = currentCanvasPlan.planNo const slope = estimateParam.roofSurfaceList[0].slope const angle = estimateParam.roofSurfaceList[0].angle - const surfaceType = managementStateLoaded.surfaceType - const setupHeight = managementStateLoaded.installHeight - const standardWindSpeedId = managementStateLoaded.standardWindSpeedId - const snowfall = managementStateLoaded.verticalSnowCover + const surfaceType = managementState.surfaceType + const setupHeight = managementState.installHeight + const standardWindSpeedId = managementState.standardWindSpeedId + const snowfall = managementState.verticalSnowCover const drawingFlg = '1' const saveEstimateData = { -- 2.47.2 From fe957102d964e0ddaa47ba20d63a19f53834a8dd Mon Sep 17 00:00:00 2001 From: yjnoh Date: Mon, 28 Apr 2025 13:20:35 +0900 Subject: [PATCH 021/109] =?UTF-8?q?=EB=AA=A8=EB=93=88=20=EC=9E=90=EB=8F=99?= =?UTF-8?q?=20=EB=A0=88=EC=9D=B4=EC=95=84=EC=9B=83=20=EC=84=A4=EC=B9=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../floor-plan/modal/basic/BasicSetting.jsx | 5 +- src/hooks/common/useMasterController.js | 2 +- src/hooks/module/useModuleBasicSetting.js | 450 +++++++++++++++--- src/locales/ja.json | 3 +- src/locales/ko.json | 3 +- 5 files changed, 397 insertions(+), 66 deletions(-) diff --git a/src/components/floor-plan/modal/basic/BasicSetting.jsx b/src/components/floor-plan/modal/basic/BasicSetting.jsx index 9a09aa1e..a930c148 100644 --- a/src/components/floor-plan/modal/basic/BasicSetting.jsx +++ b/src/components/floor-plan/modal/basic/BasicSetting.jsx @@ -334,9 +334,12 @@ export default function BasicSetting({ id, pos = { x: 50, y: 230 } }) { - + )} diff --git a/src/hooks/common/useMasterController.js b/src/hooks/common/useMasterController.js index 60aab800..21220726 100644 --- a/src/hooks/common/useMasterController.js +++ b/src/hooks/common/useMasterController.js @@ -18,7 +18,7 @@ export function useMasterController() { */ const getRoofMaterialList = async () => { return await get({ url: '/api/v1/master/getRoofMaterialList' }).then((res) => { - console.log('🚀🚀 ~ getRoofMaterialList ~ res:', res) + // console.log('🚀🚀 ~ getRoofMaterialList ~ res:', res) return res }) } diff --git a/src/hooks/module/useModuleBasicSetting.js b/src/hooks/module/useModuleBasicSetting.js index 1871e34a..86279910 100644 --- a/src/hooks/module/useModuleBasicSetting.js +++ b/src/hooks/module/useModuleBasicSetting.js @@ -95,7 +95,7 @@ export function useModuleBasicSetting(tabNum) { setTrestleDetailList(roofConstructionArray) //북면 설치 가능 판매점 - if (moduleSelectionData.common.saleStoreNorthFlg === '1') { + if (moduleSelectionData.common.saleStoreNorthFlg == '1') { setSaleStoreNorthFlg(true) } } @@ -111,6 +111,10 @@ export function useModuleBasicSetting(tabNum) { } } + useEffect(() => { + console.log('saleStoreNorthFlg', saleStoreNorthFlg) + }, [saleStoreNorthFlg]) + //가대 상세 데이터 들어오면 실행 useEffect(() => { if (trestleDetailList.length > 0) { @@ -236,8 +240,26 @@ export function useModuleBasicSetting(tabNum) { } }) + let isNorth = false + const isExistSurface = canvas.getObjects().find((obj) => obj.name === POLYGON_TYPE.MODULE_SETUP_SURFACE && obj.parentId === roof.id) + if (isExistSurface) { + if (canvasSetting.roofSizeSet != '3') { + //북면이 있지만 + if (roof.directionText && roof.directionText.indexOf('北') > -1) { + //북쪽일때 해당 서북서, 동북동은 제외한다고 한다 + if (!(roof.directionText.indexOf('西北西') > -1 || roof.directionText.indexOf('東北東') > -1)) { + isNorth = true + } + } + + isExistSurface.set({ + isNorth: isNorth, //북면여부 + isSaleStoreNorthFlg: moduleSelectionData.common.saleStoreNorthFlg == '1' ? true : false, //북면설치가능점 여부 + }) + } + addTargetMouseEventListener('mousedown', isExistSurface, function () { toggleSelection(isExistSurface) }) @@ -270,7 +292,6 @@ export function useModuleBasicSetting(tabNum) { //모듈설치영역?? 생성 const surfaceId = uuidv4() - let isNorth = false if (canvasSetting.roofSizeSet != '3') { //북면이 있지만 @@ -307,6 +328,7 @@ export function useModuleBasicSetting(tabNum) { trestleDetail: trestleDetail, isNorth: isNorth, perPixelTargetFind: true, + isSaleStoreNorthFlg: moduleSelectionData.common.saleStoreNorthFlg == '1' ? true : false, //북면설치가능점 여부 // angle: -compasDeg, }) @@ -352,8 +374,11 @@ export function useModuleBasicSetting(tabNum) { const isExist = selectedModuleInstSurfaceArray.some((obj) => obj.parentId === setupSurface.parentId) //최초 선택일때 if (!isExist) { - //설치면이 북면이고 북면설치 허용점이 아니면 - if (setupSurface.isNorth && !saleStoreNorthFlg) { + //모듈에 북면 설치 가능 모듈이 있는지 확인함 + const isNorthModuleYn = moduleSelectionData?.module.itemList.some((module) => module.northModuleYn === 'Y') + + //설치면이 북면이고 북면설치 허용점이 아니면 북면 모듈이 한개도 없으면 + if (setupSurface.isNorth && !setupSurface.isSaleStoreNorthFlg && !isNorthModuleYn) { swalFire({ text: getMessage('module.not.batch.north'), icon: 'warning' }) return } @@ -538,9 +563,15 @@ export function useModuleBasicSetting(tabNum) { parentId: moduleSetupSurfaces[i].parentId, }) + const northModuleYn = checkedModule.some((module) => module.northModuleYn === 'Y') //북면이고 북면설치상점이 아니면 그냥 return - if (trestlePolygon.isNorth && !saleStoreNorthFlg) { - return + if (trestlePolygon.isNorth && !trestlePolygon.isSaleStoreNorthFlg) { + if (!northModuleYn) { + //북면이고 설치 가능 상점이 아닌데 북면 설치 모듈이 있으면 + return + } else { + canvas?.add(tempModule) //움직여가면서 추가됨 + } } else { canvas?.add(tempModule) //움직여가면서 추가됨 } @@ -765,7 +796,7 @@ export function useModuleBasicSetting(tabNum) { const mixAsgYn = trestlePolygon.modules[0].moduleInfo.mixAsgYn //현재 체크된 모듈기준으로 혼합가능인지 확인 Y === Y, N === N 일때만 설치 가능 if (checkedModule[0].mixAsgYn !== mixAsgYn) { - swalFire({ text: getMessage('modal.module.basic.setting.module.placement.mix.asg.yn.error') }) + swalFire({ text: getMessage('modal.module.basic.setting.module.placement.mix.asg.yn.error'), icon: 'warning' }) return } } @@ -798,7 +829,7 @@ export function useModuleBasicSetting(tabNum) { const intersection = turf.intersect(turf.featureCollection([dormerTurfPolygon, tempTurfModule])) //겹치는지 확인 //겹치면 안됨 if (intersection) { - swalFire({ text: getMessage('module.place.overobject') }) + swalFire({ text: getMessage('module.place.overobject'), icon: 'warning' }) isIntersection = false } }) @@ -876,7 +907,7 @@ export function useModuleBasicSetting(tabNum) { } if (checkedModule.length === 0) { - swalFire({ text: getMessage('module.place.select.module') }) + swalFire({ text: getMessage('module.place.select.module'), icon: 'warning' }) setIsManualModuleLayoutSetup(false) setManualSetupMode(`manualLayoutSetup_false`) return @@ -890,7 +921,7 @@ export function useModuleBasicSetting(tabNum) { ) if (hasZeroLength) { - swalFire({ text: getMessage('module.layout.setup.has.zero.value') }) + swalFire({ text: getMessage('module.layout.setup.has.zero.value'), icon: 'warning' }) setIsManualModuleLayoutSetup(false) setManualSetupMode(`manualLayoutSetup_false`) return @@ -902,7 +933,7 @@ export function useModuleBasicSetting(tabNum) { //Y인 모듈과 N인 모듈이 둘다 존재하면 설치 불가 if (mixAsgY.length > 0 && mixAsgN.length > 0) { - swalFire({ text: getMessage('modal.module.basic.setting.module.placement.mix.asg.yn.error') }) + swalFire({ text: getMessage('modal.module.basic.setting.module.placement.mix.asg.yn.error'), icon: 'warning' }) return } @@ -1068,9 +1099,15 @@ export function useModuleBasicSetting(tabNum) { parentId: moduleSetupSurfaces[i].parentId, }) + const northModuleYn = checkedModule.some((module) => module.northModuleYn === 'Y') //북면이고 북면설치상점이 아니면 그냥 return - if (trestlePolygon.isNorth && !saleStoreNorthFlg) { - return + if (trestlePolygon.isNorth && !trestlePolygon.isSaleStoreNorthFlg) { + if (!northModuleYn) { + //북면이고 설치 가능 상점이 아닌데 북면 설치 모듈이 있으면 + return + } else { + canvas?.add(tempModule) //움직여가면서 추가됨 + } } else { canvas?.add(tempModule) //움직여가면서 추가됨 } @@ -1732,23 +1769,51 @@ export function useModuleBasicSetting(tabNum) { } //자동 모듈 설치(그리드 방식) - const autoModuleSetup = (placementRef) => { + const autoModuleSetup = (type, layoutSetupRef) => { initEvent() //마우스 이벤트 초기화 + //실패한 지붕재 배열 + let failAutoSetupRoof = [] + + let checkedLayoutData + + /** + * 자동 레이아웃일때 0이 있거나 혼합이 있는지 확인하는 로직 + */ + if (type === 'layout') { + checkedLayoutData = layoutSetupRef.filter((module) => module.checked) + const hasZeroLength = checkedLayoutData.some((module) => module.row === 0 || module.col === 0) + + if (hasZeroLength) { + swalFire({ text: getMessage('module.layout.setup.has.zero.value'), icon: 'warning' }) + return + } + + //혼합 가능 모듈과 혼합 불가능 모듈을 선택했을때 카운트를 해서 확인 + const mixAsgY = checkedModule.filter((obj) => obj.mixAsgYn === 'Y') + const mixAsgN = checkedModule.filter((obj) => obj.mixAsgYn === 'N') + + //Y인 모듈과 N인 모듈이 둘다 존재하면 설치 불가 + if (mixAsgY.length > 0 && mixAsgN.length > 0) { + swalFire({ text: getMessage('modal.module.basic.setting.module.placement.mix.asg.yn.error'), icon: 'warning' }) + return + } + } + if (checkedModule.length === 0) { - swalFire({ text: getMessage('module.place.select.module') }) + swalFire({ text: getMessage('module.place.select.module'), icon: 'warning' }) return } - //혼합 가능 모듈과 혼합 불가능 모듈을 선택했을때 카운트를 해서 확인 - const mixAsgY = checkedModule.filter((obj) => obj.mixAsgYn === 'Y') - const mixAsgN = checkedModule.filter((obj) => obj.mixAsgYn === 'N') + // //혼합 가능 모듈과 혼합 불가능 모듈을 선택했을때 카운트를 해서 확인 + // const mixAsgY = checkedModule.filter((obj) => obj.mixAsgYn === 'Y') + // const mixAsgN = checkedModule.filter((obj) => obj.mixAsgYn === 'N') - //Y인 모듈과 N인 모듈이 둘다 존재하면 설치 불가 - if (mixAsgY.length > 0 && mixAsgN.length > 0) { - swalFire({ text: getMessage('modal.module.basic.setting.module.placement.mix.asg.yn.error') }) - return - } + // //Y인 모듈과 N인 모듈이 둘다 존재하면 설치 불가 + // if (mixAsgY.length > 0 && mixAsgN.length > 0) { + // swalFire({ text: getMessage('modal.module.basic.setting.module.placement.mix.asg.yn.error') }) + // return + // } const isChidori = moduleSetupOption.isChidori const setupLocation = moduleSetupOption.setupLocation @@ -1820,14 +1885,50 @@ export function useModuleBasicSetting(tabNum) { return turf.booleanContains(turfModuleSetupSurface, squarePolygon) || turf.booleanWithin(squarePolygon, turfModuleSetupSurface) } + /** + * 자동 레이아웃 설치 일시 row col 초과 여부 확인 + * @param {*} trestleDetailData + * @returns + */ + const checkAutoLayoutModuleSetup = (moduleSetupSurface, trestleDetailData) => { + const isMultipleModules = checkedModule.length > 1 //모듈이 여러개면 + const maxCol = trestleDetailData.moduleMaxCols //최대 열수 -> 얘는 멀티랑 관계없음 + const maxRow = isMultipleModules + ? trestleDetailData.moduleMaxRows + : trestleDetailData.module.find((item) => item.moduleTpCd === checkedModule[0].moduleTpCd).moduleMaxRows //멀티모듈이면 밖에 maxRows로 판단 아니면 module->itemmList를 가지고 판단 + + //단수 합단수 + const sumRowCount = isMultipleModules + ? layoutSetupRef.filter((item) => item.checked).reduce((acc, cur) => acc + cur.row, 0) + : layoutSetupRef.find((item) => item.moduleId === checkedModule[0].itemId).row //멀티모듈이면 전체 합, 체크된 한개의 열 + + // + const sumColCount = layoutSetupRef.filter((item) => item.col).some((item) => item.col > maxCol) + + if (sumRowCount > maxRow || sumColCount) { + failAutoSetupRoof.push(moduleSetupSurface) + return false + } + + // 혼합일때 모듈 개별의 row를 체크함 + const isPassedObject = + isMultipleModules && layoutSetupRef.find((item, index) => item.checked && item.row > trestleDetailData.module[index].mixModuleMaxRows) + + if (isPassedObject) { + failAutoSetupRoof.push(moduleSetupSurface) + return false + } + return true + } + //흐름 방향이 남쪽(아래) const downFlowSetupModule = ( - surfaceMaxLines, + surfaceMaxLines, //deprecated maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, - isCenter = false, + isCenter = false, //deprecated intvHor, intvVer, ) => { @@ -1840,8 +1941,35 @@ export function useModuleBasicSetting(tabNum) { let installedModuleHeightCount = 0 //마지막으로 설치된 모듈의 카운트 let isChidoriLine = false let flowLines + let installedModuleMixYn + const isNorthSurface = moduleSetupSurface.isNorth + const isIncludeNorthModule = checkedModule.some((module) => module.northModuleYn === 'Y') //체크된 모듈 중에 북면 모듈이 있는지 확인하는 로직 + + let layoutRow = 0 + let layoutCol = 0 + + if (type === 'layout') { + const isPassed = checkAutoLayoutModuleSetup(moduleSetupSurface, trestleDetailData) + if (!isPassed) { + return + } + } + + for (let moduleIndex = 0; moduleIndex < checkedModule.length; moduleIndex++) { + const module = checkedModule[moduleIndex] + + if (type === 'layout' && checkedLayoutData) { + const layout = checkedLayoutData.find((item) => module.itemId === item.moduleId) + layoutRow = layout.row + layoutCol = layout.col + } + //혼합여부에 따라 설치 여부 결정 + if (installedModuleMixYn && installedModuleMixYn !== module.mixAsgYn) { + continue + } + //북면일때 + const isNorthModuleYn = module.northModuleYn === 'Y' - checkedModule.forEach((module, moduleIndex) => { const tmpModuleData = trestleDetailData.module.filter((moduleObj) => module.moduleTpCd === moduleObj.moduleTpCd)[0] //혼합모듈일때는 mixModuleMaxRows 값이 0 이상임 // let moduleMaxRows = tmpModuleData.mixModuleMaxRows === 0 ? tmpModuleData.moduleMaxRows : tmpModuleData.mixModuleMaxRows @@ -1857,18 +1985,38 @@ export function useModuleBasicSetting(tabNum) { } } + if (moduleSetupSurface.isSaleStoreNorthFlg) { + //북면일때 + if (isIncludeNorthModule) { + if (!isNorthModuleYn && isNorthSurface) { + continue + } //흐름 방향이 북쪽(위) + } + } else { + if (isNorthSurface) { + if (!isNorthModuleYn) { + continue + } + } + } + //육지붕이 아닐때만 넣는다 육지붕일땐 클릭 이벤트에 별도로 넣어놓음 const moduleArray = [] let calcAreaWidth = Math.abs(flowLines.right.x1 - flowLines.left.x1) //오른쪽 x에서 왼쪽 x를 뺀 가운데를 찾는 로직 let calcModuleWidthCount = calcAreaWidth / (width + intvHor + 1) //뺀 공간에서 모듈을 몇개를 넣을수 있는지 확인하는 로직 - let calcMaxModuleWidthCount = calcModuleWidthCount > moduleMaxCols ? moduleMaxCols : calcModuleWidthCount //최대 모듈 단수가 있기 때문에 최대 단수보다 카운트가 크면 최대 단수로 씀씀 - let totalModuleWidthCount = Math.floor(calcMaxModuleWidthCount) //치조배치일경우는 한개 더 넣는다 - let calcAreaHeight = flowLines.bottom.y1 - flowLines.top.y1 let calcModuleHeightCount = calcAreaHeight / (height + intvVer + 1) + if (type === 'layout') { + calcModuleWidthCount = layoutCol > calcModuleWidthCount ? calcModuleWidthCount : layoutCol + calcModuleHeightCount = layoutRow + } + + let calcMaxModuleWidthCount = calcModuleWidthCount > moduleMaxCols ? moduleMaxCols : calcModuleWidthCount //최대 모듈 단수가 있기 때문에 최대 단수보다 카운트가 크면 최대 단수로 씀씀 + let totalModuleWidthCount = Math.floor(calcMaxModuleWidthCount) //치조배치일경우는 한개 더 넣는다 + let calcStartPoint = flowLines.right.type === 'flat' ? (calcAreaWidth - totalModuleWidthCount * width) / 2 : 0 //반씩 나눠서 중앙에 맞춤 bottom 기준으로 양변이 직선일때만 가운데 정렬 let startPointX = flowLines.left.x1 + calcStartPoint //시작점을 만든다 @@ -1882,7 +2030,7 @@ export function useModuleBasicSetting(tabNum) { let chidoriLength = 0 //첫번재 모듈 설치 후 두번째 모듈을 몇개까지 설치 할 수 있는지 계산 - if (moduleIndex > 0) { + if (installedModuleHeightCount > 0) { // moduleMaxRows = totalModuleMaxRows - installedModuleHeightCount //두번째 모듈일때 isChidoriLine = installedModuleHeightCount % 2 != 0 ? true : false //첫번째에서 짝수에서 끝났으면 홀수는 치도리가 아님 짝수는 치도리 } @@ -1892,12 +2040,12 @@ export function useModuleBasicSetting(tabNum) { let moduleY = flowLines.bottom.y1 - height * i - 1 //살짝 여유를 준다 //두번째 모듈 -> 혼합일 경우의 설치될 모듈 높이를 계산 - if (moduleIndex > 0) { - moduleY = installedLastHeightCoord - intvVer + if (installedModuleHeightCount > 0) { + moduleY = installedLastHeightCoord } //첫번째는 붙여서 두번째는 마진을 주고 설치 - heightMargin = i === 0 ? 0 : intvVer * i + heightMargin = installedModuleHeightCount === 0 ? 0 : intvVer for (let j = 0; j < totalModuleWidthCount; j++) { let moduleX = startPointX + width * j + 1 //5정도 마진을 준다 @@ -1947,18 +2095,19 @@ export function useModuleBasicSetting(tabNum) { installedLastHeightCoord = moduleY - height - heightMargin } else { //디버깅용 - // tempModule.set({ fill: 'transparent', stroke: 'red', strokeWidth: 1 }) - // canvas?.add(tempModule) - // canvas.renderAll() + tempModule.set({ fill: 'transparent', stroke: 'red', strokeWidth: 1 }) + canvas?.add(tempModule) + canvas.renderAll() } } if (isInstall) { ++installedModuleHeightCount + installedModuleMixYn = module.mixAsgYn } } setupModule.push(moduleArray) - }) + } } const topFlowSetupModule = ( @@ -1980,8 +2129,36 @@ export function useModuleBasicSetting(tabNum) { let installedModuleHeightCount = 0 //마지막으로 설치된 모듈의 카운트 let isChidoriLine = false let flowLines + let installedModuleMixYn + const isNorthSurface = moduleSetupSurface.isNorth + const isIncludeNorthModule = checkedModule.some((module) => module.northModuleYn === 'Y') //체크된 모듈 중에 북면 모듈이 있는지 확인하는 로직 + + let layoutRow = 0 + let layoutCol = 0 + + if (type === 'layout') { + const isPassed = checkAutoLayoutModuleSetup(moduleSetupSurface, trestleDetailData) + if (!isPassed) { + return + } + } + + for (let moduleIndex = 0; moduleIndex < checkedModule.length; moduleIndex++) { + const module = checkedModule[moduleIndex] + + if (type === 'layout' && checkedLayoutData) { + const layout = checkedLayoutData.find((item) => module.itemId === item.moduleId) + layoutRow = layout.row + layoutCol = layout.col + } + + //혼합여부에 따라 설치 여부 결정 + if (installedModuleMixYn && installedModuleMixYn !== module.mixAsgYn) { + continue + } + + const isNorthModuleYn = module.northModuleYn === 'Y' - checkedModule.forEach((module, moduleIndex) => { const tmpModuleData = trestleDetailData.module.filter((moduleObj) => module.moduleTpCd === moduleObj.moduleTpCd)[0] //혼합모듈일때는 mixModuleMaxRows 값이 0 이상임 let moduleMaxRows = tmpModuleData.mixModuleMaxRows === 0 ? tmpModuleData.moduleMaxRows : tmpModuleData.mixModuleMaxRows @@ -1997,20 +2174,45 @@ export function useModuleBasicSetting(tabNum) { } } - //흐름 방향이 북쪽(위) + if (moduleSetupSurface.isSaleStoreNorthFlg) { + //북면가능 설치 대리점이면 + //북면일때 + if (isIncludeNorthModule) { + //북면 모듈이 있는지 확인하는 로직 + if (!isNorthModuleYn && isNorthSurface) { + //북면 모듈이 있으면 북면 모듈만 깔고 나머지는 스킵 + continue + } //흐름 방향이 북쪽(위) + } + } else { + // 불면설치 불가 대리점이면 + if (isNorthSurface) { + //북면일때 + if (!isNorthModuleYn) { + //북면 모듈이 아니면 스킵 + continue + } + } + } //육지붕이 아닐때만 넣는다 육지붕일땐 클릭 이벤트에 별도로 넣어놓음 const moduleArray = [] let calcAreaWidth = flowLines.right.x1 - flowLines.left.x1 //오른쪽 x에서 왼쪽 x를 뺀 가운데를 찾는 로직 let calcModuleWidthCount = calcAreaWidth / (width + intvHor + 1) //뺀 공간에서 모듈을 몇개를 넣을수 있는지 확인하는 로직 + let calcAreaHeight = flowLines.bottom.y1 - flowLines.top.y1 + let calcModuleHeightCount = calcAreaHeight / (height + intvVer + 1) + + //단수지정 자동이면 + if (type === 'layout') { + calcModuleWidthCount = layoutCol > calcModuleWidthCount ? calcModuleWidthCount : layoutCol + calcModuleHeightCount = layoutRow + } + let calcMaxModuleWidthCount = calcModuleWidthCount > moduleMaxCols ? moduleMaxCols : calcModuleWidthCount //최대 모듈 단수가 있기 때문에 최대 단수보다 카운트가 크면 최대 단수로 씀씀 // let totalModuleWidthCount = isChidori ? Math.abs(calcMaxModuleWidthCount) : Math.floor(calcMaxModuleWidthCount) //치조배치일경우는 한개 더 넣는다 //??어쩔때는 붙고 어쩔때는 안붙고 멋대로??? let totalModuleWidthCount = Math.floor(calcMaxModuleWidthCount) //치조배치일경우는 한개 더 넣는다 - let calcAreaHeight = flowLines.bottom.y1 - flowLines.top.y1 - let calcModuleHeightCount = calcAreaHeight / (height + intvVer + 1) - let calcStartPoint = flowLines.left.type === 'flat' ? (calcAreaWidth - totalModuleWidthCount * width) / 2 : 0 //반씩 나눠서 중앙에 맞춤 bottom 기준으로 양변이 직선일때만 가운데 정렬 let startPointX = flowLines.right.x1 - calcStartPoint //시작점을 만든다 @@ -2032,11 +2234,11 @@ export function useModuleBasicSetting(tabNum) { let isInstall = false let moduleY = flowLines.top.y1 + height * i //탑의 y점에서부터 아래로 그려 내려간다 - if (moduleIndex > 0) { - moduleY = installedLastHeightCoord + intvVer + 1 + if (installedModuleHeightCount > 0) { + moduleY = installedLastHeightCoord } - heightMargin = i === 0 ? 0 : intvVer * i //모듈간에 마진이 있어 마진값도 넣음 + heightMargin = installedModuleHeightCount === 0 ? 0 : intvVer //모듈간에 마진이 있어 마진값도 넣음 for (let j = 0; j < totalModuleWidthCount; j++) { //모듈 열수 만큼 반복 let moduleX = startPointX - width * j - 1 //시작점에서 우 -> 좌로 그려 내려간다 @@ -2087,11 +2289,12 @@ export function useModuleBasicSetting(tabNum) { } if (isInstall) { ++installedModuleHeightCount + installedModuleMixYn = module.mixAsgYn } } setupModule.push(moduleArray) - }) + } } //남, 북과 같은 로직으로 적용하려면 좌우는 열 -> 행 으로 그려야함 @@ -2115,8 +2318,36 @@ export function useModuleBasicSetting(tabNum) { let installedModuleHeightCount = 0 //마지막으로 설치된 모듈의 카운트 let isChidoriLine = false let flowLines + let installedModuleMixYn + const isNorthSurface = moduleSetupSurface.isNorth + const isIncludeNorthModule = checkedModule.some((module) => module.northModuleYn === 'Y') //체크된 모듈 중에 북면 모듈이 있는지 확인하는 로직 - checkedModule.forEach((module, moduleIndex) => { + let layoutRow = 0 + let layoutCol = 0 + + if (type === 'layout') { + const isPassed = checkAutoLayoutModuleSetup(moduleSetupSurface, trestleDetailData) + if (!isPassed) { + return + } + } + + for (let moduleIndex = 0; moduleIndex < checkedModule.length; moduleIndex++) { + const module = checkedModule[moduleIndex] + + //단수 지정이면 + if (type === 'layout' && checkedLayoutData) { + const layout = checkedLayoutData.find((item) => module.itemId === item.moduleId) + layoutRow = layout.row + layoutCol = layout.col + } + + //혼합여부에 따라 설치 여부 결정 + if (installedModuleMixYn && installedModuleMixYn !== module.mixAsgYn) { + continue + } + + const isNorthModuleYn = module.northModuleYn === 'Y' const tmpModuleData = trestleDetailData.module.filter((moduleObj) => module.moduleTpCd === moduleObj.moduleTpCd)[0] //혼합모듈일때는 mixModuleMaxRows 값이 0 이상임 let moduleMaxRows = tmpModuleData.mixModuleMaxRows === 0 ? tmpModuleData.moduleMaxRows : tmpModuleData.mixModuleMaxRows @@ -2135,19 +2366,46 @@ export function useModuleBasicSetting(tabNum) { } } + if (moduleSetupSurface.isSaleStoreNorthFlg) { + //북면가능 설치 대리점이면 + //북면일때 + if (isIncludeNorthModule) { + //북면 모듈이 있는지 확인하는 로직 + if (!isNorthModuleYn && isNorthSurface) { + //북면 모듈이 있으면 북면 모듈만 깔고 나머지는 스킵 + continue + } //흐름 방향이 북쪽(위) + } + } else { + // 불면설치 불가 대리점이면 + if (isNorthSurface) { + //북면일때 + if (!isNorthModuleYn) { + //북면 모듈이 아니면 스킵 + continue + } + } + } + //육지붕이 아닐때만 넣는다 육지붕일땐 클릭 이벤트에 별도로 넣어놓음 const moduleArray = [] let calcAreaWidth = flowLines.bottom.y1 - flowLines.top.y1 //아래에서 y에서 위를 y를 뺀 가운데를 찾는 로직 let calcModuleWidthCount = calcAreaWidth / (height + intvHor + 1) //뺀 공간에서 모듈을 몇개를 넣을수 있는지 확인하는 로직 + let calcAreaHeight = flowLines.right.x1 - flowLines.left.x1 + let calcModuleHeightCount = calcAreaHeight / (width + intvVer + 1) + + //단수지정 자동이면 + if (type === 'layout') { + calcModuleWidthCount = layoutCol > calcModuleWidthCount ? calcModuleWidthCount : layoutCol + calcModuleHeightCount = layoutRow + } + let calcMaxModuleWidthCount = calcModuleWidthCount > moduleMaxCols ? moduleMaxCols : calcModuleWidthCount //최대 모듈 단수가 있기 때문에 최대 단수보다 카운트가 크면 최대 단수로 씀씀 // let totalModuleWidthCount = isChidori ? Math.abs(calcMaxModuleWidthCount) : Math.floor(calcMaxModuleWidthCount) //치조배치일경우는 한개 더 넣는다 let totalModuleWidthCount = Math.floor(calcMaxModuleWidthCount) //치조배치일경우는 한개 더 넣는다 - let calcAreaHeight = flowLines.right.x1 - flowLines.left.x1 - let calcModuleHeightCount = calcAreaHeight / (width + intvVer + 1) - let calcStartPoint = flowLines.bottom.type === 'flat' ? (calcAreaWidth - totalModuleWidthCount * height) / 2 : 0 //반씩 나눠서 중앙에 맞춤 left 높이 기준으로 양변이 직선일때만 가운데 정렬 let startPointX = flowLines.top.y1 + calcStartPoint //시작점을 만든다 @@ -2171,12 +2429,12 @@ export function useModuleBasicSetting(tabNum) { let moduleY = flowLines.left.x1 + width * i + 1 //살짝 여유를 준다 //두번째 모듈 -> 혼합일 경우의 설치될 모듈 높이를 계산 - if (moduleIndex > 0) { - moduleY = installedLastHeightCoord + intvHor + if (installedModuleHeightCount > 0) { + moduleY = installedLastHeightCoord } //첫번째는 붙여서 두번째는 마진을 주고 설치 - heightMargin = i === 0 ? 0 : intvHor * i + heightMargin = installedModuleHeightCount === 0 ? 0 : intvHor for (let j = 0; j < totalModuleWidthCount; j++) { let moduleX = startPointX + height * j + 1 //5정도 마진을 준다 @@ -2227,10 +2485,11 @@ export function useModuleBasicSetting(tabNum) { if (isInstall) { ++installedModuleHeightCount + installedModuleMixYn = module.mixAsgYn } } setupModule.push(moduleArray) - }) + } } const rightFlowSetupModule = ( @@ -2252,8 +2511,35 @@ export function useModuleBasicSetting(tabNum) { let installedModuleHeightCount = 0 //마지막으로 설치된 모듈의 카운트 let isChidoriLine = false let flowLines + let installedModuleMixYn + const isNorthSurface = moduleSetupSurface.isNorth + const isIncludeNorthModule = checkedModule.some((module) => module.northModuleYn === 'Y') //체크된 모듈 중에 북면 모듈이 있는지 확인하는 로직 - checkedModule.forEach((module, moduleIndex) => { + let layoutRow = 0 + let layoutCol = 0 + + if (type === 'layout') { + const isPassed = checkAutoLayoutModuleSetup(moduleSetupSurface, trestleDetailData) + if (!isPassed) { + return + } + } + + for (let moduleIndex = 0; moduleIndex < checkedModule.length; moduleIndex++) { + const module = checkedModule[moduleIndex] + + if (type === 'layout' && checkedLayoutData) { + const layout = checkedLayoutData.find((item) => module.itemId === item.moduleId) + layoutRow = layout.row + layoutCol = layout.col + } + + //혼합여부에 따라 설치 여부 결정 + if (installedModuleMixYn && installedModuleMixYn !== module.mixAsgYn) { + continue + } + + const isNorthModuleYn = module.northModuleYn === 'Y' const tmpModuleData = trestleDetailData.module.filter((moduleObj) => module.moduleTpCd === moduleObj.moduleTpCd)[0] //혼합모듈일때는 mixModuleMaxRows 값이 0 이상임 let moduleMaxRows = tmpModuleData.mixModuleMaxRows === 0 ? tmpModuleData.moduleMaxRows : tmpModuleData.mixModuleMaxRows @@ -2272,18 +2558,45 @@ export function useModuleBasicSetting(tabNum) { } } + if (moduleSetupSurface.isSaleStoreNorthFlg) { + //북면가능 설치 대리점이면 + //북면일때 + if (isIncludeNorthModule) { + //북면 모듈이 있는지 확인하는 로직 + if (!isNorthModuleYn && isNorthSurface) { + //북면 모듈이 있으면 북면 모듈만 깔고 나머지는 스킵 + continue + } //흐름 방향이 북쪽(위) + } + } else { + // 불면설치 불가 대리점이면 + if (isNorthSurface) { + //북면일때 + if (!isNorthModuleYn) { + //북면 모듈이 아니면 스킵 + continue + } + } + } + //육지붕이 아닐때만 넣는다 육지붕일땐 클릭 이벤트에 별도로 넣어놓음 const moduleArray = [] let calcAreaWidth = flowLines.bottom.y1 - flowLines.top.y1 //아래에서 y에서 위를 y를 뺀 가운데를 찾는 로직 let calcModuleWidthCount = calcAreaWidth / (height + intvHor + 1) //뺀 공간에서 모듈을 몇개를 넣을수 있는지 확인하는 로직 + let calcAreaHeight = flowLines.right.x1 - flowLines.left.x1 + let calcModuleHeightCount = calcAreaHeight / (width + intvVer + 1) + + //단수지정 자동이면 + if (type === 'layout') { + calcModuleWidthCount = layoutCol > calcModuleWidthCount ? calcModuleWidthCount : layoutCol + calcModuleHeightCount = layoutRow + } + let calcMaxModuleWidthCount = calcModuleWidthCount > moduleMaxCols ? moduleMaxCols : calcModuleWidthCount //최대 모듈 단수가 있기 때문에 최대 단수보다 카운트가 크면 최대 단수로 씀씀 // let totalModuleWidthCount = isChidori ? Math.abs(calcMaxModuleWidthCount) : Math.floor(calcMaxModuleWidthCount) //치조배치일경우는 한개 더 넣는다 let totalModuleWidthCount = Math.floor(calcMaxModuleWidthCount) //치조배치일경우는 한개 더 넣는다 - let calcAreaHeight = flowLines.right.x1 - flowLines.left.x1 - let calcModuleHeightCount = calcAreaHeight / (width + intvVer + 1) - let calcStartPoint = flowLines.top.type === 'flat' ? (calcAreaWidth - totalModuleWidthCount * height) / 2 : 0 //반씩 나눠서 중앙에 맞춤 left 높이 기준으로 양변이 직선일때만 가운데 정렬 let startPointX = flowLines.bottom.y2 - calcStartPoint //시작점을 만든다 @@ -2307,12 +2620,12 @@ export function useModuleBasicSetting(tabNum) { let moduleY = flowLines.right.x1 - width * i - 1 //살짝 여유를 준다 //두번째 모듈 -> 혼합일 경우의 설치될 모듈 높이를 계산 - if (moduleIndex > 0) { - moduleY = installedLastHeightCoord - intvHor + if (installedModuleHeightCount > 0) { + moduleY = installedLastHeightCoord } //첫번째는 붙여서 두번째는 마진을 주고 설치 - heightMargin = i === 0 ? 0 : intvHor * i + heightMargin = installedModuleHeightCount === 0 ? 0 : intvHor for (let j = 0; j < totalModuleWidthCount; j++) { let moduleX = startPointX - height * j - 1 //5정도 마진을 준다 @@ -2366,11 +2679,12 @@ export function useModuleBasicSetting(tabNum) { if (isInstall) { ++installedModuleHeightCount + installedModuleMixYn = module.mixAsgYn } } setupModule.push(moduleArray) - }) + } } moduleSetupSurfaces.forEach((moduleSetupSurface, index) => { @@ -2460,6 +2774,18 @@ export function useModuleBasicSetting(tabNum) { } }) // calculateForApi() + + if (type === 'layout' && failAutoSetupRoof.length > 0) { + const roofNamesList = failAutoSetupRoof.map((roof) => ({ + roofName: roof.roofMaterial.roofMatlNmJp, + moduleMaxRows: roof.trestleDetail.moduleMaxRows, + })) + + const alertString = roofNamesList.map((item, index) => `${item.roofName}(${item.moduleMaxRows})`) + console.log('alertString', alertString) + + // swalFire({ text: alertString, icon: 'warning' }) + } } const coordToTurfPolygon = (points) => { diff --git a/src/locales/ja.json b/src/locales/ja.json index 5ab74329..1fc75a67 100644 --- a/src/locales/ja.json +++ b/src/locales/ja.json @@ -149,9 +149,10 @@ "modal.module.basic.setting.pitch.module.column.amount": "列数", "modal.module.basic.setting.pitch.module.column.margin": "左右間隔", "modal.module.basic.setting.prev": "前に戻る", - "modal.module.basic.setting.row.batch": "段・列数指定配置", + "modal.module.basic.setting.row.batch": "レイアウト指定", "modal.module.basic.setting.passivity.placement": "手動配置", "modal.module.basic.setting.auto.placement": "自動配置", + "modal.module.basic.setting.auto.row.batch": "自動レイアウト指定", "plan.menu.module.circuit.setting.circuit.trestle.setting": "回路設定", "modal.circuit.trestle.setting": "回路設定", "modal.circuit.trestle.setting.alloc.trestle": "架台配置", diff --git a/src/locales/ko.json b/src/locales/ko.json index 1090b4b1..cd8b855b 100644 --- a/src/locales/ko.json +++ b/src/locales/ko.json @@ -152,7 +152,8 @@ "modal.module.basic.setting.prev": "이전", "modal.module.basic.setting.row.batch": "단·열수 지정 배치", "modal.module.basic.setting.passivity.placement": "수동 배치", - "modal.module.basic.setting.auto.placement": "설정값으로 자동 배치", + "modal.module.basic.setting.auto.placement": "자동 배치", + "modal.module.basic.setting.auto.row.batch": "자동 단·열수 지정 배치 ", "plan.menu.module.circuit.setting.circuit.trestle.setting": "회로설정", "modal.circuit.trestle.setting": "회로설정", "modal.circuit.trestle.setting.alloc.trestle": "가대할당", -- 2.47.2 From 1c7b81c99f2b5bdec165298533f6446622fa3804 Mon Sep 17 00:00:00 2001 From: yjnoh Date: Mon, 28 Apr 2025 14:09:58 +0900 Subject: [PATCH 022/109] =?UTF-8?q?=EB=AA=A8=EB=93=88=20=EC=9E=90=EB=8F=99?= =?UTF-8?q?=20=EB=A0=88=EC=9D=B4=EC=95=84=EC=9B=83=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/module/useModuleBasicSetting.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/hooks/module/useModuleBasicSetting.js b/src/hooks/module/useModuleBasicSetting.js index 86279910..e60a5324 100644 --- a/src/hooks/module/useModuleBasicSetting.js +++ b/src/hooks/module/useModuleBasicSetting.js @@ -2775,6 +2775,9 @@ export function useModuleBasicSetting(tabNum) { }) // calculateForApi() + /** + * 자동 레이아웃일떄 설치 가능한 애들은 설치 해주고 실패한 애들은 이름, 최대 설치 가능한 높이 알려줄라고 + */ if (type === 'layout' && failAutoSetupRoof.length > 0) { const roofNamesList = failAutoSetupRoof.map((roof) => ({ roofName: roof.roofMaterial.roofMatlNmJp, -- 2.47.2 From 95e6f4c0a4cd3aabddd0bd5a6cc20e9ad0d4071a Mon Sep 17 00:00:00 2001 From: Jaeyoung Lee Date: Mon, 28 Apr 2025 18:06:55 +0900 Subject: [PATCH 023/109] =?UTF-8?q?=EB=B0=95=EA=B3=B5=EC=A7=80=EB=B6=95=20?= =?UTF-8?q?6=EA=B0=81=20=EB=8C=80=EC=9D=91=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?=ED=9B=84=20=EC=A0=95=EB=A6=AC.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/fabric/QPolygon.js | 7 +- src/hooks/roofcover/useMovementSetting.js | 22 +- src/util/qpolygon-utils.js | 1773 ++++++++++++++++++++- 3 files changed, 1731 insertions(+), 71 deletions(-) diff --git a/src/components/fabric/QPolygon.js b/src/components/fabric/QPolygon.js index 65fe9d11..fc2ba2ab 100644 --- a/src/components/fabric/QPolygon.js +++ b/src/components/fabric/QPolygon.js @@ -2,7 +2,7 @@ import { fabric } from 'fabric' import { v4 as uuidv4 } from 'uuid' import { QLine } from '@/components/fabric/QLine' import { distanceBetweenPoints, findTopTwoIndexesByDistance, getDirectionByPoint, sortedPointLessEightPoint, sortedPoints } from '@/util/canvas-util' -import { calculateAngle, drawGabledRoof, drawRidgeRoof, drawShedRoof, toGeoJSON } from '@/util/qpolygon-utils' +import { calculateAngle, drawRidgeRoof, drawShedRoof, toGeoJSON } from '@/util/qpolygon-utils' import * as turf from '@turf/turf' import { LINE_TYPE, POLYGON_TYPE } from '@/common/common' import Big from 'big.js' @@ -247,12 +247,13 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, { const hasShed = types.includes(LINE_TYPE.WALLLINE.SHED) // A형, B형 박공 지붕 - if ( + /* if ( (gableOdd.every((type) => type === LINE_TYPE.WALLLINE.EAVES) && gableEven.every((type) => gableType.includes(type))) || (gableEven.every((type) => type === LINE_TYPE.WALLLINE.EAVES) && gableOdd.every((type) => gableType.includes(type))) ) { drawGabledRoof(this.id, this.canvas, textMode) - } else if (hasShed) { + } else*/ + if (hasShed) { const sheds = this.lines.filter((line) => line.attributes !== undefined && line.attributes.type === LINE_TYPE.WALLLINE.SHED) const areLinesParallel = function (line1, line2) { const angle1 = calculateAngle(line1.startPoint, line1.endPoint) diff --git a/src/hooks/roofcover/useMovementSetting.js b/src/hooks/roofcover/useMovementSetting.js index fc667ec3..d7680150 100644 --- a/src/hooks/roofcover/useMovementSetting.js +++ b/src/hooks/roofcover/useMovementSetting.js @@ -299,6 +299,17 @@ export function useMovementSetting(id) { } const handleSave = () => { + if (CONFIRM_LINE_REF.current !== null) { + canvas.remove(CONFIRM_LINE_REF.current) + CONFIRM_LINE_REF.current = null + canvas.renderAll() + } + if (FOLLOW_LINE_REF.current !== null) { + canvas.remove(FOLLOW_LINE_REF.current) + FOLLOW_LINE_REF.current = null + canvas.renderAll() + } + const target = selectedObject.current !== null ? selectedObject.current : CONFIRM_LINE_REF.current?.target if (!target) return @@ -429,17 +440,6 @@ export function useMovementSetting(id) { canvas.renderAll() }) - if (CONFIRM_LINE_REF.current !== null) { - canvas.remove(CONFIRM_LINE_REF.current) - CONFIRM_LINE_REF.current = null - canvas.renderAll() - } - if (FOLLOW_LINE_REF.current !== null) { - canvas.remove(FOLLOW_LINE_REF.current) - FOLLOW_LINE_REF.current = null - canvas.renderAll() - } - roof.drawHelpLine() initEvent() closePopup(id) diff --git a/src/util/qpolygon-utils.js b/src/util/qpolygon-utils.js index 07ca86e2..9fbfc6a3 100644 --- a/src/util/qpolygon-utils.js +++ b/src/util/qpolygon-utils.js @@ -743,6 +743,7 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { const wall = canvas.getObjects().find((object) => object.name === POLYGON_TYPE.WALL && object.attributes.roofId === roofId) console.log('wall :', wall) + console.log('roof.lines :', roof.lines) //Math.abs(line.x1 - line.x2) > 1 && Math.abs(line.y1 - line.y2) > 1 const hasNonParallelLines = roof.lines.filter((line) => Big(line.x1).minus(Big(line.x2)).gt(1) && Big(line.y1).minus(Big(line.y2)).gt(1)) if (hasNonParallelLines.length > 0) { @@ -813,25 +814,40 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { const drawEavesFirstLines = [] const drawEavesSecondLines = [] + const drawGableRidgeFirst = [] + const drawGableRidgeSecond = [] + const drawGableFirstLines = [] + const drawGableSecondLines = [] + const drawGablePolygonFirst = [] + const drawGablePolygonSecond = [] /** 모양을 판단한다. */ drawBaseLines.forEach((currentBaseLine, index) => { let prevBaseLine = drawBaseLines[(index - 1 + drawBaseLines.length) % drawBaseLines.length] let nextBaseLine = drawBaseLines[(index + 1) % drawBaseLines.length] - console.log('currentLine :', currentBaseLine) - console.log('prevLine :', prevBaseLine) - console.log('nextLine :', nextBaseLine) + // console.log('currentLine :', currentBaseLine) + // console.log('prevLine :', prevBaseLine) + // console.log('nextLine :', nextBaseLine) const currentLine = currentBaseLine.line const prevLine = prevBaseLine.line const nextLine = nextBaseLine.line + const prevAngle = calculateAngle(prevLine.startPoint, prevLine.endPoint) + const nextAngle = calculateAngle(nextLine.startPoint, nextLine.endPoint) + + const checkScale = Big(10) + const xVector = Big(nextLine.x2).minus(Big(nextLine.x1)) + const yVector = Big(nextLine.y2).minus(Big(nextLine.y1)) + const currentMidX = Big(currentLine.x1).plus(Big(currentLine.x2)).div(2) + const currentMidY = Big(currentLine.y1).plus(Big(currentLine.y2)).div(2) + + const checkPoints = { + x: currentMidX.plus(checkScale.times(Math.sign(xVector.toNumber()))).toNumber(), + y: currentMidY.plus(checkScale.times(Math.sign(yVector.toNumber()))).toNumber(), + } + + // console.log('prevAngle :', prevAngle, 'nextAngle :', nextAngle) /** 현재 라인이 처마유형일 경우 */ if (eavesType.includes(currentLine.attributes?.type)) { if (eavesType.includes(nextLine.attributes?.type)) { - /** - * 현재 라인이 처마인 경우 - */ - const prevAngle = calculateAngle(prevLine.startPoint, prevLine.endPoint) - const nextAngle = calculateAngle(nextLine.startPoint, nextLine.endPoint) - console.log('prevAngle :', prevAngle, 'nextAngle :', nextAngle) /** * 이전, 다음 라인이 처마일때 라인의 방향이 반대면 ㄷ 모양으로 판단한다. */ @@ -840,21 +856,10 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { eavesType.includes(nextLine.attributes?.type) && Big(prevAngle).minus(Big(nextAngle)).abs().eq(180) ) { - const checkScale = Big(10) - const xVector = Big(nextLine.x2).minus(Big(nextLine.x1)) - const yVector = Big(nextLine.y2).minus(Big(nextLine.y1)) - const currentMidX = Big(currentLine.x1).plus(Big(currentLine.x2)).div(2) - const currentMidY = Big(currentLine.y1).plus(Big(currentLine.y2)).div(2) - /** * 다음라인 방향에 포인트를 확인해서 역방향 ㄷ 모양인지 판단한다. * @type {{x: *, y: *}} */ - const checkPoints = { - x: currentMidX.plus(checkScale.times(Math.sign(xVector.toNumber()))).toNumber(), - y: currentMidY.plus(checkScale.times(Math.sign(yVector.toNumber()))).toNumber(), - } - if (checkWallPolygon.inPolygon(checkPoints)) { drawEavesFirstLines.push({ currentBaseLine, prevBaseLine, nextBaseLine }) } else { @@ -863,24 +868,1513 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { } else { drawEavesSecondLines.push({ currentBaseLine, prevBaseLine, nextBaseLine }) } + } else if (gableType.includes(nextLine.attributes?.type) && gableType.includes(prevLine.attributes?.type)) { + console.log('currentLine :', currentBaseLine.size, 'prevAngle :', prevAngle, 'nextAngle :', nextAngle) + if (Big(prevAngle).minus(Big(nextAngle)).abs().eq(180)) { + const checkPoints = { + x: currentMidX.plus(checkScale.times(Math.sign(xVector.toNumber()))).toNumber(), + y: currentMidY.plus(checkScale.times(Math.sign(yVector.toNumber()))).toNumber(), + } + + if (checkWallPolygon.inPolygon(checkPoints)) { + drawGablePolygonFirst.push({ currentBaseLine, prevBaseLine, nextBaseLine }) + } + // if (!checkWallPolygon.inPolygon(checkPoints)) { + drawGableRidgeSecond.push({ currentBaseLine, prevBaseLine, nextBaseLine }) + // } + } else { + drawGablePolygonSecond.push({ currentBaseLine, prevBaseLine, nextBaseLine }) + } + } + } + + if (gableType.includes(currentLine.attributes?.type)) { + if (eavesType.includes(nextLine.attributes?.type)) { + if ( + eavesType.includes(prevLine.attributes?.type) && + eavesType.includes(nextLine.attributes?.type) && + Big(prevAngle).minus(Big(nextAngle)).abs().eq(180) + ) { + if (checkWallPolygon.inPolygon(checkPoints)) { + drawGableRidgeFirst.push({ currentBaseLine, prevBaseLine, nextBaseLine }) + drawGableFirstLines.push({ currentBaseLine, prevBaseLine, nextBaseLine }) + } else { + drawGableRidgeSecond.push({ currentBaseLine, prevBaseLine, nextBaseLine }) + } + } else { + drawGableSecondLines.push({ currentBaseLine, prevBaseLine, nextBaseLine }) + } } } }) drawEavesFirstLines.sort((a, b) => a.currentBaseLine.size - b.currentBaseLine.size) - console.log('drawEavesFirstLines :', drawEavesFirstLines) - console.log( - 'drawEavesFirstLines :', - drawEavesFirstLines.map((line) => line.currentBaseLine.size), - ) + drawGableRidgeFirst.sort((a, b) => a.currentBaseLine.size - b.currentBaseLine.size) + drawGableRidgeSecond.sort((a, b) => a.currentBaseLine.size - b.currentBaseLine.size) /** 추녀마루 */ let baseHipLines = [] /** 용마루 */ let baseRidgeLines = [] + /** 박공지붕 마루*/ + let baseGableRidgeLines = [] + + /** 박공지붕 라인*/ + let baseGableLines = [] /** 용마루의 갯수*/ let baseRidgeCount = 0 + // console.log('drawGableFirstLines :', drawGableFirstLines) + // console.log('drawGableSecondLines :', drawGableSecondLines) + console.log('drawEavesFirstLines :', drawEavesFirstLines) + console.log('drawEavesSecondLines :', drawEavesSecondLines) + console.log('drawGableRidgeFirst : ', drawGableRidgeFirst) + console.log('drawGableRidgeSecond : ', drawGableRidgeSecond) + console.log('drawGablePolygonFirst :', drawGablePolygonFirst) + console.log('drawGablePolygonSecond :', drawGablePolygonSecond) + + /** 박공지붕에서 파생되는 마루를 그린다. ㄷ 형태*/ + drawGableRidgeFirst.forEach((current) => { + const { currentBaseLine, prevBaseLine, nextBaseLine } = current + const currentLine = currentBaseLine.line + const prevLine = prevBaseLine.line + const nextLine = nextBaseLine.line + + let { x1, x2, y1, y2, size } = currentBaseLine + let beforePrevBaseLine, afterNextBaseLine + + /** 이전 라인의 경사 */ + const prevDegree = prevLine.attributes.pitch > 0 ? getDegreeByChon(prevLine.attributes.pitch) : prevLine.attributes.degree + /** 다음 라인의 경사 */ + const nextDegree = nextLine.attributes.pitch > 0 ? getDegreeByChon(nextLine.attributes.pitch) : nextLine.attributes.degree + /** 현재 라인의 경사 */ + const currentDegree = currentLine.attributes.pitch > 0 ? getDegreeByChon(currentLine.attributes.pitch) : currentLine.attributes.degree + + /** 이전 라인의 전라인, 다음 라인의 다음라인을 찾는다 */ + drawBaseLines.forEach((line, index) => { + if (line === prevBaseLine) { + beforePrevBaseLine = drawBaseLines[(index - 1 + drawBaseLines.length) % drawBaseLines.length] + } + if (line === nextBaseLine) { + afterNextBaseLine = drawBaseLines[(index + 1) % drawBaseLines.length] + } + }) + + const beforePrevLine = beforePrevBaseLine?.line + const afterNextLine = afterNextBaseLine?.line + + /** 각 라인의 흐름 방향을 확인한다. */ + const currentAngle = calculateAngle(currentLine.startPoint, currentLine.endPoint) + const prevAngle = calculateAngle(prevLine.startPoint, prevLine.endPoint) + const nextAngle = calculateAngle(nextLine.startPoint, nextLine.endPoint) + const beforePrevAngle = calculateAngle(beforePrevLine.startPoint, beforePrevLine.endPoint) + const afterNextAngle = calculateAngle(afterNextLine.startPoint, afterNextLine.endPoint) + + /** 현재라인의 vector*/ + const currentVectorX = Math.sign(Big(x2).minus(Big(x1)).toNumber()) + const currentVectorY = Math.sign(Big(y2).minus(Big(y1)).toNumber()) + + /** 이전라인의 vector*/ + const prevVectorX = Math.sign(Big(prevLine.x2).minus(Big(prevLine.x1))) + const prevVectorY = Math.sign(Big(prevLine.y2).minus(Big(prevLine.y1))) + + /** 다음라인의 vector*/ + const nextVectorX = Math.sign(Big(nextLine.x2).minus(Big(nextLine.x1))) + const nextVectorY = Math.sign(Big(nextLine.y2).minus(Big(nextLine.y1))) + + /** 현재라인의 기준점*/ + const currentMidX = Big(x1).plus(Big(x2)).div(2).plus(Big(prevVectorX).times(currentLine.attributes.offset)) + const currentMidY = Big(y1).plus(Big(y2)).div(2).plus(Big(prevVectorY).times(currentLine.attributes.offset)) + + if (beforePrevBaseLine === afterNextBaseLine) { + const afterNextMidX = Big(afterNextLine.x1).plus(Big(afterNextLine.x2)).div(2) + const afterNextMidY = Big(afterNextLine.y1).plus(Big(afterNextLine.y2)).div(2) + const vectorMidX = Math.sign(currentMidX.minus(afterNextMidX)) + const vectorMidY = Math.sign(currentMidY.minus(afterNextMidY)) + + let oppositeMidX, oppositeMidY + if (eavesType.includes(afterNextLine.attributes?.type)) { + const checkSize = currentMidX + .minus(afterNextMidX) + .pow(2) + .plus(currentMidY.minus(afterNextMidY).pow(2)) + .sqrt() + .minus(Big(afterNextLine.attributes.planeSize).div(20)) + .round(1) + oppositeMidX = currentMidX.plus(checkSize.times(vectorMidX).neg()) + oppositeMidY = currentMidY.plus(checkSize.times(vectorMidY).neg()) + + const xVector1 = Math.sign(Big(oppositeMidX).minus(Big(afterNextLine.x1)).neg().toNumber()) + const yVector1 = Math.sign(Big(oppositeMidY).minus(Big(afterNextLine.y1)).neg().toNumber()) + const xVector2 = Math.sign(Big(oppositeMidX).minus(Big(afterNextLine.x2)).neg().toNumber()) + const yVector2 = Math.sign(Big(oppositeMidY).minus(Big(afterNextLine.y2)).neg().toNumber()) + + let addOppositeX1 = 0, + addOppositeY1 = 0, + addOppositeX2 = 0, + addOppositeY2 = 0 + + if (!checkWallPolygon.inPolygon({ x: oppositeMidX.toNumber(), y: oppositeMidY.toNumber() })) { + const checkScale = currentMidX.minus(oppositeMidX).pow(2).plus(currentMidY.minus(oppositeMidY).pow(2)).sqrt() + addOppositeX1 = checkScale.times(xVector1).toNumber() + addOppositeY1 = checkScale.times(yVector1).toNumber() + addOppositeX2 = checkScale.times(xVector2).toNumber() + addOppositeY2 = checkScale.times(yVector2).toNumber() + } + + let scale1 = Big(afterNextLine.attributes.offset).pow(2).plus(Big(nextLine.attributes.offset).pow(2)).sqrt() + scale1 = scale1.eq(0) ? Big(1) : scale1 + let scale2 = Big(afterNextLine.attributes.offset).pow(2).plus(Big(prevLine.attributes.offset).pow(2)).sqrt() + scale2 = scale2.eq(0) ? Big(1) : scale2 + + const checkHip1 = { + x1: Big(afterNextLine.x1).plus(scale1.times(xVector1)).toNumber(), + y1: Big(afterNextLine.y1).plus(scale1.times(yVector1)).toNumber(), + x2: oppositeMidX.plus(addOppositeX1).toNumber(), + y2: oppositeMidY.plus(addOppositeY1).toNumber(), + } + + const checkHip2 = { + x1: Big(afterNextLine.x2).plus(scale2.times(xVector2)).toNumber(), + y1: Big(afterNextLine.y2).plus(scale2.times(yVector2)).toNumber(), + x2: oppositeMidX.plus(addOppositeX2).toNumber(), + y2: oppositeMidY.plus(addOppositeY2).toNumber(), + } + + const intersection1 = findRoofIntersection(roof, checkHip1, { x: oppositeMidX.plus(addOppositeX1), y: oppositeMidY.plus(addOppositeY1) }) + const intersection2 = findRoofIntersection(roof, checkHip2, { x: oppositeMidX.plus(addOppositeX2), y: oppositeMidY.plus(addOppositeY2) }) + + const afterNextDegree = afterNextLine.attributes.pitch > 0 ? getDegreeByChon(afterNextLine.attributes.pitch) : afterNextLine.attributes.degree + + if (intersection1) { + const hipLine = drawHipLine( + [intersection1.intersection.x, intersection1.intersection.y, oppositeMidX.plus(addOppositeX1), oppositeMidY.plus(addOppositeY1)], + canvas, + roof, + textMode, + null, + nextDegree, + afterNextDegree, + ) + baseHipLines.push({ + x1: afterNextLine.x1, + y1: afterNextLine.y1, + x2: oppositeMidX.plus(addOppositeX1).toNumber(), + y2: oppositeMidY.plus(addOppositeY1).toNumber(), + line: hipLine, + }) + } + if (intersection2) { + const hipLine = drawHipLine( + [intersection2.intersection.x, intersection2.intersection.y, oppositeMidX.plus(addOppositeX2), oppositeMidY.plus(addOppositeY2)], + canvas, + roof, + textMode, + null, + prevDegree, + afterNextDegree, + ) + baseHipLines.push({ + x1: afterNextLine.x2, + y1: afterNextLine.y2, + x2: oppositeMidX.plus(addOppositeX2).toNumber(), + y2: oppositeMidY.plus(addOppositeY2).toNumber(), + line: hipLine, + }) + } + } else { + oppositeMidX = Big(afterNextLine.x1).plus(Big(afterNextLine.x2)).div(2).plus(Big(prevVectorX).neg().times(afterNextLine.attributes.offset)) + oppositeMidY = Big(afterNextLine.y1).plus(Big(afterNextLine.y2)).div(2).plus(Big(prevVectorY).neg().times(afterNextLine.attributes.offset)) + } + + const vectorOppositeX = Math.sign(currentMidX.minus(oppositeMidX)) + const vectorOppositeY = Math.sign(currentMidY.minus(oppositeMidY)) + + if (vectorMidX === vectorOppositeX && vectorMidY === vectorOppositeY && baseRidgeCount < getMaxRidge(baseLines.length)) { + const ridge = drawRidgeLine( + [currentMidX.toNumber(), currentMidY.toNumber(), oppositeMidX.toNumber(), oppositeMidY.toNumber()], + canvas, + roof, + textMode, + ) + baseGableRidgeLines.push(ridge) + baseRidgeCount++ + } + } else { + const vectorMidX = Math.sign(Big(nextLine.x2).minus(nextLine.x1)) + const vectorMidY = Math.sign(Big(nextLine.y2).minus(nextLine.y1)) + let oppositeMidX = currentMidX, + oppositeMidY = currentMidY + let prevOppositeMidX, prevOppositeMidY, nextOppositeMidX, nextOppositeMidY + const beforePrevOffset = + currentAngle === beforePrevAngle + ? Big(beforePrevLine.attributes.offset) + : Big(beforePrevLine.attributes.offset).plus(currentLine.attributes.offset) + const afterNextOffset = + currentAngle === afterNextAngle + ? Big(afterNextLine.attributes.offset) + : Big(afterNextLine.attributes.offset).plus(currentLine.attributes.offset) + const prevSize = Big(prevLine.attributes.planeSize).div(10) + const nextSize = Big(nextLine.attributes.planeSize).div(10) + + let prevHipCoords, nextHipCoords + + /** 다음 라인이 그 다음 라인과의 사이에 추녀마루가 존재 하는지 확인. 처마-처마 인 경우 추녀마루*/ + if (eavesType.includes(afterNextLine.attributes?.type)) { + /** 현재 라인의 길이를 기준으로 추녀 마루의 길이를 삼각함수를 사용하여 판단한다.*/ + let hipLength = Big(size).div(10).div(2).pow(2).plus(Big(size).div(10).div(2).pow(2)).sqrt() + + const nextHalfVector = getHalfAngleVector(nextLine, afterNextLine) + let nextHipVector = { x: nextHalfVector.x, y: nextHalfVector.y } + + /** 이전 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/ + const nextCheckPoint = { + x: Big(nextLine.x2).plus(Big(nextHalfVector.x).times(10)), + y: Big(nextLine.y2).plus(Big(nextHalfVector.y).times(10)), + } + if (!checkWallPolygon.inPolygon(nextCheckPoint)) { + nextHipVector = { x: Big(nextHipVector.x).neg().toNumber(), y: Big(nextHipVector.y).neg().toNumber() } + } + + const nextEndPoint = { + x: Big(nextLine.x2).plus(Big(nextHipVector.x).times(hipLength)), + y: Big(nextLine.y2).plus(Big(nextHipVector.y).times(hipLength)), + } + + let ridgeEdge = { + vertex1: { x: currentMidX.toNumber(), y: currentMidY.toNumber() }, + vertex2: { + x: currentMidX.plus(Big(nextVectorX).times(nextBaseLine.size)).toNumber(), + y: currentMidY.plus(Big(nextVectorY).times(nextBaseLine.size)).toNumber(), + }, + } + let hipEdge = { vertex1: { x: nextLine.x2, y: nextLine.y2 }, vertex2: { x: nextEndPoint.x, y: nextEndPoint.y } } + let intersection = edgesIntersection(ridgeEdge, hipEdge) + if (intersection) { + nextHipCoords = { x1: nextLine.x2, y1: nextLine.y2, x2: intersection.x, y2: intersection.y } + nextOppositeMidY = Big(intersection.y) + nextOppositeMidX = Big(intersection.x) + } + } else { + if (vectorMidX === 0) { + nextOppositeMidY = currentMidY.plus(nextSize.plus(afterNextOffset).times(vectorMidY)) + nextOppositeMidX = currentMidX + } else { + nextOppositeMidX = currentMidX.plus(nextSize.plus(afterNextOffset).times(vectorMidX)) + nextOppositeMidY = currentMidY + } + } + + /** 이전 라인이 그 이전 라인과의 사이에 추녀마루가 존재 하는지 확인. 처마-처마 인 경우 추녀마루*/ + if (eavesType.includes(beforePrevLine.attributes?.type)) { + /** 현재 라인의 길이를 기준으로 추녀 마루의 길이를 삼각함수를 사용하여 판단한다.*/ + let hipLength = Big(size).div(10).div(2).pow(2).plus(Big(size).div(10).div(2).pow(2)).sqrt() + + const prevHalfVector = getHalfAngleVector(prevLine, beforePrevLine) + let prevHipVector = { x: prevHalfVector.x, y: prevHalfVector.y } + + /** 이전 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/ + const prevCheckPoint = { + x: Big(prevLine.x1).plus(Big(prevHalfVector.x).times(10)), + y: Big(prevLine.y1).plus(Big(prevHalfVector.y).times(10)), + } + + if (!checkWallPolygon.inPolygon(prevCheckPoint)) { + prevHipVector = { x: Big(prevHipVector.x).neg().toNumber(), y: Big(prevHipVector.y).neg().toNumber() } + } + + const prevEndPoint = { + x: Big(prevLine.x1).plus(Big(prevHipVector.x).times(hipLength)), + y: Big(prevLine.y1).plus(Big(prevHipVector.y).times(hipLength)), + } + + let ridgeEdge = { + vertex1: { x: currentMidX.toNumber(), y: currentMidY.toNumber() }, + vertex2: { + x: currentMidX.plus(Big(prevVectorX).times(prevBaseLine.size)).toNumber(), + y: currentMidY.plus(Big(prevVectorY).times(prevBaseLine.size)).toNumber(), + }, + } + let hipEdge = { vertex1: { x: prevLine.x1, y: prevLine.y1 }, vertex2: { x: prevEndPoint.x, y: prevEndPoint.y } } + let intersection = edgesIntersection(ridgeEdge, hipEdge) + if (intersection) { + prevHipCoords = { x1: prevLine.x1, y1: prevLine.y1, x2: intersection.x, y2: intersection.y } + prevOppositeMidY = Big(intersection.y) + prevOppositeMidX = Big(intersection.x) + } + } else { + if (vectorMidX === 0) { + prevOppositeMidY = currentMidY.plus(prevSize.plus(beforePrevOffset).times(vectorMidY)) + prevOppositeMidX = currentMidX + } else { + prevOppositeMidX = currentMidX.plus(prevSize.plus(beforePrevOffset).times(vectorMidX)) + prevOppositeMidY = currentMidY + } + } + const checkPrevSize = currentMidX.minus(prevOppositeMidX).pow(2).plus(currentMidY.minus(prevOppositeMidY).pow(2)).sqrt() + const checkNextSize = currentMidX.minus(nextOppositeMidX).pow(2).plus(currentMidY.minus(nextOppositeMidY).pow(2)).sqrt() + + /** 두 포인트 중에 current와 가까운 포인트를 사용*/ + if (checkPrevSize.gt(checkNextSize)) { + if (nextHipCoords) { + let intersectPoints = [] + const hipEdge = { vertex1: { x: nextHipCoords.x2, y: nextHipCoords.y2 }, vertex2: { x: nextHipCoords.x1, y: nextHipCoords.y1 } } + + /** 외벽선에서 라인 겹치는 경우에 대한 확인*/ + roof.lines.forEach((line) => { + const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } + const intersection = edgesIntersection(hipEdge, lineEdge) + if ( + intersection && + Math.sign(nextHipCoords.x2 - nextHipCoords.x1) === Math.sign(nextHipCoords.x2 - intersection.x) && + Math.sign(nextHipCoords.y2 - nextHipCoords.y1) === Math.sign(nextHipCoords.y2 - intersection.y) + ) { + const intersectEdge = { vertex1: { x: nextHipCoords.x2, y: nextHipCoords.y2 }, vertex2: { x: intersection.x, y: intersection.y } } + const is = edgesIntersection(intersectEdge, lineEdge) + if (!is.isIntersectionOutside) { + const intersectSize = Big(nextHipCoords.x2) + .minus(Big(intersection.x)) + .pow(2) + .plus(Big(nextHipCoords.y2).minus(Big(intersection.y)).pow(2)) + .abs() + .sqrt() + .toNumber() + + intersectPoints.push({ + intersection, + size: intersectSize, + line, + }) + } + } + }) + const intersect = intersectPoints.sort((a, b) => a.size - b.size)[0] + if (intersect) { + const degree = intersect.line.attributes.pitch > 0 ? getDegreeByChon(intersect.line.attributes.pitch) : intersect.line.attributes.degree + const hipLine = drawHipLine( + [intersect.intersection.x, intersect.intersection.y, nextOppositeMidX.toNumber(), nextOppositeMidY.toNumber()], + canvas, + roof, + textMode, + null, + degree, + degree, + ) + baseHipLines.push({ + x1: nextHipCoords.x1, + y1: nextHipCoords.y1, + x2: nextHipCoords.x2, + y2: nextHipCoords.y2, + line: hipLine, + }) + } + } + oppositeMidY = nextOppositeMidY + oppositeMidX = nextOppositeMidX + } else { + if (prevHipCoords) { + let intersectPoints = [] + const hipEdge = { vertex1: { x: prevHipCoords.x2, y: prevHipCoords.y2 }, vertex2: { x: prevHipCoords.x1, y: prevHipCoords.y1 } } + + /** 외벽선에서 라인 겹치는 경우에 대한 확인*/ + roof.lines.forEach((line) => { + const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } + const intersection = edgesIntersection(hipEdge, lineEdge) + if ( + intersection && + Math.sign(prevHipCoords.x2 - prevHipCoords.x1) === Math.sign(prevHipCoords.x2 - intersection.x) && + Math.sign(prevHipCoords.y2 - prevHipCoords.y1) === Math.sign(prevHipCoords.y2 - intersection.y) + ) { + const intersectEdge = { vertex1: { x: prevHipCoords.x2, y: prevHipCoords.y2 }, vertex2: { x: intersection.x, y: intersection.y } } + const is = edgesIntersection(intersectEdge, lineEdge) + if (!is.isIntersectionOutside) { + const intersectSize = Big(prevHipCoords.x2) + .minus(Big(intersection.x)) + .pow(2) + .plus(Big(prevHipCoords.y2).minus(Big(intersection.y)).pow(2)) + .abs() + .sqrt() + .toNumber() + + intersectPoints.push({ + intersection, + size: intersectSize, + line, + }) + } + } + }) + const intersect = intersectPoints.sort((a, b) => a.size - b.size)[0] + + if (intersect) { + const degree = intersect.line.attributes.pitch > 0 ? getDegreeByChon(intersect.line.attributes.pitch) : intersect.line.attributes.degree + const hipLine = drawHipLine( + [intersect.intersection.x, intersect.intersection.y, prevOppositeMidX.toNumber(), prevOppositeMidY.toNumber()], + canvas, + roof, + textMode, + null, + degree, + degree, + ) + baseHipLines.push({ + x1: prevHipCoords.x1, + y1: prevHipCoords.y1, + x2: prevHipCoords.x2, + y2: prevHipCoords.y2, + line: hipLine, + }) + } + } + oppositeMidY = prevOppositeMidY + oppositeMidX = prevOppositeMidX + } + + if (baseRidgeCount < getMaxRidge(baseLines.length)) { + /** 마루가 맞은편 외벽선에 닿는 경우 해당 부분까지로 한정한다. */ + const ridgeEdge = { + vertex1: { x: currentMidX.toNumber(), y: currentMidY.toNumber() }, + vertex2: { x: oppositeMidX.toNumber(), y: oppositeMidY.toNumber() }, + } + const ridgeVectorX = Math.sign(currentMidX.minus(oppositeMidX).toNumber()) + const ridgeVectorY = Math.sign(currentMidY.minus(oppositeMidY).toNumber()) + + roof.lines + .filter((line) => { + const lineVectorX = Math.sign(Big(line.x2).minus(Big(line.x1)).toNumber()) + const lineVectorY = Math.sign(Big(line.y2).minus(Big(line.y1)).toNumber()) + return ( + (lineVectorX === currentVectorX && lineVectorY !== currentVectorY) || (lineVectorX !== currentVectorX && lineVectorY === currentVectorY) + ) + }) + .forEach((line) => { + const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } + const intersection = edgesIntersection(ridgeEdge, lineEdge) + if (intersection && !intersection.isIntersectionOutside) { + const isVectorX = Math.sign(Big(currentMidX).minus(intersection.x).toNumber()) + const isVectorY = Math.sign(Big(currentMidY).minus(intersection.y).toNumber()) + if (isVectorX === ridgeVectorX && isVectorY === ridgeVectorY) { + oppositeMidX = Big(intersection.x) + oppositeMidY = Big(intersection.y) + } + } + }) + + const ridgeLine = drawRidgeLine([currentMidX, currentMidY, oppositeMidX, oppositeMidY], canvas, roof, textMode) + baseGableRidgeLines.push(ridgeLine) + baseRidgeCount++ + } + } + }) + + /** 박공지붕에서 파생되는 마루를 그린다. 첫번째에서 처리 하지 못한 라인이 있는 경우 */ + drawGableRidgeSecond.forEach((current) => { + const { currentBaseLine, prevBaseLine, nextBaseLine } = current + const currentLine = currentBaseLine.line + const prevLine = prevBaseLine.line + const nextLine = nextBaseLine.line + + let { x1, x2, y1, y2, size } = currentBaseLine + let beforePrevBaseLine, afterNextBaseLine + + /** 이전 라인의 경사 */ + const prevDegree = prevLine.attributes.pitch > 0 ? getDegreeByChon(prevLine.attributes.pitch) : prevLine.attributes.degree + /** 다음 라인의 경사 */ + const nextDegree = nextLine.attributes.pitch > 0 ? getDegreeByChon(nextLine.attributes.pitch) : nextLine.attributes.degree + /** 현재 라인의 경사 */ + const currentDegree = currentLine.attributes.pitch > 0 ? getDegreeByChon(currentLine.attributes.pitch) : currentLine.attributes.degree + + const currentAngle = calculateAngle(currentLine.startPoint, currentLine.endPoint) + const currentVectorX = Big(currentLine.x2).minus(currentLine.x1) + const currentVectorY = Big(currentLine.y2).minus(currentLine.y1) + const checkVectorX = Big(nextLine.x2).minus(Big(nextLine.x1)) + const checkVectorY = Big(nextLine.y2).minus(Big(nextLine.y1)) + const currentMidX = Big(currentLine.x1).plus(Big(currentLine.x2)).div(2) + const currentMidY = Big(currentLine.y1).plus(Big(currentLine.y2)).div(2) + const checkSize = Big(10) + + const checkPoints = { + x: currentMidX.plus(checkSize.times(Math.sign(checkVectorX.toNumber()))).toNumber(), + y: currentMidY.plus(checkSize.times(Math.sign(checkVectorY.toNumber()))).toNumber(), + } + if (!checkWallPolygon.inPolygon(checkPoints)) { + const currentMidEdge = { + vertex1: { x: currentMidX.toNumber(), y: currentMidY.toNumber() }, + vertex2: { + x: currentMidX.plus(checkSize.times(Math.sign(checkVectorX.neg().toNumber()))).toNumber(), + y: currentMidY.plus(checkSize.times(Math.sign(checkVectorY.neg().toNumber()))).toNumber(), + }, + } + + let oppositeLines = [] + baseLines + .filter((line, index) => { + let nextLine = baseLines[(index + 1) % baseLines.length] + let prevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length] + if ( + (gableType.includes(nextLine.attributes.type) && gableType.includes(prevLine.attributes.type)) || + (eavesType.includes(nextLine.attributes.type) && eavesType.includes(prevLine.attributes.type)) + ) { + const angle = calculateAngle(line.startPoint, line.endPoint) + switch (currentAngle) { + case 90: + return angle === -90 + case -90: + return angle === 90 + case 0: + return angle === 180 + case 180: + return angle === 0 + } + } + return false + }) + .forEach((line) => { + const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } + const intersection = edgesIntersection(currentMidEdge, lineEdge) + if (intersection) { + oppositeLines.push({ + line, + intersection, + size: Big(intersection.x).minus(currentMidX).abs().pow(2).plus(Big(intersection.y).minus(currentMidY).abs().pow(2)).sqrt(), + }) + } + }) + + if (oppositeLines.length === 0) { + return + } + const oppositeLine = oppositeLines.sort((a, b) => a.size - b.size)[0] + + let points = [] + if (eavesType.includes(oppositeLine.line.attributes.type)) { + const oppositeCurrentLine = oppositeLine.line + let oppositePrevLine, oppositeNextLine + baseLines.forEach((line, index) => { + if (line === oppositeCurrentLine) { + oppositePrevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length] + oppositeNextLine = baseLines[(index + 1) % baseLines.length] + } + }) + if (gableType.includes(oppositeNextLine.attributes.type) && gableType.includes(oppositePrevLine.attributes.type)) { + if (currentVectorX.eq(0)) { + const centerX = currentMidX.plus(oppositeLine.intersection.x).div(2).toNumber() + points = [centerX, currentLine.y1, centerX, currentLine.y2] + } else { + const centerY = currentMidY.plus(oppositeLine.intersection.y).div(2).toNumber() + points = [currentLine.x1, centerY, currentLine.x2, centerY] + } + } + if (eavesType.includes(oppositeNextLine.attributes.type) && eavesType.includes(oppositePrevLine.attributes.type)) { + /** 이전, 다음라인의 사잇각의 vector를 구한다. */ + let prevVector = getHalfAngleVector(oppositePrevLine, oppositeCurrentLine) + let nextVector = getHalfAngleVector(oppositeCurrentLine, oppositeNextLine) + + let prevHipVector = { x: Big(prevVector.x), y: Big(prevVector.y) } + let nextHipVector = { x: Big(nextVector.x), y: Big(nextVector.y) } + + /** 이전 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/ + const prevCheckPoint = { + x: Big(oppositeCurrentLine.x1).plus(Big(prevHipVector.x).times(10)), + y: Big(oppositeCurrentLine.y1).plus(Big(prevHipVector.y).times(10)), + } + if (!checkWallPolygon.inPolygon(prevCheckPoint)) { + prevHipVector = { x: Big(prevHipVector.x).neg(), y: Big(prevHipVector.y).neg() } + } + + /** 다음 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/ + const nextCheckPoint = { + x: Big(oppositeCurrentLine.x2).plus(Big(nextHipVector.x).times(10)), + y: Big(oppositeCurrentLine.y2).plus(Big(nextHipVector.y).times(10)), + } + if (!checkWallPolygon.inPolygon(nextCheckPoint)) { + nextHipVector = { x: Big(nextHipVector.x).neg(), y: Big(nextHipVector.y).neg() } + } + + /** 현재 라인의 길이를 기준으로 추녀 마루의 길이를 삼각함수를 사용하여 판단한다.*/ + let hipLength = Big(oppositeCurrentLine.attributes.planeSize) + .div(2) + .pow(2) + .plus(Big(oppositeCurrentLine.attributes.planeSize).div(2).pow(2)) + .sqrt() + .div(10) + .round(2) + + const ridgeEndPoint = { + x: Big(oppositeCurrentLine.x1).plus(hipLength.times(prevHipVector.x)).round(1), + y: Big(oppositeCurrentLine.y1).plus(hipLength.times(prevHipVector.y)).round(1), + } + + const prevHypotenuse = Big(oppositePrevLine.attributes.offset).pow(2).plus(Big(oppositeCurrentLine.attributes.offset).pow(2)).sqrt() + const prevHipPoints = { + x1: Big(oppositeCurrentLine.x1).plus(prevHypotenuse.times(prevHipVector.x.neg())).round(1).toNumber(), + y1: Big(oppositeCurrentLine.y1).plus(prevHypotenuse.times(prevHipVector.y.neg())).round(1).toNumber(), + x2: ridgeEndPoint.x.toNumber(), + y2: ridgeEndPoint.y.toNumber(), + } + + const nextHypotenuse = Big(oppositeNextLine.attributes.offset).pow(2).plus(Big(oppositeCurrentLine.attributes.offset).pow(2)).sqrt() + const nextHipPoints = { + x1: Big(oppositeCurrentLine.x2).plus(nextHypotenuse.times(nextHipVector.x.neg())).round(1).toNumber(), + y1: Big(oppositeCurrentLine.y2).plus(nextHypotenuse.times(nextHipVector.y.neg())).round(1).toNumber(), + x2: ridgeEndPoint.x.toNumber(), + y2: ridgeEndPoint.y.toNumber(), + } + + const prevIntersection = findRoofIntersection(roof, prevHipPoints, ridgeEndPoint) + const nextIntersection = findRoofIntersection(roof, nextHipPoints, ridgeEndPoint) + + if (prevIntersection) { + const prevHip = drawHipLine( + [prevIntersection.intersection.x, prevIntersection.intersection.y, ridgeEndPoint.x.toNumber(), ridgeEndPoint.y.toNumber()], + canvas, + roof, + textMode, + null, + prevDegree, + prevDegree, + ) + baseHipLines.push({ + x1: oppositeCurrentLine.x1, + y1: oppositeCurrentLine.y1, + x2: ridgeEndPoint.x, + y2: ridgeEndPoint.y, + line: prevHip, + }) + } + if (nextIntersection) { + const nextHip = drawHipLine( + [nextIntersection.intersection.x, nextIntersection.intersection.y, ridgeEndPoint.x.toNumber(), ridgeEndPoint.y.toNumber()], + canvas, + roof, + textMode, + null, + nextDegree, + nextDegree, + ) + baseHipLines.push({ + x1: ridgeEndPoint.x, + y1: ridgeEndPoint.y, + x2: oppositeCurrentLine.x2, + y2: oppositeCurrentLine.y2, + line: nextHip, + }) + } + + const ridgeVectorX = Math.sign(currentMidX.minus(ridgeEndPoint.x).toNumber()) + const ridgeVectorY = Math.sign(currentMidY.minus(ridgeEndPoint.y).toNumber()) + const ridgePoints = { + x1: currentMidX.plus(Big(currentLine.attributes.offset).times(ridgeVectorX)).toNumber(), + y1: currentMidY.plus(Big(currentLine.attributes.offset).times(ridgeVectorY)).toNumber(), + x2: ridgeEndPoint.x.toNumber(), + y2: ridgeEndPoint.y.toNumber(), + } + const ridgeIntersection = findRoofIntersection(roof, ridgePoints, { x: Big(ridgePoints.x2), y: Big(ridgePoints.y2) }) + if (ridgeIntersection) { + points = [ridgeIntersection.intersection.x, ridgeIntersection.intersection.y, ridgeEndPoint.x, ridgeEndPoint.y] + } + } + } else { + if (currentVectorX.eq(0)) { + points = [oppositeLine.intersection.x, currentLine.y1, oppositeLine.intersection.x, currentLine.y2] + } else { + points = [currentLine.x1, oppositeLine.intersection.y, currentLine.x2, oppositeLine.intersection.y] + } + } + + if (baseRidgeCount < getMaxRidge(baseLines.length)) { + const ridgeLine = drawRidgeLine(points, canvas, roof, textMode) + baseGableRidgeLines.push(ridgeLine) + baseRidgeCount++ + } + + canvas + .getObjects() + .filter((obj) => obj.name === 'checkLine' || obj.name === 'checkPoint') + .forEach((obj) => canvas.remove(obj)) + canvas.renderAll() + } else { + const oppositeLines = baseLines.filter((line) => { + const lineAngle = calculateAngle(line.startPoint, line.endPoint) + switch (currentAngle) { + case 90: + return lineAngle === -90 + case -90: + return lineAngle === 90 + case 0: + return lineAngle === 180 + case 180: + return lineAngle === 0 + } + }) + + if (oppositeLines.length > 0 && baseRidgeCount < getMaxRidge(baseLines.length)) { + let ridgePoints = [] + const oppositeLine = oppositeLines.sort((a, b) => { + let diffCurrentA, diffCurrentB + if (Math.sign(currentVectorX) === 0) { + diffCurrentA = currentMidY.minus(a.y1).abs() + diffCurrentB = currentMidY.minus(b.y1).abs() + } else { + diffCurrentA = currentMidX.minus(a.x1).abs() + diffCurrentB = currentMidX.minus(b.x1).abs() + } + return diffCurrentA.minus(diffCurrentB).toNumber() + })[0] + + const prevOffset = prevLine.attributes.offset + const nextOffset = nextLine.attributes.offset + if (Math.sign(currentVectorX) === 0) { + const prevY = Big(currentLine.y1) + .plus(Big(Math.sign(currentVectorY)).neg().times(prevOffset)) + .toNumber() + const nextY = Big(currentLine.y2) + .plus(Big(Math.sign(currentVectorY)).times(nextOffset)) + .toNumber() + const midX = Big(currentLine.x1).plus(oppositeLine.x1).div(2).toNumber() + ridgePoints = [midX, prevY, midX, nextY] + } else { + const prevX = Big(currentLine.x1) + .plus(Big(Math.sign(currentVectorX)).neg().times(prevOffset)) + .toNumber() + const nextX = Big(currentLine.x2) + .plus(Big(Math.sign(currentVectorX)).times(nextOffset)) + .toNumber() + const midY = Big(currentLine.y1).plus(oppositeLine.y1).div(2).toNumber() + ridgePoints = [prevX, midY, nextX, midY] + } + const ridge = drawRidgeLine(ridgePoints, canvas, roof, textMode) + baseGableRidgeLines.push(ridge) + baseRidgeCount++ + } + } + canvas + .getObjects() + .filter((obj) => obj.name === 'checkLine' || obj.name === 'checkPoint') + .forEach((obj) => canvas.remove(obj)) + canvas.renderAll() + }) + + /** 케라바 지붕으로 생성된 마루의 지붕선을 그린다.*/ + baseGableRidgeLines.forEach((ridge) => { + return + const { x1, x2, y1, y2 } = ridge + const checkLine = new fabric.Line([x1, y1, x2, y2], { + stroke: 'red', + strokeWidth: 4, + parentId: roofId, + name: 'checkLine', + }) + canvas.add(checkLine) + canvas.renderAll() + + const ridgeVectorX = Math.sign(Big(x1).minus(x2).toNumber()) + const ridgeVectorY = Math.sign(Big(y1).minus(y2).toNumber()) + const firstPoint = { x: x1, y: y1 } + const secondPoint = { x: x2, y: y2 } + let firstRoofLine, secondRoofLine + roof.lines + .filter((line) => { + if (ridgeVectorX === 0) { + return line.x1 !== line.x2 + } else { + return line.y1 !== line.y2 + } + }) + .forEach((line) => { + if (ridgeVectorX === 0) { + if (line.y1 === firstPoint.y) { + firstRoofLine = line + } + if (line.y1 === secondPoint.y) { + secondRoofLine = line + } + } else { + if (line.x1 === firstPoint.x) { + firstRoofLine = line + } + if (line.x1 === secondPoint.x) { + secondRoofLine = line + } + } + }) + /** 마루 1개에 (위,아래), (좌,우) 로 두가지의 지붕면이 생길 수 있다.*/ + let firstCheckPoints = [] + let secondCheckPoints = [] + + /** + * @param startPoint + * @param nextPoint + * @param endPoint + */ + const findPointToPolygon = (startPoint, nextPoint, endPoint) => { + let findPoint = nextPoint + const points = [startPoint, nextPoint] + let index = 1 + while (!(points[points.length - 1].x === endPoint.x && points[points.length - 1].y === endPoint.y)) { + const prevVector = { x: Math.sign(points[index - 1].x - points[index].x), y: Math.sign(points[index - 1].y - points[index].y) } + const currentPoint = points[index] + + const hipLine = baseHipLines.find( + (line) => + (line.line.x1 === currentPoint.x && line.line.y1 === currentPoint.y) || + (line.line.x2 === currentPoint.x && line.line.y2 === currentPoint.y), + ) + if (hipLine) { + if (hipLine.line.x1 === currentPoint.x && hipLine.line.y1 === currentPoint.y) { + points.push({ x: hipLine.line.x2, y: hipLine.line.y2 }) + } else { + points.push({ x: hipLine.line.x1, y: hipLine.line.y1 }) + } + } else { + const nextRoofLine = roof.lines.find((line) => { + if (prevVector.x !== 0) { + return ( + line.y1 !== line.y2 && + ((line.x1 <= currentPoint.x && currentPoint.x <= line.x2) || (line.x2 <= currentPoint.x && currentPoint.x <= line.x1)) + ) + } else { + return ( + line.x1 !== line.x2 && + ((line.y1 <= currentPoint.y && currentPoint.y <= line.y2) || (line.y2 <= currentPoint.y && currentPoint.y <= line.y1)) + ) + } + }) + const checkLine = new fabric.Line([nextRoofLine.x1, nextRoofLine.y1, nextRoofLine.x2, nextRoofLine.y2], { + stroke: 'yellow', + strokeWidth: 4, + parentId: roofId, + name: 'checkLine', + }) + canvas.add(checkLine) + canvas.renderAll() + + const lineEdge = { vertex1: { x: nextRoofLine.x1, y: nextRoofLine.y1 }, vertex2: { x: nextRoofLine.x2, y: nextRoofLine.y2 } } + let ridgeIntersection + baseGableRidgeLines.forEach((ridgeLine) => { + const ridgeEdge = { vertex1: { x: ridgeLine.x1, y: ridgeLine.y1 }, vertex2: { x: ridgeLine.x2, y: ridgeLine.y2 } } + const intersection = edgesIntersection(lineEdge, ridgeEdge) + console.log('intersection', intersection) + if (intersection && !intersection.isIntersectionOutside) { + ridgeIntersection = { x: intersection.x, y: intersection.y } + } + }) + if (ridgeIntersection) { + points.push(ridgeIntersection) + } else { + if (nextRoofLine.x1 === currentPoint.x && nextRoofLine.y1 === currentPoint.y) { + points.push({ x: nextRoofLine.x2, y: nextRoofLine.y2 }) + } else { + points.push({ x: nextRoofLine.x1, y: nextRoofLine.y1 }) + } + } + } + console.log('points', points, points[points.length - 1], endPoint) + index = index + 1 + } + + canvas + .getObjects() + .filter((obj) => obj.name === 'checkLine' || obj.name === 'checkPoint') + .forEach((obj) => canvas.remove(obj)) + canvas.renderAll() + return points + } + + let startVector + console.log('!firstRoofLine && !secondRoofLine', !firstRoofLine && !secondRoofLine) + let firstPoints, secondPoints + if (!firstRoofLine && !secondRoofLine) { + } else { + if (firstRoofLine) { + firstPoints = findPointToPolygon(firstPoint, { x: firstRoofLine.x1, y: firstRoofLine.y1 }, secondPoint) + secondPoints = findPointToPolygon(firstPoint, { x: firstRoofLine.x2, y: firstRoofLine.y2 }, secondPoint) + } else { + firstPoints = findPointToPolygon(secondPoint, { x: secondRoofLine.x1, y: secondRoofLine.y1 }, firstPoint) + secondPoints = findPointToPolygon(secondPoint, { x: secondRoofLine.x2, y: secondRoofLine.y2 }, firstPoint) + } + } + const firstPolygonPoints = getSortedPoint(firstPoints) + const secondPolygonPoints = getSortedPoint(secondPoints) + + firstPolygonPoints.forEach((point, index) => { + let endPoint + if (index === firstPolygonPoints.length - 1) { + endPoint = firstPolygonPoints[0] + } else { + endPoint = firstPolygonPoints[index + 1] + } + const hipLine = drawHipLine([point.x, point.y, endPoint.x, endPoint.y], canvas, roof, textMode, null, currentDegree, currentDegree) + baseHipLines.push({ x1: point.x, y1: point.y, x2: endPoint.x, y2: endPoint.y, line: hipLine }) + + const checkLine = new fabric.Line([point.x, point.y, endPoint.x, endPoint.y], { + stroke: 'red', + strokeWidth: 4, + parentId: roofId, + name: 'checkLine', + }) + canvas.add(checkLine) + canvas.renderAll() + }) + secondPolygonPoints.forEach((point, index) => { + let endPoint + if (index === secondPolygonPoints.length - 1) { + endPoint = secondPolygonPoints[0] + } else { + endPoint = secondPolygonPoints[index + 1] + } + const hipLine = drawHipLine([point.x, point.y, endPoint.x, endPoint.y], canvas, roof, textMode, null, currentDegree, currentDegree) + baseHipLines.push({ x1: point.x, y1: point.y, x2: endPoint.x, y2: endPoint.y, line: hipLine }) + + const checkLine = new fabric.Line([point.x, point.y, endPoint.x, endPoint.y], { + stroke: 'red', + strokeWidth: 4, + parentId: roofId, + name: 'checkLine', + }) + canvas.add(checkLine) + canvas.renderAll() + }) + }) + + drawGableFirstLines.forEach((current) => { + return + const { currentBaseLine, prevBaseLine, nextBaseLine } = current + const currentLine = currentBaseLine.line + const prevLine = prevBaseLine.line + const nextLine = nextBaseLine.line + let { x1, x2, y1, y2, size } = currentBaseLine + let beforePrevBaseLine, afterNextBaseLine + + /** 이전 라인의 경사 */ + const prevDegree = prevLine.attributes.pitch > 0 ? getDegreeByChon(prevLine.attributes.pitch) : prevLine.attributes.degree + /** 다음 라인의 경사 */ + const nextDegree = nextLine.attributes.pitch > 0 ? getDegreeByChon(nextLine.attributes.pitch) : nextLine.attributes.degree + /** 현재 라인의 경사 */ + const currentDegree = currentLine.attributes.pitch > 0 ? getDegreeByChon(currentLine.attributes.pitch) : currentLine.attributes.degree + + /** 이전 라인의 전라인, 다음 라인의 다음라인을 찾는다 */ + drawBaseLines.forEach((line, index) => { + if (line === prevBaseLine) { + beforePrevBaseLine = drawBaseLines[(index - 1 + drawBaseLines.length) % drawBaseLines.length] + } + if (line === nextBaseLine) { + afterNextBaseLine = drawBaseLines[(index + 1) % drawBaseLines.length] + } + }) + + const beforePrevLine = beforePrevBaseLine?.line + const afterNextLine = afterNextBaseLine?.line + + /** 각 라인의 흐름 방향을 확인한다. */ + const currentAngle = calculateAngle(currentLine.startPoint, currentLine.endPoint) + const prevAngle = calculateAngle(prevLine.startPoint, prevLine.endPoint) + const nextAngle = calculateAngle(nextLine.startPoint, nextLine.endPoint) + const beforePrevAngle = calculateAngle(beforePrevLine.startPoint, beforePrevLine.endPoint) + const afterNextAngle = calculateAngle(afterNextLine.startPoint, afterNextLine.endPoint) + + /** 이전라인의 vector*/ + const prevVectorX = Math.sign(Big(prevLine.x2).minus(Big(prevLine.x1))) + const prevVectorY = Math.sign(Big(prevLine.y2).minus(Big(prevLine.y1))) + + /** 현재라인의 기준점*/ + const currentMidX = Big(x1).plus(Big(x2)).div(2).plus(Big(prevVectorX).times(currentLine.attributes.offset)) + const currentMidY = Big(y1).plus(Big(y2)).div(2).plus(Big(prevVectorY).times(currentLine.attributes.offset)) + + if (beforePrevBaseLine === afterNextBaseLine) { + console.log('박공지붕 사각') + + const afterNextMidX = Big(afterNextLine.x1).plus(Big(afterNextLine.x2)).div(2) + const afterNextMidY = Big(afterNextLine.y1).plus(Big(afterNextLine.y2)).div(2) + const vectorMidX = Math.sign(currentMidX.minus(afterNextMidX)) + const vectorMidY = Math.sign(currentMidY.minus(afterNextMidY)) + + let oppositeMidX, oppositeMidY + if (eavesType.includes(afterNextLine.attributes?.type)) { + const checkSize = currentMidX + .minus(afterNextMidX) + .pow(2) + .plus(currentMidY.minus(afterNextMidY).pow(2)) + .sqrt() + .minus(Big(afterNextLine.attributes.planeSize).div(20)) + .round(1) + oppositeMidX = currentMidX.plus(checkSize.times(vectorMidX).neg()) + oppositeMidY = currentMidY.plus(checkSize.times(vectorMidY).neg()) + + const xVector1 = Math.sign(Big(oppositeMidX).minus(Big(afterNextLine.x1)).neg().toNumber()) + const yVector1 = Math.sign(Big(oppositeMidY).minus(Big(afterNextLine.y1)).neg().toNumber()) + const xVector2 = Math.sign(Big(oppositeMidX).minus(Big(afterNextLine.x2)).neg().toNumber()) + const yVector2 = Math.sign(Big(oppositeMidY).minus(Big(afterNextLine.y2)).neg().toNumber()) + + let addOppositeX1 = 0, + addOppositeY1 = 0, + addOppositeX2 = 0, + addOppositeY2 = 0 + + if (!checkWallPolygon.inPolygon({ x: oppositeMidX.toNumber(), y: oppositeMidY.toNumber() })) { + const checkScale = currentMidX.minus(oppositeMidX).pow(2).plus(currentMidY.minus(oppositeMidY).pow(2)).sqrt() + addOppositeX1 = checkScale.times(xVector1).toNumber() + addOppositeY1 = checkScale.times(yVector1).toNumber() + addOppositeX2 = checkScale.times(xVector2).toNumber() + addOppositeY2 = checkScale.times(yVector2).toNumber() + } + + let scale1 = Big(afterNextLine.attributes.offset).pow(2).plus(Big(nextLine.attributes.offset).pow(2)).sqrt() + scale1 = scale1.eq(0) ? Big(1) : scale1 + let scale2 = Big(afterNextLine.attributes.offset).pow(2).plus(Big(prevLine.attributes.offset).pow(2)).sqrt() + scale2 = scale2.eq(0) ? Big(1) : scale2 + + const checkHip1 = { + x1: Big(afterNextLine.x1).plus(scale1.times(xVector1)).toNumber(), + y1: Big(afterNextLine.y1).plus(scale1.times(yVector1)).toNumber(), + x2: oppositeMidX.plus(addOppositeX1).toNumber(), + y2: oppositeMidY.plus(addOppositeY1).toNumber(), + } + + const checkHip2 = { + x1: Big(afterNextLine.x2).plus(scale2.times(xVector2)).toNumber(), + y1: Big(afterNextLine.y2).plus(scale2.times(yVector2)).toNumber(), + x2: oppositeMidX.plus(addOppositeX2).toNumber(), + y2: oppositeMidY.plus(addOppositeY2).toNumber(), + } + + const intersection1 = findRoofIntersection(roof, checkHip1, { x: oppositeMidX.plus(addOppositeX1), y: oppositeMidY.plus(addOppositeY1) }) + const intersection2 = findRoofIntersection(roof, checkHip2, { x: oppositeMidX.plus(addOppositeX2), y: oppositeMidY.plus(addOppositeY2) }) + + const afterNextDegree = afterNextLine.attributes.pitch > 0 ? getDegreeByChon(afterNextLine.attributes.pitch) : afterNextLine.attributes.degree + + if (intersection1) { + const hipLine = drawHipLine( + [intersection1.intersection.x, intersection1.intersection.y, oppositeMidX.plus(addOppositeX1), oppositeMidY.plus(addOppositeY1)], + canvas, + roof, + textMode, + null, + nextDegree, + afterNextDegree, + ) + baseHipLines.push({ + x1: afterNextLine.x1, + y1: afterNextLine.y1, + x2: oppositeMidX.plus(addOppositeX1).toNumber(), + y2: oppositeMidY.plus(addOppositeY1).toNumber(), + line: hipLine, + }) + } + if (intersection2) { + const hipLine = drawHipLine( + [intersection2.intersection.x, intersection2.intersection.y, oppositeMidX.plus(addOppositeX2), oppositeMidY.plus(addOppositeY2)], + canvas, + roof, + textMode, + null, + prevDegree, + afterNextDegree, + ) + baseHipLines.push({ + x1: afterNextLine.x2, + y1: afterNextLine.y2, + x2: oppositeMidX.plus(addOppositeX2).toNumber(), + y2: oppositeMidY.plus(addOppositeY2).toNumber(), + line: hipLine, + }) + } + } else { + oppositeMidX = Big(afterNextLine.x1).plus(Big(afterNextLine.x2)).div(2).plus(Big(prevVectorX).neg().times(afterNextLine.attributes.offset)) + oppositeMidY = Big(afterNextLine.y1).plus(Big(afterNextLine.y2)).div(2).plus(Big(prevVectorY).neg().times(afterNextLine.attributes.offset)) + } + + const vectorOppositeX = Math.sign(currentMidX.minus(oppositeMidX)) + const vectorOppositeY = Math.sign(currentMidY.minus(oppositeMidY)) + + if (vectorMidX === vectorOppositeX && vectorMidY === vectorOppositeY && baseRidgeCount < getMaxRidge(baseLines.length)) { + const ridge = drawRidgeLine( + [currentMidX.toNumber(), currentMidY.toNumber(), oppositeMidX.toNumber(), oppositeMidY.toNumber()], + canvas, + roof, + textMode, + ) + baseRidgeLines.push(ridge) + baseRidgeCount++ + } + } else { + console.log('4각 아님') + const vectorMidX = Math.sign(Big(nextLine.x2).minus(nextLine.x1)) + const vectorMidY = Math.sign(Big(nextLine.y2).minus(nextLine.y1)) + let oppositeMidX = currentMidX, + oppositeMidY = currentMidY + let prevOppositeMidX, prevOppositeMidY, nextOppositeMidX, nextOppositeMidY + const beforePrevOffset = + currentAngle === beforePrevAngle + ? Big(beforePrevLine.attributes.offset) + : Big(beforePrevLine.attributes.offset).plus(currentLine.attributes.offset) + const afterNextOffset = + currentAngle === afterNextAngle + ? Big(afterNextLine.attributes.offset) + : Big(afterNextLine.attributes.offset).plus(currentLine.attributes.offset) + const prevSize = Big(prevLine.attributes.planeSize).div(10) + const nextSize = Big(nextLine.attributes.planeSize).div(10) + + /** 다음 라인이 그 다음 라인과의 사이에 추녀마루가 존재 하는지 확인. 처마-처마 인 경우 추녀마루*/ + if (eavesType.includes(afterNextLine.attributes?.type)) { + } else { + if (vectorMidX === 0) { + prevOppositeMidY = currentMidY.plus(prevSize.plus(beforePrevOffset).times(vectorMidY)) + prevOppositeMidX = currentMidX + } else { + prevOppositeMidX = currentMidX.plus(prevSize.plus(beforePrevOffset).times(vectorMidX)) + prevOppositeMidY = currentMidY + } + } + + /** 이전 라인이 그 이전 라인과의 사이에 추녀마루가 존재 하는지 확인. 처마-처마 인 경우 추녀마루*/ + if (eavesType.includes(beforePrevLine.attributes?.type)) { + } else { + if (vectorMidX === 0) { + nextOppositeMidY = currentMidY.plus(nextSize.plus(afterNextOffset).times(vectorMidY)) + nextOppositeMidX = currentMidX + } else { + nextOppositeMidX = currentMidX.plus(nextSize.plus(afterNextOffset).times(vectorMidX)) + nextOppositeMidY = currentMidY + } + } + + const checkPrevSize = currentMidX.minus(prevOppositeMidX).pow(2).plus(currentMidY.minus(prevOppositeMidY).pow(2)).sqrt() + const checkNextSize = currentMidX.minus(nextOppositeMidX).pow(2).plus(currentMidY.minus(nextOppositeMidY).pow(2)).sqrt() + + /** 두 포인트 중에 current와 가까운 포인트를 사용*/ + if (checkPrevSize.gt(checkNextSize)) { + oppositeMidY = nextOppositeMidY + oppositeMidX = nextOppositeMidX + } else { + oppositeMidY = prevOppositeMidY + oppositeMidX = prevOppositeMidX + } + + if (baseRidgeCount < getMaxRidge(baseLines.length)) { + const ridgeLine = drawRidgeLine([currentMidX, currentMidY, oppositeMidX, oppositeMidY], canvas, roof, textMode) + baseRidgeLines.push(ridgeLine) + baseRidgeCount++ + } + } + }) + + /** 박공지붕 polygon 생성 */ + drawGablePolygonFirst.forEach((current) => { + const { currentBaseLine, prevBaseLine, nextBaseLine } = current + const currentLine = currentBaseLine.line + const prevLine = prevBaseLine.line + const nextLine = nextBaseLine.line + let { x1, x2, y1, y2 } = currentBaseLine + let prevLineRidges = [], + nextLineRidges = [] + + const currentVectorX = Math.sign(currentLine.x2 - currentLine.x1) + const currentVectorY = Math.sign(currentLine.y2 - currentLine.y1) + const prevVectorX = Math.sign(prevLine.x2 - prevLine.x1) + const prevVectorY = Math.sign(prevLine.y2 - prevLine.y1) + const nextVectorX = Math.sign(nextLine.x1 - nextLine.x2) + const nextVectorY = Math.sign(nextLine.y1 - nextLine.y2) + const currentDegree = currentLine.attributes.pitch > 0 ? getDegreeByChon(currentLine.attributes.pitch) : currentLine.attributes.degree + const currentOffset = currentLine.attributes.offset + const prevOffset = prevLine.attributes.offset + const nextOffset = nextLine.attributes.offset + + let roofX1, roofY1, roofX2, roofY2 + if (currentVectorX === 0) { + roofX1 = Big(x1).plus(Big(currentLine.attributes.offset).times(prevVectorX)) + roofX2 = roofX1 + roofY1 = Big(y1).plus(Big(prevLine.attributes.offset).times(currentVectorY * -1)) + roofY2 = Big(y2).plus(Big(nextLine.attributes.offset).times(currentVectorY)) + } else { + roofX1 = Big(x1).plus(Big(prevLine.attributes.offset).times(currentVectorX * -1)) + roofX2 = Big(x2).plus(Big(nextLine.attributes.offset).times(currentVectorX)) + roofY1 = Big(y1).plus(Big(currentLine.attributes.offset).times(prevVectorY)) + roofY2 = roofY1 + } + + const prevRoofLine = roof.lines.find( + (line) => currentVectorX !== Math.sign(line.x2 - line.x1) && line.x2 === roofX1.toNumber() && line.y2 === roofY1.toNumber(), + ) + const nextRoofLine = roof.lines.find( + (line) => currentVectorX !== Math.sign(line.x2 - line.x1) && line.x1 === roofX2.toNumber() && line.y1 === roofY2.toNumber(), + ) + + const prevRoofEdge = { vertex1: { x: prevRoofLine.x1, y: prevRoofLine.y1 }, vertex2: { x: prevRoofLine.x2, y: prevRoofLine.y2 } } + const nextRoofEdge = { vertex1: { x: nextRoofLine.x1, y: nextRoofLine.y1 }, vertex2: { x: nextRoofLine.x2, y: nextRoofLine.y2 } } + + baseGableRidgeLines.forEach((ridge) => { + const ridgeEdge = { vertex1: { x: ridge.x1, y: ridge.y1 }, vertex2: { x: ridge.x2, y: ridge.y2 } } + const prevIs = edgesIntersection(prevRoofEdge, ridgeEdge) + const nextIs = edgesIntersection(nextRoofEdge, ridgeEdge) + if (prevIs) { + prevLineRidges.push({ + ridge, + size: calcLinePlaneSize({ x1: ridgeEdge.vertex1.x, y1: ridgeEdge.vertex1.y, x2: prevIs.x, y2: prevIs.y }), + }) + } + if (nextIs) { + nextLineRidges.push({ + ridge, + size: calcLinePlaneSize({ x1: ridgeEdge.vertex1.x, y1: ridgeEdge.vertex1.y, x2: nextIs.x, y2: nextIs.y }), + }) + } + }) + + const polygonPoints = [ + { x: roofX1.toNumber(), y: roofY1.toNumber() }, + { x: roofX2.toNumber(), y: roofY2.toNumber() }, + ] + + let prevLineRidge, nextLineRidge + if (prevLineRidges.length > 0) { + prevLineRidges = prevLineRidges + .filter((line) => { + const ridge = line.ridge + if (currentVectorX === 0) { + return ridge.y1 === roofY1.toNumber() || ridge.y2 === roofY1.toNumber() + } else { + return ridge.x1 === roofX1.toNumber() || ridge.x2 === roofX1.toNumber() + } + }) + .sort((a, b) => a.size - b.size) + prevLineRidge = prevLineRidges[0].ridge + } + if (nextLineRidges.length > 0) { + nextLineRidges = nextLineRidges + .filter((line) => { + const ridge = line.ridge + if (currentVectorX === 0) { + return ridge.y1 === roofY2.toNumber() || ridge.y2 === roofY2.toNumber() + } else { + return ridge.x1 === roofX2.toNumber() || ridge.x2 === roofX2.toNumber() + } + }) + .sort((a, b) => a.size - b.size) + nextLineRidge = nextLineRidges[0].ridge + } + + if (prevLineRidge === nextLineRidge) { + polygonPoints.push({ x: prevLineRidge.x1, y: prevLineRidge.y1 }, { x: prevLineRidge.x2, y: prevLineRidge.y2 }) + } else { + let isOverLap = + currentVectorX === 0 + ? (prevLineRidge.y1 <= nextLineRidge.y1 && prevLineRidge.y2 >= nextLineRidge.y1) || + (prevLineRidge.y1 >= nextLineRidge.y1 && prevLineRidge.y2 <= nextLineRidge.y1) || + (prevLineRidge.y1 <= nextLineRidge.y2 && prevLineRidge.y2 >= nextLineRidge.y2) || + (prevLineRidge.y1 >= nextLineRidge.y2 && prevLineRidge.y2 <= nextLineRidge.y2) + : (prevLineRidge.x1 <= nextLineRidge.x1 && prevLineRidge.x2 >= nextLineRidge.x1) || + (prevLineRidge.x1 >= nextLineRidge.x1 && prevLineRidge.x2 <= nextLineRidge.x1) || + (prevLineRidge.x1 <= nextLineRidge.x2 && prevLineRidge.x2 >= nextLineRidge.x2) || + (prevLineRidge.x1 >= nextLineRidge.x2 && prevLineRidge.x2 <= nextLineRidge.x2) + console.log('isOverLap : ', isOverLap) + if (isOverLap) { + const prevDistance = currentVectorX === 0 ? Math.abs(prevLineRidge.x1 - roofX1.toNumber()) : Math.abs(prevLineRidge.y1 - roofY1.toNumber()) + const nextDistance = currentVectorX === 0 ? Math.abs(nextLineRidge.x1 - roofX1.toNumber()) : Math.abs(nextLineRidge.y1 - roofY1.toNumber()) + /** 현재 지붕 라인과 먼 라인의 포인트를 온전히 사용한다. */ + if (prevDistance < nextDistance) { + polygonPoints.push({ x: nextLineRidge.x1, y: nextLineRidge.y1 }, { x: nextLineRidge.x2, y: nextLineRidge.y2 }) + /** 이전라인과 교차한 마루의 포인트*/ + const prevRidgePoint1 = + currentVectorX === 0 + ? roofY1.toNumber() === prevLineRidge.y1 + ? { x: prevLineRidge.x1, y: prevLineRidge.y1 } + : { x: prevLineRidge.x2, y: prevLineRidge.y2 } + : roofX1.toNumber() === prevLineRidge.x1 + ? { x: prevLineRidge.x1, y: prevLineRidge.y1 } + : { x: prevLineRidge.x2, y: prevLineRidge.y2 } + + polygonPoints.push(prevRidgePoint1) + + /** 다음 라인과 교차한 마루의 포인트 중 라인과 접하지 않은 포인트*/ + const checkRidgePoint = + currentVectorX === 0 + ? roofY2.toNumber() !== nextLineRidge.y1 + ? { x: nextLineRidge.x1, y: nextLineRidge.y1 } + : { x: nextLineRidge.x2, y: nextLineRidge.y2 } + : roofX2.toNumber() !== nextLineRidge.x1 + ? { x: nextLineRidge.x1, y: nextLineRidge.y1 } + : { x: nextLineRidge.x2, y: nextLineRidge.y2 } + + const prevRidgePoint2 = + currentVectorX === 0 ? { x: prevRidgePoint1.x, y: checkRidgePoint.y } : { x: checkRidgePoint.x, y: prevRidgePoint1.y } + polygonPoints.push(prevRidgePoint2) + } else { + polygonPoints.push({ x: prevLineRidge.x1, y: prevLineRidge.y1 }, { x: prevLineRidge.x2, y: prevLineRidge.y2 }) + + /** 다음라인과 교차한 마루의 포인트*/ + const nextRidgePoint1 = + currentVectorX === 0 + ? roofY2.toNumber() === nextLineRidge.y1 + ? { x: nextLineRidge.x1, y: nextLineRidge.y1 } + : { x: nextLineRidge.x2, y: nextLineRidge.y2 } + : roofX2.toNumber() === nextLineRidge.x1 + ? { x: nextLineRidge.x1, y: nextLineRidge.y1 } + : { x: nextLineRidge.x2, y: nextLineRidge.y2 } + polygonPoints.push(nextRidgePoint1) + + /** 이전 라인과 교차한 마루의 포인트 중 라인과 접하지 않은 포인트*/ + const checkRidgePoint = + currentVectorX === 0 + ? roofY1.toNumber() !== prevLineRidge.y1 + ? { x: prevLineRidge.x1, y: prevLineRidge.y1 } + : { x: prevLineRidge.x2, y: prevLineRidge.y2 } + : roofX1.toNumber() !== prevLineRidge.x1 + ? { x: prevLineRidge.x1, y: prevLineRidge.y1 } + : { x: prevLineRidge.x2, y: prevLineRidge.y2 } + const nextRidgePoint2 = + currentVectorX === 0 ? { x: nextRidgePoint1.x, y: checkRidgePoint.y } : { x: checkRidgePoint.x, y: nextRidgePoint1.y } + polygonPoints.push(nextRidgePoint2) + } + } else { + } + } + + const sortedPolygonPoints = getSortedPoint(polygonPoints) + + sortedPolygonPoints.forEach((startPoint, index) => { + let endPoint + if (index === sortedPolygonPoints.length - 1) { + endPoint = sortedPolygonPoints[0] + } else { + endPoint = sortedPolygonPoints[index + 1] + } + const hipLine = drawHipLine([startPoint.x, startPoint.y, endPoint.x, endPoint.y], canvas, roof, textMode, null, currentDegree, currentDegree) + if (currentVectorX === 0) { + if (Math.sign(startPoint.x - endPoint.x) === 0) { + hipLine.attributes.actualSize = hipLine.attributes.planeSize + } + } else { + if (Math.sign(startPoint.y - endPoint.y) === 0) { + hipLine.attributes.actualSize = hipLine.attributes.planeSize + } + } + baseHipLines.push({ x1: hipLine.x1, y1: hipLine.y1, x2: hipLine.x2, y2: hipLine.y2, line: hipLine }) + }) + + canvas + .getObjects() + .filter((obj) => obj.name === 'checkLine' || obj.name === 'checkRoofLine') + .forEach((obj) => canvas.remove(obj)) + canvas.renderAll() + }) + + drawGablePolygonSecond.forEach((current) => { + const { currentBaseLine, prevBaseLine, nextBaseLine } = current + const currentLine = currentBaseLine.line + const prevLine = prevBaseLine.line + const nextLine = nextBaseLine.line + let { x1, x2, y1, y2 } = currentBaseLine + let prevLineRidges = [], + nextLineRidges = [] + + const currentVectorX = Math.sign(currentLine.x2 - currentLine.x1) + const currentVectorY = Math.sign(currentLine.y2 - currentLine.y1) + const prevVectorX = Math.sign(prevLine.x2 - prevLine.x1) + const prevVectorY = Math.sign(prevLine.y2 - prevLine.y1) + const nextVectorX = Math.sign(nextLine.x1 - nextLine.x2) + const nextVectorY = Math.sign(nextLine.y1 - nextLine.y2) + const currentDegree = currentLine.attributes.pitch > 0 ? getDegreeByChon(currentLine.attributes.pitch) : currentLine.attributes.degree + const currentOffset = currentLine.attributes.offset + const prevOffset = prevLine.attributes.offset + const nextOffset = nextLine.attributes.offset + + const prevEdge = { vertex1: { x: prevLine.x1, y: prevLine.y1 }, vertex2: { x: prevLine.x2, y: prevLine.y2 } } + const prevRidge = baseGableRidgeLines.find((ridge) => { + const ridgeEdge = { vertex1: { x: ridge.x1, y: ridge.y1 }, vertex2: { x: ridge.x2, y: ridge.y2 } } + const is = edgesIntersection(prevEdge, ridgeEdge) + if (is && !is.isIntersectionOutside) { + return ridge + } + }) + + const nextEdge = { vertex1: { x: nextLine.x1, y: nextLine.y1 }, vertex2: { x: nextLine.x2, y: nextLine.y2 } } + const nextRidge = baseGableRidgeLines.find((ridge) => { + const ridgeEdge = { vertex1: { x: ridge.x1, y: ridge.y1 }, vertex2: { x: ridge.x2, y: ridge.y2 } } + const is = edgesIntersection(nextEdge, ridgeEdge) + if (is && !is.isIntersectionOutside) { + return ridge + } + }) + + let currentRidge + + if (prevRidge) { + if ( + currentVectorX === 0 && + ((prevRidge.y1 <= currentLine.y1 && prevRidge.y2 >= currentLine.y1 && prevRidge.y1 <= currentLine.y2 && prevRidge.y2 >= currentLine.y2) || + (prevRidge.y1 >= currentLine.y1 && prevRidge.y2 <= currentLine.y1 && prevRidge.y1 >= currentLine.y2 && prevRidge.y2 <= currentLine.y2)) + ) { + currentRidge = prevRidge + } + if ( + currentVectorY === 0 && + ((prevRidge.x1 <= currentLine.x1 && prevRidge.x2 >= currentLine.x1 && prevRidge.x1 <= currentLine.x2 && prevRidge.x2 >= currentLine.x2) || + (prevRidge.x1 >= currentLine.x1 && prevRidge.x2 <= currentLine.x1 && prevRidge.x1 >= currentLine.x2 && prevRidge.x2 <= currentLine.x2)) + ) { + currentRidge = prevRidge + } + } + if (nextRidge) { + if ( + currentVectorX === 0 && + ((nextRidge.y1 <= currentLine.y1 && nextRidge.y2 >= currentLine.y1 && nextRidge.y1 <= currentLine.y2 && nextRidge.y2 >= currentLine.y2) || + (nextRidge.y1 >= currentLine.y1 && nextRidge.y2 <= currentLine.y1 && nextRidge.y1 >= currentLine.y2 && nextRidge.y2 <= currentLine.y2)) + ) { + currentRidge = nextRidge + } + if ( + currentVectorY === 0 && + ((nextRidge.x1 <= currentLine.x1 && nextRidge.x2 >= currentLine.x1 && nextRidge.x1 <= currentLine.x2 && nextRidge.x2 >= currentLine.x2) || + (nextRidge.x1 >= currentLine.x1 && nextRidge.x2 <= currentLine.x1 && nextRidge.x1 >= currentLine.x2 && nextRidge.x2 <= currentLine.x2)) + ) { + currentRidge = nextRidge + } + } + if (currentRidge) { + const vectorX = currentVectorX === 0 ? Math.sign(currentLine.x1 - currentRidge.x1) : 0 + const vectorY = currentVectorY === 0 ? Math.sign(currentLine.y1 - currentRidge.y1) : 0 + + const polygonPoints = [ + { x: currentRidge.x1, y: currentRidge.y1 }, + { x: currentRidge.x2, y: currentRidge.y2 }, + ] + if (currentVectorX === 0) { + polygonPoints.push( + { x: Big(currentLine.x1).plus(Big(currentOffset).times(vectorX)).toNumber(), y: currentRidge.y1 }, + { x: Big(currentLine.x2).plus(Big(currentOffset).times(vectorX)).toNumber(), y: currentRidge.y2 }, + ) + } else { + polygonPoints.push( + { x: currentRidge.x1, y: Big(currentLine.y1).plus(Big(currentOffset).times(vectorY)).toNumber() }, + { x: currentRidge.x2, y: Big(currentLine.y2).plus(Big(currentOffset).times(vectorY)).toNumber() }, + ) + } + + const sortedPolygonPoints = getSortedPoint(polygonPoints) + + sortedPolygonPoints.forEach((startPoint, index) => { + let endPoint + if (index === sortedPolygonPoints.length - 1) { + endPoint = sortedPolygonPoints[0] + } else { + endPoint = sortedPolygonPoints[index + 1] + } + const hipLine = drawHipLine([startPoint.x, startPoint.y, endPoint.x, endPoint.y], canvas, roof, textMode, null, currentDegree, currentDegree) + if (currentVectorX === 0) { + if (Math.sign(startPoint.x - endPoint.x) === 0) { + hipLine.attributes.actualSize = hipLine.attributes.planeSize + } + } else { + if (Math.sign(startPoint.y - endPoint.y) === 0) { + hipLine.attributes.actualSize = hipLine.attributes.planeSize + } + } + baseHipLines.push({ x1: hipLine.x1, y1: hipLine.y1, x2: hipLine.x2, y2: hipLine.y2, line: hipLine }) + }) + } + }) + /** ⨆ 모양 처마에 추녀마루를 그린다. */ drawEavesFirstLines.forEach((current) => { // 확인용 라인, 포인트 제거 @@ -1408,13 +2902,14 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { }, } - /*const checkLine = new fabric.Line([checkEdges.vertex1.x, checkEdges.vertex1.y, checkEdges.vertex2.x, checkEdges.vertex2.y], { + const checkLine = new fabric.Line([checkEdges.vertex1.x, checkEdges.vertex1.y, checkEdges.vertex2.x, checkEdges.vertex2.y], { stroke: 'purple', strokeWidth: 2, parentId: roof.id, + name: 'checkLine', }) canvas.add(checkLine) - canvas.renderAll()*/ + canvas.renderAll() /** 맞은편 벽 까지의 길이 판단을 위한 교차되는 line*/ const intersectBaseLine = [] @@ -1464,7 +2959,7 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } const intersection = edgesIntersection(checkEdges, lineEdge) if (intersection) { - /*const intersectPoint = new fabric.Circle({ + /* const intersectPoint = new fabric.Circle({ left: intersection.x - 2, top: intersection.y - 2, radius: 4, @@ -1494,7 +2989,7 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { return prevDistance < currentDistance ? prev : current }, intersectBaseLine[0]) - // console.log('oppositeLine : ', oppositeLine) + console.log('oppositeLine : ', oppositeLine) /** 맞은편 라인까지의 길이 = 전체 길이 - 현재라인의 길이 */ const oppositeSize = oppositeLine @@ -1521,8 +3016,7 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { // console.log('oppositeSize : ', oppositeSize, 'lineMinSize : ', lineMinSize.toNumber(), 'ridgeSize : ', ridgeSize.toNumber()) - if (ridgeSize.gt(0)) { - baseRidgeCount = baseRidgeCount + 1 + if (ridgeSize.gt(0) && baseRidgeCount < getMaxRidge(baseLines.length)) { // console.log('baseRidgeCount : ', baseRidgeCount) const points = [ startPoint.x, @@ -1557,10 +3051,9 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { canvas.add(checkMidLine) canvas.renderAll()*/ // console.log('points : ', points) - - /** 마루 생성 */ const ridgeLine = drawRidgeLine(points, canvas, roof, textMode) baseRidgeLines.push(ridgeLine) + baseRidgeCount = baseRidgeCount + 1 /** 포인트 조정*/ baseHipLines @@ -1619,6 +3112,16 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { const nextLine = nextBaseLine.line let { x1, x2, y1, y2, size } = currentBaseLine + // const checkLine = new fabric.Line([x1, y1, x2, y2], { + // stroke: 'yellow', + // strokeWidth: 4, + // parentId: roof.id, + // name: 'checkLine', + // }) + // canvas.add(checkLine) + // canvas.renderAll() + // checkLine.bringToFront() + /** 이전 라인의 경사 */ const prevDegree = prevLine.attributes.pitch > 0 ? getDegreeByChon(prevLine.attributes.pitch) : prevLine.attributes.degree /** 다음 라인의 경사 */ @@ -1675,7 +3178,7 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { let prevHipLine, nextHipLine /** 이전라인과의 연결지점에 추녀마루를 그린다. */ // console.log('이전라인 : ', baseHipLines.filter((line) => line.x1 === x1 && line.y1 === y1).length === 0) - if (baseHipLines.filter((line) => line.x1 === x1 && line.y1 === y1).length === 0) { + if (baseHipLines.filter((line) => line.x1 === x1 && line.y1 === y1).length === 0 && eavesType.includes(prevLine.attributes.type)) { let prevEndPoint = { x: Big(x1).plus(Big(prevHipVector.x).times(hipLength)).round(2), y: Big(y1).plus(Big(prevHipVector.y).times(hipLength)).round(2), @@ -1869,7 +3372,7 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { } /** 다음라인과의 연결지점에 추녀마루를 그린다. */ // console.log('다음라인 : ', baseHipLines.filter((line) => line.x1 === x2 && line.y1 === y2).length === 0) - if (baseHipLines.filter((line) => line.x1 === x2 && line.y1 === y2).length === 0) { + if (baseHipLines.filter((line) => line.x1 === x2 && line.y1 === y2).length === 0 && eavesType.includes(nextLine.attributes.type)) { let nextEndPoint = { x: Big(x2).plus(Big(nextHipVector.x).times(hipLength)).round(2), y: Big(y2).plus(Big(nextHipVector.y).times(hipLength)).round(2), @@ -2097,7 +3600,6 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { } } }) - intersectRidgePoints.sort((prev, current) => prev.size.minus(current.size).toNumber()) if (intersectRidgePoints.length > 0) { const oldPlaneSize = hipLine.line.attributes.planeSize @@ -2116,15 +3618,15 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { } }) - // console.log('baseHipLines : ', baseHipLines) - // console.log('baseRidgeLines : ', baseRidgeLines) + console.log('baseHipLines : ', baseHipLines) + console.log('baseRidgeLines : ', baseRidgeLines) /** 지붕선에 맞닫지 않은 부분을 확인하여 처리 한다.*/ /** 1. 그려진 마루 중 추녀마루가 부재하는 경우를 판단. 부재는 연결된 라인이 홀수인 경우로 판단한다.*/ let unFinishedRidge = [] baseRidgeLines.forEach((current) => { let checkPoint = [ - { x: current.x1, y: current.y1, line: current, cnt: 0 }, - { x: current.x2, y: current.y2, line: current, cnt: 0 }, + { x: current.x1, y: current.y1, line: current, cnt: 0, onRoofLine: false }, + { x: current.x2, y: current.y2, line: current, cnt: 0, onRoofLine: false }, ] baseHipLines.forEach((line) => { if ((line.x1 === current.x1 && line.y1 === current.y1) || (line.x2 === current.x1 && line.y2 === current.y1)) { @@ -2134,11 +3636,43 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { checkPoint[1].cnt = checkPoint[1].cnt + 1 } }) + + /** 마루의 포인트가 지붕선 위에 있는경우는 제외 (케라바 등)*/ + roof.lines.forEach((line) => { + if ( + line.x1 === line.x2 && + checkPoint[0].x === line.x1 && + ((line.y1 <= checkPoint[0].y && line.y2 >= checkPoint[0].y) || (line.y1 >= checkPoint[0].y && line.y2 <= checkPoint[0].y)) + ) { + checkPoint[0].onRoofLine = true + } + if ( + line.y1 === line.y2 && + checkPoint[0].y === line.y1 && + ((line.x1 <= checkPoint[0].x && line.x2 >= checkPoint[0].x) || (line.x1 >= checkPoint[0].x && line.x2 <= checkPoint[0].x)) + ) { + checkPoint[0].onRoofLine = true + } + if ( + line.x1 === line.x2 && + checkPoint[1].x === line.x1 && + ((line.y1 <= checkPoint[1].y && line.y2 >= checkPoint[1].y) || (line.y1 >= checkPoint[1].y && line.y2 <= checkPoint[1].y)) + ) { + checkPoint[1].onRoofLine = true + } + if ( + line.y1 === line.y2 && + checkPoint[1].y === line.y1 && + ((line.x1 <= checkPoint[1].x && line.x2 >= checkPoint[1].x) || (line.x1 >= checkPoint[1].x && line.x2 <= checkPoint[1].x)) + ) { + checkPoint[1].onRoofLine = true + } + }) // console.log('checkPoint : ', checkPoint) - if (checkPoint[0].cnt === 0 || checkPoint[0].cnt % 2 !== 0) { + if ((checkPoint[0].cnt === 0 || checkPoint[0].cnt % 2 !== 0) && !checkPoint[0].onRoofLine) { unFinishedRidge.push(checkPoint[0]) } - if (checkPoint[1].cnt === 0 || checkPoint[1].cnt % 2 !== 0) { + if ((checkPoint[1].cnt === 0 || checkPoint[1].cnt % 2 !== 0) && !checkPoint[1].onRoofLine) { unFinishedRidge.push(checkPoint[1]) } }) @@ -2185,19 +3719,25 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { /**3. 라인이 부재인 마루의 모자란 라인을 찾는다. 라인은 그려진 추녀마루 중에 완성되지 않은 추녀마루와 확인한다.*/ /**3-1 라인을 그릴때 각도가 필요하기 때문에 각도를 구한다. 각도는 전체 지부의 각도가 같다면 하나로 처리 */ let degreeAllLine = [] - baseLines.forEach((line) => { - const pitch = line.attributes.pitch - const degree = line.attributes.degree - degreeAllLine.push(pitch > 0 ? getDegreeByChon(pitch) : degree) - }) + baseLines + .filter((line) => eavesType.includes(line.attributes.type)) + .forEach((line) => { + const pitch = line.attributes.pitch + const degree = line.attributes.degree + degreeAllLine.push(pitch > 0 ? getDegreeByChon(pitch) : degree) + }) let currentDegree, prevDegree degreeAllLine = [...new Set(degreeAllLine)] currentDegree = degreeAllLine[0] if (degreeAllLine.length === 1) { prevDegree = currentDegree + } else { + prevDegree = degreeAllLine[1] } + console.log('unFinishedRidge : ', unFinishedRidge) + console.log('unFinishedPoint : ', unFinishedPoint) /** 라인이 부재한 마루에 라인을 찾아 그린다.*/ unFinishedRidge.forEach((current) => { let checkPoints = [] @@ -2270,6 +3810,13 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { hipBasePoint = { x1: line.x1, y1: line.y1, x2: line.x2, y2: line.y2 } point = [mergePoint[0].x, mergePoint[0].y, mergePoint[3].x, mergePoint[3].y] + const theta = Big(Math.acos(Big(line.line.attributes.planeSize).div(line.line.attributes.actualSize))) + .times(180) + .div(Math.PI) + .round(1) + console.log('theta : ', theta.toNumber()) + prevDegree = theta.toNumber() + currentDegree = theta.toNumber() canvas.remove(line.line) baseHipLines = baseHipLines.filter((baseLine) => baseLine.line !== line.line) } @@ -2408,6 +3955,16 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { intersectPoints.forEach((is) => { const points = [current.x, current.y, is.x, is.y] + /** 추녀마루의 연결점이 처마라인이 아닌경우 return */ + let hasGable = false + baseLines + .filter((line) => (line.x1 === points[2] && line.y1 === points[3]) || (line.x2 === points[2] && line.y2 === points[3])) + .forEach((line) => { + if (!eavesType.includes(line.attributes.type)) { + hasGable = true + } + }) + if (hasGable) return const pointEdge = { vertex1: { x: points[0], y: points[1] }, vertex2: { x: points[2], y: points[3] } } const vectorX = Math.sign(Big(points[2]).minus(Big(points[0])).toNumber()) const vectorY = Math.sign(Big(points[3]).minus(Big(points[1])).toNumber()) @@ -2502,7 +4059,7 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { // canvas.renderAll() /** 직교 하는 포인트가 존재 할때 마루를 그린다. */ - if (orthogonalPoints.length > 0) { + if (orthogonalPoints.length > 0 && baseRidgeCount < getMaxRidge(baseLines.length)) { baseRidgeCount = baseRidgeCount + 1 const points = [current.x2, current.y2, orthogonalPoints[0].x2, orthogonalPoints[0].y2] const ridgeLine = drawRidgeLine(points, canvas, roof, textMode) @@ -2626,7 +4183,7 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { }) ridgePoints.sort((a, b) => a.distance - b.distance) - if (ridgePoints.length > 0) { + if (ridgePoints.length > 0 && baseRidgeCount < getMaxRidge(baseLines.length)) { const intersection = ridgePoints[0].intersection const isPoint = intersection.intersection const points = [hipPoint.x2, hipPoint.y2, isPoint.x, isPoint.y] @@ -2749,8 +4306,9 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { // console.log('hipStartPoint : ', hipStartPoint) /** 직선인 경우 마루를 그린다.*/ if ( - (hipStartPoint[0] === hipStartPoint[2] && hipStartPoint[1] !== hipStartPoint[3]) || - (hipStartPoint[0] !== hipStartPoint[2] && hipStartPoint[1] === hipStartPoint[3]) + ((hipStartPoint[0] === hipStartPoint[2] && hipStartPoint[1] !== hipStartPoint[3]) || + (hipStartPoint[0] !== hipStartPoint[2] && hipStartPoint[1] === hipStartPoint[3])) && + baseRidgeCount < getMaxRidge(baseLines.length) ) { // console.log('릿지1') const ridgeLine = drawRidgeLine(hipStartPoint, canvas, roof, textMode) @@ -2790,8 +4348,9 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { linePoint.intersectPoint.intersection.y, ] if ( - (isStartPoint[0] === isStartPoint[2] && isStartPoint[1] !== isStartPoint[3]) || - (isStartPoint[0] !== isStartPoint[2] && isStartPoint[1] === isStartPoint[3]) + ((isStartPoint[0] === isStartPoint[2] && isStartPoint[1] !== isStartPoint[3]) || + (isStartPoint[0] !== isStartPoint[2] && isStartPoint[1] === isStartPoint[3])) && + baseRidgeCount < getMaxRidge(baseLines.length) ) { const ridgeLine = drawRidgeLine(isStartPoint, canvas, roof, textMode) baseRidgeCount = baseRidgeCount + 1 @@ -3095,7 +4654,12 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { (line.x1 === ridgePoints[2] && line.y1 === ridgePoints[3] && line.x2 === ridgePoints[0] && line.y2 === ridgePoints[1]), ) - if (!baseIntersection && alreadyRidges.length === 0 && otherRidgeInterSection.length === 0) { + if ( + !baseIntersection && + alreadyRidges.length === 0 && + otherRidgeInterSection.length === 0 && + baseRidgeCount < getMaxRidge(baseLines.length) + ) { baseRidgeCount = baseRidgeCount + 1 const ridgeLine = drawRidgeLine(ridgePoints, canvas, roof, textMode) baseRidgeLines.push(ridgeLine) @@ -3109,7 +4673,7 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { const hipEdge = { vertex1: { x: hipPoints[0], y: hipPoints[1] }, vertex2: { x: hipPoints[2], y: hipPoints[3] } } baseLines.forEach((line) => { const intersection = edgesIntersection(hipEdge, { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }) - if (intersection && !intersection.isIntersectionOutside) { + if (intersection && !intersection.isIntersectionOutside && eavesType.includes(line.attributes.type)) { baseIntersection = true } }) @@ -3151,7 +4715,25 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { baseHipLines.filter((hipLine2) => hipLine !== hipLine2).forEach((hipLine2) => {}) }) - roof.innerLines = [...baseRidgeLines, ...baseHipLines.map((line) => line.line)] + const innerLines = [...baseRidgeLines, ...baseGableRidgeLines, ...baseHipLines.map((line) => line.line)] + const uniqueInnerLines = [] + + innerLines.forEach((currentLine) => { + const sameLines = uniqueInnerLines.filter( + (line) => + line !== currentLine && + ((line.x1 === currentLine.x1 && line.y1 === currentLine.y1 && line.x2 === currentLine.x2 && line.y2 === currentLine.y2) || + (line.x1 === currentLine.x2 && line.y1 === currentLine.y2 && line.x2 === currentLine.x1 && line.y2 === currentLine.y1)), + ) + + if (sameLines.length === 0) { + uniqueInnerLines.push(currentLine) + } else { + sameLines.forEach((line) => canvas.remove(line)) + } + }) + canvas.renderAll() + roof.innerLines = uniqueInnerLines /** 확인용 라인, 포인트 제거 */ canvas @@ -3210,10 +4792,57 @@ const drawHipLine = (points, canvas, roof, textMode, currentRoof, prevDegree, cu }) canvas.add(hip) + hip.bringToFront() canvas.renderAll() return hip } +/** + * 라인의 흐름 방향에서 마주치는 지붕선의 포인트를 찾는다. + * @param roof + * @param baseLine + * @param endPoint + * @returns {*} + */ +const findRoofIntersection = (roof, baseLine, endPoint) => { + let intersectPoints = [] + const { x1, y1, x2, y2 } = baseLine + + const baseEdge = { + vertex1: { x: x1, y: y1 }, + vertex2: { x: x2, y: y2 }, + } + + /** 외벽선에서 라인 겹치는 경우에 대한 확인*/ + roof.lines.forEach((line) => { + const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } + const intersection = edgesIntersection(baseEdge, lineEdge) + if ( + intersection && + !intersection.isIntersectionOutside && + Math.sign(endPoint.x - baseLine.x1) === Math.sign(endPoint.x - intersection.x) && + Math.sign(endPoint.y - baseLine.y1) === Math.sign(endPoint.y - intersection.y) + ) { + const intersectSize = endPoint.x + .minus(Big(intersection.x)) + .pow(2) + .plus(endPoint.y.minus(Big(intersection.y)).pow(2)) + .abs() + .sqrt() + .toNumber() + + intersectPoints.push({ + intersection, + size: intersectSize, + }) + } + }) + + return intersectPoints.reduce((prev, current) => { + return prev.size < current.size ? prev : current + }, intersectPoints[0]) +} + /** * 마루를 그린다. * @param points @@ -3247,6 +4876,7 @@ const drawRidgeLine = (points, canvas, roof, textMode) => { }, }) canvas.add(ridge) + ridge.bringToFront() canvas.renderAll() return ridge @@ -6068,7 +7698,7 @@ export const calcLinePlaneSize = (points) => { export const calcLineActualSize = (points, degree) => { const planeSize = calcLinePlaneSize(points) const theta = Big(Math.cos(Big(degree).times(Math.PI).div(180))) - return Big(planeSize).div(theta).round(1).toNumber() + return Big(planeSize).div(theta).round().toNumber() } export const createLinesFromPolygon = (points) => { @@ -6085,3 +7715,32 @@ export const createLinesFromPolygon = (points) => { } return lines } + +/** 포인트 정렬 가장왼쪽, 가장위 부터 */ +const getSortedPoint = (points) => { + const startPoint = points + .filter((point) => point.x === Math.min(...points.map((point) => point.x))) + .reduce((prev, curr) => { + return prev.y < curr.y ? prev : curr + }) + const sortedPoints = [] + sortedPoints.push(startPoint) + + let prevPoint = startPoint + + for (let i = 0; i < points.length - 1; i++) { + points + .filter((point) => !sortedPoints.includes(point)) + .forEach((point) => { + if (i % 2 === 1 && prevPoint.y === point.y) { + sortedPoints.push(point) + prevPoint = point + } + if (i % 2 === 0 && prevPoint.x === point.x) { + sortedPoints.push(point) + prevPoint = point + } + }) + } + return sortedPoints +} -- 2.47.2 From 5f648632dded73953c8c5e7ef935fcebebcd248a Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Mon, 28 Apr 2025 18:07:54 +0900 Subject: [PATCH 024/109] =?UTF-8?q?=EC=B5=9C=EB=8B=A8=EA=B1=B0=EB=A6=AC=20?= =?UTF-8?q?roof=20=EB=82=98=EB=88=84=EA=B8=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/usePolygon.js | 280 ++++++++++++++++++++++++++-------------- 1 file changed, 180 insertions(+), 100 deletions(-) diff --git a/src/hooks/usePolygon.js b/src/hooks/usePolygon.js index fd5b5a69..9a5f39f8 100644 --- a/src/hooks/usePolygon.js +++ b/src/hooks/usePolygon.js @@ -1,7 +1,7 @@ import { ANGLE_TYPE, canvasState, currentAngleTypeSelector, pitchTextSelector } from '@/store/canvasAtom' import { useRecoilValue } from 'recoil' import { fabric } from 'fabric' -import { findAndRemoveClosestPoint, getDegreeByChon, getDegreeInOrientation, isPointOnLine } from '@/util/canvas-util' +import { findAndRemoveClosestPoint, getDegreeByChon, getDegreeInOrientation, isPointOnLine, toFixedWithoutRounding } from '@/util/canvas-util' import { QPolygon } from '@/components/fabric/QPolygon' import { isSamePoint, removeDuplicatePolygons } from '@/util/qpolygon-utils' import { flowDisplaySelector } from '@/store/settingAtom' @@ -757,7 +757,7 @@ export const usePolygon = () => { polygon.set({ visible: false }) let innerLines = [...polygon.innerLines] - // innerLine이 세팅이 안되어있는경우 찾아서 세팅한다. + /*// innerLine이 세팅이 안되어있는경우 찾아서 세팅한다. if (!innerLines || innerLines.length === 0) { let innerLineTypes = Object.keys(LINE_TYPE.SUBLINE).map((key, value) => LINE_TYPE.SUBLINE[key]) polygon.innerLines = canvas @@ -771,8 +771,7 @@ export const usePolygon = () => { ) innerLines = [...polygon.innerLines] - } - + }*/ canvas.renderAll() let polygonLines = [...polygon.lines] const roofs = [] @@ -830,8 +829,14 @@ export const usePolygon = () => { canvas.renderAll() polygonLines.forEach((line) => { + /*const originStroke = line.stroke + line.set({ stroke: 'red' }) + canvas.renderAll()*/ const intersections = [] innerLines.forEach((innerLine) => { + /*const originInnerStroke = innerLine.stroke + innerLine.set({ stroke: 'red' }) + canvas.renderAll()*/ if (isPointOnLine(line, innerLine.startPoint)) { if (isSamePoint(line.startPoint, innerLine.startPoint) || isSamePoint(line.endPoint, innerLine.startPoint)) { return @@ -844,8 +849,13 @@ export const usePolygon = () => { } intersections.push(innerLine.endPoint) } + /*innerLine.set({ stroke: originInnerStroke }) + canvas.renderAll()*/ }) line.set({ intersections }) + + /*line.set({ stroke: originStroke }) + canvas.renderAll()*/ }) const divideLines = polygonLines.filter((line) => line.intersections.length > 0) @@ -924,10 +934,52 @@ export const usePolygon = () => { }) //polygonLines에서 divideLines를 제거하고 newLines를 추가한다. - polygonLines = polygonLines.filter((line) => !divideLines.includes(line)) + polygonLines = polygonLines.filter((line) => line.intersections?.length === 0) + polygonLines = [...polygonLines, ...newLines] - const allLines = [...polygonLines, ...innerLines] + let allLines = [...polygonLines, ...innerLines] + + /*allLines.forEach((line) => { + const originColor = line.stroke + + line.set('stroke', 'red') + canvas.renderAll() + + line.set('stroke', originColor) + canvas.renderAll() + })*/ + + const allPoints = [] + + // test용 좌표 + const polygonLinesPoints = polygonLines.map((line) => { + return { startPoint: line.startPoint, endPoint: line.endPoint } + }) + + const innerLinesPoints = innerLines.map((line) => { + return { startPoint: line.startPoint, endPoint: line.endPoint } + }) + + polygonLinesPoints.forEach(({ startPoint, endPoint }) => { + allPoints.push(startPoint) + allPoints.push(endPoint) + }) + + innerLinesPoints.forEach(({ startPoint, endPoint }) => { + allPoints.push(startPoint) + allPoints.push(endPoint) + }) + + // allPoints에서 중복을 제거한다. + const uniquePoints = allPoints.filter((point, index, self) => { + return ( + index === + self.findIndex((p) => { + return isSamePoint(p, point) + }) + ) + }) // 2025-02-19 대각선은 케라바, 직선은 용마루로 세팅 innerLines.forEach((innerLine) => { @@ -978,102 +1030,23 @@ export const usePolygon = () => { line.endPoint = endPoint }) - // polygon line에서 각각 출발한다. - polygonLines.forEach((line) => { - /*line.set({ strokeWidth: 5, stroke: 'green' }) - canvas.add(line) - canvas.renderAll()*/ - const startPoint = { ...line.startPoint } // 시작점 - let arrivalPoint = { ...line.endPoint } // 도착점 + // polygonLines에서 시작점 혹은 끝점이 innerLines와 연결된 line만 가져온다. + let startLines = polygonLines.filter((line) => { + const startPoint = line.startPoint + const endPoint = line.endPoint - let currentPoint = startPoint - let roofPoints = [startPoint] - - let startLine = line - let visitPoints = [startPoint] - let visitLines = [startLine] - let notVisitedLines = [] - let cnt = 0 - - while (!isSamePoint(currentPoint, arrivalPoint)) { - //현재 점으로 부터 갈 수 있는 다른 라인을 찾는다. - let nextLines = allLines.filter( - (line2) => - (isSamePoint(line2.startPoint, currentPoint) || isSamePoint(line2.endPoint, currentPoint)) && - line !== line2 && - innerLines.includes(line2) && - !visitLines.includes(line2), + return innerLines.some((innerLine) => { + return ( + isSamePoint(innerLine.startPoint, startPoint) || + isSamePoint(innerLine.endPoint, startPoint) || + isSamePoint(innerLine.startPoint, endPoint) || + isSamePoint(innerLine.endPoint, endPoint) ) - - if (nextLines.length === 0) { - nextLines = allLines.filter( - (line2) => - (isSamePoint(line2.startPoint, currentPoint) || isSamePoint(line2.endPoint, currentPoint)) && - line !== line2 && - !visitLines.includes(line2), - ) - } - - if (nextLines.length === 0) { - //아직 안갔던 line중 0번째를 선택한다. - if (notVisitedLines.length === 0) { - break - } else { - let notVisitedLine = notVisitedLines.shift() - roofPoints = [...notVisitedLine.roofPoints] - currentPoint = { ...notVisitedLine.currentPoint } - continue - } - } - - let comparisonPoints = [] - - nextLines.forEach((nextLine) => { - if (isSamePoint(nextLine.startPoint, currentPoint)) { - comparisonPoints.push(nextLine.endPoint) - } else { - comparisonPoints.push(nextLine.startPoint) - } - }) - - comparisonPoints = comparisonPoints.filter((point) => !visitPoints.some((visitPoint) => isSamePoint(visitPoint, point))) - comparisonPoints = comparisonPoints.filter((point) => !isSamePoint(point, currentPoint)) - - const minDistancePoint = comparisonPoints.reduce((prev, current) => { - const prevDistance = Math.sqrt(Math.pow(prev.x - arrivalPoint.x, 2) + Math.pow(prev.y - arrivalPoint.y, 2)) - const currentDistance = Math.sqrt(Math.pow(current.x - arrivalPoint.x, 2) + Math.pow(current.y - arrivalPoint.y, 2)) - - return prevDistance < currentDistance ? prev : current - }, comparisonPoints[0]) - - nextLines.forEach((nextLine) => { - if (isSamePoint(nextLine.startPoint, minDistancePoint) || isSamePoint(nextLine.endPoint, minDistancePoint)) { - visitLines.push(nextLine) - } else { - notVisitedLines.push({ - line: nextLine, - endPoint: nextLine.endPoint, - startPoint: nextLine.startPoint, - currentPoint: { ...currentPoint }, - roofPoints: [...roofPoints], - }) - } - }) - - currentPoint = { ...minDistancePoint } - roofPoints.push(currentPoint) - cnt++ - - if (cnt > 100) { - break - } - } - roofs.push(roofPoints) - canvas.remove(line) - canvas.renderAll() + }) }) - const newRoofs = removeDuplicatePolygons(roofs.filter((roof) => roof.length < 100)) + // 나눠서 중복 제거된 roof return + const newRoofs = getSplitRoofsPoints(startLines, allLines, innerLines, uniquePoints) newRoofs.forEach((roofPoint, index) => { let defense, pitch @@ -1104,7 +1077,7 @@ export const usePolygon = () => { }) // blue로 생성된 것들은 대표라인이 될 수 없음. - representLines = representLines.filter((line) => line.stroke !== 'blue') + // representLines = representLines.filter((line) => line.stroke !== 'blue') // representLines중 가장 긴 line을 찾는다. representLines.forEach((line) => { if (!representLine) { @@ -1115,7 +1088,7 @@ export const usePolygon = () => { } } }) - const direction = polygon.direction ?? representLine.direction + const direction = polygon.direction ?? representLine?.direction const polygonDirection = polygon.direction switch (direction) { @@ -1174,6 +1147,113 @@ export const usePolygon = () => { }) } + const getSplitRoofsPoints = (startLines, allLines, innerLines, uniquePoints) => { + // 거리 계산 + function calcDistance(p1, p2) { + return Math.hypot(p2.x - p1.x, p2.y - p1.y) + } + + // 바로 연결 체크 + function isDirectlyConnected(start, end, graph) { + const startKey = `${start.x},${start.y}` + if (!graph[startKey]) return false + return graph[startKey].some((neighbor) => neighbor.point.x === end.x && neighbor.point.y === end.y) + } + + // Dijkstra 최단 경로 + function findShortestPath(start, end, graph) { + const startKey = `${start.x},${start.y}` + const endKey = `${end.x},${end.y}` + + const distances = {} + const previous = {} + const visited = new Set() + const queue = [{ key: startKey, dist: 0 }] + + for (const key in graph) { + distances[key] = Infinity + } + distances[startKey] = 0 + + while (queue.length > 0) { + queue.sort((a, b) => a.dist - b.dist) + const { key } = queue.shift() + if (visited.has(key)) continue + visited.add(key) + + for (const neighbor of graph[key]) { + const neighborKey = `${neighbor.point.x},${neighbor.point.y}` + const alt = distances[key] + neighbor.distance + if (alt < distances[neighborKey]) { + distances[neighborKey] = alt + previous[neighborKey] = key + queue.push({ key: neighborKey, dist: alt }) + } + } + } + + // 경로 복구 + const path = [] + let currentKey = endKey + + if (!previous[currentKey]) { + return null // 경로 없음 + } + + while (currentKey !== startKey) { + const [x, y] = currentKey.split(',').map(Number) + path.unshift({ x, y }) + currentKey = previous[currentKey] + } + path.unshift(start) + + return path + } + + // 최종 함수 + function getPath(start, end, graph) { + if (isDirectlyConnected(start, end, graph)) { + return null + } else { + const path = findShortestPath(start, end, graph) + if (!path || path.length < 3) { + return null + } + return path + } + } + + const roofs = [] + + startLines.forEach((line) => { + // 그래프 생성 + const graph = {} + const edges = allLines + .filter((line2) => line !== line2) + .map((line) => { + return [line.startPoint, line.endPoint] + }) + + for (const [p1, p2] of edges) { + const key1 = `${p1.x},${p1.y}` + const key2 = `${p2.x},${p2.y}` + const distance = calcDistance(p1, p2) + + if (!graph[key1]) graph[key1] = [] + if (!graph[key2]) graph[key2] = [] + + graph[key1].push({ point: p2, distance }) + graph[key2].push({ point: p1, distance }) + } + + const startPoint = { ...line.startPoint } // 시작점 + let arrivalPoint = { ...line.endPoint } // 도착점 + roofs.push(getPath(startPoint, arrivalPoint, graph)) + }) + + return removeDuplicatePolygons(roofs.filter((roof) => roof.length < 100)) + } + const splitPolygonWithSeparate = (separates) => { separates.forEach((separate) => { const points = separate.lines.map((line) => { -- 2.47.2 From ed27f2ed93ad0eff56ffb659e96f9ae6fb33b873 Mon Sep 17 00:00:00 2001 From: yjnoh Date: Tue, 29 Apr 2025 13:09:46 +0900 Subject: [PATCH 025/109] =?UTF-8?q?=EB=AA=A8=EB=93=88=20=EC=9E=90=EB=8F=99?= =?UTF-8?q?=20=EB=A0=88=EC=9D=B4=EC=95=84=EC=9B=83=20=EC=84=A4=EC=B9=98=20?= =?UTF-8?q?validate=20=EC=9E=91=EC=97=85=EC=A4=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/common/common.js | 5 + .../floor-plan/modal/basic/BasicSetting.jsx | 6 +- .../floor-plan/modal/basic/step/Placement.jsx | 6 +- src/hooks/module/useModuleBasicSetting.js | 190 ++++++++++++------ 4 files changed, 140 insertions(+), 67 deletions(-) diff --git a/src/common/common.js b/src/common/common.js index abda5acd..c98cf385 100644 --- a/src/common/common.js +++ b/src/common/common.js @@ -125,6 +125,11 @@ export const TRESTLE_MATERIAL = { BRACKET: 'bracket', } +export const MODULE_SETUP_TYPE = { + LAYOUT: 'layout', + AUTO: 'auto', +} + export const SAVE_KEY = [ 'selectable', 'name', diff --git a/src/components/floor-plan/modal/basic/BasicSetting.jsx b/src/components/floor-plan/modal/basic/BasicSetting.jsx index a930c148..66a76d71 100644 --- a/src/components/floor-plan/modal/basic/BasicSetting.jsx +++ b/src/components/floor-plan/modal/basic/BasicSetting.jsx @@ -1,4 +1,4 @@ -import { POLYGON_TYPE } from '@/common/common' +import { POLYGON_TYPE, MODULE_SETUP_TYPE } from '@/common/common' import WithDraggable from '@/components/common/draggable/WithDraggable' import { Orientation } from '@/components/floor-plan/modal/basic/step/Orientation' import PitchPlacement from '@/components/floor-plan/modal/basic/step/pitch/PitchPlacement' @@ -334,10 +334,10 @@ export default function BasicSetting({ id, pos = { x: 50, y: 230 } }) { - - diff --git a/src/components/floor-plan/modal/basic/step/Placement.jsx b/src/components/floor-plan/modal/basic/step/Placement.jsx index 909f5229..5d2c8c6a 100644 --- a/src/components/floor-plan/modal/basic/step/Placement.jsx +++ b/src/components/floor-plan/modal/basic/step/Placement.jsx @@ -64,9 +64,9 @@ const Placement = forwardRef((props, refs) => { } }, []) - useEffect(() => { - console.log('moduleRowColArray', moduleRowColArray) - }, [moduleRowColArray]) + // useEffect(() => { + // console.log('moduleRowColArray', moduleRowColArray) + // }, [moduleRowColArray]) //최초 지입시 체크 useEffect(() => { diff --git a/src/hooks/module/useModuleBasicSetting.js b/src/hooks/module/useModuleBasicSetting.js index e60a5324..1ce7298f 100644 --- a/src/hooks/module/useModuleBasicSetting.js +++ b/src/hooks/module/useModuleBasicSetting.js @@ -19,7 +19,7 @@ import offsetPolygon, { calculateAngle, createLinesFromPolygon } from '@/util/qp import { QPolygon } from '@/components/fabric/QPolygon' import { moduleSetupSurfaceState } from '@/store/canvasAtom' import { useEvent } from '@/hooks/useEvent' -import { POLYGON_TYPE, BATCH_TYPE, LINE_TYPE } from '@/common/common' +import { POLYGON_TYPE, BATCH_TYPE, LINE_TYPE, MODULE_SETUP_TYPE } from '@/common/common' import * as turf from '@turf/turf' import { useSwal } from '@/hooks/useSwal' import { compasDegAtom } from '@/store/orientationAtom' @@ -1772,6 +1772,8 @@ export function useModuleBasicSetting(tabNum) { const autoModuleSetup = (type, layoutSetupRef) => { initEvent() //마우스 이벤트 초기화 + console.log('checkedModule', checkedModule) + //실패한 지붕재 배열 let failAutoSetupRoof = [] @@ -1780,7 +1782,7 @@ export function useModuleBasicSetting(tabNum) { /** * 자동 레이아웃일때 0이 있거나 혼합이 있는지 확인하는 로직 */ - if (type === 'layout') { + if (type === MODULE_SETUP_TYPE.LAYOUT) { checkedLayoutData = layoutSetupRef.filter((module) => module.checked) const hasZeroLength = checkedLayoutData.some((module) => module.row === 0 || module.col === 0) @@ -1789,15 +1791,15 @@ export function useModuleBasicSetting(tabNum) { return } - //혼합 가능 모듈과 혼합 불가능 모듈을 선택했을때 카운트를 해서 확인 - const mixAsgY = checkedModule.filter((obj) => obj.mixAsgYn === 'Y') - const mixAsgN = checkedModule.filter((obj) => obj.mixAsgYn === 'N') + // //혼합 가능 모듈과 혼합 불가능 모듈을 선택했을때 카운트를 해서 확인 + // const mixAsgY = checkedModule.filter((obj) => obj.mixAsgYn === 'Y') + // const mixAsgN = checkedModule.filter((obj) => obj.mixAsgYn === 'N') - //Y인 모듈과 N인 모듈이 둘다 존재하면 설치 불가 - if (mixAsgY.length > 0 && mixAsgN.length > 0) { - swalFire({ text: getMessage('modal.module.basic.setting.module.placement.mix.asg.yn.error'), icon: 'warning' }) - return - } + // //Y인 모듈과 N인 모듈이 둘다 존재하면 설치 불가 + // if (mixAsgY.length > 0 && mixAsgN.length > 0) { + // swalFire({ text: getMessage('modal.module.basic.setting.module.placement.mix.asg.yn.error'), icon: 'warning' }) + // return + // } } if (checkedModule.length === 0) { @@ -1805,16 +1807,6 @@ export function useModuleBasicSetting(tabNum) { return } - // //혼합 가능 모듈과 혼합 불가능 모듈을 선택했을때 카운트를 해서 확인 - // const mixAsgY = checkedModule.filter((obj) => obj.mixAsgYn === 'Y') - // const mixAsgN = checkedModule.filter((obj) => obj.mixAsgYn === 'N') - - // //Y인 모듈과 N인 모듈이 둘다 존재하면 설치 불가 - // if (mixAsgY.length > 0 && mixAsgN.length > 0) { - // swalFire({ text: getMessage('modal.module.basic.setting.module.placement.mix.asg.yn.error') }) - // return - // } - const isChidori = moduleSetupOption.isChidori const setupLocation = moduleSetupOption.setupLocation const isMaxSetup = false @@ -1859,6 +1851,9 @@ export function useModuleBasicSetting(tabNum) { name: POLYGON_TYPE.MODULE, } + //체크된 모듈 중에 북면 모듈이 있는지 확인하는 로직 + const isIncludeNorthModule = checkedModule.some((module) => module.northModuleYn === 'Y') + //선택된 지붕안에 오브젝트(도머, 개구등)이 있는지 확인하는 로직 포함되면 배열 반환 const objectsIncludeSurface = (turfModuleSetupSurface) => { let containsBatchObjects = [] @@ -1891,32 +1886,108 @@ export function useModuleBasicSetting(tabNum) { * @returns */ const checkAutoLayoutModuleSetup = (moduleSetupSurface, trestleDetailData) => { - const isMultipleModules = checkedModule.length > 1 //모듈이 여러개면 - const maxCol = trestleDetailData.moduleMaxCols //최대 열수 -> 얘는 멀티랑 관계없음 - const maxRow = isMultipleModules - ? trestleDetailData.moduleMaxRows - : trestleDetailData.module.find((item) => item.moduleTpCd === checkedModule[0].moduleTpCd).moduleMaxRows //멀티모듈이면 밖에 maxRows로 판단 아니면 module->itemmList를 가지고 판단 + /** + * 체크된 모듈중에 북면 모듈이 있으면 북면 모듈만 따로 계산하고 아니면 전체 모듈을 계산 + * 북면 모듈이 있고 북면이 들어오면 북면의 row를 계산한다 + * + */ - //단수 합단수 - const sumRowCount = isMultipleModules - ? layoutSetupRef.filter((item) => item.checked).reduce((acc, cur) => acc + cur.row, 0) - : layoutSetupRef.find((item) => item.moduleId === checkedModule[0].itemId).row //멀티모듈이면 전체 합, 체크된 한개의 열 + //북면 모듈이 없을때 + if (!isIncludeNorthModule) { + const isMultipleModules = checkedModule.length > 1 //모듈이 여러개면 + const maxCol = trestleDetailData.moduleMaxCols //최대 열수 -> 얘는 멀티랑 관계없음 + const maxRow = isMultipleModules + ? trestleDetailData.moduleMaxRows + : trestleDetailData.module.find((item) => item.moduleTpCd === checkedModule[0].moduleTpCd).moduleMaxRows //멀티모듈이면 밖에 maxRows로 판단 아니면 module->itemmList를 가지고 판단 - // - const sumColCount = layoutSetupRef.filter((item) => item.col).some((item) => item.col > maxCol) + //단수 합단수 + const sumRowCount = isMultipleModules + ? layoutSetupRef.filter((item) => item.checked).reduce((acc, cur) => acc + cur.row, 0) + : layoutSetupRef.find((item) => item.moduleId === checkedModule[0].itemId).row //멀티모듈이면 전체 합, 체크된 한개의 열 - if (sumRowCount > maxRow || sumColCount) { - failAutoSetupRoof.push(moduleSetupSurface) - return false - } + // + const sumColCount = layoutSetupRef.filter((item) => item.col).some((item) => item.col > maxCol) - // 혼합일때 모듈 개별의 row를 체크함 - const isPassedObject = - isMultipleModules && layoutSetupRef.find((item, index) => item.checked && item.row > trestleDetailData.module[index].mixModuleMaxRows) + if (sumRowCount > maxRow || sumColCount) { + failAutoSetupRoof.push(moduleSetupSurface) + return false + } - if (isPassedObject) { - failAutoSetupRoof.push(moduleSetupSurface) - return false + // 혼합일때 모듈 개별의 row를 체크함 + const isPassedObject = + isMultipleModules && layoutSetupRef.find((item, index) => item.checked && item.row > trestleDetailData.module[index].mixModuleMaxRows) + + if (isPassedObject) { + failAutoSetupRoof.push(moduleSetupSurface) + return false + } + } else { + //북면 모듈만 선택했을때 + if (checkedModule.length === 1) { + const maxCol = trestleDetailData.moduleMaxCols //최대 열수 -> 얘는 멀티랑 관계없음 + const maxRow = trestleDetailData.module.find((item) => item.moduleTpCd === checkedModule[0].moduleTpCd).moduleMaxRows //멀티모듈이면 밖에 maxRows로 판단 아니면 module->itemmList를 가지고 판단 + + //단수 합단수 + const sumRowCount = layoutSetupRef.find((item) => item.moduleId === checkedModule[0].itemId).row //멀티모듈이면 전체 합, 체크된 한개의 열 + const sumColCount = layoutSetupRef.filter((item) => item.col).some((item) => item.col > maxCol) + + if (sumRowCount > maxRow || sumColCount) { + failAutoSetupRoof.push(moduleSetupSurface) + return false + } + } else { + const normalModule = checkedModule.filter((item) => item.northModuleYn === 'N') + const northModule = checkedModule.filter((item) => item.northModuleYn === 'Y') + + //만약 북면 모듈이 2개면 이 하위 로직 가져다가 쓰면됨 northModule === 만 바꾸면 될듯 + // northModule을 배열로 만들고 include로 해서 체크 해야됨 + if (normalModule.length > 0 && !moduleSetupSurface.isNorth) { + const isMultipleModules = normalModule.length > 1 //모듈이 여러개면 + const maxCol = trestleDetailData.moduleMaxCols //최대 열수 -> 얘는 멀티랑 관계없음 + const maxRow = isMultipleModules + ? trestleDetailData.moduleMaxRows + : trestleDetailData.module.find((item) => item.moduleTpCd === normalModule[0].moduleTpCd).moduleMaxRows //멀티모듈이면 밖에 maxRows로 판단 아니면 module->itemmList를 가지고 판단 + + //단수 합단수 + const sumRowCount = isMultipleModules + ? layoutSetupRef.filter((item) => item.checked && item.moduleId !== northModule[0].itemId).reduce((acc, cur) => acc + cur.row, 0) + : layoutSetupRef.find((item) => item.moduleId === normalModule[0].itemId).row //멀티모듈이면 전체 합, 체크된 한개의 열 + + // + const sumColCount = layoutSetupRef.filter((item) => item.col && item.moduleId !== northModule[0].itemId).some((item) => item.col > maxCol) + + if (sumRowCount > maxRow || sumColCount) { + failAutoSetupRoof.push(moduleSetupSurface) + return false + } + + // 혼합일때 모듈 개별의 row를 체크함 + const isPassedObject = + isMultipleModules && + layoutSetupRef.find( + (item, index) => + item.checked && item.moduleId !== northModule[0].itemId && item.row > trestleDetailData.module[index].mixModuleMaxRows, + ) + + if (isPassedObject) { + failAutoSetupRoof.push(moduleSetupSurface) + return false + } + } + + if (northModule.length > 0 && moduleSetupSurface.isNorth) { + const maxCol = trestleDetailData.moduleMaxCols //최대 열수 -> 얘는 멀티랑 관계없음 + const maxRow = trestleDetailData.module.find((item) => item.moduleTpCd === checkedModule[0].moduleTpCd).moduleMaxRows //멀티모듈이면 밖에 maxRows로 판단 아니면 module->itemmList를 가지고 판단 + + const sumRowCount = layoutSetupRef.find((item) => item.moduleId === northModule[0].itemId).row + const sumColCount = layoutSetupRef.filter((item) => item.col && item.moduleId !== northModule[0].itemId).some((item) => item.col > maxCol) + + if (sumRowCount > maxRow || sumColCount) { + failAutoSetupRoof.push(moduleSetupSurface) + return false + } + } + } } return true } @@ -1943,12 +2014,11 @@ export function useModuleBasicSetting(tabNum) { let flowLines let installedModuleMixYn const isNorthSurface = moduleSetupSurface.isNorth - const isIncludeNorthModule = checkedModule.some((module) => module.northModuleYn === 'Y') //체크된 모듈 중에 북면 모듈이 있는지 확인하는 로직 let layoutRow = 0 let layoutCol = 0 - if (type === 'layout') { + if (type === MODULE_SETUP_TYPE.LAYOUT) { const isPassed = checkAutoLayoutModuleSetup(moduleSetupSurface, trestleDetailData) if (!isPassed) { return @@ -1958,7 +2028,7 @@ export function useModuleBasicSetting(tabNum) { for (let moduleIndex = 0; moduleIndex < checkedModule.length; moduleIndex++) { const module = checkedModule[moduleIndex] - if (type === 'layout' && checkedLayoutData) { + if (type === MODULE_SETUP_TYPE.LAYOUT && checkedLayoutData) { const layout = checkedLayoutData.find((item) => module.itemId === item.moduleId) layoutRow = layout.row layoutCol = layout.col @@ -2009,7 +2079,7 @@ export function useModuleBasicSetting(tabNum) { let calcAreaHeight = flowLines.bottom.y1 - flowLines.top.y1 let calcModuleHeightCount = calcAreaHeight / (height + intvVer + 1) - if (type === 'layout') { + if (type === MODULE_SETUP_TYPE.LAYOUT) { calcModuleWidthCount = layoutCol > calcModuleWidthCount ? calcModuleWidthCount : layoutCol calcModuleHeightCount = layoutRow } @@ -2095,9 +2165,9 @@ export function useModuleBasicSetting(tabNum) { installedLastHeightCoord = moduleY - height - heightMargin } else { //디버깅용 - tempModule.set({ fill: 'transparent', stroke: 'red', strokeWidth: 1 }) - canvas?.add(tempModule) - canvas.renderAll() + // tempModule.set({ fill: 'transparent', stroke: 'red', strokeWidth: 1 }) + // canvas?.add(tempModule) + // canvas.renderAll() } } if (isInstall) { @@ -2136,7 +2206,7 @@ export function useModuleBasicSetting(tabNum) { let layoutRow = 0 let layoutCol = 0 - if (type === 'layout') { + if (type === MODULE_SETUP_TYPE.LAYOUT) { const isPassed = checkAutoLayoutModuleSetup(moduleSetupSurface, trestleDetailData) if (!isPassed) { return @@ -2146,7 +2216,7 @@ export function useModuleBasicSetting(tabNum) { for (let moduleIndex = 0; moduleIndex < checkedModule.length; moduleIndex++) { const module = checkedModule[moduleIndex] - if (type === 'layout' && checkedLayoutData) { + if (type === MODULE_SETUP_TYPE.LAYOUT && checkedLayoutData) { const layout = checkedLayoutData.find((item) => module.itemId === item.moduleId) layoutRow = layout.row layoutCol = layout.col @@ -2204,7 +2274,7 @@ export function useModuleBasicSetting(tabNum) { let calcModuleHeightCount = calcAreaHeight / (height + intvVer + 1) //단수지정 자동이면 - if (type === 'layout') { + if (type === MODULE_SETUP_TYPE.LAYOUT) { calcModuleWidthCount = layoutCol > calcModuleWidthCount ? calcModuleWidthCount : layoutCol calcModuleHeightCount = layoutRow } @@ -2325,7 +2395,7 @@ export function useModuleBasicSetting(tabNum) { let layoutRow = 0 let layoutCol = 0 - if (type === 'layout') { + if (type === MODULE_SETUP_TYPE.LAYOUT) { const isPassed = checkAutoLayoutModuleSetup(moduleSetupSurface, trestleDetailData) if (!isPassed) { return @@ -2336,7 +2406,7 @@ export function useModuleBasicSetting(tabNum) { const module = checkedModule[moduleIndex] //단수 지정이면 - if (type === 'layout' && checkedLayoutData) { + if (type === MODULE_SETUP_TYPE.LAYOUT && checkedLayoutData) { const layout = checkedLayoutData.find((item) => module.itemId === item.moduleId) layoutRow = layout.row layoutCol = layout.col @@ -2397,7 +2467,7 @@ export function useModuleBasicSetting(tabNum) { let calcModuleHeightCount = calcAreaHeight / (width + intvVer + 1) //단수지정 자동이면 - if (type === 'layout') { + if (type === MODULE_SETUP_TYPE.LAYOUT) { calcModuleWidthCount = layoutCol > calcModuleWidthCount ? calcModuleWidthCount : layoutCol calcModuleHeightCount = layoutRow } @@ -2518,7 +2588,7 @@ export function useModuleBasicSetting(tabNum) { let layoutRow = 0 let layoutCol = 0 - if (type === 'layout') { + if (type === MODULE_SETUP_TYPE.LAYOUT) { const isPassed = checkAutoLayoutModuleSetup(moduleSetupSurface, trestleDetailData) if (!isPassed) { return @@ -2528,7 +2598,7 @@ export function useModuleBasicSetting(tabNum) { for (let moduleIndex = 0; moduleIndex < checkedModule.length; moduleIndex++) { const module = checkedModule[moduleIndex] - if (type === 'layout' && checkedLayoutData) { + if (type === MODULE_SETUP_TYPE.LAYOUT && checkedLayoutData) { const layout = checkedLayoutData.find((item) => module.itemId === item.moduleId) layoutRow = layout.row layoutCol = layout.col @@ -2588,7 +2658,7 @@ export function useModuleBasicSetting(tabNum) { let calcModuleHeightCount = calcAreaHeight / (width + intvVer + 1) //단수지정 자동이면 - if (type === 'layout') { + if (type === MODULE_SETUP_TYPE.LAYOUT) { calcModuleWidthCount = layoutCol > calcModuleWidthCount ? calcModuleWidthCount : layoutCol calcModuleHeightCount = layoutRow } @@ -2778,16 +2848,14 @@ export function useModuleBasicSetting(tabNum) { /** * 자동 레이아웃일떄 설치 가능한 애들은 설치 해주고 실패한 애들은 이름, 최대 설치 가능한 높이 알려줄라고 */ - if (type === 'layout' && failAutoSetupRoof.length > 0) { + if (type === MODULE_SETUP_TYPE.LAYOUT && failAutoSetupRoof.length > 0) { const roofNamesList = failAutoSetupRoof.map((roof) => ({ roofName: roof.roofMaterial.roofMatlNmJp, moduleMaxRows: roof.trestleDetail.moduleMaxRows, })) const alertString = roofNamesList.map((item, index) => `${item.roofName}(${item.moduleMaxRows})`) - console.log('alertString', alertString) - - // swalFire({ text: alertString, icon: 'warning' }) + swalFire({ text: alertString, icon: 'warning' }) } } -- 2.47.2 From a45bba3a7bc0e2dfad959d7c5d363be81dc310ab Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Tue, 29 Apr 2025 14:26:21 +0900 Subject: [PATCH 026/109] =?UTF-8?q?=EC=98=A4=EC=B0=A8=EB=B2=94=EC=9C=84=20?= =?UTF-8?q?=EA=B3=84=EC=82=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/usePolygon.js | 82 +++++++++++++++++++++++++---------------- 1 file changed, 50 insertions(+), 32 deletions(-) diff --git a/src/hooks/usePolygon.js b/src/hooks/usePolygon.js index 9a5f39f8..43a78eae 100644 --- a/src/hooks/usePolygon.js +++ b/src/hooks/usePolygon.js @@ -822,10 +822,10 @@ export const usePolygon = () => { line.endPoint = endPoint }) - polygonLines.forEach((line) => { + /*polygonLines.forEach((line) => { line.set({ strokeWidth: 10 }) canvas.add(line) - }) + })*/ canvas.renderAll() polygonLines.forEach((line) => { @@ -1148,31 +1148,47 @@ export const usePolygon = () => { } const getSplitRoofsPoints = (startLines, allLines, innerLines, uniquePoints) => { + // ==== Utility functions ==== + + function isSamePoint(p1, p2, epsilon = 1) { + return Math.abs(p1.x - p2.x) <= epsilon && Math.abs(p1.y - p2.y) <= epsilon + } + + function normalizePoint(p, epsilon = 1) { + return { + x: Math.round(p.x / epsilon) * epsilon, + y: Math.round(p.y / epsilon) * epsilon, + } + } + + function pointToKey(p, epsilon = 1) { + const norm = normalizePoint(p, epsilon) + return `${norm.x},${norm.y}` + } + // 거리 계산 function calcDistance(p1, p2) { return Math.hypot(p2.x - p1.x, p2.y - p1.y) } - // 바로 연결 체크 - function isDirectlyConnected(start, end, graph) { - const startKey = `${start.x},${start.y}` - if (!graph[startKey]) return false - return graph[startKey].some((neighbor) => neighbor.point.x === end.x && neighbor.point.y === end.y) - } + // ==== Direct edge check ==== - // Dijkstra 최단 경로 - function findShortestPath(start, end, graph) { - const startKey = `${start.x},${start.y}` - const endKey = `${end.x},${end.y}` + function isDirectlyConnected(start, end, graph, epsilon = 1) { + const startKey = pointToKey(start, epsilon) + return (graph[startKey] || []).some((neighbor) => isSamePoint(neighbor.point, end, epsilon)) + } + // ==== Dijkstra pathfinding ==== + + function findShortestPath(start, end, graph, epsilon = 1) { + const startKey = pointToKey(start, epsilon) + const endKey = pointToKey(end, epsilon) const distances = {} const previous = {} const visited = new Set() const queue = [{ key: startKey, dist: 0 }] - for (const key in graph) { - distances[key] = Infinity - } + for (const key in graph) distances[key] = Infinity distances[startKey] = 0 while (queue.length > 0) { @@ -1181,8 +1197,8 @@ export const usePolygon = () => { if (visited.has(key)) continue visited.add(key) - for (const neighbor of graph[key]) { - const neighborKey = `${neighbor.point.x},${neighbor.point.y}` + for (const neighbor of graph[key] || []) { + const neighborKey = pointToKey(neighbor.point, epsilon) const alt = distances[key] + neighbor.distance if (alt < distances[neighborKey]) { distances[neighborKey] = alt @@ -1192,35 +1208,37 @@ export const usePolygon = () => { } } - // 경로 복구 const path = [] let currentKey = endKey - if (!previous[currentKey]) { - return null // 경로 없음 - } + if (!previous[currentKey]) return null while (currentKey !== startKey) { const [x, y] = currentKey.split(',').map(Number) path.unshift({ x, y }) currentKey = previous[currentKey] } - path.unshift(start) + + const [sx, sy] = startKey.split(',').map(Number) + path.unshift({ x: sx, y: sy }) return path } // 최종 함수 - function getPath(start, end, graph) { - if (isDirectlyConnected(start, end, graph)) { + function getPath(start, end, graph, epsilon = 1) { + if (isDirectlyConnected(start, end, graph, epsilon)) { + console.log('직선 연결 있음. 무시.') return null - } else { - const path = findShortestPath(start, end, graph) - if (!path || path.length < 3) { - return null - } - return path } + + const path = findShortestPath(start, end, graph, epsilon) + if (!path || path.length < 3) { + console.log('경로 존재하나 3개 미만 좌표. 무시.') + return null + } + + return path } const roofs = [] @@ -1235,8 +1253,8 @@ export const usePolygon = () => { }) for (const [p1, p2] of edges) { - const key1 = `${p1.x},${p1.y}` - const key2 = `${p2.x},${p2.y}` + const key1 = pointToKey(p1) + const key2 = pointToKey(p2) const distance = calcDistance(p1, p2) if (!graph[key1]) graph[key1] = [] -- 2.47.2 From 9eda4aa834cf597cc2d51ce6ba1de66476ef5f75 Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Tue, 29 Apr 2025 17:48:38 +0900 Subject: [PATCH 027/109] =?UTF-8?q?=EC=A0=9C=EB=8C=80=EB=A1=9C=20=EC=95=88?= =?UTF-8?q?=EB=82=98=EB=88=A0=EC=A7=80=EB=8A=94=20=ED=98=84=EC=83=81=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/usePolygon.js | 9 +++++++-- src/util/canvas-util.js | 15 ++++++++++----- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/hooks/usePolygon.js b/src/hooks/usePolygon.js index 43a78eae..93659a08 100644 --- a/src/hooks/usePolygon.js +++ b/src/hooks/usePolygon.js @@ -754,7 +754,7 @@ export const usePolygon = () => { } const splitPolygonWithLines = (polygon) => { - polygon.set({ visible: false }) + // polygon.set({ visible: false }) let innerLines = [...polygon.innerLines] /*// innerLine이 세팅이 안되어있는경우 찾아서 세팅한다. @@ -934,8 +934,9 @@ export const usePolygon = () => { }) //polygonLines에서 divideLines를 제거하고 newLines를 추가한다. + newLines = newLines.filter((line) => !(Math.abs(line.startPoint.x - line.endPoint.x) < 1 && Math.abs(line.startPoint.y - line.endPoint.y) < 1)) polygonLines = polygonLines.filter((line) => line.intersections?.length === 0) - + const originPolygonLines = [...polygonLines] polygonLines = [...polygonLines, ...newLines] let allLines = [...polygonLines, ...innerLines] @@ -1045,6 +1046,10 @@ export const usePolygon = () => { }) }) + if (startLines.length === 0) { + startLines = originPolygonLines + } + // 나눠서 중복 제거된 roof return const newRoofs = getSplitRoofsPoints(startLines, allLines, innerLines, uniquePoints) diff --git a/src/util/canvas-util.js b/src/util/canvas-util.js index 1146afa7..9eb6e0e5 100644 --- a/src/util/canvas-util.js +++ b/src/util/canvas-util.js @@ -515,26 +515,31 @@ export const sortedPointLessEightPoint = (points) => { * @param line * @param point * @returns {boolean} + * @param epsilon */ // 직선의 방정식. // 방정식은 ax + by + c = 0이며, 점의 좌표를 대입하여 계산된 값은 직선과 점 사이의 관계를 나타낸다. -export function isPointOnLine({ x1, y1, x2, y2 }, { x, y }) { - /*const a = line.y2 - line.y1 +export function isPointOnLine({ x1, y1, x2, y2 }, { x, y }, epsilon = 2) { + const a = y2 - y1 + const b = x1 - x2 + const c = x2 * y1 - x1 * y2 + return Math.abs(a * x + b * y + c) < 1000 + /*/!*const a = line.y2 - line.y1 const b = line.x1 - line.x2 const c = line.x2 * line.y1 - line.x1 * line.y2 const result = Math.abs(a * point.x + b * point.y + c) / 100 // 점이 선 위에 있는지 확인 - return result <= 10*/ + return result <= 10*!/ // 직선 방정식 만족 여부 확인 const crossProduct = (y - y1) * (x2 - x1) - (x - x1) * (y2 - y1) - if (Math.abs(crossProduct) > 5) return false // 작은 오차 허용 + if (Math.abs(crossProduct) > 10) return false // 작은 오차 허용 // 점이 선분의 범위 내에 있는지 확인 const withinXRange = Math.min(x1, x2) <= x && x <= Math.max(x1, x2) const withinYRange = Math.min(y1, y2) <= y && y <= Math.max(y1, y2) - return withinXRange && withinYRange + return withinXRange && withinYRange*/ } /** * 점과 가까운 line 찾기 -- 2.47.2 From 9f88880ac2a93c25edff47632a9f070e070ec29d Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Tue, 29 Apr 2025 17:50:36 +0900 Subject: [PATCH 028/109] =?UTF-8?q?=EC=A3=BC=EC=84=9D=20=EB=82=B4=EC=9A=A9?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/usePolygon.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks/usePolygon.js b/src/hooks/usePolygon.js index 93659a08..8fc150e7 100644 --- a/src/hooks/usePolygon.js +++ b/src/hooks/usePolygon.js @@ -754,7 +754,7 @@ export const usePolygon = () => { } const splitPolygonWithLines = (polygon) => { - // polygon.set({ visible: false }) + polygon.set({ visible: false }) let innerLines = [...polygon.innerLines] /*// innerLine이 세팅이 안되어있는경우 찾아서 세팅한다. -- 2.47.2 From 8412462a8b7ef229fa834e3780573477e4882040 Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Wed, 30 Apr 2025 13:08:58 +0900 Subject: [PATCH 029/109] =?UTF-8?q?=EB=9D=BC=EC=9D=B8=20=EC=9C=84=EC=97=90?= =?UTF-8?q?=20=EC=9E=88=EB=8A=94=EC=A7=80=20=ED=99=95=EC=9D=B8=20=ED=95=A8?= =?UTF-8?q?=EC=88=98=20=EC=88=98=EC=A0=95=20=EC=A7=80=EB=B6=95=EC=9E=AC=20?= =?UTF-8?q?=ED=95=A0=EB=8B=B9=20=EC=8B=9C=20=EC=98=A4=EB=A5=98=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/common/useRoofFn.js | 250 +++++++++++++++++----------------- src/hooks/usePolygon.js | 4 +- src/util/canvas-util.js | 8 +- 3 files changed, 133 insertions(+), 129 deletions(-) diff --git a/src/hooks/common/useRoofFn.js b/src/hooks/common/useRoofFn.js index c49128e7..38246273 100644 --- a/src/hooks/common/useRoofFn.js +++ b/src/hooks/common/useRoofFn.js @@ -23,102 +23,65 @@ export function useRoofFn() { //면형상 선택 클릭시 지붕 패턴 입히기 function setSurfaceShapePattern(polygon, mode = 'onlyBorder', trestleMode = false, roofMaterial, isForceChange = false, isDisplay = false) { - if (!polygon) { - return - } - if (polygon.points.length < 3) { - return - } - if (isForceChange && !isDisplay) { - /*if (polygon.roofMaterial) { + try { + if (!polygon) { + return + } + if (polygon.points.length < 3) { + return + } + if (isForceChange && !isDisplay) { + /*if (polygon.roofMaterial) { polygon.roofMaterial = null }*/ - } - if (!roofMaterial) { - roofMaterial = polygon.roofMaterial ?? selectedRoofMaterial - } - - const ratio = window.devicePixelRatio || 1 - const layout = roofMaterial.layout - - let width = (roofMaterial.width || 226) / 10 - let height = (roofMaterial.length || 158) / 10 - const index = roofMaterial.index ?? 0 - let roofStyle = 2 - const inputPatternSize = { width: width, height: height } //임시 사이즈 - const patternSize = { ...inputPatternSize } // 입력된 값을 뒤집기 위해 - - if (polygon.direction === 'east' || polygon.direction === 'west') { - //세로형이면 width height를 바꿈 - ;[patternSize.width, patternSize.height] = [inputPatternSize.height, patternSize.width] - } - - // 패턴 소스를 위한 임시 캔버스 생성 - const patternSourceCanvas = document.createElement('canvas') - patternSourceCanvas.width = polygon.width * ratio - patternSourceCanvas.height = polygon.height * ratio - const ctx = patternSourceCanvas.getContext('2d') - let offset = roofStyle === 1 ? 0 : patternSize.width / 2 - - const rows = Math.floor(patternSourceCanvas.height / patternSize.height) - const cols = Math.floor(patternSourceCanvas.width / patternSize.width) - - ctx.strokeStyle = mode === 'allPainted' ? 'black' : ROOF_COLOR[index] - ctx.lineWidth = 2 - ctx.fillStyle = mode === 'allPainted' ? 'rgba(0, 159, 64, 0.7)' : 'white' - - if (trestleMode) { - ctx.strokeStyle = 'black' - ctx.lineWidth = 0.2 - ctx.fillStyle = 'rgba(0, 0, 0, 0.1)' - } else { - ctx.fillStyle = 'rgba(255, 255, 255, 1)' - } - - if (polygon.direction === 'east' || polygon.direction === 'west') { - offset = roofStyle === 1 ? 0 : patternSize.height / 2 - for (let col = 0; col <= cols; col++) { - const x = col * patternSize.width - const yStart = 0 - const yEnd = patternSourceCanvas.height - ctx.beginPath() - ctx.moveTo(x, yStart) // 선 시작점 - ctx.lineTo(x, yEnd) // 선 끝점 - ctx.stroke() - if (mode === 'allPainted' || trestleMode) { - ctx.fillRect(x, yStart, patternSize.width, yEnd - yStart) - } - - for (let row = 0; row <= rows; row++) { - const y = layout === ROOF_MATERIAL_LAYOUT.STAIRS ? row * patternSize.height + (col % 2 === 0 ? 0 : offset) : row * patternSize.height - const xStart = col * patternSize.width - const xEnd = xStart + patternSize.width - ctx.beginPath() - ctx.moveTo(xStart, y) // 선 시작점 - ctx.lineTo(xEnd, y) // 선 끝점 - ctx.stroke() - if (mode === 'allPainted' || trestleMode) { - ctx.fillRect(xStart, y, xEnd - xStart, patternSize.height) - } - } } - } else { - for (let row = 0; row <= rows; row++) { - const y = row * patternSize.height + if (!roofMaterial) { + roofMaterial = polygon.roofMaterial ?? selectedRoofMaterial + } - ctx.beginPath() - ctx.moveTo(0, y) // 선 시작점 - ctx.lineTo(patternSourceCanvas.width, y) // 선 끝점 - ctx.stroke() - if (mode === 'allPainted' || trestleMode) { - ctx.fillRect(0, y, patternSourceCanvas.width, patternSize.height) - } + const ratio = window.devicePixelRatio || 1 + const layout = roofMaterial.layout + let width = (roofMaterial.width || 226) / 10 + let height = (roofMaterial.length || 158) / 10 + const index = roofMaterial.index ?? 0 + let roofStyle = 2 + const inputPatternSize = { width: width, height: height } //임시 사이즈 + const patternSize = { ...inputPatternSize } // 입력된 값을 뒤집기 위해 + + if (polygon.direction === 'east' || polygon.direction === 'west') { + //세로형이면 width height를 바꿈 + ;[patternSize.width, patternSize.height] = [inputPatternSize.height, patternSize.width] + } + + // 패턴 소스를 위한 임시 캔버스 생성 + const patternSourceCanvas = document.createElement('canvas') + patternSourceCanvas.width = polygon.width * ratio + patternSourceCanvas.height = polygon.height * ratio + const ctx = patternSourceCanvas.getContext('2d') + let offset = roofStyle === 1 ? 0 : patternSize.width / 2 + + const rows = Math.floor(patternSourceCanvas.height / patternSize.height) + const cols = Math.floor(patternSourceCanvas.width / patternSize.width) + + ctx.strokeStyle = mode === 'allPainted' ? 'black' : ROOF_COLOR[index] + ctx.lineWidth = 2 + ctx.fillStyle = mode === 'allPainted' ? 'rgba(0, 159, 64, 0.7)' : 'white' + + if (trestleMode) { + ctx.strokeStyle = 'black' + ctx.lineWidth = 0.2 + ctx.fillStyle = 'rgba(0, 0, 0, 0.1)' + } else { + ctx.fillStyle = 'rgba(255, 255, 255, 1)' + } + + if (polygon.direction === 'east' || polygon.direction === 'west') { + offset = roofStyle === 1 ? 0 : patternSize.height / 2 for (let col = 0; col <= cols; col++) { - const x = layout === ROOF_MATERIAL_LAYOUT.STAIRS ? col * patternSize.width + (row % 2 === 0 ? 0 : offset) : col * patternSize.width - const yStart = row * patternSize.height - const yEnd = yStart + patternSize.height - + const x = col * patternSize.width + const yStart = 0 + const yEnd = patternSourceCanvas.height ctx.beginPath() ctx.moveTo(x, yStart) // 선 시작점 ctx.lineTo(x, yEnd) // 선 끝점 @@ -126,49 +89,90 @@ export function useRoofFn() { if (mode === 'allPainted' || trestleMode) { ctx.fillRect(x, yStart, patternSize.width, yEnd - yStart) } + + for (let row = 0; row <= rows; row++) { + const y = layout === ROOF_MATERIAL_LAYOUT.STAIRS ? row * patternSize.height + (col % 2 === 0 ? 0 : offset) : row * patternSize.height + const xStart = col * patternSize.width + const xEnd = xStart + patternSize.width + ctx.beginPath() + ctx.moveTo(xStart, y) // 선 시작점 + ctx.lineTo(xEnd, y) // 선 끝점 + ctx.stroke() + if (mode === 'allPainted' || trestleMode) { + ctx.fillRect(xStart, y, xEnd - xStart, patternSize.height) + } + } + } + } else { + for (let row = 0; row <= rows; row++) { + const y = row * patternSize.height + + ctx.beginPath() + ctx.moveTo(0, y) // 선 시작점 + ctx.lineTo(patternSourceCanvas.width, y) // 선 끝점 + ctx.stroke() + if (mode === 'allPainted' || trestleMode) { + ctx.fillRect(0, y, patternSourceCanvas.width, patternSize.height) + } + + for (let col = 0; col <= cols; col++) { + const x = layout === ROOF_MATERIAL_LAYOUT.STAIRS ? col * patternSize.width + (row % 2 === 0 ? 0 : offset) : col * patternSize.width + const yStart = row * patternSize.height + const yEnd = yStart + patternSize.height + + ctx.beginPath() + ctx.moveTo(x, yStart) // 선 시작점 + ctx.lineTo(x, yEnd) // 선 끝점 + ctx.stroke() + if (mode === 'allPainted' || trestleMode) { + ctx.fillRect(x, yStart, patternSize.width, yEnd - yStart) + } + } } } - } - const hachingPatternSourceCanvas = document.createElement('canvas') + const hachingPatternSourceCanvas = document.createElement('canvas') - if (mode === 'lineHatch') { - hachingPatternSourceCanvas.width = polygon.width * ratio - hachingPatternSourceCanvas.height = polygon.height * ratio + if (mode === 'lineHatch') { + hachingPatternSourceCanvas.width = polygon.width * ratio + hachingPatternSourceCanvas.height = polygon.height * ratio - const ctx1 = hachingPatternSourceCanvas.getContext('2d') + const ctx1 = hachingPatternSourceCanvas.getContext('2d') - const gap = 10 + const gap = 10 - ctx1.strokeStyle = 'green' // 선 색상 - ctx1.lineWidth = 0.3 // 선 두께 + ctx1.strokeStyle = 'green' // 선 색상 + ctx1.lineWidth = 0.3 // 선 두께 - for (let x = 0; x < hachingPatternSourceCanvas.width + hachingPatternSourceCanvas.height; x += gap) { - ctx1.beginPath() - ctx1.moveTo(x, 0) // 선 시작점 - ctx1.lineTo(0, x) // 선 끝점 - ctx1.stroke() + for (let x = 0; x < hachingPatternSourceCanvas.width + hachingPatternSourceCanvas.height; x += gap) { + ctx1.beginPath() + ctx1.moveTo(x, 0) // 선 시작점 + ctx1.lineTo(0, x) // 선 끝점 + ctx1.stroke() + } } + + const combinedPatternCanvas = document.createElement('canvas') + combinedPatternCanvas.width = polygon.width * ratio + combinedPatternCanvas.height = polygon.height * ratio + const combinedCtx = combinedPatternCanvas.getContext('2d') + // 첫 번째 패턴을 그린 후 두 번째 패턴을 덧입힘 + combinedCtx.drawImage(patternSourceCanvas, 0, 0) + combinedCtx.drawImage(hachingPatternSourceCanvas, 0, 0) + + // 패턴 생성 + const pattern = new fabric.Pattern({ + source: combinedPatternCanvas, + repeat: 'repeat', + }) + + polygon.set('fill', null) + polygon.set('fill', pattern) + polygon.roofMaterial = roofMaterial + polygon.canvas?.renderAll() + } catch (e) { + console.log(e) } - - const combinedPatternCanvas = document.createElement('canvas') - combinedPatternCanvas.width = polygon.width * ratio - combinedPatternCanvas.height = polygon.height * ratio - const combinedCtx = combinedPatternCanvas.getContext('2d') - // 첫 번째 패턴을 그린 후 두 번째 패턴을 덧입힘 - combinedCtx.drawImage(patternSourceCanvas, 0, 0) - combinedCtx.drawImage(hachingPatternSourceCanvas, 0, 0) - - // 패턴 생성 - const pattern = new fabric.Pattern({ - source: combinedPatternCanvas, - repeat: 'repeat', - }) - - polygon.set('fill', null) - polygon.set('fill', pattern) - polygon.roofMaterial = roofMaterial - polygon.canvas?.renderAll() } function removeRoofMaterial(roof = currentObject) { diff --git a/src/hooks/usePolygon.js b/src/hooks/usePolygon.js index 8fc150e7..49472b08 100644 --- a/src/hooks/usePolygon.js +++ b/src/hooks/usePolygon.js @@ -1234,13 +1234,13 @@ export const usePolygon = () => { function getPath(start, end, graph, epsilon = 1) { if (isDirectlyConnected(start, end, graph, epsilon)) { console.log('직선 연결 있음. 무시.') - return null + return [] } const path = findShortestPath(start, end, graph, epsilon) if (!path || path.length < 3) { console.log('경로 존재하나 3개 미만 좌표. 무시.') - return null + return [] } return path diff --git a/src/util/canvas-util.js b/src/util/canvas-util.js index 9eb6e0e5..74c5c2a4 100644 --- a/src/util/canvas-util.js +++ b/src/util/canvas-util.js @@ -520,17 +520,17 @@ export const sortedPointLessEightPoint = (points) => { // 직선의 방정식. // 방정식은 ax + by + c = 0이며, 점의 좌표를 대입하여 계산된 값은 직선과 점 사이의 관계를 나타낸다. export function isPointOnLine({ x1, y1, x2, y2 }, { x, y }, epsilon = 2) { - const a = y2 - y1 + /*const a = y2 - y1 const b = x1 - x2 const c = x2 * y1 - x1 * y2 - return Math.abs(a * x + b * y + c) < 1000 + return Math.abs(a * x + b * y + c) < 1000*/ /*/!*const a = line.y2 - line.y1 const b = line.x1 - line.x2 const c = line.x2 * line.y1 - line.x1 * line.y2 const result = Math.abs(a * point.x + b * point.y + c) / 100 // 점이 선 위에 있는지 확인 - return result <= 10*!/ + return result <= 10*!*/ // 직선 방정식 만족 여부 확인 const crossProduct = (y - y1) * (x2 - x1) - (x - x1) * (y2 - y1) if (Math.abs(crossProduct) > 10) return false // 작은 오차 허용 @@ -539,7 +539,7 @@ export function isPointOnLine({ x1, y1, x2, y2 }, { x, y }, epsilon = 2) { const withinXRange = Math.min(x1, x2) <= x && x <= Math.max(x1, x2) const withinYRange = Math.min(y1, y2) <= y && y <= Math.max(y1, y2) - return withinXRange && withinYRange*/ + return withinXRange && withinYRange } /** * 점과 가까운 line 찾기 -- 2.47.2 From adc0c41a672ca990d3c30e3cbbe3abb25f7566b4 Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Wed, 30 Apr 2025 13:18:48 +0900 Subject: [PATCH 030/109] =?UTF-8?q?=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useLine.js | 8 ++++---- src/hooks/usePolygon.js | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/hooks/useLine.js b/src/hooks/useLine.js index b447d4f0..bdbd5d6f 100644 --- a/src/hooks/useLine.js +++ b/src/hooks/useLine.js @@ -38,12 +38,12 @@ export const useLine = () => { line.set({ visible: false, }) - canvas - ?.getObjects() - .find((obj) => obj.parentId === line.id) - .set({ + const obj = canvas?.getObjects().find((obj) => obj.parentId === line.id) + if (obj) { + obj.set({ visible: false, }) + } canvas?.renderAll() } diff --git a/src/hooks/usePolygon.js b/src/hooks/usePolygon.js index 49472b08..89f1b40b 100644 --- a/src/hooks/usePolygon.js +++ b/src/hooks/usePolygon.js @@ -1110,7 +1110,7 @@ export const usePolygon = () => { defense = 'north' break } - pitch = polygon.lines[index].attributes?.pitch ?? 0 + pitch = polygon.lines[index]?.attributes?.pitch ?? 0 const roof = new QPolygon(roofPoint, { fontSize: polygon.fontSize, -- 2.47.2 From 937f2eb3feadaa95ad1742ddb84c9db352ed61c6 Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Wed, 30 Apr 2025 13:26:05 +0900 Subject: [PATCH 031/109] =?UTF-8?q?x=EA=B0=80=20=EC=A0=84=EB=B6=80=20?= =?UTF-8?q?=EA=B0=99=EA=B1=B0=EB=82=98,=20y=EA=B0=80=20=EC=A0=84=EB=B6=80?= =?UTF-8?q?=20=EA=B0=99=EC=9D=80=EA=B2=BD=EC=9A=B0=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/util/qpolygon-utils.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/util/qpolygon-utils.js b/src/util/qpolygon-utils.js index 9fbfc6a3..131e9232 100644 --- a/src/util/qpolygon-utils.js +++ b/src/util/qpolygon-utils.js @@ -303,7 +303,13 @@ export function removeDuplicatePolygons(polygons) { } }) - return uniquePolygons + // x가 전부 같거나, y가 전부 같은 경우 제거 + return uniquePolygons.filter((polygon) => { + const xValues = polygon.map((point) => point.x) + const yValues = polygon.map((point) => point.y) + + return !(xValues.every((x) => x === xValues[0]) || yValues.every((y) => y === yValues[0])) + }) } export const isSamePoint = (a, b) => { -- 2.47.2 From 902d13b2739e2a757fe50a0de58d46b4ae3f362a Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Wed, 30 Apr 2025 16:17:03 +0900 Subject: [PATCH 032/109] =?UTF-8?q?=EC=98=A4=EC=B0=A8=20=EB=B2=94=EC=9C=84?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/util/canvas-util.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util/canvas-util.js b/src/util/canvas-util.js index 74c5c2a4..6eef894e 100644 --- a/src/util/canvas-util.js +++ b/src/util/canvas-util.js @@ -533,7 +533,7 @@ export function isPointOnLine({ x1, y1, x2, y2 }, { x, y }, epsilon = 2) { return result <= 10*!*/ // 직선 방정식 만족 여부 확인 const crossProduct = (y - y1) * (x2 - x1) - (x - x1) * (y2 - y1) - if (Math.abs(crossProduct) > 10) return false // 작은 오차 허용 + if (Math.abs(crossProduct) > 500) return false // 작은 오차 허용 // 점이 선분의 범위 내에 있는지 확인 const withinXRange = Math.min(x1, x2) <= x && x <= Math.max(x1, x2) -- 2.47.2 From a7eb2f7598212932adca274bf433d193ae9439e1 Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Wed, 30 Apr 2025 18:05:25 +0900 Subject: [PATCH 033/109] =?UTF-8?q?=EC=A7=80=EB=B6=95=EC=9E=AC=20=ED=95=A0?= =?UTF-8?q?=EB=8B=B9=20=ED=95=A8=EC=88=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/usePolygon.js | 70 ++++++++++++++++++++++++----------------- 1 file changed, 41 insertions(+), 29 deletions(-) diff --git a/src/hooks/usePolygon.js b/src/hooks/usePolygon.js index 89f1b40b..a2c8c9aa 100644 --- a/src/hooks/usePolygon.js +++ b/src/hooks/usePolygon.js @@ -1,7 +1,14 @@ import { ANGLE_TYPE, canvasState, currentAngleTypeSelector, pitchTextSelector } from '@/store/canvasAtom' import { useRecoilValue } from 'recoil' import { fabric } from 'fabric' -import { findAndRemoveClosestPoint, getDegreeByChon, getDegreeInOrientation, isPointOnLine, toFixedWithoutRounding } from '@/util/canvas-util' +import { + distanceBetweenPoints, + findAndRemoveClosestPoint, + getDegreeByChon, + getDegreeInOrientation, + isPointOnLine, + toFixedWithoutRounding, +} from '@/util/canvas-util' import { QPolygon } from '@/components/fabric/QPolygon' import { isSamePoint, removeDuplicatePolygons } from '@/util/qpolygon-utils' import { flowDisplaySelector } from '@/store/settingAtom' @@ -755,7 +762,7 @@ export const usePolygon = () => { const splitPolygonWithLines = (polygon) => { polygon.set({ visible: false }) - let innerLines = [...polygon.innerLines] + let innerLines = [...polygon.innerLines].filter((line) => line.visible) /*// innerLine이 세팅이 안되어있는경우 찾아서 세팅한다. if (!innerLines || innerLines.length === 0) { @@ -825,8 +832,8 @@ export const usePolygon = () => { /*polygonLines.forEach((line) => { line.set({ strokeWidth: 10 }) canvas.add(line) - })*/ - canvas.renderAll() + }) + canvas.renderAll()*/ polygonLines.forEach((line) => { /*const originStroke = line.stroke @@ -838,12 +845,14 @@ export const usePolygon = () => { innerLine.set({ stroke: 'red' }) canvas.renderAll()*/ if (isPointOnLine(line, innerLine.startPoint)) { + canvas.renderAll() if (isSamePoint(line.startPoint, innerLine.startPoint) || isSamePoint(line.endPoint, innerLine.startPoint)) { return } intersections.push(innerLine.startPoint) } if (isPointOnLine(line, innerLine.endPoint)) { + canvas.renderAll() if (isSamePoint(line.startPoint, innerLine.endPoint) || isSamePoint(line.endPoint, innerLine.endPoint)) { return } @@ -852,13 +861,14 @@ export const usePolygon = () => { /*innerLine.set({ stroke: originInnerStroke }) canvas.renderAll()*/ }) - line.set({ intersections }) + /*line.set({ intersections }) - /*line.set({ stroke: originStroke }) + line.set({ stroke: originStroke }) canvas.renderAll()*/ }) - const divideLines = polygonLines.filter((line) => line.intersections.length > 0) + const divideLines = polygonLines.filter((line) => line.intersections?.length > 0) + let newLines = [] divideLines.forEach((line) => { @@ -873,12 +883,14 @@ export const usePolygon = () => { strokeWidth: 3, fontSize: polygon.fontSize, attributes: line.attributes, + name: 'newLine', }) const newLine2 = new QLine(newLinePoint2, { stroke: 'blue', strokeWidth: 3, fontSize: polygon.fontSize, attributes: line.attributes, + name: 'newLine', }) newLine1.attributes = { ...line.attributes, @@ -907,6 +919,7 @@ export const usePolygon = () => { strokeWidth: 3, fontSize: polygon.fontSize, attributes: line.attributes, + name: 'newLine', }) newLine.attributes = { ...line.attributes, @@ -923,6 +936,7 @@ export const usePolygon = () => { strokeWidth: 3, fontSize: polygon.fontSize, attributes: line.attributes, + name: 'newLine', }) newLine.attributes = { ...line.attributes, @@ -936,7 +950,7 @@ export const usePolygon = () => { //polygonLines에서 divideLines를 제거하고 newLines를 추가한다. newLines = newLines.filter((line) => !(Math.abs(line.startPoint.x - line.endPoint.x) < 1 && Math.abs(line.startPoint.y - line.endPoint.y) < 1)) polygonLines = polygonLines.filter((line) => line.intersections?.length === 0) - const originPolygonLines = [...polygonLines] + polygonLines = [...polygonLines, ...newLines] let allLines = [...polygonLines, ...innerLines] @@ -1005,6 +1019,13 @@ export const usePolygon = () => { } }) + /*innerLines.forEach((line) => { + const startPoint = line.startPoint + const endPoint = line.endPoint + /!*canvas.add(new fabric.Circle({ left: startPoint.x, top: startPoint.y + 10, radius: 5, fill: 'red' })) + canvas.add(new fabric.Circle({ left: endPoint.x, top: endPoint.y - 10, radius: 5, fill: 'blue' }))*!/ + })*/ + /** * 왼쪽 상단을 startPoint로 전부 변경 */ @@ -1031,27 +1052,18 @@ export const usePolygon = () => { line.endPoint = endPoint }) - // polygonLines에서 시작점 혹은 끝점이 innerLines와 연결된 line만 가져온다. - let startLines = polygonLines.filter((line) => { - const startPoint = line.startPoint - const endPoint = line.endPoint - - return innerLines.some((innerLine) => { - return ( - isSamePoint(innerLine.startPoint, startPoint) || - isSamePoint(innerLine.endPoint, startPoint) || - isSamePoint(innerLine.startPoint, endPoint) || - isSamePoint(innerLine.endPoint, endPoint) - ) - }) + //allLines에서 중복을 제거한다. + allLines = allLines.filter((line, index, self) => { + return ( + index === + self.findIndex((l) => { + return isSamePoint(l.startPoint, line.startPoint) && isSamePoint(l.endPoint, line.endPoint) + }) + ) }) - if (startLines.length === 0) { - startLines = originPolygonLines - } - // 나눠서 중복 제거된 roof return - const newRoofs = getSplitRoofsPoints(startLines, allLines, innerLines, uniquePoints) + const newRoofs = getSplitRoofsPoints(allLines) newRoofs.forEach((roofPoint, index) => { let defense, pitch @@ -1152,11 +1164,11 @@ export const usePolygon = () => { }) } - const getSplitRoofsPoints = (startLines, allLines, innerLines, uniquePoints) => { + const getSplitRoofsPoints = (allLines) => { // ==== Utility functions ==== function isSamePoint(p1, p2, epsilon = 1) { - return Math.abs(p1.x - p2.x) <= epsilon && Math.abs(p1.y - p2.y) <= epsilon + return Math.abs(p1.x - p2.x) <= 2 && Math.abs(p1.y - p2.y) <= 2 } function normalizePoint(p, epsilon = 1) { @@ -1248,7 +1260,7 @@ export const usePolygon = () => { const roofs = [] - startLines.forEach((line) => { + allLines.forEach((line) => { // 그래프 생성 const graph = {} const edges = allLines -- 2.47.2 From d5bba49ca975bd5c694f4ed1e1a81b9b5ae9250e Mon Sep 17 00:00:00 2001 From: yjnoh Date: Fri, 2 May 2025 09:23:59 +0900 Subject: [PATCH 034/109] =?UTF-8?q?console.log=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/module/useModuleBasicSetting.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/hooks/module/useModuleBasicSetting.js b/src/hooks/module/useModuleBasicSetting.js index 1ce7298f..ffff4344 100644 --- a/src/hooks/module/useModuleBasicSetting.js +++ b/src/hooks/module/useModuleBasicSetting.js @@ -1772,8 +1772,6 @@ export function useModuleBasicSetting(tabNum) { const autoModuleSetup = (type, layoutSetupRef) => { initEvent() //마우스 이벤트 초기화 - console.log('checkedModule', checkedModule) - //실패한 지붕재 배열 let failAutoSetupRoof = [] -- 2.47.2 From 08722e8b51e3392b1f1960445f7f8ef6f0f1bf0e Mon Sep 17 00:00:00 2001 From: yjnoh Date: Fri, 2 May 2025 15:50:27 +0900 Subject: [PATCH 035/109] =?UTF-8?q?=EB=AA=A8=EB=93=88=20=EB=A0=88=EC=9D=B4?= =?UTF-8?q?=EC=95=84=EC=9B=83=20=EC=84=A4=EC=B9=98=20=EC=9E=91=EC=97=85?= =?UTF-8?q?=EC=A4=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/module/useModuleBasicSetting.js | 79 +++++++++++++++++------ 1 file changed, 58 insertions(+), 21 deletions(-) diff --git a/src/hooks/module/useModuleBasicSetting.js b/src/hooks/module/useModuleBasicSetting.js index ffff4344..785c6b0f 100644 --- a/src/hooks/module/useModuleBasicSetting.js +++ b/src/hooks/module/useModuleBasicSetting.js @@ -1822,20 +1822,35 @@ export function useModuleBasicSetting(tabNum) { } //어짜피 자동으로 누르면 선택안된데도 다 날아간다 - canvas.getObjects().forEach((obj) => { - if (obj.name === POLYGON_TYPE.MODULE) { - canvas.remove(obj) - } - }) + // canvas.getObjects().forEach((obj) => { + // if (obj.name === POLYGON_TYPE.MODULE) { + // canvas.remove(obj) + // } + // }) - notSelectedTrestlePolygons.forEach((obj) => { - if (obj.modules) { - obj.modules.forEach((module) => { - canvas?.remove(module) - }) - obj.modules = [] - } - }) + //자동일때만 선택 안된 모듈 삭제 + if (type === MODULE_SETUP_TYPE.AUTO) { + notSelectedTrestlePolygons.forEach((obj) => { + if (obj.modules) { + obj.modules.forEach((module) => { + canvas?.remove(module) + canvas?.renderAll() + }) + obj.modules = [] + } + }) + } else { + //레이아웃일때는 기존에 깔린 모듈은 삭제 하지 않음 선택된 영역만 모듈 삭제 + moduleSetupSurfaces.forEach((obj) => { + if (obj.modules) { + obj.modules.forEach((module) => { + canvas?.remove(module) + canvas?.renderAll() + }) + obj.modules = [] + } + }) + } let moduleOptions = { stroke: 'black', @@ -1936,23 +1951,25 @@ export function useModuleBasicSetting(tabNum) { } else { const normalModule = checkedModule.filter((item) => item.northModuleYn === 'N') const northModule = checkedModule.filter((item) => item.northModuleYn === 'Y') + const northModuleIds = northModule.map((item) => item.itemId) //만약 북면 모듈이 2개면 이 하위 로직 가져다가 쓰면됨 northModule === 만 바꾸면 될듯 // northModule을 배열로 만들고 include로 해서 체크 해야됨 if (normalModule.length > 0 && !moduleSetupSurface.isNorth) { + //C1C2 모듈일 경우ㅁㅁ const isMultipleModules = normalModule.length > 1 //모듈이 여러개면 const maxCol = trestleDetailData.moduleMaxCols //최대 열수 -> 얘는 멀티랑 관계없음 const maxRow = isMultipleModules ? trestleDetailData.moduleMaxRows : trestleDetailData.module.find((item) => item.moduleTpCd === normalModule[0].moduleTpCd).moduleMaxRows //멀티모듈이면 밖에 maxRows로 판단 아니면 module->itemmList를 가지고 판단 - //단수 합단수 + //북면 모듈 id를 제외한 모듈의 단 체크 const sumRowCount = isMultipleModules - ? layoutSetupRef.filter((item) => item.checked && item.moduleId !== northModule[0].itemId).reduce((acc, cur) => acc + cur.row, 0) + ? layoutSetupRef.filter((item) => item.checked && !northModuleIds.includes(item.moduleId)).reduce((acc, cur) => acc + cur.row, 0) : layoutSetupRef.find((item) => item.moduleId === normalModule[0].itemId).row //멀티모듈이면 전체 합, 체크된 한개의 열 - // - const sumColCount = layoutSetupRef.filter((item) => item.col && item.moduleId !== northModule[0].itemId).some((item) => item.col > maxCol) + //북면 모듈 id를 제외한 모듈의 열 체크 + const sumColCount = layoutSetupRef.filter((item) => item.col && !northModuleIds.includes(item.moduleId)).some((item) => item.col > maxCol) if (sumRowCount > maxRow || sumColCount) { failAutoSetupRoof.push(moduleSetupSurface) @@ -1964,7 +1981,7 @@ export function useModuleBasicSetting(tabNum) { isMultipleModules && layoutSetupRef.find( (item, index) => - item.checked && item.moduleId !== northModule[0].itemId && item.row > trestleDetailData.module[index].mixModuleMaxRows, + item.checked && !item.moduleId.includes(northModuleIds) && item.row > trestleDetailData.module[index].mixModuleMaxRows, ) if (isPassedObject) { @@ -1974,16 +1991,36 @@ export function useModuleBasicSetting(tabNum) { } if (northModule.length > 0 && moduleSetupSurface.isNorth) { + const isMultipleModules = northModule.length > 1 //모듈이 여러개면 const maxCol = trestleDetailData.moduleMaxCols //최대 열수 -> 얘는 멀티랑 관계없음 - const maxRow = trestleDetailData.module.find((item) => item.moduleTpCd === checkedModule[0].moduleTpCd).moduleMaxRows //멀티모듈이면 밖에 maxRows로 판단 아니면 module->itemmList를 가지고 판단 - const sumRowCount = layoutSetupRef.find((item) => item.moduleId === northModule[0].itemId).row - const sumColCount = layoutSetupRef.filter((item) => item.col && item.moduleId !== northModule[0].itemId).some((item) => item.col > maxCol) + const maxRow = isMultipleModules + ? trestleDetailData.moduleMaxRows + : trestleDetailData.module.find((item) => item.moduleTpCd === northModule[0].moduleTpCd).moduleMaxRows //멀티모듈이면 밖에 maxRows로 판단 아니면 module->itemmList를 가지고 판단 + + const sumRowCount = isMultipleModules + ? layoutSetupRef.filter((item) => northModuleIds.includes(item.moduleId)).reduce((acc, cur) => acc + cur.row, 0) + : layoutSetupRef.find((item) => item.moduleId === northModule[0].itemId).row //멀티모듈이면 전체 합, 체크된 한개의 열 + + const sumColCount = layoutSetupRef.filter((item) => item.col && northModuleIds.includes(item.moduleId)).some((item) => item.col > maxCol) if (sumRowCount > maxRow || sumColCount) { failAutoSetupRoof.push(moduleSetupSurface) return false } + + // 혼합일때 모듈 개별의 row를 체크함 + const isPassedObject = + isMultipleModules && + layoutSetupRef.find( + (item, index) => + item.checked && northModuleIds.includes(item.moduleId) && item.row > trestleDetailData.module[index].mixModuleMaxRows, + ) + + if (isPassedObject) { + failAutoSetupRoof.push(moduleSetupSurface) + return false + } } } } -- 2.47.2 From 92c33c9c11c88b4eeed96a1db77e0664e0fe08dd Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Wed, 7 May 2025 10:46:57 +0900 Subject: [PATCH 036/109] =?UTF-8?q?polygonLines=EA=B0=80=20=EC=97=86?= =?UTF-8?q?=EB=8A=94=20=EA=B2=BD=EC=9A=B0=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/usePolygon.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/hooks/usePolygon.js b/src/hooks/usePolygon.js index a2c8c9aa..7b14b906 100644 --- a/src/hooks/usePolygon.js +++ b/src/hooks/usePolygon.js @@ -949,7 +949,8 @@ export const usePolygon = () => { //polygonLines에서 divideLines를 제거하고 newLines를 추가한다. newLines = newLines.filter((line) => !(Math.abs(line.startPoint.x - line.endPoint.x) < 1 && Math.abs(line.startPoint.y - line.endPoint.y) < 1)) - polygonLines = polygonLines.filter((line) => line.intersections?.length === 0) + + polygonLines = polygonLines.filter((line) => !line.intersections || line.intersections.length === 0) polygonLines = [...polygonLines, ...newLines] -- 2.47.2 From 381c639d187280c5ca891ac79ffce6883e801a80 Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Wed, 7 May 2025 11:21:26 +0900 Subject: [PATCH 037/109] =?UTF-8?q?roofPoints=20valid=20=EC=B2=B4=ED=81=AC?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/util/qpolygon-utils.js | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/src/util/qpolygon-utils.js b/src/util/qpolygon-utils.js index 131e9232..b1cf5ae1 100644 --- a/src/util/qpolygon-utils.js +++ b/src/util/qpolygon-utils.js @@ -294,7 +294,7 @@ function arePolygonsEqual(polygon1, polygon2) { } export function removeDuplicatePolygons(polygons) { - const uniquePolygons = [] + let uniquePolygons = [] polygons.forEach((polygon) => { const isDuplicate = uniquePolygons.some((uniquePolygon) => arePolygonsEqual(polygon, uniquePolygon)) @@ -304,12 +304,32 @@ export function removeDuplicatePolygons(polygons) { }) // x가 전부 같거나, y가 전부 같은 경우 제거 - return uniquePolygons.filter((polygon) => { - const xValues = polygon.map((point) => point.x) - const yValues = polygon.map((point) => point.y) - - return !(xValues.every((x) => x === xValues[0]) || yValues.every((y) => y === yValues[0])) + uniquePolygons = uniquePolygons.filter((polygon) => { + return isValidPoints(polygon) }) + + return uniquePolygons +} + +// 현재 point의 x와 이전 포인트의 x와 같을경우, 다음 포인트의 x와 달라야 함. +// 현재 point의 y와 이전 포인트의 y와 같을경우, 다음 포인트의 y와 달라야 함. +const isValidPoints = (points) => { + for (let i = 1; i < points.length - 1; i++) { + const prev = points[i - 1] + const curr = points[i] + const next = points[i + 1] + + // 현재와 이전의 x가 같다면 다음의 x는 달라야 함 + if (curr.x === prev.x && curr.x === next.x) { + return false + } + + // 현재와 이전의 y가 같다면 다음의 y는 달라야 함 + if (curr.y === prev.y && curr.y === next.y) { + return false + } + } + return true } export const isSamePoint = (a, b) => { -- 2.47.2 From 8da2ab08202f6fce4073243bb2169bc387ff68c7 Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Wed, 7 May 2025 13:31:00 +0900 Subject: [PATCH 038/109] =?UTF-8?q?=EC=98=A4=EC=B0=A8=20=EB=B2=94=EC=9C=84?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/usePolygon.js | 5 ++--- src/util/canvas-util.js | 6 +++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/hooks/usePolygon.js b/src/hooks/usePolygon.js index 7b14b906..62130acd 100644 --- a/src/hooks/usePolygon.js +++ b/src/hooks/usePolygon.js @@ -861,14 +861,13 @@ export const usePolygon = () => { /*innerLine.set({ stroke: originInnerStroke }) canvas.renderAll()*/ }) - /*line.set({ intersections }) + line.set({ intersections }) - line.set({ stroke: originStroke }) + /*line.set({ stroke: originStroke }) canvas.renderAll()*/ }) const divideLines = polygonLines.filter((line) => line.intersections?.length > 0) - let newLines = [] divideLines.forEach((line) => { diff --git a/src/util/canvas-util.js b/src/util/canvas-util.js index 6eef894e..0f0b69dc 100644 --- a/src/util/canvas-util.js +++ b/src/util/canvas-util.js @@ -533,11 +533,11 @@ export function isPointOnLine({ x1, y1, x2, y2 }, { x, y }, epsilon = 2) { return result <= 10*!*/ // 직선 방정식 만족 여부 확인 const crossProduct = (y - y1) * (x2 - x1) - (x - x1) * (y2 - y1) - if (Math.abs(crossProduct) > 500) return false // 작은 오차 허용 + if (Math.abs(crossProduct) > 1000) return false // 작은 오차 허용 // 점이 선분의 범위 내에 있는지 확인 - const withinXRange = Math.min(x1, x2) <= x && x <= Math.max(x1, x2) - const withinYRange = Math.min(y1, y2) <= y && y <= Math.max(y1, y2) + const withinXRange = Math.abs(Math.min(x1, x2) - x) <= 2 || 2 <= Math.abs(Math.max(x1, x2) - x) + const withinYRange = Math.abs(Math.min(y1, y2) - y) <= 2 || 2 <= Math.abs(Math.max(y1, y2) - y) return withinXRange && withinYRange } -- 2.47.2 From d6b0e4994af85b6360f2b60759027913e68ae235 Mon Sep 17 00:00:00 2001 From: yjnoh Date: Wed, 7 May 2025 14:47:08 +0900 Subject: [PATCH 039/109] =?UTF-8?q?#=20[1014]=20:=20819=E3=81=AE=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0=E3=80=80=E3=83=AC=E3=82=A4=E3=82=A2=E3=82=A6=E3=83=88?= =?UTF-8?q?=E6=8C=87=E5=AE=9A=E9=85=8D=E7=BD=AE=E3=81=AE=E8=87=AA=E5=8B=95?= =?UTF-8?q?=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # [작업내용] : http://1.248.227.176:43333/issues/1014 --- .../floor-plan/modal/basic/BasicSetting.jsx | 6 +- .../floor-plan/modal/basic/step/Placement.jsx | 6 +- src/hooks/module/useModuleBasicSetting.js | 170 +++++------------- src/locales/ja.json | 4 +- 4 files changed, 57 insertions(+), 129 deletions(-) diff --git a/src/components/floor-plan/modal/basic/BasicSetting.jsx b/src/components/floor-plan/modal/basic/BasicSetting.jsx index 66a76d71..a70b9a22 100644 --- a/src/components/floor-plan/modal/basic/BasicSetting.jsx +++ b/src/components/floor-plan/modal/basic/BasicSetting.jsx @@ -331,15 +331,15 @@ export default function BasicSetting({ id, pos = { x: 50, y: 230 } }) { + - )} diff --git a/src/components/floor-plan/modal/basic/step/Placement.jsx b/src/components/floor-plan/modal/basic/step/Placement.jsx index 5d2c8c6a..c6556ea3 100644 --- a/src/components/floor-plan/modal/basic/step/Placement.jsx +++ b/src/components/floor-plan/modal/basic/step/Placement.jsx @@ -37,7 +37,7 @@ const Placement = forwardRef((props, refs) => { const resetModuleSetupOption = useResetRecoilState(moduleSetupOptionState) const [colspan, setColspan] = useState(1) - const [moduleRowColArray, setModuleRowColArray] = useRecoilState(moduleRowColArrayState) + const moduleRowColArray = useRecoilValue(moduleRowColArrayState) //모듈 배치면 생성 useEffect(() => { @@ -337,6 +337,7 @@ const Placement = forwardRef((props, refs) => { ))} + {colspan > 1 && {getMessage('total')}} {selectedModules.itemList.map((item) => ( @@ -356,10 +357,11 @@ const Placement = forwardRef((props, refs) => { {item.addRoof?.roofMatlNmJp} - {moduleRowColArray[index]?.map((item) => ( + {moduleRowColArray[index]?.map((item, index2) => ( <> {item.moduleMaxRows} {colspan > 1 && {item.mixModuleMaxRows}} + {colspan > 1 && index2 === moduleRowColArray[index].length - 1 && {item.maxRow}} ))} diff --git a/src/hooks/module/useModuleBasicSetting.js b/src/hooks/module/useModuleBasicSetting.js index 785c6b0f..c43477d7 100644 --- a/src/hooks/module/useModuleBasicSetting.js +++ b/src/hooks/module/useModuleBasicSetting.js @@ -49,20 +49,17 @@ export function useModuleBasicSetting(tabNum) { const [isManualModuleLayoutSetup, setIsManualModuleLayoutSetup] = useRecoilState(isManualModuleLayoutSetupState) const canvasSetting = useRecoilValue(canvasSettingState) const moduleSelectionData = useRecoilValue(moduleSelectionDataState) - const [trestleDetailParams, setTrestleDetailParams] = useState([]) const [trestleDetailList, setTrestleDetailList] = useState([]) const selectedModules = useRecoilValue(selectedModuleState) - const { getTrestleDetailList } = useMasterController() const [saleStoreNorthFlg, setSaleStoreNorthFlg] = useState(false) const setCurrentObject = useSetRecoilState(currentObjectState) const { setModuleStatisticsData } = useCircuitTrestle() const { createRoofPolygon, createMarginPolygon, createPaddingPolygon } = useMode() - const { drawDirectionArrow } = usePolygon() const moduleSetupOption = useRecoilValue(moduleSetupOptionState) const setManualSetupMode = useSetRecoilState(toggleManualSetupModeState) - const [moduleRowColArray, setModuleRowColArray] = useRecoilState(moduleRowColArrayState) + const setModuleRowColArray = useSetRecoilState(moduleRowColArrayState) useEffect(() => { return () => { @@ -111,10 +108,6 @@ export function useModuleBasicSetting(tabNum) { } } - useEffect(() => { - console.log('saleStoreNorthFlg', saleStoreNorthFlg) - }, [saleStoreNorthFlg]) - //가대 상세 데이터 들어오면 실행 useEffect(() => { if (trestleDetailList.length > 0) { @@ -153,7 +146,7 @@ export function useModuleBasicSetting(tabNum) { const moduleRowArray = [] if (isObjectNotEmpty(detail) && detail.module.length > 0) { detail.module.forEach((module) => { - moduleRowArray.push({ moduleMaxRows: module.moduleMaxRows, mixModuleMaxRows: module.mixModuleMaxRows }) + moduleRowArray.push({ moduleMaxRows: module.moduleMaxRows, mixModuleMaxRows: module.mixModuleMaxRows, maxRow: detail.moduleMaxRows }) }) } rowColArray.push(moduleRowArray) @@ -456,14 +449,14 @@ export function useModuleBasicSetting(tabNum) { } if (checkedModule.length === 0) { - swalFire({ text: getMessage('module.place.select.module') }) + swalFire({ text: getMessage('module.place.select.module'), icon: 'warning' }) setIsManualModuleSetup(false) setManualSetupMode(`manualSetup_false`) return } if (checkedModule.length > 1) { - swalFire({ text: getMessage('module.place.select.one.module') }) + swalFire({ text: getMessage('module.place.select.one.module'), icon: 'warning' }) setIsManualModuleSetup(false) setManualSetupMode(`manualSetup_false`) return @@ -1048,26 +1041,6 @@ export function useModuleBasicSetting(tabNum) { }, ] - //아래래 - // let points = [ - // { - // x: Number(mousePoint.x.toFixed(1)) - calcHalfWidth, - // y: Number(mousePoint.y.toFixed(1)) - calcHalfHeight, - // }, - // { - // x: Number(mousePoint.x.toFixed(1)) - calcHalfWidth, - // y: Number(mousePoint.y.toFixed(1)) + calcHalfHeight, - // }, - // { - // x: Number(mousePoint.x.toFixed(1)) + calcHalfWidth, - // y: Number(mousePoint.y.toFixed(1)) + calcHalfHeight, - // }, - // { - // x: Number(mousePoint.x.toFixed(1)) + calcHalfWidth, - // y: Number(mousePoint.y.toFixed(1)) - calcHalfHeight, - // }, - // ] - const turfPoints = coordToTurfPolygon(points) if (turf.booleanWithin(turfPoints, turfPolygon)) { @@ -1829,6 +1802,16 @@ export function useModuleBasicSetting(tabNum) { // }) //자동일때만 선택 안된 모듈 삭제 + moduleSetupSurfaces.forEach((obj) => { + if (obj.modules) { + obj.modules.forEach((module) => { + canvas?.remove(module) + canvas?.renderAll() + }) + obj.modules = [] + } + }) + if (type === MODULE_SETUP_TYPE.AUTO) { notSelectedTrestlePolygons.forEach((obj) => { if (obj.modules) { @@ -1839,17 +1822,6 @@ export function useModuleBasicSetting(tabNum) { obj.modules = [] } }) - } else { - //레이아웃일때는 기존에 깔린 모듈은 삭제 하지 않음 선택된 영역만 모듈 삭제 - moduleSetupSurfaces.forEach((obj) => { - if (obj.modules) { - obj.modules.forEach((module) => { - canvas?.remove(module) - canvas?.renderAll() - }) - obj.modules = [] - } - }) } let moduleOptions = { @@ -1864,9 +1836,6 @@ export function useModuleBasicSetting(tabNum) { name: POLYGON_TYPE.MODULE, } - //체크된 모듈 중에 북면 모듈이 있는지 확인하는 로직 - const isIncludeNorthModule = checkedModule.some((module) => module.northModuleYn === 'Y') - //선택된 지붕안에 오브젝트(도머, 개구등)이 있는지 확인하는 로직 포함되면 배열 반환 const objectsIncludeSurface = (turfModuleSetupSurface) => { let containsBatchObjects = [] @@ -1895,49 +1864,46 @@ export function useModuleBasicSetting(tabNum) { /** * 자동 레이아웃 설치 일시 row col 초과 여부 확인 + * 체크된 모듈중에 북면 모듈이 있으면 북면 모듈만 따로 계산하고 아니면 전체 모듈을 계산 + * 북면 모듈이 있고 북면이 들어오면 북면의 row를 계산한다 + * * @param {*} trestleDetailData * @returns */ const checkAutoLayoutModuleSetup = (moduleSetupSurface, trestleDetailData) => { - /** - * 체크된 모듈중에 북면 모듈이 있으면 북면 모듈만 따로 계산하고 아니면 전체 모듈을 계산 - * 북면 모듈이 있고 북면이 들어오면 북면의 row를 계산한다 - * - */ - //북면 모듈이 없을때 + //체크된 모듈 중에 북면 모듈이 있는지 확인하는 로직 + const isIncludeNorthModule = checkedModule.some((module) => module.northModuleYn === 'Y') + const maxCol = trestleDetailData.moduleMaxCols //최대 열수 -> 얘는 멀티랑 관계없음 + + //북면 모듈이 없으면 if (!isIncludeNorthModule) { - const isMultipleModules = checkedModule.length > 1 //모듈이 여러개면 - const maxCol = trestleDetailData.moduleMaxCols //최대 열수 -> 얘는 멀티랑 관계없음 + const isMultipleModules = checkedModule.length > 1 //모듈이 여러개인지 체크하고 + + //멀티 모듈이면 모듈밖에 내려온 정보의 maxRow 단일 모듈이면 모듈에 maxRow const maxRow = isMultipleModules ? trestleDetailData.moduleMaxRows : trestleDetailData.module.find((item) => item.moduleTpCd === checkedModule[0].moduleTpCd).moduleMaxRows //멀티모듈이면 밖에 maxRows로 판단 아니면 module->itemmList를 가지고 판단 - //단수 합단수 + //멀티 모듈이면 모듈밖에 내려온 정보의 row 합, 단일 모듈이면 모듈에 row const sumRowCount = isMultipleModules ? layoutSetupRef.filter((item) => item.checked).reduce((acc, cur) => acc + cur.row, 0) : layoutSetupRef.find((item) => item.moduleId === checkedModule[0].itemId).row //멀티모듈이면 전체 합, 체크된 한개의 열 - // + //col는 moduleItems 배열 밖에 내려옴 const sumColCount = layoutSetupRef.filter((item) => item.col).some((item) => item.col > maxCol) - if (sumRowCount > maxRow || sumColCount) { - failAutoSetupRoof.push(moduleSetupSurface) - return false - } - - // 혼합일때 모듈 개별의 row를 체크함 + // 혼합일때 모듈 개별의 maxRow를 체크해서 가능여부 확인 const isPassedObject = - isMultipleModules && layoutSetupRef.find((item, index) => item.checked && item.row > trestleDetailData.module[index].mixModuleMaxRows) - - if (isPassedObject) { + isMultipleModules && layoutSetupRef.find((item, index) => item.checked && item.row > trestleDetailData.module[index].mixModuleMaxRows) //체크된 배열은 checked여부로 체크해서 index와 동일함 + //전체 카은트된 열수가 크거나 혼합카운트가 존재 하면 실패 + if (sumRowCount > maxRow || sumColCount || isPassedObject) { failAutoSetupRoof.push(moduleSetupSurface) return false } } else { //북면 모듈만 선택했을때 if (checkedModule.length === 1) { - const maxCol = trestleDetailData.moduleMaxCols //최대 열수 -> 얘는 멀티랑 관계없음 const maxRow = trestleDetailData.module.find((item) => item.moduleTpCd === checkedModule[0].moduleTpCd).moduleMaxRows //멀티모듈이면 밖에 maxRows로 판단 아니면 module->itemmList를 가지고 판단 //단수 합단수 @@ -1958,7 +1924,6 @@ export function useModuleBasicSetting(tabNum) { if (normalModule.length > 0 && !moduleSetupSurface.isNorth) { //C1C2 모듈일 경우ㅁㅁ const isMultipleModules = normalModule.length > 1 //모듈이 여러개면 - const maxCol = trestleDetailData.moduleMaxCols //최대 열수 -> 얘는 멀티랑 관계없음 const maxRow = isMultipleModules ? trestleDetailData.moduleMaxRows : trestleDetailData.module.find((item) => item.moduleTpCd === normalModule[0].moduleTpCd).moduleMaxRows //멀티모듈이면 밖에 maxRows로 판단 아니면 module->itemmList를 가지고 판단 @@ -1971,11 +1936,6 @@ export function useModuleBasicSetting(tabNum) { //북면 모듈 id를 제외한 모듈의 열 체크 const sumColCount = layoutSetupRef.filter((item) => item.col && !northModuleIds.includes(item.moduleId)).some((item) => item.col > maxCol) - if (sumRowCount > maxRow || sumColCount) { - failAutoSetupRoof.push(moduleSetupSurface) - return false - } - // 혼합일때 모듈 개별의 row를 체크함 const isPassedObject = isMultipleModules && @@ -1984,16 +1944,15 @@ export function useModuleBasicSetting(tabNum) { item.checked && !item.moduleId.includes(northModuleIds) && item.row > trestleDetailData.module[index].mixModuleMaxRows, ) - if (isPassedObject) { + // 합산 단수가 맥스단수보다 크거나 열이 맥스열수보다 크거나 혼합일때 모듈 개별의 row가 맥스단수보다 크면 실패 + if (sumRowCount > maxRow || sumColCount || isPassedObject) { failAutoSetupRoof.push(moduleSetupSurface) return false } } - if (northModule.length > 0 && moduleSetupSurface.isNorth) { + if (northModule.length > 0) { const isMultipleModules = northModule.length > 1 //모듈이 여러개면 - const maxCol = trestleDetailData.moduleMaxCols //최대 열수 -> 얘는 멀티랑 관계없음 - const maxRow = isMultipleModules ? trestleDetailData.moduleMaxRows : trestleDetailData.module.find((item) => item.moduleTpCd === northModule[0].moduleTpCd).moduleMaxRows //멀티모듈이면 밖에 maxRows로 판단 아니면 module->itemmList를 가지고 판단 @@ -2004,11 +1963,6 @@ export function useModuleBasicSetting(tabNum) { const sumColCount = layoutSetupRef.filter((item) => item.col && northModuleIds.includes(item.moduleId)).some((item) => item.col > maxCol) - if (sumRowCount > maxRow || sumColCount) { - failAutoSetupRoof.push(moduleSetupSurface) - return false - } - // 혼합일때 모듈 개별의 row를 체크함 const isPassedObject = isMultipleModules && @@ -2017,7 +1971,8 @@ export function useModuleBasicSetting(tabNum) { item.checked && northModuleIds.includes(item.moduleId) && item.row > trestleDetailData.module[index].mixModuleMaxRows, ) - if (isPassedObject) { + // 합산 단수가 맥스단수보다 크거나 열이 맥스열수보다 크거나 혼합일때 모듈 개별의 row가 맥스단수보다 크면 실패 + if (sumRowCount > maxRow || sumColCount || isPassedObject) { failAutoSetupRoof.push(moduleSetupSurface) return false } @@ -2028,16 +1983,7 @@ export function useModuleBasicSetting(tabNum) { } //흐름 방향이 남쪽(아래) - const downFlowSetupModule = ( - surfaceMaxLines, //deprecated - maxLengthLine, - moduleSetupArray, - moduleSetupSurface, - containsBatchObjects, - isCenter = false, //deprecated - intvHor, - intvVer, - ) => { + const downFlowSetupModule = (maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, intvHor, intvVer) => { let setupModule = [] const trestleDetailData = moduleSetupSurface.trestleDetail @@ -2215,16 +2161,7 @@ export function useModuleBasicSetting(tabNum) { } } - const topFlowSetupModule = ( - surfaceMaxLines, - maxLengthLine, - moduleSetupArray, - moduleSetupSurface, - containsBatchObjects, - isCenter = false, - intvHor, - intvVer, - ) => { + const topFlowSetupModule = (maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, intvHor, intvVer) => { let setupModule = [] const trestleDetailData = moduleSetupSurface.trestleDetail @@ -2405,12 +2342,11 @@ export function useModuleBasicSetting(tabNum) { //남, 북과 같은 로직으로 적용하려면 좌우는 열 -> 행 으로 그려야함 //변수명은 bottom 기준으로 작성하여 동일한 방향으로 진행한다 const leftFlowSetupModule = ( - surfaceMaxLines, maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, - isCenter = false, + intvHor, intvVer, ) => { @@ -2597,16 +2533,7 @@ export function useModuleBasicSetting(tabNum) { } } - const rightFlowSetupModule = ( - surfaceMaxLines, - maxLengthLine, - moduleSetupArray, - moduleSetupSurface, - containsBatchObjects, - isCenter = false, - intvHor, - intvVer, - ) => { + const rightFlowSetupModule = (maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, intvHor, intvVer) => { let setupModule = [] const trestleDetailData = moduleSetupSurface.trestleDetail //가대 상세 데이터 @@ -2798,7 +2725,6 @@ export function useModuleBasicSetting(tabNum) { const turfModuleSetupSurface = polygonToTurfPolygon(moduleSetupSurface) //폴리곤을 turf 객체로 변환 const containsBatchObjects = objectsIncludeSurface(turfModuleSetupSurface) //배치면에 오브젝트(도머, 개구등)이 있는지 확인하는 로직 - const surfaceMaxLines = findSetupSurfaceMaxLines(moduleSetupSurface) let maxLengthLine = moduleSetupSurface.lines.reduce((acc, cur) => { return acc.length > cur.length ? acc : cur @@ -2819,30 +2745,30 @@ export function useModuleBasicSetting(tabNum) { if (setupLocation === 'eaves') { // 흐름방향이 남쪽일때 if (moduleSetupSurface.direction === 'south') { - downFlowSetupModule(surfaceMaxLines, maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, false, intvHor, intvVer) + downFlowSetupModule(maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, intvHor, intvVer) } if (moduleSetupSurface.direction === 'west') { - leftFlowSetupModule(surfaceMaxLines, maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, false, intvHor, intvVer) + leftFlowSetupModule(maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, intvHor, intvVer) } if (moduleSetupSurface.direction === 'east') { - rightFlowSetupModule(surfaceMaxLines, maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, false, intvHor, intvVer) + rightFlowSetupModule(maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, intvHor, intvVer) } if (moduleSetupSurface.direction === 'north') { - topFlowSetupModule(surfaceMaxLines, maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, false, intvHor, intvVer) + topFlowSetupModule(maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, intvHor, intvVer) } } else if (setupLocation === 'ridge') { //용마루 if (moduleSetupSurface.direction === 'south') { - topFlowSetupModule(surfaceMaxLines, maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, false, intvHor, intvVer) + topFlowSetupModule(maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, intvHor, intvVer) } if (moduleSetupSurface.direction === 'west') { - rightFlowSetupModule(surfaceMaxLines, maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, false, intvHor, intvVer) + rightFlowSetupModule(maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, intvHor, intvVer) } if (moduleSetupSurface.direction === 'east') { - leftFlowSetupModule(surfaceMaxLines, maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, false, intvHor, intvVer) + leftFlowSetupModule(maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, intvHor, intvVer) } if (moduleSetupSurface.direction === 'north') { - downFlowSetupModule(surfaceMaxLines, maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, false, intvHor, intvVer) + downFlowSetupModule(maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, intvHor, intvVer) } } diff --git a/src/locales/ja.json b/src/locales/ja.json index c6a1ea0e..44bf3bb4 100644 --- a/src/locales/ja.json +++ b/src/locales/ja.json @@ -1071,8 +1071,8 @@ "module.layout.setup.max.count.multiple": "モジュール{0}の単体での最大段数は{1}、最大列数は{2}です。 (JA)", "roofAllocation.not.found": "割り当てる屋根がありません。 (JA)", "modal.module.basic.setting.module.placement.max.size.check": "屋根材別モジュールの単体の単体での最大段数、2種混合の段数を確認して下さい", - "modal.module.basic.setting.module.placement.max.row": "単体での最大段数", - "modal.module.basic.setting.module.placement.max.rows.multiple": "2種混合時の最大段数", + "modal.module.basic.setting.module.placement.max.row": "単体で\rの最大段数", + "modal.module.basic.setting.module.placement.max.rows.multiple": "2種混合時\rの最大段数", "modal.module.basic.setting.module.placement.mix.asg.yn.error": "混合インストール不可能なモジュールです。 (JA)", "modal.module.basic.setting.module.placement.mix.asg.yn": "混合", "modal.module.basic.setting.layoutpassivity.placement": "layout配置 (JA)" -- 2.47.2 From 78258fc9c10e9a58695843112851a0a3917061d5 Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Wed, 7 May 2025 15:05:44 +0900 Subject: [PATCH 040/109] =?UTF-8?q?roof=20=EB=82=98=EB=88=8C=EB=95=8C=20po?= =?UTF-8?q?int=20=EC=A4=91=EB=B3=B5=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/util/qpolygon-utils.js | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/util/qpolygon-utils.js b/src/util/qpolygon-utils.js index b1cf5ae1..d9923616 100644 --- a/src/util/qpolygon-utils.js +++ b/src/util/qpolygon-utils.js @@ -296,6 +296,7 @@ function arePolygonsEqual(polygon1, polygon2) { export function removeDuplicatePolygons(polygons) { let uniquePolygons = [] + // x가 전부 같거나, y가 전부 같은 경우 제거 polygons.forEach((polygon) => { const isDuplicate = uniquePolygons.some((uniquePolygon) => arePolygonsEqual(polygon, uniquePolygon)) if (!isDuplicate) { @@ -303,7 +304,6 @@ export function removeDuplicatePolygons(polygons) { } }) - // x가 전부 같거나, y가 전부 같은 경우 제거 uniquePolygons = uniquePolygons.filter((polygon) => { return isValidPoints(polygon) }) @@ -314,18 +314,24 @@ export function removeDuplicatePolygons(polygons) { // 현재 point의 x와 이전 포인트의 x와 같을경우, 다음 포인트의 x와 달라야 함. // 현재 point의 y와 이전 포인트의 y와 같을경우, 다음 포인트의 y와 달라야 함. const isValidPoints = (points) => { - for (let i = 1; i < points.length - 1; i++) { - const prev = points[i - 1] - const curr = points[i] - const next = points[i + 1] + for (let i = 1; i < points.length; i++) { + let prev = points[i - 1] + let curr = points[i] + let next = points[i + 1] + + if (i === points.length - 1) { + prev = points[i - 1] + curr = points[i] + next = points[0] + } // 현재와 이전의 x가 같다면 다음의 x는 달라야 함 - if (curr.x === prev.x && curr.x === next.x) { + if (Math.abs(curr.x - prev.x) < 1 && Math.abs(curr.x - next.x) < 1) { return false } // 현재와 이전의 y가 같다면 다음의 y는 달라야 함 - if (curr.y === prev.y && curr.y === next.y) { + if (Math.abs(curr.y - prev.y) < 1 && Math.abs(curr.y - next.y) < 1) { return false } } -- 2.47.2 From d244ba3b97c426cefbfa7923a7a8c438c37d5b0e Mon Sep 17 00:00:00 2001 From: yjnoh Date: Wed, 7 May 2025 15:18:59 +0900 Subject: [PATCH 041/109] =?UTF-8?q?[1014]=20:=20[819=E3=81=AE=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0=E3=80=80=E3=83=AC=E3=82=A4=E3=82=A2=E3=82=A6=E3=83=88?= =?UTF-8?q?=E6=8C=87=E5=AE=9A=E9=85=8D=E7=BD=AE=E3=81=AE=E8=87=AA=E5=8B=95?= =?UTF-8?q?=E9=85=8D=E7=BD=AE]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [작업내용] : 고도화 단수 열수 자동 배치 작업 --- .gitmessage.txt | 3 +++ .../floor-plan/modal/basic/step/Placement.jsx | 9 +++++---- src/hooks/module/useModuleBasicSetting.js | 11 +++-------- src/locales/ja.json | 3 ++- src/locales/ko.json | 3 ++- 5 files changed, 15 insertions(+), 14 deletions(-) create mode 100644 .gitmessage.txt diff --git a/.gitmessage.txt b/.gitmessage.txt new file mode 100644 index 00000000..07932f22 --- /dev/null +++ b/.gitmessage.txt @@ -0,0 +1,3 @@ +[일감번호] : [제목] + +[작업내용] : \ No newline at end of file diff --git a/src/components/floor-plan/modal/basic/step/Placement.jsx b/src/components/floor-plan/modal/basic/step/Placement.jsx index c6556ea3..1d2545e6 100644 --- a/src/components/floor-plan/modal/basic/step/Placement.jsx +++ b/src/components/floor-plan/modal/basic/step/Placement.jsx @@ -330,20 +330,21 @@ const Placement = forwardRef((props, refs) => { {selectedModules && selectedModules.itemList.map((item) => ( - + // +
{item.itemNm}
))} - {colspan > 1 && {getMessage('total')}} + {colspan > 1 && {getMessage('modal.module.basic.setting.module.placement.max.rows.multiple')}} {selectedModules.itemList.map((item) => ( <> {getMessage('modal.module.basic.setting.module.placement.max.row')} - {colspan > 1 && {getMessage('modal.module.basic.setting.module.placement.max.rows.multiple')}} + {/* {colspan > 1 && {getMessage('modal.module.basic.setting.module.placement.max.rows.multiple')}} */} ))} @@ -360,7 +361,7 @@ const Placement = forwardRef((props, refs) => { {moduleRowColArray[index]?.map((item, index2) => ( <> {item.moduleMaxRows} - {colspan > 1 && {item.mixModuleMaxRows}} + {/* {colspan > 1 && {item.mixModuleMaxRows}} */} {colspan > 1 && index2 === moduleRowColArray[index].length - 1 && {item.maxRow}} ))} diff --git a/src/hooks/module/useModuleBasicSetting.js b/src/hooks/module/useModuleBasicSetting.js index c43477d7..c7173dca 100644 --- a/src/hooks/module/useModuleBasicSetting.js +++ b/src/hooks/module/useModuleBasicSetting.js @@ -2809,14 +2809,9 @@ export function useModuleBasicSetting(tabNum) { /** * 자동 레이아웃일떄 설치 가능한 애들은 설치 해주고 실패한 애들은 이름, 최대 설치 가능한 높이 알려줄라고 */ - if (type === MODULE_SETUP_TYPE.LAYOUT && failAutoSetupRoof.length > 0) { - const roofNamesList = failAutoSetupRoof.map((roof) => ({ - roofName: roof.roofMaterial.roofMatlNmJp, - moduleMaxRows: roof.trestleDetail.moduleMaxRows, - })) - - const alertString = roofNamesList.map((item, index) => `${item.roofName}(${item.moduleMaxRows})`) - swalFire({ text: alertString, icon: 'warning' }) + if (type === MODULE_SETUP_TYPE.LAYOUT && failAutoSetupRoof.length) { + const roofNames = failAutoSetupRoof.map((roof) => roof.roofMaterial.roofMatlNmJp).join(', ') + swalFire({ text: getMessage('modal.module.basic.setting.module.placement.over.max.row', [roofNames]), icon: 'warning' }) } } diff --git a/src/locales/ja.json b/src/locales/ja.json index 44bf3bb4..343673d5 100644 --- a/src/locales/ja.json +++ b/src/locales/ja.json @@ -1075,5 +1075,6 @@ "modal.module.basic.setting.module.placement.max.rows.multiple": "2種混合時\rの最大段数", "modal.module.basic.setting.module.placement.mix.asg.yn.error": "混合インストール不可能なモジュールです。 (JA)", "modal.module.basic.setting.module.placement.mix.asg.yn": "混合", - "modal.module.basic.setting.layoutpassivity.placement": "layout配置 (JA)" + "modal.module.basic.setting.layoutpassivity.placement": "layout配置 (JA)", + "modal.module.basic.setting.module.placement.over.max.row": "{0}의 최대단수를 초과했습니다. 최대단수표를 참고해 주세요.(JA)" } diff --git a/src/locales/ko.json b/src/locales/ko.json index cd8b855b..b3e3e99b 100644 --- a/src/locales/ko.json +++ b/src/locales/ko.json @@ -1076,5 +1076,6 @@ "modal.module.basic.setting.module.placement.max.rows.multiple": "2종 혼합 최대단수", "modal.module.basic.setting.module.placement.mix.asg.yn.error": "혼합 설치 불가능한 모듈입니다.", "modal.module.basic.setting.module.placement.mix.asg.yn": "혼합", - "modal.module.basic.setting.layoutpassivity.placement": "레이아웃 배치" + "modal.module.basic.setting.layoutpassivity.placement": "레이아웃 배치", + "modal.module.basic.setting.module.placement.over.max.row": "{0}의 최대단수를 초과했습니다. 최대단수표를 참고해 주세요." } -- 2.47.2 From f3b8ee40fab1f229f975c57ae7707b6a7b9b0494 Mon Sep 17 00:00:00 2001 From: yjnoh Date: Wed, 7 May 2025 16:56:28 +0900 Subject: [PATCH 042/109] =?UTF-8?q?[1014]=20:=20[819=E3=81=AE=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0=E3=80=80=E3=83=AC=E3=82=A4=E3=82=A2=E3=82=A6=E3=83=88?= =?UTF-8?q?=E6=8C=87=E5=AE=9A=E9=85=8D=E7=BD=AE=E3=81=AE=E8=87=AA=E5=8B=95?= =?UTF-8?q?=E9=85=8D=E7=BD=AE]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [작업내용] : 문구 수정 및 alert icon 정리 --- src/hooks/module/useModuleBasicSetting.js | 34 +++++++++++------------ src/locales/ja.json | 2 +- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/hooks/module/useModuleBasicSetting.js b/src/hooks/module/useModuleBasicSetting.js index c7173dca..4471bc74 100644 --- a/src/hooks/module/useModuleBasicSetting.js +++ b/src/hooks/module/useModuleBasicSetting.js @@ -89,12 +89,12 @@ export function useModuleBasicSetting(tabNum) { //roofIndex 넣기 const roofConstructionArray = roofConstructions.map((detail) => ({ ...detail.trestleDetail, roofIndex: detail.roofIndex })) - setTrestleDetailList(roofConstructionArray) - //북면 설치 가능 판매점 if (moduleSelectionData.common.saleStoreNorthFlg == '1') { setSaleStoreNorthFlg(true) } + + setTrestleDetailList(roofConstructionArray) } } else { //육지붕 일경우에는 바로 배치면 설치LL @@ -865,10 +865,10 @@ export function useModuleBasicSetting(tabNum) { // getModuleStatistics() } else { - swalFire({ text: getMessage('module.place.overlab') }) + swalFire({ text: getMessage('module.place.overlab'), icon: 'warning' }) } } else { - swalFire({ text: getMessage('module.place.out') }) + swalFire({ text: getMessage('module.place.out'), icon: 'warning' }) } } }) @@ -1266,7 +1266,7 @@ export function useModuleBasicSetting(tabNum) { const mixAsgYn = trestlePolygon.modules[0].moduleInfo.mixAsgYn //현재 체크된 모듈기준으로 혼합가능인지 확인 Y === Y, N === N 일때만 설치 가능 if (checkedModule[0].mixAsgYn !== mixAsgYn) { - swalFire({ text: getMessage('modal.module.basic.setting.module.placement.mix.asg.yn.error') }) + swalFire({ text: getMessage('modal.module.basic.setting.module.placement.mix.asg.yn.error'), icon: 'warning' }) return } } @@ -1425,7 +1425,7 @@ export function useModuleBasicSetting(tabNum) { // canvas.renderAll() } } else { - swalFire({ text: getMessage('module.place.overlab') }) + swalFire({ text: getMessage('module.place.overlab'), icon: 'warning' }) return } } @@ -1520,7 +1520,7 @@ export function useModuleBasicSetting(tabNum) { // canvas.renderAll() } } else { - swalFire({ text: getMessage('module.place.overlab') }) + swalFire({ text: getMessage('module.place.overlab'), icon: 'warning' }) return } } @@ -1613,7 +1613,7 @@ export function useModuleBasicSetting(tabNum) { // canvas.renderAll() } } else { - swalFire({ text: getMessage('module.place.overlab') }) + swalFire({ text: getMessage('module.place.overlab'), icon: 'warning' }) return } } @@ -1705,7 +1705,7 @@ export function useModuleBasicSetting(tabNum) { // canvas.renderAll() } } else { - swalFire({ text: getMessage('module.place.overlab') }) + swalFire({ text: getMessage('module.place.overlab'), icon: 'warning' }) return } } @@ -1790,7 +1790,7 @@ export function useModuleBasicSetting(tabNum) { const batchObjects = canvas?.getObjects().filter((obj) => obj.name === POLYGON_TYPE.OBJECT_SURFACE) //도머s 객체 if (moduleSetupSurfaces.length === 0) { - swalFire({ text: getMessage('module.place.no.surface') }) + swalFire({ text: getMessage('module.place.no.surface'), icon: 'warning' }) return } @@ -3119,13 +3119,13 @@ export function useModuleBasicSetting(tabNum) { let moduleSetupSurfaces = canvas?.getObjects().filter((obj) => obj.name === POLYGON_TYPE.MODULE_SETUP_SURFACE) //모듈설치면를 가져옴 if (isManualModuleSetup) { if (checkedModule.length === 0) { - swalFire({ text: getMessage('module.place.select.module') }) + swalFire({ text: getMessage('module.place.select.module'), icon: 'warning' }) setIsManualModuleSetup(!isManualModuleSetup) return } if (checkedModule.length > 1) { - swalFire({ text: getMessage('module.place.select.one.module') }) + swalFire({ text: getMessage('module.place.select.one.module'), icon: 'warning' }) setIsManualModuleSetup(!isManualModuleSetup) return } @@ -3438,7 +3438,7 @@ export function useModuleBasicSetting(tabNum) { const mixAsgYn = trestlePolygon.modules[0].moduleInfo.mixAsgYn //현재 체크된 모듈기준으로 혼합가능인지 확인 Y === Y, N === N 일때만 설치 가능 if (checkedModule[0].mixAsgYn !== mixAsgYn) { - swalFire({ text: getMessage('modal.module.basic.setting.module.placement.mix.asg.yn.error') }) + swalFire({ text: getMessage('modal.module.basic.setting.module.placement.mix.asg.yn.error'), icon: 'warning' }) return } } @@ -3471,7 +3471,7 @@ export function useModuleBasicSetting(tabNum) { const intersection = turf.intersect(turf.featureCollection([dormerTurfPolygon, tempTurfModule])) //겹치는지 확인 //겹치면 안됨 if (intersection) { - swalFire({ text: getMessage('module.place.overobject') }) + swalFire({ text: getMessage('module.place.overobject'), icon: 'warning' }) isIntersection = false } }) @@ -3489,10 +3489,10 @@ export function useModuleBasicSetting(tabNum) { manualDrawModules.push(manualModule) setModuleStatisticsData() } else { - swalFire({ text: getMessage('module.place.overlab') }) + swalFire({ text: getMessage('module.place.overlab'), icon: 'warning' }) } } else { - swalFire({ text: getMessage('module.place.out') }) + swalFire({ text: getMessage('module.place.out'), icon: 'warning' }) } } }) @@ -3522,7 +3522,7 @@ export function useModuleBasicSetting(tabNum) { //Y인 모듈과 N인 모듈이 둘다 존재하면 설치 불가 if (mixAsgY.length > 0 && mixAsgN.length > 0) { - swalFire({ text: getMessage('modal.module.basic.setting.module.placement.mix.asg.yn.error') }) + swalFire({ text: getMessage('modal.module.basic.setting.module.placement.mix.asg.yn.error'), icon: 'warning' }) return } diff --git a/src/locales/ja.json b/src/locales/ja.json index b37fbc3a..d378ff36 100644 --- a/src/locales/ja.json +++ b/src/locales/ja.json @@ -1079,5 +1079,5 @@ "modal.module.basic.setting.module.placement.mix.asg.yn.error": "混合インストール不可能なモジュールです。 (JA)", "modal.module.basic.setting.module.placement.mix.asg.yn": "混合", "modal.module.basic.setting.layoutpassivity.placement": "layout配置 (JA)", - "modal.module.basic.setting.module.placement.over.max.row": "{0}의 최대단수를 초과했습니다. 최대단수표를 참고해 주세요.(JA)" + "modal.module.basic.setting.module.placement.over.max.row": "{0} 最大段数超過しました。最大段数表を参考にしてください。" } -- 2.47.2 From 47245e438c5200849078d9dc7a80c7d92700f372 Mon Sep 17 00:00:00 2001 From: yjnoh Date: Thu, 8 May 2025 09:48:31 +0900 Subject: [PATCH 043/109] =?UTF-8?q?[1014]=20:=20[819=E3=81=AE=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0=E3=80=80=E3=83=AC=E3=82=A4=E3=82=A2=E3=82=A6=E3=83=88?= =?UTF-8?q?=E6=8C=87=E5=AE=9A=E9=85=8D=E7=BD=AE=E3=81=AE=E8=87=AA=E5=8B=95?= =?UTF-8?q?=E9=85=8D=E7=BD=AE]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [작업내용] : 불필요 소스 삭제 --- src/locales/ja.json | 1 - src/locales/ko.json | 1 - 2 files changed, 2 deletions(-) diff --git a/src/locales/ja.json b/src/locales/ja.json index d378ff36..796eea85 100644 --- a/src/locales/ja.json +++ b/src/locales/ja.json @@ -1078,6 +1078,5 @@ "modal.module.basic.setting.module.placement.max.rows.multiple": "2種混合時\rの最大段数", "modal.module.basic.setting.module.placement.mix.asg.yn.error": "混合インストール不可能なモジュールです。 (JA)", "modal.module.basic.setting.module.placement.mix.asg.yn": "混合", - "modal.module.basic.setting.layoutpassivity.placement": "layout配置 (JA)", "modal.module.basic.setting.module.placement.over.max.row": "{0} 最大段数超過しました。最大段数表を参考にしてください。" } diff --git a/src/locales/ko.json b/src/locales/ko.json index 9bc55162..c67e3823 100644 --- a/src/locales/ko.json +++ b/src/locales/ko.json @@ -1079,6 +1079,5 @@ "modal.module.basic.setting.module.placement.max.rows.multiple": "2종 혼합 최대단수", "modal.module.basic.setting.module.placement.mix.asg.yn.error": "혼합 설치 불가능한 모듈입니다.", "modal.module.basic.setting.module.placement.mix.asg.yn": "혼합", - "modal.module.basic.setting.layoutpassivity.placement": "레이아웃 배치", "modal.module.basic.setting.module.placement.over.max.row": "{0}의 최대단수를 초과했습니다. 최대단수표를 참고해 주세요." } -- 2.47.2 From 14764d295c27f0934ffc8eaa671a373a76fd3de7 Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Thu, 8 May 2025 09:59:09 +0900 Subject: [PATCH 044/109] =?UTF-8?q?polygonLines=EC=A4=91=20intersection=20?= =?UTF-8?q?=ED=8F=AC=EC=9D=B8=ED=8A=B8=EA=B0=80=20=EC=9E=88=EB=8A=94=20?= =?UTF-8?q?=EA=B2=BD=EC=9A=B0=20=EC=B2=98=EB=A6=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/usePolygon.js | 42 +++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/src/hooks/usePolygon.js b/src/hooks/usePolygon.js index 62130acd..4d59d292 100644 --- a/src/hooks/usePolygon.js +++ b/src/hooks/usePolygon.js @@ -869,14 +869,15 @@ export const usePolygon = () => { const divideLines = polygonLines.filter((line) => line.intersections?.length > 0) let newLines = [] - - divideLines.forEach((line) => { + polygonLines = polygonLines.filter((line) => !line.intersections || line.intersections.length === 0) + for (let i = divideLines.length - 1; i >= 0; i--) { + const line = divideLines[i] const { intersections, startPoint, endPoint } = line 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] + const newLine1 = new QLine(newLinePoint1, { stroke: 'blue', strokeWidth: 3, @@ -891,28 +892,27 @@ export const usePolygon = () => { attributes: line.attributes, name: 'newLine', }) + newLine1.attributes = { ...line.attributes, - planeSize: Math.round(Math.sqrt(Math.pow(newLine1.x1 - newLine1.x2, 2) + Math.pow(newLine1.y1 - newLine1.y2, 2))) * 10, - actualSize: Math.round(Math.sqrt(Math.pow(newLine1.x1 - newLine1.x2, 2) + Math.pow(newLine1.y1 - newLine1.y2, 2))) * 10, + planeSize: Math.round(Math.hypot(newLine1.x1 - newLine1.x2, newLine1.y1 - newLine1.y2)) * 10, + actualSize: Math.round(Math.hypot(newLine1.x1 - newLine1.x2, newLine1.y1 - newLine1.y2)) * 10, } newLine2.attributes = { ...line.attributes, - planeSize: Math.round(Math.sqrt(Math.pow(newLine2.x1 - newLine2.x2, 2) + Math.pow(newLine2.y1 - newLine2.y2, 2))) * 10, - actualSize: Math.round(Math.sqrt(Math.pow(newLine2.x1 - newLine2.x2, 2) + Math.pow(newLine2.y1 - newLine2.y2, 2))) * 10, + planeSize: Math.round(Math.hypot(newLine2.x1 - newLine2.x2, newLine2.y1 - newLine2.y2)) * 10, + actualSize: Math.round(Math.hypot(newLine2.x1 - newLine2.x2, newLine2.y1 - newLine2.y2)) * 10, } - newLines.push(newLine1) - newLines.push(newLine2) - } else { - // 두 점 이상 교차하는 경우 - //1. intersections중에 startPoint와 가장 가까운 점을 찾는다. - //2. 가장 가까운 점을 기준으로 line을 나눈다. + newLines.push(newLine1, newLine2) + divideLines.splice(i, 1) // 기존 line 제거 + } else { let currentPoint = startPoint while (intersections.length !== 0) { const minDistancePoint = findAndRemoveClosestPoint(currentPoint, intersections) const newLinePoint = [currentPoint.x, currentPoint.y, minDistancePoint.x, minDistancePoint.y] + const newLine = new QLine(newLinePoint, { stroke: 'blue', strokeWidth: 3, @@ -920,11 +920,13 @@ export const usePolygon = () => { attributes: line.attributes, name: 'newLine', }) + newLine.attributes = { ...line.attributes, - planeSize: Math.round(Math.sqrt(Math.pow(newLine.x1 - newLine.x2, 2) + Math.pow(newLine.y1 - newLine.y2, 2))) * 10, - actualSize: Math.round(Math.sqrt(Math.pow(newLine.x1 - newLine.x2, 2) + Math.pow(newLine.y1 - newLine.y2, 2))) * 10, + planeSize: Math.round(Math.hypot(newLine.x1 - newLine.x2, newLine.y1 - newLine.y2)) * 10, + actualSize: Math.round(Math.hypot(newLine.x1 - newLine.x2, newLine.y1 - newLine.y2)) * 10, } + newLines.push(newLine) currentPoint = minDistancePoint } @@ -939,18 +941,18 @@ export const usePolygon = () => { }) newLine.attributes = { ...line.attributes, - planeSize: Math.round(Math.sqrt(Math.pow(newLine.x1 - newLine.x2, 2) + Math.pow(newLine.y1 - newLine.y2, 2))) * 10, - actualSize: Math.round(Math.sqrt(Math.pow(newLine.x1 - newLine.x2, 2) + Math.pow(newLine.y1 - newLine.y2, 2))) * 10, + planeSize: Math.round(Math.hypot(newLine.x1 - newLine.x2, newLine.y1 - newLine.y2)) * 10, + actualSize: Math.round(Math.hypot(newLine.x1 - newLine.x2, newLine.y1 - newLine.y2)) * 10, } + newLines.push(newLine) + divideLines.splice(i, 1) // 기존 line 제거 } - }) + } //polygonLines에서 divideLines를 제거하고 newLines를 추가한다. newLines = newLines.filter((line) => !(Math.abs(line.startPoint.x - line.endPoint.x) < 1 && Math.abs(line.startPoint.y - line.endPoint.y) < 1)) - polygonLines = polygonLines.filter((line) => !line.intersections || line.intersections.length === 0) - polygonLines = [...polygonLines, ...newLines] let allLines = [...polygonLines, ...innerLines] -- 2.47.2 From c0c3295b04385f14433f02d58295c19f481ce490 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=AF=BC=EC=8B=9D?= <43837214+Minsiki@users.noreply.github.com> Date: Thu, 8 May 2025 13:21:54 +0900 Subject: [PATCH 045/109] =?UTF-8?q?[1027]=20:=20[=E3=80=90HANASYS=20DESIGN?= =?UTF-8?q?=E3=80=91=E6=A8=AA=E8=91=BA=E3=81=AB=E4=BD=BF=E7=94=A8=E3=81=99?= =?UTF-8?q?=E3=82=8B=E9=87=91=E5=85=B7=E3=81=AE=E9=9B=A2=E9=9A=94=E3=81=AB?= =?UTF-8?q?=E3=81=A4=E3=81=84=E3=81=A6]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [작업내용] : length 변경시 하위 필드 초기화 --- .../floor-plan/modal/basic/step/Trestle.jsx | 21 +++++++++++++++---- src/hooks/module/useModuleTrestle.js | 17 ++++++++++++++- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/src/components/floor-plan/modal/basic/step/Trestle.jsx b/src/components/floor-plan/modal/basic/step/Trestle.jsx index b184670e..6a1ef3e2 100644 --- a/src/components/floor-plan/modal/basic/step/Trestle.jsx +++ b/src/components/floor-plan/modal/basic/step/Trestle.jsx @@ -124,7 +124,6 @@ const Trestle = forwardRef((props, ref) => { useEffect(() => { if (constructionList.length > 0) { - console.log(constructionList) setSelectedConstruction(constructionList.find((construction) => construction.constTp === trestleState?.construction?.constTp) ?? null) } else { setSelectedConstruction(null) @@ -144,6 +143,19 @@ const Trestle = forwardRef((props, ref) => { return 'no-click' } + const onChangeLength = (e) => { + setLengthBase(e) + dispatch({ + type: 'SET_LENGTH', + roof: { + length: e, + moduleTpCd: selectedModules.itemTp ?? '', + roofMatlCd: selectedRoof?.roofMatlCd ?? '', + raftBaseCd: selectedRaftBase?.clCode, + }, + }) + } + const onChangeRaftBase = (e) => { setSelectedRaftBase(e) dispatch({ @@ -225,7 +237,8 @@ const Trestle = forwardRef((props, ref) => { snowGdPossYn: constructionList[index].snowGdPossYn, cvrYn: constructionList[index].cvrYn, mixMatlNo: selectedModules.mixMatlNo, - workingWidth: selectedRoof?.length?.toString() ?? '', + // workingWidth: selectedRoof?.length?.toString() ?? '', + workingWidth: lengthBase, }, }) @@ -242,7 +255,7 @@ const Trestle = forwardRef((props, ref) => { return { ...selectedRoof, hajebichi, - lenBase: lengthBase, + length: lengthBase, eavesMargin, ridgeMargin, kerabaMargin, @@ -518,7 +531,7 @@ const Trestle = forwardRef((props, ref) => { type="text" className="input-origin block" value={lengthBase} - onChange={(e) => setLengthBase(e.target.value)} + onChange={(e) => onChangeLength(e.target.value)} disabled={selectedRoof.lenAuth === 'R'} /> diff --git a/src/hooks/module/useModuleTrestle.js b/src/hooks/module/useModuleTrestle.js index 361ad541..e89d6089 100644 --- a/src/hooks/module/useModuleTrestle.js +++ b/src/hooks/module/useModuleTrestle.js @@ -10,6 +10,7 @@ const RAFT_BASE_CODE = '203800' const trestleReducer = (state, action) => { switch (action.type) { + case 'SET_LENGTH': case 'SET_RAFT_BASE': case 'SET_TRESTLE_MAKER': case 'SET_CONST_MTHD': @@ -96,11 +97,15 @@ export function useModuleTrestle(props) { useEffect(() => { if (trestleState) { handleSetTrestleList() + if (!trestleState?.trestleMkrCd) { setConstMthdList([]) setRoofBaseList([]) setConstructionList([]) setTrestleDetail(null) + setEavesMargin(0) + setRidgeMargin(0) + setKerabaMargin(0) return } @@ -109,6 +114,9 @@ export function useModuleTrestle(props) { setRoofBaseList([]) setConstructionList([]) setTrestleDetail(null) + setEavesMargin(0) + setRidgeMargin(0) + setKerabaMargin(0) return } @@ -116,12 +124,18 @@ export function useModuleTrestle(props) { if (!trestleState?.roofBaseCd) { setConstructionList([]) setTrestleDetail(null) + setEavesMargin(0) + setRidgeMargin(0) + setKerabaMargin(0) return } handleSetConstructionList() if (!trestleState?.constTp) { setTrestleDetail(null) + setEavesMargin(0) + setRidgeMargin(0) + setKerabaMargin(0) return } @@ -224,7 +238,8 @@ export function useModuleTrestle(props) { constTp: trestleState.constTp ?? '', mixMatlNo: trestleState.mixMatlNo ?? '', roofPitch: trestleState.roofPitch ?? '', - workingWidth: trestleState.workingWidth ?? '', + // workingWidth: trestleState.length ?? '', + workingWidth: lengthBase ?? '', }, ]) .then((res) => { -- 2.47.2 From ef75ad0ef8a080d58e086de2d4f98204a254bd63 Mon Sep 17 00:00:00 2001 From: yoosangwook Date: Thu, 8 May 2025 13:29:13 +0900 Subject: [PATCH 046/109] chore: update environment variables for Japanese server configuration --- .env.development | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/.env.development b/.env.development index ceac1712..6488b496 100644 --- a/.env.development +++ b/.env.development @@ -10,8 +10,16 @@ NEXT_PUBLIC_CONVERTER_API_URL="https://v2.convertapi.com/convert/dwg/to/png?Secr NEXT_PUBLIC_Q_ORDER_AUTO_LOGIN_URL="http://q-order-stg.q-cells.jp:8120/eos/login/autoLogin" NEXT_PUBLIC_Q_MUSUBI_AUTO_LOGIN_URL="http://q-musubi-stg.q-cells.jp:8120/qm/login/autoLogin" -AWS_REGION="ap-northeast-2" -AMPLIFY_BUCKET="interplug" -AWS_ACCESS_KEY_ID="AKIAVWMWJCUXFHEAZ4FR" -AWS_SECRET_ACCESS_KEY="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" -NEXT_PUBLIC_AWS_S3_BASE_URL="https://interplug.s3.ap-northeast-2.amazonaws.com" +# 테스트용 +# AWS_REGION="ap-northeast-2" +# AMPLIFY_BUCKET="interplug" +# AWS_ACCESS_KEY_ID="AKIAVWMWJCUXFHEAZ4FR" +# AWS_SECRET_ACCESS_KEY="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" +# NEXT_PUBLIC_AWS_S3_BASE_URL="https://interplug.s3.ap-northeast-2.amazonaws.com" + +# 실제 일본 서버 +AWS_REGION="ap-northeast-1" +AMPLIFY_BUCKET="files.hanasys.jp" +AWS_ACCESS_KEY_ID="AKIA3K4QWLZHFZRJOM2E" +AWS_SECRET_ACCESS_KEY="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" +NEXT_PUBLIC_AWS_S3_BASE_URL="http://files.hanasys.jp.s3-website-ap-northeast-1.amazonaws.com" \ No newline at end of file -- 2.47.2 From bb6a796b9fd8a170d08711aad83b0030ba2d2d63 Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Thu, 8 May 2025 13:44:31 +0900 Subject: [PATCH 047/109] =?UTF-8?q?=EC=A7=80=EB=B6=95=EC=9E=AC=20=ED=95=A0?= =?UTF-8?q?=EB=8B=B9=20=EC=8B=9C=20=EC=B6=94=EA=B0=80=20=EC=9E=91=EC=97=85?= =?UTF-8?q?=20=EB=B0=8F=20=EB=B3=B4=EC=A1=B0=EC=84=A0=20=EC=A0=9C=EA=B1=B0?= =?UTF-8?q?=20=EC=A1=B0=EA=B1=B4=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/roofcover/useAuxiliaryDrawing.js | 10 ++++++++-- src/hooks/roofcover/useRoofAllocationSetting.js | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/hooks/roofcover/useAuxiliaryDrawing.js b/src/hooks/roofcover/useAuxiliaryDrawing.js index 6023bce8..1510825b 100644 --- a/src/hooks/roofcover/useAuxiliaryDrawing.js +++ b/src/hooks/roofcover/useAuxiliaryDrawing.js @@ -849,8 +849,14 @@ export function useAuxiliaryDrawing(id, isUseEffect = true) { if ( lineHistory.current.some( (history) => - JSON.stringify(history.startPoint) === JSON.stringify(line.startPoint) && - JSON.stringify(history.endPoint) === JSON.stringify(line.endPoint), + (Math.abs(history.startPoint.x - line.startPoint.x) < 2 && + Math.abs(history.startPoint.y - line.startPoint.y) < 2 && + Math.abs(history.endPoint.x - line.endPoint.x) < 2 && + Math.abs(history.endPoint.y - line.endPoint.y) < 2) || + (Math.abs(history.startPoint.x - line.endPoint.x) < 2 && + Math.abs(history.startPoint.y - line.endPoint.y) < 2 && + Math.abs(history.endPoint.x - line.startPoint.x) < 2 && + Math.abs(history.endPoint.y - line.startPoint.y) < 2), ) ) { canvas.remove(line) diff --git a/src/hooks/roofcover/useRoofAllocationSetting.js b/src/hooks/roofcover/useRoofAllocationSetting.js index fb7d2114..00c17c1d 100644 --- a/src/hooks/roofcover/useRoofAllocationSetting.js +++ b/src/hooks/roofcover/useRoofAllocationSetting.js @@ -412,7 +412,7 @@ export function useRoofAllocationSetting(id) { }) /** 외곽선 삭제 */ - const removeTargets = canvas.getObjects().filter((obj) => obj.name === 'outerLinePoint' || obj.name === 'outerLine') + const removeTargets = canvas.getObjects().filter((obj) => obj.name === 'outerLinePoint' || obj.name === 'outerLine' || obj.name === 'pitchText') removeTargets.forEach((obj) => { canvas.remove(obj) }) -- 2.47.2 From dc743cee8fc167c94c4d4a0a6fbde648e1237752 Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Thu, 8 May 2025 14:21:44 +0900 Subject: [PATCH 048/109] =?UTF-8?q?getRoofMaterialList=20=ED=95=9C?= =?UTF-8?q?=EB=B2=88=EB=A7=8C=20=ED=98=B8=EC=B6=9C=ED=95=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/floor-plan/CanvasFrame.jsx | 29 ++++++++++++++++++++++- src/hooks/option/useCanvasSetting.js | 2 +- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/components/floor-plan/CanvasFrame.jsx b/src/components/floor-plan/CanvasFrame.jsx index ddce32a6..b43fd559 100644 --- a/src/components/floor-plan/CanvasFrame.jsx +++ b/src/components/floor-plan/CanvasFrame.jsx @@ -12,7 +12,7 @@ import { usePlan } from '@/hooks/usePlan' import { useContextMenu } from '@/hooks/useContextMenu' import { useCanvasConfigInitialize } from '@/hooks/common/useCanvasConfigInitialize' import { currentMenuState } from '@/store/canvasAtom' -import { totalDisplaySelector } from '@/store/settingAtom' +import { roofMaterialsAtom, totalDisplaySelector } from '@/store/settingAtom' import { MENU, POLYGON_TYPE } from '@/common/common' import { FloorPlanContext } from '@/app/floor-plan/FloorPlanProvider' import { QcastContext } from '@/app/QcastProvider' @@ -30,8 +30,35 @@ import { useCanvasSetting } from '@/hooks/option/useCanvasSetting' import { useCanvasMenu } from '@/hooks/common/useCanvasMenu' import { useEvent } from '@/hooks/useEvent' import { compasDegAtom } from '@/store/orientationAtom' +import { ROOF_MATERIAL_LAYOUT } from '@/components/floor-plan/modal/placementShape/PlacementShapeSetting' +import { useMasterController } from '@/hooks/common/useMasterController' export default function CanvasFrame() { + const [roofMaterials, setRoofMaterials] = useRecoilState(roofMaterialsAtom) + const { getRoofMaterialList } = useMasterController() + useEffect(async () => { + if (roofMaterials.length !== 0) { + return + } + const { data } = await getRoofMaterialList() + + const roofLists = data.map((item, idx) => ({ + ...item, + id: item.roofMatlCd, + name: item.roofMatlNm, + selected: idx === 0, + index: idx, + nameJp: item.roofMatlNmJp, + length: item.lenBase && parseInt(item.lenBase), + width: item.widBase && parseInt(item.widBase), + raft: item.raftBase && parseInt(item.raftBase), + layout: ['ROOF_ID_SLATE', 'ROOF_ID_SINGLE'].includes(item.roofMatlCd) ? ROOF_MATERIAL_LAYOUT.STAIRS : ROOF_MATERIAL_LAYOUT.PARALLEL, + hajebichi: item.roofPchBase && parseInt(item.roofPchBase), + pitch: item.pitch ? parseInt(item.pitch) : 4, + angle: item.angle ? parseInt(item.angle) : 21.8, + })) + setRoofMaterials(roofLists) + }, []) const canvasRef = useRef(null) const { canvas } = useCanvas('canvas') const { canvasLoadInit, gridInit } = useCanvasConfigInitialize() diff --git a/src/hooks/option/useCanvasSetting.js b/src/hooks/option/useCanvasSetting.js index 76f42075..2688502a 100644 --- a/src/hooks/option/useCanvasSetting.js +++ b/src/hooks/option/useCanvasSetting.js @@ -148,7 +148,7 @@ export function useCanvasSetting(executeEffect = true) { } /** 초 1회만 실행하도록 처리 */ - addRoofMaterials() + //addRoofMaterials() }, []) /** -- 2.47.2 From b2065e76998bde391b30566490b348d7aaccab49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=AF=BC=EC=8B=9D?= <43837214+Minsiki@users.noreply.github.com> Date: Thu, 8 May 2025 14:48:05 +0900 Subject: [PATCH 049/109] =?UTF-8?q?[1030]=20:=20[=E3=80=90HANASYS=20DESIGN?= =?UTF-8?q?=E3=80=91=E6=96=87=E5=AD=97=E4=BF=AE=E6=AD=A3]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [작업내용] : 다국어 수정 --- src/locales/ja.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/locales/ja.json b/src/locales/ja.json index 5aa10f4b..cea000ad 100644 --- a/src/locales/ja.json +++ b/src/locales/ja.json @@ -3,7 +3,7 @@ "welcome": "ようこそ。 {0}さん", "header.menus.home": "ホーム", "header.menus.management": "見積書管理画面", - "header.menus.management.newStuff": "新規見積登録", + "header.menus.management.newStuff": "新規物件登録", "header.menus.management.detail": "物件詳細", "header.menus.management.stuffList": "物件検索", "header.menus.community": "コミュニティ", @@ -186,7 +186,7 @@ "modal.circuit.trestle.setting.circuit.allocation.passivity.all.power.conditional.validation.error02": "シリーズを選択してください。", "modal.circuit.trestle.setting.circuit.allocation.passivity.circuit.num.fix": "番号確定", "modal.circuit.trestle.setting.step.up.allocation": "昇圧設定", - "modal.circuit.trestle.setting.step.up.allocation.serial.amount": "シリアル枚数", + "modal.circuit.trestle.setting.step.up.allocation.serial.amount": "直列枚数", "modal.circuit.trestle.setting.step.up.allocation.total.amount": "総回路数", "modal.circuit.trestle.setting.step.up.allocation.connected": "接続する", "modal.circuit.trestle.setting.step.up.allocation.circuit.amount": "昇圧回路数", -- 2.47.2 From e961648d853bbbcc9e1e4b98626f9059524cb4bc Mon Sep 17 00:00:00 2001 From: yjnoh Date: Thu, 8 May 2025 16:55:39 +0900 Subject: [PATCH 050/109] =?UTF-8?q?[1014]=20:=20[819=E3=81=AE=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0=E3=80=80=E3=83=AC=E3=82=A4=E3=82=A2=E3=82=A6=E3=83=88?= =?UTF-8?q?=E6=8C=87=E5=AE=9A=E9=85=8D=E7=BD=AE=E3=81=AE=E8=87=AA=E5=8B=95?= =?UTF-8?q?=E9=85=8D=E7=BD=AE]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [작업내용] : 멀티모듈일때 북면 모듈 포함일 경우 북면이 아닌 면에 설치되는 오류 수정 --- src/hooks/module/useModuleBasicSetting.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks/module/useModuleBasicSetting.js b/src/hooks/module/useModuleBasicSetting.js index 4471bc74..2880d29a 100644 --- a/src/hooks/module/useModuleBasicSetting.js +++ b/src/hooks/module/useModuleBasicSetting.js @@ -1951,7 +1951,7 @@ export function useModuleBasicSetting(tabNum) { } } - if (northModule.length > 0) { + if (northModule.length > 0 && moduleSetupSurface.isNorth) { const isMultipleModules = northModule.length > 1 //모듈이 여러개면 const maxRow = isMultipleModules ? trestleDetailData.moduleMaxRows -- 2.47.2 From 694923696013a462e0a65dfab99f03cc36491a34 Mon Sep 17 00:00:00 2001 From: yjnoh Date: Thu, 8 May 2025 18:02:41 +0900 Subject: [PATCH 051/109] =?UTF-8?q?[1014]=20:=20[819=E3=81=AE=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0=E3=80=80=E3=83=AC=E3=82=A4=E3=82=A2=E3=82=A6=E3=83=88?= =?UTF-8?q?=E6=8C=87=E5=AE=9A=E9=85=8D=E7=BD=AE=E3=81=AE=E8=87=AA=E5=8B=95?= =?UTF-8?q?=E9=85=8D=E7=BD=AE]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [작업내용] : 북면설치 모듈일 경우, 북면과, 북면이 아닐때 분기 처리 --- src/hooks/module/useModuleBasicSetting.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/hooks/module/useModuleBasicSetting.js b/src/hooks/module/useModuleBasicSetting.js index 2880d29a..1d9c7d66 100644 --- a/src/hooks/module/useModuleBasicSetting.js +++ b/src/hooks/module/useModuleBasicSetting.js @@ -1902,7 +1902,7 @@ export function useModuleBasicSetting(tabNum) { return false } } else { - //북면 모듈만 선택했을때 + //북면 모듈이 1개만있을때 if (checkedModule.length === 1) { const maxRow = trestleDetailData.module.find((item) => item.moduleTpCd === checkedModule[0].moduleTpCd).moduleMaxRows //멀티모듈이면 밖에 maxRows로 판단 아니면 module->itemmList를 가지고 판단 @@ -1951,7 +1951,8 @@ export function useModuleBasicSetting(tabNum) { } } - if (northModule.length > 0 && moduleSetupSurface.isNorth) { + //북면 모듈이 있고 북면에 있을때 + if (northModule.length > 0 && (moduleSetupSurface.isNorth || !moduleSetupSurface.isNorth)) { const isMultipleModules = northModule.length > 1 //모듈이 여러개면 const maxRow = isMultipleModules ? trestleDetailData.moduleMaxRows -- 2.47.2 From 2f261cb0c0a9418a13e2e416f04c2493b49ed798 Mon Sep 17 00:00:00 2001 From: Jaeyoung Lee Date: Thu, 8 May 2025 18:05:38 +0900 Subject: [PATCH 052/109] =?UTF-8?q?=EB=B0=95=EA=B3=B5=EC=A7=80=EB=B6=95=20?= =?UTF-8?q?=EB=8F=99=EC=84=A0=EC=9D=B4=EB=8F=99=20=EB=8C=80=EC=9D=91=20?= =?UTF-8?q?=EC=A0=95=EB=A6=AC=20(=ED=98=BC=ED=95=A9=20=EC=B2=98=EB=A6=AC?= =?UTF-8?q?=20=EC=A4=91)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/util/qpolygon-utils.js | 935 +++++++++++++++++-------------------- 1 file changed, 422 insertions(+), 513 deletions(-) diff --git a/src/util/qpolygon-utils.js b/src/util/qpolygon-utils.js index 9fbfc6a3..677c9a5b 100644 --- a/src/util/qpolygon-utils.js +++ b/src/util/qpolygon-utils.js @@ -1,6 +1,6 @@ import { fabric } from 'fabric' import { QLine } from '@/components/fabric/QLine' -import { getDegreeByChon } from '@/util/canvas-util' +import { getDegreeByChon, isPointOnLine } from '@/util/canvas-util' import { QPolygon } from '@/components/fabric/QPolygon' import * as turf from '@turf/turf' @@ -878,6 +878,8 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { if (checkWallPolygon.inPolygon(checkPoints)) { drawGablePolygonFirst.push({ currentBaseLine, prevBaseLine, nextBaseLine }) + } else { + drawGablePolygonSecond.push({ currentBaseLine, prevBaseLine, nextBaseLine }) } // if (!checkWallPolygon.inPolygon(checkPoints)) { drawGableRidgeSecond.push({ currentBaseLine, prevBaseLine, nextBaseLine }) @@ -1655,410 +1657,27 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { canvas.renderAll() }) - /** 케라바 지붕으로 생성된 마루의 지붕선을 그린다.*/ - baseGableRidgeLines.forEach((ridge) => { - return - const { x1, x2, y1, y2 } = ridge - const checkLine = new fabric.Line([x1, y1, x2, y2], { - stroke: 'red', - strokeWidth: 4, - parentId: roofId, - name: 'checkLine', - }) - canvas.add(checkLine) - canvas.renderAll() - - const ridgeVectorX = Math.sign(Big(x1).minus(x2).toNumber()) - const ridgeVectorY = Math.sign(Big(y1).minus(y2).toNumber()) - const firstPoint = { x: x1, y: y1 } - const secondPoint = { x: x2, y: y2 } - let firstRoofLine, secondRoofLine - roof.lines - .filter((line) => { - if (ridgeVectorX === 0) { - return line.x1 !== line.x2 - } else { - return line.y1 !== line.y2 - } - }) - .forEach((line) => { - if (ridgeVectorX === 0) { - if (line.y1 === firstPoint.y) { - firstRoofLine = line - } - if (line.y1 === secondPoint.y) { - secondRoofLine = line - } - } else { - if (line.x1 === firstPoint.x) { - firstRoofLine = line - } - if (line.x1 === secondPoint.x) { - secondRoofLine = line - } - } - }) - /** 마루 1개에 (위,아래), (좌,우) 로 두가지의 지붕면이 생길 수 있다.*/ - let firstCheckPoints = [] - let secondCheckPoints = [] - - /** - * @param startPoint - * @param nextPoint - * @param endPoint - */ - const findPointToPolygon = (startPoint, nextPoint, endPoint) => { - let findPoint = nextPoint - const points = [startPoint, nextPoint] - let index = 1 - while (!(points[points.length - 1].x === endPoint.x && points[points.length - 1].y === endPoint.y)) { - const prevVector = { x: Math.sign(points[index - 1].x - points[index].x), y: Math.sign(points[index - 1].y - points[index].y) } - const currentPoint = points[index] - - const hipLine = baseHipLines.find( - (line) => - (line.line.x1 === currentPoint.x && line.line.y1 === currentPoint.y) || - (line.line.x2 === currentPoint.x && line.line.y2 === currentPoint.y), - ) - if (hipLine) { - if (hipLine.line.x1 === currentPoint.x && hipLine.line.y1 === currentPoint.y) { - points.push({ x: hipLine.line.x2, y: hipLine.line.y2 }) - } else { - points.push({ x: hipLine.line.x1, y: hipLine.line.y1 }) - } - } else { - const nextRoofLine = roof.lines.find((line) => { - if (prevVector.x !== 0) { - return ( - line.y1 !== line.y2 && - ((line.x1 <= currentPoint.x && currentPoint.x <= line.x2) || (line.x2 <= currentPoint.x && currentPoint.x <= line.x1)) - ) - } else { - return ( - line.x1 !== line.x2 && - ((line.y1 <= currentPoint.y && currentPoint.y <= line.y2) || (line.y2 <= currentPoint.y && currentPoint.y <= line.y1)) - ) - } - }) - const checkLine = new fabric.Line([nextRoofLine.x1, nextRoofLine.y1, nextRoofLine.x2, nextRoofLine.y2], { - stroke: 'yellow', - strokeWidth: 4, - parentId: roofId, - name: 'checkLine', - }) - canvas.add(checkLine) - canvas.renderAll() - - const lineEdge = { vertex1: { x: nextRoofLine.x1, y: nextRoofLine.y1 }, vertex2: { x: nextRoofLine.x2, y: nextRoofLine.y2 } } - let ridgeIntersection - baseGableRidgeLines.forEach((ridgeLine) => { - const ridgeEdge = { vertex1: { x: ridgeLine.x1, y: ridgeLine.y1 }, vertex2: { x: ridgeLine.x2, y: ridgeLine.y2 } } - const intersection = edgesIntersection(lineEdge, ridgeEdge) - console.log('intersection', intersection) - if (intersection && !intersection.isIntersectionOutside) { - ridgeIntersection = { x: intersection.x, y: intersection.y } - } - }) - if (ridgeIntersection) { - points.push(ridgeIntersection) - } else { - if (nextRoofLine.x1 === currentPoint.x && nextRoofLine.y1 === currentPoint.y) { - points.push({ x: nextRoofLine.x2, y: nextRoofLine.y2 }) - } else { - points.push({ x: nextRoofLine.x1, y: nextRoofLine.y1 }) - } - } - } - console.log('points', points, points[points.length - 1], endPoint) - index = index + 1 - } - - canvas - .getObjects() - .filter((obj) => obj.name === 'checkLine' || obj.name === 'checkPoint') - .forEach((obj) => canvas.remove(obj)) - canvas.renderAll() - return points - } - - let startVector - console.log('!firstRoofLine && !secondRoofLine', !firstRoofLine && !secondRoofLine) - let firstPoints, secondPoints - if (!firstRoofLine && !secondRoofLine) { + const uniqueRidgeLines = [] + /** 중복제거 */ + baseGableRidgeLines.forEach((currentLine, index) => { + if (index === 0) { + uniqueRidgeLines.push(currentLine) } else { - if (firstRoofLine) { - firstPoints = findPointToPolygon(firstPoint, { x: firstRoofLine.x1, y: firstRoofLine.y1 }, secondPoint) - secondPoints = findPointToPolygon(firstPoint, { x: firstRoofLine.x2, y: firstRoofLine.y2 }, secondPoint) - } else { - firstPoints = findPointToPolygon(secondPoint, { x: secondRoofLine.x1, y: secondRoofLine.y1 }, firstPoint) - secondPoints = findPointToPolygon(secondPoint, { x: secondRoofLine.x2, y: secondRoofLine.y2 }, firstPoint) - } - } - const firstPolygonPoints = getSortedPoint(firstPoints) - const secondPolygonPoints = getSortedPoint(secondPoints) - - firstPolygonPoints.forEach((point, index) => { - let endPoint - if (index === firstPolygonPoints.length - 1) { - endPoint = firstPolygonPoints[0] - } else { - endPoint = firstPolygonPoints[index + 1] - } - const hipLine = drawHipLine([point.x, point.y, endPoint.x, endPoint.y], canvas, roof, textMode, null, currentDegree, currentDegree) - baseHipLines.push({ x1: point.x, y1: point.y, x2: endPoint.x, y2: endPoint.y, line: hipLine }) - - const checkLine = new fabric.Line([point.x, point.y, endPoint.x, endPoint.y], { - stroke: 'red', - strokeWidth: 4, - parentId: roofId, - name: 'checkLine', - }) - canvas.add(checkLine) - canvas.renderAll() - }) - secondPolygonPoints.forEach((point, index) => { - let endPoint - if (index === secondPolygonPoints.length - 1) { - endPoint = secondPolygonPoints[0] - } else { - endPoint = secondPolygonPoints[index + 1] - } - const hipLine = drawHipLine([point.x, point.y, endPoint.x, endPoint.y], canvas, roof, textMode, null, currentDegree, currentDegree) - baseHipLines.push({ x1: point.x, y1: point.y, x2: endPoint.x, y2: endPoint.y, line: hipLine }) - - const checkLine = new fabric.Line([point.x, point.y, endPoint.x, endPoint.y], { - stroke: 'red', - strokeWidth: 4, - parentId: roofId, - name: 'checkLine', - }) - canvas.add(checkLine) - canvas.renderAll() - }) - }) - - drawGableFirstLines.forEach((current) => { - return - const { currentBaseLine, prevBaseLine, nextBaseLine } = current - const currentLine = currentBaseLine.line - const prevLine = prevBaseLine.line - const nextLine = nextBaseLine.line - let { x1, x2, y1, y2, size } = currentBaseLine - let beforePrevBaseLine, afterNextBaseLine - - /** 이전 라인의 경사 */ - const prevDegree = prevLine.attributes.pitch > 0 ? getDegreeByChon(prevLine.attributes.pitch) : prevLine.attributes.degree - /** 다음 라인의 경사 */ - const nextDegree = nextLine.attributes.pitch > 0 ? getDegreeByChon(nextLine.attributes.pitch) : nextLine.attributes.degree - /** 현재 라인의 경사 */ - const currentDegree = currentLine.attributes.pitch > 0 ? getDegreeByChon(currentLine.attributes.pitch) : currentLine.attributes.degree - - /** 이전 라인의 전라인, 다음 라인의 다음라인을 찾는다 */ - drawBaseLines.forEach((line, index) => { - if (line === prevBaseLine) { - beforePrevBaseLine = drawBaseLines[(index - 1 + drawBaseLines.length) % drawBaseLines.length] - } - if (line === nextBaseLine) { - afterNextBaseLine = drawBaseLines[(index + 1) % drawBaseLines.length] - } - }) - - const beforePrevLine = beforePrevBaseLine?.line - const afterNextLine = afterNextBaseLine?.line - - /** 각 라인의 흐름 방향을 확인한다. */ - const currentAngle = calculateAngle(currentLine.startPoint, currentLine.endPoint) - const prevAngle = calculateAngle(prevLine.startPoint, prevLine.endPoint) - const nextAngle = calculateAngle(nextLine.startPoint, nextLine.endPoint) - const beforePrevAngle = calculateAngle(beforePrevLine.startPoint, beforePrevLine.endPoint) - const afterNextAngle = calculateAngle(afterNextLine.startPoint, afterNextLine.endPoint) - - /** 이전라인의 vector*/ - const prevVectorX = Math.sign(Big(prevLine.x2).minus(Big(prevLine.x1))) - const prevVectorY = Math.sign(Big(prevLine.y2).minus(Big(prevLine.y1))) - - /** 현재라인의 기준점*/ - const currentMidX = Big(x1).plus(Big(x2)).div(2).plus(Big(prevVectorX).times(currentLine.attributes.offset)) - const currentMidY = Big(y1).plus(Big(y2)).div(2).plus(Big(prevVectorY).times(currentLine.attributes.offset)) - - if (beforePrevBaseLine === afterNextBaseLine) { - console.log('박공지붕 사각') - - const afterNextMidX = Big(afterNextLine.x1).plus(Big(afterNextLine.x2)).div(2) - const afterNextMidY = Big(afterNextLine.y1).plus(Big(afterNextLine.y2)).div(2) - const vectorMidX = Math.sign(currentMidX.minus(afterNextMidX)) - const vectorMidY = Math.sign(currentMidY.minus(afterNextMidY)) - - let oppositeMidX, oppositeMidY - if (eavesType.includes(afterNextLine.attributes?.type)) { - const checkSize = currentMidX - .minus(afterNextMidX) - .pow(2) - .plus(currentMidY.minus(afterNextMidY).pow(2)) - .sqrt() - .minus(Big(afterNextLine.attributes.planeSize).div(20)) - .round(1) - oppositeMidX = currentMidX.plus(checkSize.times(vectorMidX).neg()) - oppositeMidY = currentMidY.plus(checkSize.times(vectorMidY).neg()) - - const xVector1 = Math.sign(Big(oppositeMidX).minus(Big(afterNextLine.x1)).neg().toNumber()) - const yVector1 = Math.sign(Big(oppositeMidY).minus(Big(afterNextLine.y1)).neg().toNumber()) - const xVector2 = Math.sign(Big(oppositeMidX).minus(Big(afterNextLine.x2)).neg().toNumber()) - const yVector2 = Math.sign(Big(oppositeMidY).minus(Big(afterNextLine.y2)).neg().toNumber()) - - let addOppositeX1 = 0, - addOppositeY1 = 0, - addOppositeX2 = 0, - addOppositeY2 = 0 - - if (!checkWallPolygon.inPolygon({ x: oppositeMidX.toNumber(), y: oppositeMidY.toNumber() })) { - const checkScale = currentMidX.minus(oppositeMidX).pow(2).plus(currentMidY.minus(oppositeMidY).pow(2)).sqrt() - addOppositeX1 = checkScale.times(xVector1).toNumber() - addOppositeY1 = checkScale.times(yVector1).toNumber() - addOppositeX2 = checkScale.times(xVector2).toNumber() - addOppositeY2 = checkScale.times(yVector2).toNumber() - } - - let scale1 = Big(afterNextLine.attributes.offset).pow(2).plus(Big(nextLine.attributes.offset).pow(2)).sqrt() - scale1 = scale1.eq(0) ? Big(1) : scale1 - let scale2 = Big(afterNextLine.attributes.offset).pow(2).plus(Big(prevLine.attributes.offset).pow(2)).sqrt() - scale2 = scale2.eq(0) ? Big(1) : scale2 - - const checkHip1 = { - x1: Big(afterNextLine.x1).plus(scale1.times(xVector1)).toNumber(), - y1: Big(afterNextLine.y1).plus(scale1.times(yVector1)).toNumber(), - x2: oppositeMidX.plus(addOppositeX1).toNumber(), - y2: oppositeMidY.plus(addOppositeY1).toNumber(), - } - - const checkHip2 = { - x1: Big(afterNextLine.x2).plus(scale2.times(xVector2)).toNumber(), - y1: Big(afterNextLine.y2).plus(scale2.times(yVector2)).toNumber(), - x2: oppositeMidX.plus(addOppositeX2).toNumber(), - y2: oppositeMidY.plus(addOppositeY2).toNumber(), - } - - const intersection1 = findRoofIntersection(roof, checkHip1, { x: oppositeMidX.plus(addOppositeX1), y: oppositeMidY.plus(addOppositeY1) }) - const intersection2 = findRoofIntersection(roof, checkHip2, { x: oppositeMidX.plus(addOppositeX2), y: oppositeMidY.plus(addOppositeY2) }) - - const afterNextDegree = afterNextLine.attributes.pitch > 0 ? getDegreeByChon(afterNextLine.attributes.pitch) : afterNextLine.attributes.degree - - if (intersection1) { - const hipLine = drawHipLine( - [intersection1.intersection.x, intersection1.intersection.y, oppositeMidX.plus(addOppositeX1), oppositeMidY.plus(addOppositeY1)], - canvas, - roof, - textMode, - null, - nextDegree, - afterNextDegree, - ) - baseHipLines.push({ - x1: afterNextLine.x1, - y1: afterNextLine.y1, - x2: oppositeMidX.plus(addOppositeX1).toNumber(), - y2: oppositeMidY.plus(addOppositeY1).toNumber(), - line: hipLine, - }) - } - if (intersection2) { - const hipLine = drawHipLine( - [intersection2.intersection.x, intersection2.intersection.y, oppositeMidX.plus(addOppositeX2), oppositeMidY.plus(addOppositeY2)], - canvas, - roof, - textMode, - null, - prevDegree, - afterNextDegree, - ) - baseHipLines.push({ - x1: afterNextLine.x2, - y1: afterNextLine.y2, - x2: oppositeMidX.plus(addOppositeX2).toNumber(), - y2: oppositeMidY.plus(addOppositeY2).toNumber(), - line: hipLine, - }) - } - } else { - oppositeMidX = Big(afterNextLine.x1).plus(Big(afterNextLine.x2)).div(2).plus(Big(prevVectorX).neg().times(afterNextLine.attributes.offset)) - oppositeMidY = Big(afterNextLine.y1).plus(Big(afterNextLine.y2)).div(2).plus(Big(prevVectorY).neg().times(afterNextLine.attributes.offset)) - } - - const vectorOppositeX = Math.sign(currentMidX.minus(oppositeMidX)) - const vectorOppositeY = Math.sign(currentMidY.minus(oppositeMidY)) - - if (vectorMidX === vectorOppositeX && vectorMidY === vectorOppositeY && baseRidgeCount < getMaxRidge(baseLines.length)) { - const ridge = drawRidgeLine( - [currentMidX.toNumber(), currentMidY.toNumber(), oppositeMidX.toNumber(), oppositeMidY.toNumber()], - canvas, - roof, - textMode, - ) - baseRidgeLines.push(ridge) - baseRidgeCount++ - } - } else { - console.log('4각 아님') - const vectorMidX = Math.sign(Big(nextLine.x2).minus(nextLine.x1)) - const vectorMidY = Math.sign(Big(nextLine.y2).minus(nextLine.y1)) - let oppositeMidX = currentMidX, - oppositeMidY = currentMidY - let prevOppositeMidX, prevOppositeMidY, nextOppositeMidX, nextOppositeMidY - const beforePrevOffset = - currentAngle === beforePrevAngle - ? Big(beforePrevLine.attributes.offset) - : Big(beforePrevLine.attributes.offset).plus(currentLine.attributes.offset) - const afterNextOffset = - currentAngle === afterNextAngle - ? Big(afterNextLine.attributes.offset) - : Big(afterNextLine.attributes.offset).plus(currentLine.attributes.offset) - const prevSize = Big(prevLine.attributes.planeSize).div(10) - const nextSize = Big(nextLine.attributes.planeSize).div(10) - - /** 다음 라인이 그 다음 라인과의 사이에 추녀마루가 존재 하는지 확인. 처마-처마 인 경우 추녀마루*/ - if (eavesType.includes(afterNextLine.attributes?.type)) { - } else { - if (vectorMidX === 0) { - prevOppositeMidY = currentMidY.plus(prevSize.plus(beforePrevOffset).times(vectorMidY)) - prevOppositeMidX = currentMidX - } else { - prevOppositeMidX = currentMidX.plus(prevSize.plus(beforePrevOffset).times(vectorMidX)) - prevOppositeMidY = currentMidY - } - } - - /** 이전 라인이 그 이전 라인과의 사이에 추녀마루가 존재 하는지 확인. 처마-처마 인 경우 추녀마루*/ - if (eavesType.includes(beforePrevLine.attributes?.type)) { - } else { - if (vectorMidX === 0) { - nextOppositeMidY = currentMidY.plus(nextSize.plus(afterNextOffset).times(vectorMidY)) - nextOppositeMidX = currentMidX - } else { - nextOppositeMidX = currentMidX.plus(nextSize.plus(afterNextOffset).times(vectorMidX)) - nextOppositeMidY = currentMidY - } - } - - const checkPrevSize = currentMidX.minus(prevOppositeMidX).pow(2).plus(currentMidY.minus(prevOppositeMidY).pow(2)).sqrt() - const checkNextSize = currentMidX.minus(nextOppositeMidX).pow(2).plus(currentMidY.minus(nextOppositeMidY).pow(2)).sqrt() - - /** 두 포인트 중에 current와 가까운 포인트를 사용*/ - if (checkPrevSize.gt(checkNextSize)) { - oppositeMidY = nextOppositeMidY - oppositeMidX = nextOppositeMidX - } else { - oppositeMidY = prevOppositeMidY - oppositeMidX = prevOppositeMidX - } - - if (baseRidgeCount < getMaxRidge(baseLines.length)) { - const ridgeLine = drawRidgeLine([currentMidX, currentMidY, oppositeMidX, oppositeMidY], canvas, roof, textMode) - baseRidgeLines.push(ridgeLine) - baseRidgeCount++ + const duplicateLines = uniqueRidgeLines.filter( + (line) => + (currentLine.x1 === line.x1 && currentLine.y1 === line.y1 && currentLine.x2 === line.x2 && currentLine.y2 === line.y2) || + (currentLine.x1 === line.x2 && currentLine.y1 === line.y2 && currentLine.x2 === line.x1 && currentLine.y2 === line.y1), + ) + if (duplicateLines.length === 0) { + uniqueRidgeLines.push(currentLine) } } }) + baseGableRidgeLines = uniqueRidgeLines + + console.log('baseGableRidgeLines : ', baseGableRidgeLines) + /** 박공지붕 polygon 생성 */ drawGablePolygonFirst.forEach((current) => { const { currentBaseLine, prevBaseLine, nextBaseLine } = current @@ -2093,14 +1712,24 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { roofY2 = roofY1 } - const prevRoofLine = roof.lines.find( - (line) => currentVectorX !== Math.sign(line.x2 - line.x1) && line.x2 === roofX1.toNumber() && line.y2 === roofY1.toNumber(), - ) - const nextRoofLine = roof.lines.find( - (line) => currentVectorX !== Math.sign(line.x2 - line.x1) && line.x1 === roofX2.toNumber() && line.y1 === roofY2.toNumber(), - ) + let prevRoofLine, nextRoofLine + const roofEdge = { vertex1: { x: roofX1.toNumber(), y: roofY1.toNumber() }, vertex2: { x: roofX2.toNumber(), y: roofY2.toNumber() } } + roof.lines + .filter((line) => (currentVectorX === 0 ? line.y1 === line.y2 : line.x1 === line.x2)) + .forEach((line) => { + const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } + const intersection = edgesIntersection(roofEdge, lineEdge) + if (intersection) { + if (roofX1.eq(intersection.x) && roofY1.eq(intersection.y)) { + prevRoofLine = line + } + if (roofX2.eq(intersection.x) && roofY2.eq(intersection.y)) { + nextRoofLine = line + } + } + }) - const prevRoofEdge = { vertex1: { x: prevRoofLine.x1, y: prevRoofLine.y1 }, vertex2: { x: prevRoofLine.x2, y: prevRoofLine.y2 } } + const prevRoofEdge = { vertex1: { x: prevRoofLine.x2, y: prevRoofLine.y2 }, vertex2: { x: prevRoofLine.x1, y: prevRoofLine.y1 } } const nextRoofEdge = { vertex1: { x: nextRoofLine.x1, y: nextRoofLine.y1 }, vertex2: { x: nextRoofLine.x2, y: nextRoofLine.y2 } } baseGableRidgeLines.forEach((ridge) => { @@ -2110,21 +1739,18 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { if (prevIs) { prevLineRidges.push({ ridge, - size: calcLinePlaneSize({ x1: ridgeEdge.vertex1.x, y1: ridgeEdge.vertex1.y, x2: prevIs.x, y2: prevIs.y }), + size: calcLinePlaneSize({ x1: roofX1, y1: roofY1, x2: prevIs.x, y2: prevIs.y }), }) } if (nextIs) { nextLineRidges.push({ ridge, - size: calcLinePlaneSize({ x1: ridgeEdge.vertex1.x, y1: ridgeEdge.vertex1.y, x2: nextIs.x, y2: nextIs.y }), + size: calcLinePlaneSize({ x1: roofX2, y1: roofY2, x2: nextIs.x, y2: nextIs.y }), }) } }) - const polygonPoints = [ - { x: roofX1.toNumber(), y: roofY1.toNumber() }, - { x: roofX2.toNumber(), y: roofY2.toNumber() }, - ] + const polygonPoints = [] let prevLineRidge, nextLineRidge if (prevLineRidges.length > 0) { @@ -2154,9 +1780,44 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { nextLineRidge = nextLineRidges[0].ridge } + /** 마루에서 현재라인으로 향하는 외벽선까지의 포인트를 확인 하여 처리*/ + let checkEdge + if (currentVectorX === 0) { + checkEdge = { vertex1: { x: prevLineRidge.x1, y: roofY1.toNumber() }, vertex2: { x: roofX1.toNumber(), y: roofY1.toNumber() } } + } else { + checkEdge = { vertex1: { x: roofX1.toNumber(), y: prevLineRidge.y1 }, vertex2: { x: roofX1.toNumber(), y: roofY1.toNumber() } } + } + const checkVectorX = Math.sign(checkEdge.vertex1.x - checkEdge.vertex2.x) + const checkVectorY = Math.sign(checkEdge.vertex1.y - checkEdge.vertex2.y) + const intersectPoints = [] + roof.lines + .filter((line) => (currentVectorX === 0 ? line.x1 === line.x2 : line.y1 === line.y2)) + .forEach((line) => { + const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } + const is = edgesIntersection(checkEdge, lineEdge) + if (is) { + const isVectorX = Math.sign(checkEdge.vertex1.x - is.x) + const isVectorY = Math.sign(checkEdge.vertex1.y - is.y) + const isLineOtherPoint = is.x === line.x1 && is.y === line.y1 ? { x: line.x2, y: line.y2 } : { x: line.x1, y: line.y1 } + const lineVectorX = Math.sign(isLineOtherPoint.x - is.x) + const lineVectorY = Math.sign(isLineOtherPoint.y - is.y) + if (checkVectorX === isVectorX && checkVectorY === isVectorY && lineVectorX === currentVectorX && lineVectorY === currentVectorY) { + intersectPoints.push(is) + } + } + }) + let intersect = intersectPoints[0] + if (currentVectorX === 0) { + polygonPoints.push({ x: intersect.x, y: roofY1.toNumber() }, { x: intersect.x, y: roofY2.toNumber() }) + } else { + polygonPoints.push({ x: roofX1.toNumber(), y: intersect.y }, { x: roofX2.toNumber(), y: intersect.y }) + } + if (prevLineRidge === nextLineRidge) { + /** 4각*/ polygonPoints.push({ x: prevLineRidge.x1, y: prevLineRidge.y1 }, { x: prevLineRidge.x2, y: prevLineRidge.y2 }) } else { + /** 6각이상*/ let isOverLap = currentVectorX === 0 ? (prevLineRidge.y1 <= nextLineRidge.y1 && prevLineRidge.y2 >= nextLineRidge.y1) || @@ -2167,12 +1828,13 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { (prevLineRidge.x1 >= nextLineRidge.x1 && prevLineRidge.x2 <= nextLineRidge.x1) || (prevLineRidge.x1 <= nextLineRidge.x2 && prevLineRidge.x2 >= nextLineRidge.x2) || (prevLineRidge.x1 >= nextLineRidge.x2 && prevLineRidge.x2 <= nextLineRidge.x2) - console.log('isOverLap : ', isOverLap) if (isOverLap) { const prevDistance = currentVectorX === 0 ? Math.abs(prevLineRidge.x1 - roofX1.toNumber()) : Math.abs(prevLineRidge.y1 - roofY1.toNumber()) const nextDistance = currentVectorX === 0 ? Math.abs(nextLineRidge.x1 - roofX1.toNumber()) : Math.abs(nextLineRidge.y1 - roofY1.toNumber()) /** 현재 지붕 라인과 먼 라인의 포인트를 온전히 사용한다. */ - if (prevDistance < nextDistance) { + if (prevDistance === nextDistance) { + console.log('prevDistance === nextDistance') + } else if (prevDistance < nextDistance) { polygonPoints.push({ x: nextLineRidge.x1, y: nextLineRidge.y1 }, { x: nextLineRidge.x2, y: nextLineRidge.y2 }) /** 이전라인과 교차한 마루의 포인트*/ const prevRidgePoint1 = @@ -2227,11 +1889,178 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { polygonPoints.push(nextRidgePoint2) } } else { + /** 마루가 겹치지 않을때 */ + const otherRidgeLines = [] + + baseGableRidgeLines + .filter((ridge) => ridge !== prevLineRidge && ridge !== nextLineRidge) + .filter((ridge) => (currentVectorX === 0 ? ridge.x1 === ridge.x2 : ridge.y1 === ridge.y2)) + .filter((ridge) => + currentVectorX === 0 ? nextVectorX === Math.sign(nextLine.x1 - ridge.x1) : nextVectorY === Math.sign(nextLine.y1 - ridge.y1), + ) + .forEach((ridge) => { + const size = currentVectorX === 0 ? Math.abs(nextLine.x1 - ridge.x1) : Math.abs(nextLine.y1 - ridge.y1) + otherRidgeLines.push({ ridge, size }) + }) + if (otherRidgeLines.length > 0) { + const otherRidge = otherRidgeLines.sort((a, b) => a.size - b.size)[0].ridge + /** + * otherRidge이 prevRidgeLine, nextRidgeLine 과 currentLine의 사이에 있는지 확인해서 분할하여 작업 + * 지붕의 덮힘이 다르기 때문 + */ + const isInside = + currentVectorX === 0 + ? Math.abs(currentLine.x1 - otherRidge.x1) < Math.abs(currentLine.x1 - prevLineRidge.x1) && + Math.abs(currentLine.x1 - otherRidge.x1) < Math.abs(currentLine.x1 - nextLineRidge.x1) + : Math.abs(currentLine.y1 - otherRidge.y1) < Math.abs(currentLine.y1 - prevLineRidge.y1) && + Math.abs(currentLine.y1 - otherRidge.y1) < Math.abs(currentLine.y1 - nextLineRidge.y1) + + if (isInside) { + polygonPoints.push( + { x: prevLineRidge.x1, y: prevLineRidge.y1 }, + { x: prevLineRidge.x2, y: prevLineRidge.y2 }, + { x: nextLineRidge.x1, y: nextLineRidge.y1 }, + { x: nextLineRidge.x2, y: nextLineRidge.y2 }, + ) + + let ridgeAllPoints = [ + { x: prevLineRidge.x1, y: prevLineRidge.y1 }, + { x: prevLineRidge.x2, y: prevLineRidge.y2 }, + { x: nextLineRidge.x1, y: nextLineRidge.y1 }, + { x: nextLineRidge.x2, y: nextLineRidge.y2 }, + ] + let ridgePoints = [] + ridgeAllPoints.forEach((point) => { + let isOnLine = false + roof.lines.forEach((line) => { + if (isPointOnLine({ x1: line.x1, y1: line.y1, x2: line.x2, y2: line.y2 }, point)) { + isOnLine = true + } + }) + if (!isOnLine) { + ridgePoints.push(point) + } + }) + if (ridgePoints.length === 2) { + if (Math.sign(otherRidge.x1 - otherRidge.x2) === 0) { + polygonPoints.push({ x: otherRidge.x1, y: ridgePoints[0].y }, { x: otherRidge.x1, y: ridgePoints[1].y }) + } else { + polygonPoints.push({ x: ridgePoints[0].x, y: otherRidge.y1 }, { x: ridgePoints[1].x, y: otherRidge.y1 }) + } + } + } else { + polygonPoints.push({ x: otherRidge.x1, y: otherRidge.y1 }, { x: otherRidge.x2, y: otherRidge.y2 }) + + let ridgePoints = [ + { x: prevLineRidge.x1, y: prevLineRidge.y1 }, + { x: prevLineRidge.x2, y: prevLineRidge.y2 }, + { x: nextLineRidge.x1, y: nextLineRidge.y1 }, + { x: nextLineRidge.x2, y: nextLineRidge.y2 }, + ] + + ridgePoints.forEach((point) => { + let isOnLine = false + roof.lines.forEach((line) => { + if (isPointOnLine({ x1: line.x1, y1: line.y1, x2: line.x2, y2: line.y2 }, point)) { + isOnLine = true + } + }) + if (isOnLine) { + polygonPoints.push(point) + } + }) + + if (Math.sign(otherRidge.x1 - otherRidge.x2) === 0) { + const prevY = + (prevLineRidge.y1 <= otherRidge.y1 && otherRidge.y1 <= prevLineRidge.y2) || + (prevLineRidge.y1 >= otherRidge.y1 && otherRidge.y1 >= prevLineRidge.y2) + ? otherRidge.y1 + : otherRidge.y2 + const nextY = + (nextLineRidge.y1 <= otherRidge.y1 && otherRidge.y1 <= nextLineRidge.y2) || + (nextLineRidge.y1 >= otherRidge.y1 && otherRidge.y1 >= nextLineRidge.y2) + ? otherRidge.y1 + : otherRidge.y2 + polygonPoints.push({ x: prevLineRidge.x1, y: prevY }, { x: nextLineRidge.x1, y: nextY }) + } else { + const prevX = + (prevLineRidge.x1 <= otherRidge.x1 && otherRidge.x1 <= prevLineRidge.x2) || + (prevLineRidge.x1 >= otherRidge.x1 && otherRidge.x1 >= prevLineRidge.x2) + ? otherRidge.x1 + : otherRidge.x2 + const nextX = + (nextLineRidge.x1 <= otherRidge.x1 && otherRidge.x1 <= nextLineRidge.x2) || + (nextLineRidge.x1 >= otherRidge.x1 && otherRidge.x1 >= nextLineRidge.x2) + ? otherRidge.x1 + : otherRidge.x2 + polygonPoints.push({ x: prevX, y: prevLineRidge.y1 }, { x: nextX, y: nextLineRidge.y1 }) + } + } + } } } const sortedPolygonPoints = getSortedPoint(polygonPoints) + /** 외벽선 밖으로 나가있는 포인트*/ + const outsidePoints = polygonPoints.filter((point) => { + let isOutside = true + roof.lines.forEach((line) => { + if ( + (line.x1 <= point.x && line.x2 >= point.x && line.y1 <= point.y && line.y2 >= point.y) || + (line.x1 >= point.x && line.x2 <= point.x && line.y1 >= point.y && line.y2 <= point.y) + ) { + isOutside = false + } + }) + baseGableRidgeLines.forEach((line) => { + if ( + (line.x1 <= point.x && line.x2 >= point.x && line.y1 <= point.y && line.y2 >= point.y) || + (line.x1 >= point.x && line.x2 <= point.x && line.y1 >= point.y && line.y2 <= point.y) + ) { + isOutside = false + } + }) + return isOutside + }) + + if (outsidePoints.length > 0) { + sortedPolygonPoints.forEach((currentPoint, index) => { + if (outsidePoints.includes(currentPoint)) { + const prevPoint = sortedPolygonPoints[(index - 1 + sortedPolygonPoints.length) % sortedPolygonPoints.length] + const nextPoint = sortedPolygonPoints[(index + 1) % sortedPolygonPoints.length] + const vectorX = Math.sign(currentPoint.x - prevPoint.x) + const vectorY = Math.sign(currentPoint.y - prevPoint.y) + + const checkEdge = { vertex1: { x: prevPoint.x, y: prevPoint.y }, vertex2: { x: currentPoint.x, y: currentPoint.y } } + const intersectPoints = [] + roof.lines + .filter((line) => (vectorX === 0 ? line.x1 !== line.x2 : line.y1 !== line.y2)) + .forEach((line) => { + const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } + const is = edgesIntersection(checkEdge, lineEdge) + if (is && !is.isIntersectionOutside) { + const isVectorX = Math.sign(is.x - prevPoint.x) + const isVectorY = Math.sign(is.y - prevPoint.y) + if ((vectorX === 0 && vectorY === isVectorY) || (vectorY === 0 && vectorX === isVectorX)) { + intersectPoints.push(is) + } + } + }) + if (intersectPoints.length > 0) { + const intersection = intersectPoints[0] + if (vectorX === 0) { + currentPoint.y = intersection.y + nextPoint.y = intersection.y + } else { + currentPoint.x = intersection.x + nextPoint.x = intersection.x + } + } + } + }) + } + sortedPolygonPoints.forEach((startPoint, index) => { let endPoint if (index === sortedPolygonPoints.length - 1) { @@ -2239,6 +2068,7 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { } else { endPoint = sortedPolygonPoints[index + 1] } + const hipLine = drawHipLine([startPoint.x, startPoint.y, endPoint.x, endPoint.y], canvas, roof, textMode, null, currentDegree, currentDegree) if (currentVectorX === 0) { if (Math.sign(startPoint.x - endPoint.x) === 0) { @@ -2251,12 +2081,6 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { } baseHipLines.push({ x1: hipLine.x1, y1: hipLine.y1, x2: hipLine.x2, y2: hipLine.y2, line: hipLine }) }) - - canvas - .getObjects() - .filter((obj) => obj.name === 'checkLine' || obj.name === 'checkRoofLine') - .forEach((obj) => canvas.remove(obj)) - canvas.renderAll() }) drawGablePolygonSecond.forEach((current) => { @@ -2279,100 +2103,182 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { const prevOffset = prevLine.attributes.offset const nextOffset = nextLine.attributes.offset - const prevEdge = { vertex1: { x: prevLine.x1, y: prevLine.y1 }, vertex2: { x: prevLine.x2, y: prevLine.y2 } } - const prevRidge = baseGableRidgeLines.find((ridge) => { - const ridgeEdge = { vertex1: { x: ridge.x1, y: ridge.y1 }, vertex2: { x: ridge.x2, y: ridge.y2 } } - const is = edgesIntersection(prevEdge, ridgeEdge) - if (is && !is.isIntersectionOutside) { - return ridge - } - }) + const prevAngle = calculateAngle(prevLine.startPoint, prevLine.endPoint) + const nextAngle = calculateAngle(nextLine.startPoint, nextLine.endPoint) - const nextEdge = { vertex1: { x: nextLine.x1, y: nextLine.y1 }, vertex2: { x: nextLine.x2, y: nextLine.y2 } } - const nextRidge = baseGableRidgeLines.find((ridge) => { - const ridgeEdge = { vertex1: { x: ridge.x1, y: ridge.y1 }, vertex2: { x: ridge.x2, y: ridge.y2 } } - const is = edgesIntersection(nextEdge, ridgeEdge) - if (is && !is.isIntersectionOutside) { - return ridge - } - }) + const polygonPoints = [] + if (Big(prevAngle).minus(Big(nextAngle)).abs().eq(180)) { + const currentRidge = baseGableRidgeLines.find((line) => + currentVectorX === 0 + ? (line.y1 === y1 && line.y2 === y2) || (line.y1 === y2 && line.y2 === y1) + : (line.x1 === x1 && line.x2 === x2) || (line.x1 === x2 && line.x2 === x1), + ) + if (currentRidge) { + const ridgeVectorX = Math.sign(currentRidge.x1 - currentRidge.x2) + const ridgeVectorY = Math.sign(currentRidge.y1 - currentRidge.y2) - let currentRidge - - if (prevRidge) { - if ( - currentVectorX === 0 && - ((prevRidge.y1 <= currentLine.y1 && prevRidge.y2 >= currentLine.y1 && prevRidge.y1 <= currentLine.y2 && prevRidge.y2 >= currentLine.y2) || - (prevRidge.y1 >= currentLine.y1 && prevRidge.y2 <= currentLine.y1 && prevRidge.y1 >= currentLine.y2 && prevRidge.y2 <= currentLine.y2)) - ) { - currentRidge = prevRidge - } - if ( - currentVectorY === 0 && - ((prevRidge.x1 <= currentLine.x1 && prevRidge.x2 >= currentLine.x1 && prevRidge.x1 <= currentLine.x2 && prevRidge.x2 >= currentLine.x2) || - (prevRidge.x1 >= currentLine.x1 && prevRidge.x2 <= currentLine.x1 && prevRidge.x1 >= currentLine.x2 && prevRidge.x2 <= currentLine.x2)) - ) { - currentRidge = prevRidge - } - } - if (nextRidge) { - if ( - currentVectorX === 0 && - ((nextRidge.y1 <= currentLine.y1 && nextRidge.y2 >= currentLine.y1 && nextRidge.y1 <= currentLine.y2 && nextRidge.y2 >= currentLine.y2) || - (nextRidge.y1 >= currentLine.y1 && nextRidge.y2 <= currentLine.y1 && nextRidge.y1 >= currentLine.y2 && nextRidge.y2 <= currentLine.y2)) - ) { - currentRidge = nextRidge - } - if ( - currentVectorY === 0 && - ((nextRidge.x1 <= currentLine.x1 && nextRidge.x2 >= currentLine.x1 && nextRidge.x1 <= currentLine.x2 && nextRidge.x2 >= currentLine.x2) || - (nextRidge.x1 >= currentLine.x1 && nextRidge.x2 <= currentLine.x1 && nextRidge.x1 >= currentLine.x2 && nextRidge.x2 <= currentLine.x2)) - ) { - currentRidge = nextRidge - } - } - if (currentRidge) { - const vectorX = currentVectorX === 0 ? Math.sign(currentLine.x1 - currentRidge.x1) : 0 - const vectorY = currentVectorY === 0 ? Math.sign(currentLine.y1 - currentRidge.y1) : 0 - - const polygonPoints = [ - { x: currentRidge.x1, y: currentRidge.y1 }, - { x: currentRidge.x2, y: currentRidge.y2 }, - ] - if (currentVectorX === 0) { - polygonPoints.push( - { x: Big(currentLine.x1).plus(Big(currentOffset).times(vectorX)).toNumber(), y: currentRidge.y1 }, - { x: Big(currentLine.x2).plus(Big(currentOffset).times(vectorX)).toNumber(), y: currentRidge.y2 }, - ) - } else { - polygonPoints.push( - { x: currentRidge.x1, y: Big(currentLine.y1).plus(Big(currentOffset).times(vectorY)).toNumber() }, - { x: currentRidge.x2, y: Big(currentLine.y2).plus(Big(currentOffset).times(vectorY)).toNumber() }, - ) - } - - const sortedPolygonPoints = getSortedPoint(polygonPoints) - - sortedPolygonPoints.forEach((startPoint, index) => { - let endPoint - if (index === sortedPolygonPoints.length - 1) { - endPoint = sortedPolygonPoints[0] - } else { - endPoint = sortedPolygonPoints[index + 1] - } - const hipLine = drawHipLine([startPoint.x, startPoint.y, endPoint.x, endPoint.y], canvas, roof, textMode, null, currentDegree, currentDegree) + let checkEdge if (currentVectorX === 0) { - if (Math.sign(startPoint.x - endPoint.x) === 0) { - hipLine.attributes.actualSize = hipLine.attributes.planeSize + checkEdge = { vertex1: { x: currentRidge.x1, y: currentRidge.y1 }, vertex2: { x: currentLine.x1, y: currentRidge.y1 } } + } else { + checkEdge = { vertex1: { x: currentRidge.x1, y: currentRidge.y1 }, vertex2: { x: currentRidge.x1, y: currentLine.y1 } } + } + const isRoofLines = [] + roof.lines + .filter((line) => (currentVectorX === 0 ? line.x1 === line.x2 : line.y1 === line.y2)) + .forEach((line) => { + const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } + const is = edgesIntersection(checkEdge, lineEdge) + if (is) { + const checkVectorX = Math.sign(checkEdge.vertex1.x - checkEdge.vertex2.x) + const checkVectorY = Math.sign(checkEdge.vertex1.y - checkEdge.vertex2.y) + const isVectorX = Math.sign(checkEdge.vertex1.x - is.x) + const isVectorY = Math.sign(checkEdge.vertex1.y - is.y) + if ((ridgeVectorX === 0 && checkVectorX === isVectorX) || (ridgeVectorY === 0 && checkVectorY === isVectorY)) { + const size = ridgeVectorX === 0 ? Math.abs(checkEdge.vertex1.x - is.x) : Math.abs(checkEdge.vertex1.y - is.y) + isRoofLines.push({ line, size }) + } + } + }) + isRoofLines.sort((a, b) => a.size - b.size) + const roofLine = isRoofLines[0].line + polygonPoints.push({ x: currentRidge.x1, y: currentRidge.y1 }, { x: currentRidge.x2, y: currentRidge.y2 }) + if (ridgeVectorX === 0) { + polygonPoints.push({ x: roofLine.x1, y: currentRidge.y1 }, { x: roofLine.x1, y: currentRidge.y2 }) + } else { + polygonPoints.push({ x: currentRidge.x1, y: roofLine.y1 }, { x: currentRidge.x2, y: roofLine.y1 }) + } + } + } else { + const prevEdge = { vertex1: { x: prevLine.x1, y: prevLine.y1 }, vertex2: { x: prevLine.x2, y: prevLine.y2 } } + const prevRidge = baseGableRidgeLines.find((ridge) => { + const ridgeEdge = { vertex1: { x: ridge.x1, y: ridge.y1 }, vertex2: { x: ridge.x2, y: ridge.y2 } } + const is = edgesIntersection(prevEdge, ridgeEdge) + if (is && !is.isIntersectionOutside) { + return ridge + } + }) + + const nextEdge = { vertex1: { x: nextLine.x1, y: nextLine.y1 }, vertex2: { x: nextLine.x2, y: nextLine.y2 } } + const nextRidge = baseGableRidgeLines.find((ridge) => { + const ridgeEdge = { vertex1: { x: ridge.x1, y: ridge.y1 }, vertex2: { x: ridge.x2, y: ridge.y2 } } + const is = edgesIntersection(nextEdge, ridgeEdge) + if (is && !is.isIntersectionOutside) { + return ridge + } + }) + + let currentRidge + + if (prevRidge) { + if ( + currentVectorX === 0 && + ((prevRidge.y1 <= currentLine.y1 && prevRidge.y2 >= currentLine.y1 && prevRidge.y1 <= currentLine.y2 && prevRidge.y2 >= currentLine.y2) || + (prevRidge.y1 >= currentLine.y1 && prevRidge.y2 <= currentLine.y1 && prevRidge.y1 >= currentLine.y2 && prevRidge.y2 <= currentLine.y2)) + ) { + currentRidge = prevRidge + } + if ( + currentVectorY === 0 && + ((prevRidge.x1 <= currentLine.x1 && prevRidge.x2 >= currentLine.x1 && prevRidge.x1 <= currentLine.x2 && prevRidge.x2 >= currentLine.x2) || + (prevRidge.x1 >= currentLine.x1 && prevRidge.x2 <= currentLine.x1 && prevRidge.x1 >= currentLine.x2 && prevRidge.x2 <= currentLine.x2)) + ) { + currentRidge = prevRidge + } + } + if (nextRidge) { + if ( + currentVectorX === 0 && + ((nextRidge.y1 <= currentLine.y1 && nextRidge.y2 >= currentLine.y1 && nextRidge.y1 <= currentLine.y2 && nextRidge.y2 >= currentLine.y2) || + (nextRidge.y1 >= currentLine.y1 && nextRidge.y2 <= currentLine.y1 && nextRidge.y1 >= currentLine.y2 && nextRidge.y2 <= currentLine.y2)) + ) { + currentRidge = nextRidge + } + if ( + currentVectorY === 0 && + ((nextRidge.x1 <= currentLine.x1 && nextRidge.x2 >= currentLine.x1 && nextRidge.x1 <= currentLine.x2 && nextRidge.x2 >= currentLine.x2) || + (nextRidge.x1 >= currentLine.x1 && nextRidge.x2 <= currentLine.x1 && nextRidge.x1 >= currentLine.x2 && nextRidge.x2 <= currentLine.x2)) + ) { + currentRidge = nextRidge + } + } + if (currentRidge) { + polygonPoints.push({ x: currentRidge.x1, y: currentRidge.y1 }, { x: currentRidge.x2, y: currentRidge.y2 }) + + /** 마루에서 현재라인으로 향하는 외벽선까지의 포인트를 확인 하여 처리*/ + let checkEdge + if (currentVectorX === 0) { + checkEdge = { + vertex1: { x: currentRidge.x1, y: currentRidge.y1 }, + vertex2: { x: currentLine.x1, y: currentRidge.y1 }, } } else { - if (Math.sign(startPoint.y - endPoint.y) === 0) { - hipLine.attributes.actualSize = hipLine.attributes.planeSize + checkEdge = { + vertex1: { x: currentRidge.x1, y: currentRidge.y1 }, + vertex2: { x: currentRidge.x1, y: currentLine.y1 }, } } - baseHipLines.push({ x1: hipLine.x1, y1: hipLine.y1, x2: hipLine.x2, y2: hipLine.y2, line: hipLine }) - }) + + const checkVectorX = Math.sign(checkEdge.vertex1.x - checkEdge.vertex2.x) + const checkVectorY = Math.sign(checkEdge.vertex1.y - checkEdge.vertex2.y) + const intersectPoints = [] + roof.lines + .filter((line) => (currentVectorX === 0 ? line.x1 === line.x2 : line.y1 === line.y2)) + .forEach((line) => { + const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } + const is = edgesIntersection(checkEdge, lineEdge) + if (is) { + const isVectorX = Math.sign(checkEdge.vertex1.x - is.x) + const isVectorY = Math.sign(checkEdge.vertex1.y - is.y) + const isLineOtherPoint = + is.x === line.x1 && is.y === line.y1 + ? { x: line.x2, y: line.y2 } + : { + x: line.x1, + y: line.y1, + } + const isInPoint = + checkVectorX === 0 + ? (currentRidge.x1 <= isLineOtherPoint.x && isLineOtherPoint.x <= currentRidge.x2) || + (currentRidge.x1 >= isLineOtherPoint.x && isLineOtherPoint.x >= currentRidge.x2) + : (currentRidge.y1 <= isLineOtherPoint.y && isLineOtherPoint.y <= currentRidge.y2) || + (currentRidge.y1 >= isLineOtherPoint.y && isLineOtherPoint.y >= currentRidge.y2) + if (checkVectorX === isVectorX && checkVectorY === isVectorY && isInPoint) { + const size = Big(checkEdge.vertex1.x).minus(is.x).abs().pow(2).plus(Big(checkEdge.vertex1.y).minus(is.y).abs().pow(2)).sqrt() + intersectPoints.push({ is, size }) + } + } + }) + intersectPoints.sort((a, b) => a.size - b.size) + let intersect = intersectPoints[0].is + if (currentVectorX === 0) { + polygonPoints.push({ x: intersect.x, y: currentRidge.y1 }, { x: intersect.x, y: currentRidge.y2 }) + } else { + polygonPoints.push({ x: currentRidge.x1, y: intersect.y }, { x: currentRidge.x2, y: intersect.y }) + } + } } + + const sortedPolygonPoints = getSortedPoint(polygonPoints) + sortedPolygonPoints.forEach((startPoint, index) => { + let endPoint + if (index === sortedPolygonPoints.length - 1) { + endPoint = sortedPolygonPoints[0] + } else { + endPoint = sortedPolygonPoints[index + 1] + } + const hipLine = drawHipLine([startPoint.x, startPoint.y, endPoint.x, endPoint.y], canvas, roof, textMode, null, currentDegree, currentDegree) + if (currentVectorX === 0) { + if (Math.sign(startPoint.x - endPoint.x) === 0) { + hipLine.attributes.actualSize = hipLine.attributes.planeSize + } + } else { + if (Math.sign(startPoint.y - endPoint.y) === 0) { + hipLine.attributes.actualSize = hipLine.attributes.planeSize + } + } + baseHipLines.push({ x1: hipLine.x1, y1: hipLine.y1, x2: hipLine.x2, y2: hipLine.y2, line: hipLine }) + }) }) /** ⨆ 모양 처마에 추녀마루를 그린다. */ @@ -4716,7 +4622,8 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { }) const innerLines = [...baseRidgeLines, ...baseGableRidgeLines, ...baseHipLines.map((line) => line.line)] - const uniqueInnerLines = [] + roof.innerLines = innerLines + /*const uniqueInnerLines = [] innerLines.forEach((currentLine) => { const sameLines = uniqueInnerLines.filter( @@ -4733,7 +4640,7 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { } }) canvas.renderAll() - roof.innerLines = uniqueInnerLines + roof.innerLines = uniqueInnerLines*/ /** 확인용 라인, 포인트 제거 */ canvas @@ -7729,18 +7636,20 @@ const getSortedPoint = (points) => { let prevPoint = startPoint for (let i = 0; i < points.length - 1; i++) { + const samePoints = [] points .filter((point) => !sortedPoints.includes(point)) .forEach((point) => { if (i % 2 === 1 && prevPoint.y === point.y) { - sortedPoints.push(point) - prevPoint = point + samePoints.push({ point, size: Math.abs(point.x - prevPoint.x) }) } if (i % 2 === 0 && prevPoint.x === point.x) { - sortedPoints.push(point) - prevPoint = point + samePoints.push({ point, size: Math.abs(point.y - prevPoint.y) }) } }) + const samePoint = samePoints.sort((a, b) => a.size - b.size)[0].point + sortedPoints.push(samePoint) + prevPoint = samePoint } return sortedPoints } -- 2.47.2 From d54103943d8605d212f712b9fde7dacf51c3111b Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Fri, 9 May 2025 13:45:10 +0900 Subject: [PATCH 053/109] =?UTF-8?q?0=EB=B2=88=EC=A7=B8=EC=9D=BC=20?= =?UTF-8?q?=EA=B2=BD=EC=9A=B0=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/util/qpolygon-utils.js | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/util/qpolygon-utils.js b/src/util/qpolygon-utils.js index 475d2ae6..1af80680 100644 --- a/src/util/qpolygon-utils.js +++ b/src/util/qpolygon-utils.js @@ -314,15 +314,22 @@ export function removeDuplicatePolygons(polygons) { // 현재 point의 x와 이전 포인트의 x와 같을경우, 다음 포인트의 x와 달라야 함. // 현재 point의 y와 이전 포인트의 y와 같을경우, 다음 포인트의 y와 달라야 함. const isValidPoints = (points) => { - for (let i = 1; i < points.length; i++) { - let prev = points[i - 1] - let curr = points[i] - let next = points[i + 1] - - if (i === points.length - 1) { + let prev + let curr + let next + for (let i = 0; i < points.length; i++) { + if (i === 0) { + prev = points[points.length - 1] + curr = points[i] + next = points[i + 1] + } else if (i === points.length - 1) { prev = points[i - 1] curr = points[i] next = points[0] + } else { + prev = points[i - 1] + curr = points[i] + next = points[i + 1] } // 현재와 이전의 x가 같다면 다음의 x는 달라야 함 -- 2.47.2 From b8dad6211697ceea8522f805c55c5ee8f89df825 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=AF=BC=EC=8B=9D?= <43837214+Minsiki@users.noreply.github.com> Date: Fri, 9 May 2025 14:19:05 +0900 Subject: [PATCH 054/109] =?UTF-8?q?[929]=20:=20[=E7=BF=BB=E8=A8=B3?= =?UTF-8?q?=E5=A4=89=E6=9B=B4]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [작업내용] : 다국어 수정 및 validation 로직 추가 --- .../floor-plan/modal/basic/step/Trestle.jsx | 10 +++++++-- src/locales/ja.json | 21 ++++++++++--------- src/locales/ko.json | 1 + 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/src/components/floor-plan/modal/basic/step/Trestle.jsx b/src/components/floor-plan/modal/basic/step/Trestle.jsx index 6a1ef3e2..e1cca8db 100644 --- a/src/components/floor-plan/modal/basic/step/Trestle.jsx +++ b/src/components/floor-plan/modal/basic/step/Trestle.jsx @@ -125,6 +125,12 @@ const Trestle = forwardRef((props, ref) => { useEffect(() => { if (constructionList.length > 0) { setSelectedConstruction(constructionList.find((construction) => construction.constTp === trestleState?.construction?.constTp) ?? null) + if (constructionList.filter((construction) => construction.constPossYn === 'Y').length === 0) { + Swal.fire({ + title: getMessage('modal.module.basic.settting.module.error4', [selectedRoof?.nameJp]), // 시공법법을 선택해주세요. + icon: 'warning', + }) + } } else { setSelectedConstruction(null) } @@ -342,7 +348,7 @@ const Trestle = forwardRef((props, ref) => { } if (!roof.trestle?.roofBaseCd) { Swal.fire({ - title: getMessage('modal.module.basic.settting.module.error3', [roof.nameJp]), // 지붕밑바탕탕을 선택해주세요. + title: getMessage('modal.module.basic.settting.module.error3', [roof.nameJp]), // 지붕밑바탕을 선택해주세요. icon: 'warning', }) result = false @@ -350,7 +356,7 @@ const Trestle = forwardRef((props, ref) => { } if (!roof.construction?.constTp) { Swal.fire({ - title: getMessage('modal.module.basic.settting.module.error4', [roof.nameJp]), // 시공법법을 선택해주세요. + title: getMessage('modal.module.basic.settting.module.error12', [roof.nameJp]), // 시공법법을 선택해주세요. icon: 'warning', }) result = false diff --git a/src/locales/ja.json b/src/locales/ja.json index cea000ad..e866e95e 100644 --- a/src/locales/ja.json +++ b/src/locales/ja.json @@ -117,17 +117,18 @@ "modal.module.basic.setting.module.eaves.bar.fitting": "軒カバーの設置", "modal.module.basic.setting.module.blind.metal.fitting": "落雪防止金具設置", "modal.module.basic.setting.module.select": "モジュール/架台選択", - "modal.module.basic.settting.module.error1": "架台メーカーを選択してください。\n(屋根材: {0})(JA)", - "modal.module.basic.settting.module.error2": "工法を選択してください。\n(屋根材: {0})(JA)", - "modal.module.basic.settting.module.error3": "屋根の下を選択してください。\n(屋根材: {0})(JA)", + "modal.module.basic.settting.module.error1": "架台メーカーを選択してください。\n(屋根材: {0})", + "modal.module.basic.settting.module.error2": "工法を選択してください。\n(屋根材: {0})", + "modal.module.basic.settting.module.error3": "屋根の下を選択してください。\n(屋根材: {0})", "modal.module.basic.settting.module.error4": "設置可能な施工条件がないので設置条件を変更してください。\n(屋根材: {0})", - "modal.module.basic.settting.module.error5": "L を選択してください。\n(屋根材: {0})(JA)", - "modal.module.basic.settting.module.error6": "垂木の間隔を入力してください。\n(屋根材: {0})(JA)", - "modal.module.basic.settting.module.error7": "下在ビーチを入力してください。\n(屋根材: {0})(JA)", - "modal.module.basic.settting.module.error8": "モジュール配置領域の値を入力してください。\n(屋根材: {0})(JA)", - "modal.module.basic.settting.module.error9": "軒側の値は{0} mm以上でなければなりません。\n(屋根材: {1})(JA)", - "modal.module.basic.settting.module.error10": "吊下側の値は{0} mm以上でなければなりません。\n(屋根材: {1})(JA)", - "modal.module.basic.settting.module.error11": "ケラバ側の値は{0} mm以上でなければなりません。\n(屋根材: {1})(JA)", + "modal.module.basic.settting.module.error5": "L を選択してください。\n(屋根材: {0})", + "modal.module.basic.settting.module.error6": "垂木の間隔を入力してください。\n(屋根材: {0})", + "modal.module.basic.settting.module.error7": "下在ビーチを入力してください。\n(屋根材: {0})", + "modal.module.basic.settting.module.error8": "モジュール配置領域の値を入力してください。\n(屋根材: {0})", + "modal.module.basic.settting.module.error9": "軒側の値は{0} mm以上でなければなりません。\n(屋根材: {1})", + "modal.module.basic.settting.module.error10": "吊下側の値は{0} mm以上でなければなりません。\n(屋根材: {1})", + "modal.module.basic.settting.module.error11": "ケラバ側の値は{0} mm以上でなければなりません。\n(屋根材: {1})", + "modal.module.basic.settting.module.error12": "施工方法を選択してください。\n(屋根材: {0})", "modal.module.basic.setting.module.placement": "モジュールの配置", "modal.module.basic.setting.module.placement.select.fitting.type": "設置形態を選択してください。", "modal.module.basic.setting.module.placement.waterfowl.arrangement": "千鳥配置", diff --git a/src/locales/ko.json b/src/locales/ko.json index 675dc84f..43b45c52 100644 --- a/src/locales/ko.json +++ b/src/locales/ko.json @@ -128,6 +128,7 @@ "modal.module.basic.settting.module.error9": "처마쪽 값은 {0}mm 이상이어야 합니다.\n(지붕재: {1})", "modal.module.basic.settting.module.error10": "용마루쪽 값은 {0}mm 이상이어야 합니다.\n(지붕재: {1})", "modal.module.basic.settting.module.error11": "케라바쪽 값은 {0}mm 이상이어야 합니다.\n(지붕재: {1})", + "modal.module.basic.settting.module.error12": "시공법을 선택해주세요.\n(지붕재: {0})", "modal.module.basic.setting.module.placement": "모듈 배치", "modal.module.basic.setting.module.placement.select.fitting.type": "설치형태를 선택합니다.", "modal.module.basic.setting.module.placement.waterfowl.arrangement": "물떼새 배치", -- 2.47.2 From 391fe39a2d48ab39c659b19b7e0891b764bd4bfa Mon Sep 17 00:00:00 2001 From: yjnoh Date: Fri, 9 May 2025 15:04:21 +0900 Subject: [PATCH 055/109] =?UTF-8?q?[1014]=20:=20[819=E3=81=AE=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0=E3=80=80=E3=83=AC=E3=82=A4=E3=82=A2=E3=82=A6=E3=83=88?= =?UTF-8?q?=E6=8C=87=E5=AE=9A=E9=85=8D=E7=BD=AE=E3=81=AE=E8=87=AA=E5=8B=95?= =?UTF-8?q?=E9=85=8D=E7=BD=AE]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [작업내용] : 하단방향 오류 수정 --- src/hooks/module/useModuleBasicSetting.js | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/hooks/module/useModuleBasicSetting.js b/src/hooks/module/useModuleBasicSetting.js index 1d9c7d66..57218019 100644 --- a/src/hooks/module/useModuleBasicSetting.js +++ b/src/hooks/module/useModuleBasicSetting.js @@ -1996,6 +1996,7 @@ export function useModuleBasicSetting(tabNum) { let flowLines let installedModuleMixYn const isNorthSurface = moduleSetupSurface.isNorth + const isIncludeNorthModule = checkedModule.some((module) => module.northModuleYn === 'Y') //체크된 모듈 중에 북면 모듈이 있는지 확인하는 로직 let layoutRow = 0 let layoutCol = 0 @@ -2342,15 +2343,7 @@ export function useModuleBasicSetting(tabNum) { //남, 북과 같은 로직으로 적용하려면 좌우는 열 -> 행 으로 그려야함 //변수명은 bottom 기준으로 작성하여 동일한 방향으로 진행한다 - const leftFlowSetupModule = ( - maxLengthLine, - moduleSetupArray, - moduleSetupSurface, - containsBatchObjects, - - intvHor, - intvVer, - ) => { + const leftFlowSetupModule = (maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, intvHor, intvVer) => { let setupModule = [] const trestleDetailData = moduleSetupSurface.trestleDetail //가대 상세 데이터 -- 2.47.2 From 35708f65e9f5186aa41af9281c87206497731cd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=AF=BC=EC=8B=9D?= <43837214+Minsiki@users.noreply.github.com> Date: Fri, 9 May 2025 15:36:28 +0900 Subject: [PATCH 056/109] =?UTF-8?q?=EC=A7=80=EB=B6=95=EC=9E=AC=EB=B3=84=20?= =?UTF-8?q?=EA=B0=80=EB=8C=80=EC=A0=95=EB=B3=B4=20=EC=A0=80=EC=9E=A5=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../floor-plan/modal/basic/step/Trestle.jsx | 22 +++++++++---------- src/hooks/module/useModuleTrestle.js | 11 +++++----- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/src/components/floor-plan/modal/basic/step/Trestle.jsx b/src/components/floor-plan/modal/basic/step/Trestle.jsx index e1cca8db..44a68236 100644 --- a/src/components/floor-plan/modal/basic/step/Trestle.jsx +++ b/src/components/floor-plan/modal/basic/step/Trestle.jsx @@ -92,7 +92,7 @@ const Trestle = forwardRef((props, ref) => { useEffect(() => { if (raftBaseList.length > 0) { - setSelectedRaftBase(raftBaseList.find((raft) => raft.clCode === trestleState?.raftBaseCd) ?? null) + setSelectedRaftBase(raftBaseList.find((raft) => raft.clCode === selectedRoof?.raft) ?? null) } else { setSelectedRaftBase(null) } @@ -157,7 +157,7 @@ const Trestle = forwardRef((props, ref) => { length: e, moduleTpCd: selectedModules.itemTp ?? '', roofMatlCd: selectedRoof?.roofMatlCd ?? '', - raftBaseCd: selectedRaftBase?.clCode, + raft: selectedRaftBase?.clCode, }, }) } @@ -169,7 +169,7 @@ const Trestle = forwardRef((props, ref) => { roof: { moduleTpCd: selectedModules.itemTp ?? '', roofMatlCd: selectedRoof?.roofMatlCd ?? '', - raftBaseCd: e.clCode, + raft: e.clCode, }, }) } @@ -181,7 +181,7 @@ const Trestle = forwardRef((props, ref) => { roof: { moduleTpCd: selectedModules.itemTp ?? '', roofMatlCd: selectedRoof?.roofMatlCd ?? '', - raftBaseCd: selectedRaftBase?.clCode, + raft: selectedRaftBase?.clCode, trestleMkrCd: e.trestleMkrCd, }, }) @@ -194,7 +194,7 @@ const Trestle = forwardRef((props, ref) => { roof: { moduleTpCd: selectedModules.itemTp ?? '', roofMatlCd: selectedRoof?.roofMatlCd ?? '', - raftBaseCd: selectedRaftBase?.clCode, + raft: selectedRaftBase?.clCode, trestleMkrCd: selectedTrestle.trestleMkrCd, constMthdCd: e.constMthdCd, }, @@ -208,7 +208,7 @@ const Trestle = forwardRef((props, ref) => { roof: { moduleTpCd: selectedModules.itemTp ?? '', roofMatlCd: selectedRoof?.roofMatlCd ?? '', - raftBaseCd: selectedRaftBase?.clCode, + raft: selectedRaftBase?.clCode, trestleMkrCd: selectedTrestle.trestleMkrCd, constMthdCd: selectedConstMthd.constMthdCd, roofBaseCd: e.roofBaseCd, @@ -229,7 +229,7 @@ const Trestle = forwardRef((props, ref) => { roof: { moduleTpCd: selectedModules.itemTp ?? '', roofMatlCd: selectedRoof?.roofMatlCd ?? '', - raftBaseCd: selectedRaftBase?.clCode, + raft: selectedRaftBase?.clCode, trestleMkrCd: selectedTrestle.trestleMkrCd, constMthdCd: selectedConstMthd.constMthdCd, roofBaseCd: selectedRoofBase.roofBaseCd, @@ -266,6 +266,7 @@ const Trestle = forwardRef((props, ref) => { ridgeMargin, kerabaMargin, roofIndex: selectedRoof.index, + raft: selectedRaftBase?.clCode, trestle: { hajebichi: hajebichi, length: lengthBase, @@ -302,6 +303,7 @@ const Trestle = forwardRef((props, ref) => { ridgeMargin, kerabaMargin, roofIndex: roof.index, + raft: selectedRaftBase?.clCode, trestle: { length: lengthBase, hajebichi: hajebichi, @@ -374,7 +376,7 @@ const Trestle = forwardRef((props, ref) => { } } if (['C', 'R'].includes(roof.raftAuth)) { - if (!roof?.raftBaseCd) { + if (!roof?.raft) { Swal.fire({ title: getMessage('modal.module.basic.settting.module.error6', [roof.nameJp]), // 서까래 간격을 입력해주세요. icon: 'warning', @@ -477,7 +479,7 @@ const Trestle = forwardRef((props, ref) => { addRoof: newRoofs[index], trestle: { ...roof.trestle, - raftBaseCd: roof.raftBaseCd, + raft: roof.raftBaseCd, }, construction: { // ...constructionList.find((construction) => newAddedRoofs[index].construction.constTp === construction.constTp), @@ -502,8 +504,6 @@ const Trestle = forwardRef((props, ref) => { return false } - const onMarginCheck = (target, data) => {} - useImperativeHandle(ref, () => ({ isComplete, })) diff --git a/src/hooks/module/useModuleTrestle.js b/src/hooks/module/useModuleTrestle.js index e89d6089..0143e92e 100644 --- a/src/hooks/module/useModuleTrestle.js +++ b/src/hooks/module/useModuleTrestle.js @@ -25,7 +25,7 @@ const trestleReducer = (state, action) => { moduleTpCd: action.roof.module?.itemTp ?? '', roofMatlCd: action.roof?.roofMatlCd ?? '', hajebichi: action.roof?.hajebichi ?? 0, - raftBaseCd: action.roof?.raft ?? null, + raft: action.roof?.raft ?? null, trestleMkrCd: action.roof.trestle?.trestleMkrCd ?? null, constMthdCd: action.roof.trestle?.constMthdCd ?? null, constTp: action.roof.construction?.constTp ?? null, @@ -72,7 +72,6 @@ export function useModuleTrestle(props) { useEffect(() => { const raftCodeList = findCommonCode(RAFT_BASE_CODE) - setRaftBaseList(raftCodeList) setTrestleList([]) setConstMthdList([]) @@ -150,7 +149,7 @@ export function useModuleTrestle(props) { getTrestleList({ moduleTpCd: trestleState?.moduleTpCd ?? '', roofMatlCd: trestleState?.roofMatlCd ?? '', - raftBaseCd: trestleState?.raftBaseCd ?? '', + raftBaseCd: trestleState?.raft ?? '', }) .then((res) => { if (res?.data) setTrestleList(res.data) @@ -166,7 +165,7 @@ export function useModuleTrestle(props) { getTrestleList({ moduleTpCd: trestleState?.moduleTpCd ?? '', roofMatlCd: trestleState?.roofMatlCd ?? '', - raftBaseCd: trestleState?.raftBaseCd ?? '', + raftBaseCd: trestleState?.raft ?? '', trestleMkrCd: trestleState?.trestleMkrCd ?? '', }) .then((res) => { @@ -183,7 +182,7 @@ export function useModuleTrestle(props) { getTrestleList({ moduleTpCd: trestleState?.moduleTpCd ?? '', roofMatlCd: trestleState?.roofMatlCd ?? '', - raftBaseCd: trestleState?.raftBaseCd ?? '', + raftBaseCd: trestleState?.raft ?? '', trestleMkrCd: trestleState?.trestleMkrCd ?? '', constMthdCd: trestleState?.constMthdCd ?? '', }) @@ -209,7 +208,7 @@ export function useModuleTrestle(props) { stdWindSpeed: trestleState.stdWindSpeed ?? '', stdSnowLd: trestleState.stdSnowLd ?? '', inclCd: trestleState.inclCd ?? '', - raftBaseCd: trestleState.raftBaseCd ?? '', + raftBaseCd: trestleState.raft ?? '', roofPitch: Math.round(trestleState.roofPitch) ?? '', }) .then((res) => { -- 2.47.2 From 56b891734597a9e1409477f2a688007668780113 Mon Sep 17 00:00:00 2001 From: yjnoh Date: Fri, 9 May 2025 15:48:08 +0900 Subject: [PATCH 057/109] =?UTF-8?q?[1014]=20:=20[819=E3=81=AE=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0=E3=80=80=E3=83=AC=E3=82=A4=E3=82=A2=E3=82=A6=E3=83=88?= =?UTF-8?q?=E6=8C=87=E5=AE=9A=E9=85=8D=E7=BD=AE=E3=81=AE=E8=87=AA=E5=8B=95?= =?UTF-8?q?=E9=85=8D=E7=BD=AE]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [작업내용] : 멀티모듈일 경우에 북면 모듈이 같이 계산되는 로직 수정 --- src/hooks/module/useModuleBasicSetting.js | 80 +++++++++++------------ 1 file changed, 37 insertions(+), 43 deletions(-) diff --git a/src/hooks/module/useModuleBasicSetting.js b/src/hooks/module/useModuleBasicSetting.js index 57218019..a3f2eb0e 100644 --- a/src/hooks/module/useModuleBasicSetting.js +++ b/src/hooks/module/useModuleBasicSetting.js @@ -1902,57 +1902,51 @@ export function useModuleBasicSetting(tabNum) { return false } } else { - //북면 모듈이 1개만있을때 - if (checkedModule.length === 1) { - const maxRow = trestleDetailData.module.find((item) => item.moduleTpCd === checkedModule[0].moduleTpCd).moduleMaxRows //멀티모듈이면 밖에 maxRows로 판단 아니면 module->itemmList를 가지고 판단 + const normalModule = checkedModule.filter((item) => item.northModuleYn === 'N') + const northModule = checkedModule.filter((item) => item.northModuleYn === 'Y') + const northModuleIds = northModule.map((item) => item.itemId) + let isPassedNormalModule = false - //단수 합단수 - const sumRowCount = layoutSetupRef.find((item) => item.moduleId === checkedModule[0].itemId).row //멀티모듈이면 전체 합, 체크된 한개의 열 - const sumColCount = layoutSetupRef.filter((item) => item.col).some((item) => item.col > maxCol) + //만약 북면 모듈이 2개면 이 하위 로직 가져다가 쓰면됨 northModule === 만 바꾸면 될듯 + // northModule을 배열로 만들고 include로 해서 체크 해야됨 + if (normalModule.length > 0 && !moduleSetupSurface.isNorth) { + //C1C2 모듈일 경우ㅁㅁ + const isMultipleModules = normalModule.length > 1 //모듈이 여러개면 + const maxRow = isMultipleModules + ? trestleDetailData.moduleMaxRows + : trestleDetailData.module.find((item) => item.moduleTpCd === normalModule[0].moduleTpCd).moduleMaxRows //멀티모듈이면 밖에 maxRows로 판단 아니면 module->itemmList를 가지고 판단 - if (sumRowCount > maxRow || sumColCount) { + //북면 모듈 id를 제외한 모듈의 단 체크 + const sumRowCount = isMultipleModules + ? layoutSetupRef.filter((item) => item.checked && !northModuleIds.includes(item.moduleId)).reduce((acc, cur) => acc + cur.row, 0) + : layoutSetupRef.find((item) => item.moduleId === normalModule[0].itemId).row //멀티모듈이면 전체 합, 체크된 한개의 열 + + //북면 모듈 id를 제외한 모듈의 열 체크 + const sumColCount = layoutSetupRef.filter((item) => item.col && !northModuleIds.includes(item.moduleId)).some((item) => item.col > maxCol) + + // 혼합일때 모듈 개별의 row를 체크함 + const isPassedObject = + isMultipleModules && + layoutSetupRef.find( + (item, index) => item.checked && !item.moduleId.includes(northModuleIds) && item.row > trestleDetailData.module[index].mixModuleMaxRows, + ) + + // 합산 단수가 맥스단수보다 크거나 열이 맥스열수보다 크거나 혼합일때 모듈 개별의 row가 맥스단수보다 크면 실패 + if (sumRowCount > maxRow || sumColCount || isPassedObject) { failAutoSetupRoof.push(moduleSetupSurface) return false + } else { + isPassedNormalModule = true } - } else { - const normalModule = checkedModule.filter((item) => item.northModuleYn === 'N') - const northModule = checkedModule.filter((item) => item.northModuleYn === 'Y') - const northModuleIds = northModule.map((item) => item.itemId) - - //만약 북면 모듈이 2개면 이 하위 로직 가져다가 쓰면됨 northModule === 만 바꾸면 될듯 - // northModule을 배열로 만들고 include로 해서 체크 해야됨 - if (normalModule.length > 0 && !moduleSetupSurface.isNorth) { - //C1C2 모듈일 경우ㅁㅁ - const isMultipleModules = normalModule.length > 1 //모듈이 여러개면 - const maxRow = isMultipleModules - ? trestleDetailData.moduleMaxRows - : trestleDetailData.module.find((item) => item.moduleTpCd === normalModule[0].moduleTpCd).moduleMaxRows //멀티모듈이면 밖에 maxRows로 판단 아니면 module->itemmList를 가지고 판단 - - //북면 모듈 id를 제외한 모듈의 단 체크 - const sumRowCount = isMultipleModules - ? layoutSetupRef.filter((item) => item.checked && !northModuleIds.includes(item.moduleId)).reduce((acc, cur) => acc + cur.row, 0) - : layoutSetupRef.find((item) => item.moduleId === normalModule[0].itemId).row //멀티모듈이면 전체 합, 체크된 한개의 열 - - //북면 모듈 id를 제외한 모듈의 열 체크 - const sumColCount = layoutSetupRef.filter((item) => item.col && !northModuleIds.includes(item.moduleId)).some((item) => item.col > maxCol) - - // 혼합일때 모듈 개별의 row를 체크함 - const isPassedObject = - isMultipleModules && - layoutSetupRef.find( - (item, index) => - item.checked && !item.moduleId.includes(northModuleIds) && item.row > trestleDetailData.module[index].mixModuleMaxRows, - ) - - // 합산 단수가 맥스단수보다 크거나 열이 맥스열수보다 크거나 혼합일때 모듈 개별의 row가 맥스단수보다 크면 실패 - if (sumRowCount > maxRow || sumColCount || isPassedObject) { - failAutoSetupRoof.push(moduleSetupSurface) - return false - } - } + } + //위에서 일반 모듈이 설치가 완료면 그냥 넘어간다 + //일반 모듈이 pass라면 일반 모듈이 설치됨 + //만약 일반모듈이 체크가 안되어 있으면 밑에 로직을 탐 + if (!isPassedNormalModule) { //북면 모듈이 있고 북면에 있을때 if (northModule.length > 0 && (moduleSetupSurface.isNorth || !moduleSetupSurface.isNorth)) { + //북면 모듈이 있는데 일반 모듈이 있을때 북면이 아니면 그냥 북면은 그냥 pass const isMultipleModules = northModule.length > 1 //모듈이 여러개면 const maxRow = isMultipleModules ? trestleDetailData.moduleMaxRows -- 2.47.2 From 553cbd44dbc86a5b157402fbf513a1dea59d0398 Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Fri, 9 May 2025 16:52:04 +0900 Subject: [PATCH 058/109] =?UTF-8?q?=EC=98=A4=EC=B0=A8=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/util/canvas-util.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/util/canvas-util.js b/src/util/canvas-util.js index 0f0b69dc..5955cbce 100644 --- a/src/util/canvas-util.js +++ b/src/util/canvas-util.js @@ -360,16 +360,16 @@ export const calculateIntersection = (line1, line2) => { // Check if the intersection X and Y are within the range of both lines if ( - result[0] >= line1MinX && - result[0] <= line1MaxX && - result[0] >= line2MinX && - result[0] <= line2MaxX && - result[1] >= line1MinY && - result[1] <= line1MaxY && - result[1] >= line2MinY && - result[1] <= line2MaxY + result[0] >= line1MinX - 1 && + result[0] <= line1MaxX + 1 && + result[0] >= line2MinX - 1 && + result[0] <= line2MaxX + 1 && + result[1] >= line1MinY - 1 && + result[1] <= line1MaxY + 1 && + result[1] >= line2MinY - 1 && + result[1] <= line2MaxY + 1 ) { - return { x: Math.round(result[0]), y: Math.round(result[1]) } + return { x: result[0], y: result[1] } } else { return null // Intersection is out of range } -- 2.47.2 From f03d35ec8db9bbcef240561fe5430139f177faf4 Mon Sep 17 00:00:00 2001 From: yjnoh Date: Mon, 12 May 2025 11:11:09 +0900 Subject: [PATCH 059/109] =?UTF-8?q?[1037]=20:=20[=E3=83=A2=E3=82=B8?= =?UTF-8?q?=E3=83=A5=E3=83=BC=E3=83=AB1=E6=9E=9A=E3=82=92=E5=89=8A?= =?UTF-8?q?=E9=99=A4=E3=81=99=E3=82=8B=E6=99=82=E3=81=AE=E8=A1=A8=E7=A4=BA?= =?UTF-8?q?]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [작업내용] : 모듈 선택시 아웃라인 두께 조정 --- src/hooks/useCanvasEvent.js | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/src/hooks/useCanvasEvent.js b/src/hooks/useCanvasEvent.js index 4a601ed8..22aad7cf 100644 --- a/src/hooks/useCanvasEvent.js +++ b/src/hooks/useCanvasEvent.js @@ -4,7 +4,7 @@ import { v4 as uuidv4 } from 'uuid' import { canvasSizeState, canvasState, canvasZoomState, currentMenuState, currentObjectState } from '@/store/canvasAtom' import { QPolygon } from '@/components/fabric/QPolygon' import { fontSelector } from '@/store/fontAtom' -import { MENU } from '@/common/common' +import { MENU, POLYGON_TYPE } from '@/common/common' // 캔버스에 필요한 이벤트 export function useCanvasEvent() { @@ -204,9 +204,12 @@ export function useCanvasEvent() { if (selected?.length > 0) { selected.forEach((obj) => { - if (obj.type === 'QPolygon' && currentMenu !== MENU.MODULE_CIRCUIT_SETTING.BASIC_SETTING) { + // if (obj.type === 'QPolygon' && currentMenu !== MENU.MODULE_CIRCUIT_SETTING.BASIC_SETTING) { + if (obj.type === 'QPolygon') { obj.set({ stroke: 'red' }) - obj.bringToFront() + if (obj.name === POLYGON_TYPE.MODULE && currentMenu === MENU.MODULE_CIRCUIT_SETTING.BASIC_SETTING) { + obj.set({ strokeWidth: 3 }) + } } }) canvas.renderAll() @@ -218,10 +221,13 @@ export function useCanvasEvent() { if (deselected?.length > 0) { deselected.forEach((obj) => { - if (obj.type === 'QPolygon' && currentMenu !== MENU.MODULE_CIRCUIT_SETTING.BASIC_SETTING) { + if (obj.type === 'QPolygon') { if (obj.name !== 'moduleSetupSurface') { obj.set({ stroke: 'black' }) } + if (obj.name === POLYGON_TYPE.MODULE && currentMenu === MENU.MODULE_CIRCUIT_SETTING.BASIC_SETTING) { + obj.set({ strokeWidth: 0.3 }) + } } }) } @@ -234,17 +240,24 @@ export function useCanvasEvent() { if (deselected?.length > 0) { deselected.forEach((obj) => { - if (obj.type === 'QPolygon' && currentMenu !== MENU.MODULE_CIRCUIT_SETTING.BASIC_SETTING) { + if (obj.type === 'QPolygon') { obj.set({ stroke: 'black' }) + if (obj.name === POLYGON_TYPE.MODULE && currentMenu === MENU.MODULE_CIRCUIT_SETTING.BASIC_SETTING) { + //모듈 미선택시 라인 두께 변경 + obj.set({ strokeWidth: 0.3 }) + } } }) } if (selected?.length > 0) { selected.forEach((obj) => { - if (obj.type === 'QPolygon' && currentMenu !== MENU.MODULE_CIRCUIT_SETTING.BASIC_SETTING) { + if (obj.type === 'QPolygon') { obj.set({ stroke: 'red' }) - obj.bringToFront() + if (obj.name === POLYGON_TYPE.MODULE && currentMenu === MENU.MODULE_CIRCUIT_SETTING.BASIC_SETTING) { + //모듈 선택시 라인 두께 변경 + obj.set({ strokeWidth: 3 }) + } } }) } -- 2.47.2 From f48a8b31d370981e5dceabb3628b5a8678e9b9e4 Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Mon, 12 May 2025 14:09:48 +0900 Subject: [PATCH 060/109] =?UTF-8?q?=EC=A7=80=EB=B6=95=EC=9E=AC=20=EB=B6=84?= =?UTF-8?q?=ED=95=A0=20=EC=8B=9C=20addLengthText=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/usePolygon.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks/usePolygon.js b/src/hooks/usePolygon.js index 4d59d292..735a85f5 100644 --- a/src/hooks/usePolygon.js +++ b/src/hooks/usePolygon.js @@ -1160,7 +1160,7 @@ export const usePolygon = () => { }) canvas.add(roof) - addLengthText(roof) + // addLengthText(roof) canvas.remove(polygon) canvas.renderAll() }) -- 2.47.2 From f4ba3058c7443b67406d12461bf84315983cacc6 Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Mon, 12 May 2025 16:04:53 +0900 Subject: [PATCH 061/109] =?UTF-8?q?range=20=EA=B3=84=EC=82=B0=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/util/canvas-util.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/util/canvas-util.js b/src/util/canvas-util.js index 5955cbce..9420e910 100644 --- a/src/util/canvas-util.js +++ b/src/util/canvas-util.js @@ -536,8 +536,8 @@ export function isPointOnLine({ x1, y1, x2, y2 }, { x, y }, epsilon = 2) { if (Math.abs(crossProduct) > 1000) return false // 작은 오차 허용 // 점이 선분의 범위 내에 있는지 확인 - const withinXRange = Math.abs(Math.min(x1, x2) - x) <= 2 || 2 <= Math.abs(Math.max(x1, x2) - x) - const withinYRange = Math.abs(Math.min(y1, y2) - y) <= 2 || 2 <= Math.abs(Math.max(y1, y2) - y) + const withinXRange = Math.min(x1, x2) - x <= 2 && 2 <= Math.max(x1, x2) - x + const withinYRange = Math.min(y1, y2) - y <= 2 && 2 <= Math.max(y1, y2) - y return withinXRange && withinYRange } -- 2.47.2 From 976396cd787a0b30f66668494e2fdda9fecfe288 Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Mon, 12 May 2025 18:18:31 +0900 Subject: [PATCH 062/109] =?UTF-8?q?valid=EC=97=90=20=ED=95=B4=EB=8B=B9?= =?UTF-8?q?=ED=95=98=EC=A7=80=EC=95=8A=EB=8A=94=20polygon=EC=9D=B4=202?= =?UTF-8?q?=EA=B0=9C=20=EC=9D=B4=EC=83=81=EC=9D=BC=20=EA=B2=BD=EC=9A=B0=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/util/qpolygon-utils.js | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/util/qpolygon-utils.js b/src/util/qpolygon-utils.js index 1af80680..280245c6 100644 --- a/src/util/qpolygon-utils.js +++ b/src/util/qpolygon-utils.js @@ -304,9 +304,19 @@ export function removeDuplicatePolygons(polygons) { } }) - uniquePolygons = uniquePolygons.filter((polygon) => { - return isValidPoints(polygon) - }) + //uniquePolygons중 isValidPoints의 조건을 만족하는 카운트 계산 + const validCount = uniquePolygons.reduce((acc, polygon) => { + if (!isValidPoints(polygon)) { + return acc + 1 + } + return acc + }, 0) + + if (validCount > 1) { + uniquePolygons = uniquePolygons.filter((polygon) => { + return isValidPoints(polygon) + }) + } return uniquePolygons } -- 2.47.2 From 72c020aceaf9eedbc9d2852e4acf4177d13c2dd0 Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Tue, 13 May 2025 09:57:36 +0900 Subject: [PATCH 063/109] =?UTF-8?q?pointOnLine=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/util/canvas-util.js | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/util/canvas-util.js b/src/util/canvas-util.js index 9420e910..3e3f4059 100644 --- a/src/util/canvas-util.js +++ b/src/util/canvas-util.js @@ -535,9 +535,21 @@ export function isPointOnLine({ x1, y1, x2, y2 }, { x, y }, epsilon = 2) { const crossProduct = (y - y1) * (x2 - x1) - (x - x1) * (y2 - y1) if (Math.abs(crossProduct) > 1000) return false // 작은 오차 허용 + const isSameX = Math.abs(x1 - x2) < 2 + const isSameY = Math.abs(y1 - y2) < 2 + // 점이 선분의 범위 내에 있는지 확인 - const withinXRange = Math.min(x1, x2) - x <= 2 && 2 <= Math.max(x1, x2) - x - const withinYRange = Math.min(y1, y2) - y <= 2 && 2 <= Math.max(y1, y2) - y + let withinXRange = Math.min(x1, x2) - x <= 2 + if (!isSameX) { + withinXRange = withinXRange && 2 <= Math.max(x1, x2) - x + } + + let withinYRange = Math.min(y1, y2) - y <= 2 + if (!isSameY) { + withinYRange = withinYRange && 2 <= Math.max(y1, y2) - y + } + + console.log(Math.min(x1, x2) - x, Math.max(x1, x2) - x) return withinXRange && withinYRange } -- 2.47.2 From a652d09b8d7eb99f879f05d7a180d114e67bc91e Mon Sep 17 00:00:00 2001 From: yjnoh Date: Tue, 13 May 2025 10:46:26 +0900 Subject: [PATCH 064/109] =?UTF-8?q?[1036]=20:=20[=E3=83=97=E3=83=A9?= =?UTF-8?q?=E3=83=B3=E3=82=92=E3=82=B3=E3=83=94=E3=83=BC/=E7=A7=BB?= =?UTF-8?q?=E5=8B=95=E3=81=99=E3=82=8B=E6=99=82=E3=81=AE=E8=87=AA=E5=8B=95?= =?UTF-8?q?=E4=BF=9D=E5=AD=98=E3=81=AB=E3=81=A4=E3=81=84=E3=81=A6]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [작업내용] : 탭간 이동, 복사시 저장 여부 확인 로직 추가 --- src/components/floor-plan/CanvasLayout.jsx | 2 +- .../floor-plan/modal/basic/step/Placement.jsx | 19 ++++----- src/hooks/useCanvasEvent.js | 12 +++++- src/hooks/usePlan.js | 40 +++++++++++++++---- 4 files changed, 53 insertions(+), 20 deletions(-) diff --git a/src/components/floor-plan/CanvasLayout.jsx b/src/components/floor-plan/CanvasLayout.jsx index 059e96aa..cce79df2 100644 --- a/src/components/floor-plan/CanvasLayout.jsx +++ b/src/components/floor-plan/CanvasLayout.jsx @@ -37,7 +37,7 @@ export default function CanvasLayout({ children }) {
+
{getMessage('estimate.detail.header.singleCable')}
+
+
+
+
{getMessage('estimate.detail.header.doubleCable')}
+
+
@@ -1927,7 +1976,7 @@ export default function Estimate({}) { onChangeSelect(item.dispOrder)} checked={!!selection.has(item.dispOrder)} /> diff --git a/src/locales/ja.json b/src/locales/ja.json index 35f0b757..c59d0a16 100644 --- a/src/locales/ja.json +++ b/src/locales/ja.json @@ -944,6 +944,8 @@ "estimate.detail.sepcialEstimateProductInfo.pkgPrice": "PKG金額", "estimate.detail.header.showPrice": "価格表示", "estimate.detail.header.unitPrice": "定価", + "estimate.detail.header.singleCable": "片端ケーブル長さ", + "estimate.detail.header.doubleCable": "両端ケーブル長さ", "estimate.detail.showPrice.pricingBtn": "Pricing", "estimate.detail.showPrice.pricingBtn.noItemId": "Pricingが欠落しているアイテムがあります。 Pricingを進めてください。", "estimate.detail.showPrice.pricingBtn.confirm": "価格登録初期化されますがよろしいですか?", diff --git a/src/locales/ko.json b/src/locales/ko.json index daf9ad60..3374c1b9 100644 --- a/src/locales/ko.json +++ b/src/locales/ko.json @@ -945,6 +945,8 @@ "estimate.detail.sepcialEstimateProductInfo.pkgPrice": "PKG 금액", "estimate.detail.header.showPrice": "가격표시", "estimate.detail.header.unitPrice": "정가", + "estimate.detail.header.singleCable": "편당케이블 길이", + "estimate.detail.header.doubleCable": "양단케이블 길이", "estimate.detail.showPrice.pricingBtn": "Pricing", "estimate.detail.showPrice.pricingBtn.noItemId": "Pricing이 누락된 아이템이 있습니다. 제품 선택 후 Pricing을 진행해주세요.", "estimate.detail.showPrice.pricingBtn.confirm": "가격등록을 초기화 하시겠습니까?", -- 2.47.2 From 5b322d0143a0aa0d800e7de76eeb6417b2b3e986 Mon Sep 17 00:00:00 2001 From: ysCha Date: Wed, 14 May 2025 14:06:45 +0900 Subject: [PATCH 084/109] feature/ysCha (#37) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [948] - 견적서 양단케이블 selectbox 추가 케이블 체크 가능. 단어 추가 --- src/components/estimate/Estimate.jsx | 75 +++++++++++++++++++++++----- src/locales/ja.json | 2 + src/locales/ko.json | 2 + 3 files changed, 66 insertions(+), 13 deletions(-) diff --git a/src/components/estimate/Estimate.jsx b/src/components/estimate/Estimate.jsx index 8ce54402..7237e056 100644 --- a/src/components/estimate/Estimate.jsx +++ b/src/components/estimate/Estimate.jsx @@ -60,7 +60,7 @@ export default function Estimate({}) { const [cableItemList, setCableItemList] = useState([]) //케이블 리스트 const [cableItem, setCableItem] = useState('') //케이블 선택값 - + const [cableDbItem, setCableDbItem] = useState('') //케이블 선택값 const [startDate, setStartDate] = useState(new Date()) const singleDatePickerProps = { startDate, @@ -98,7 +98,7 @@ export default function Estimate({}) { } const initEstimate = (currPid = currentPid) => { - console.log('🚀 ~ initEstimate ~ currPid:', currPid) + // console.log('🚀 ~ initEstimate ~ currPid:', currPid) closeAll() setObjectNo(objectRecoil.floorPlanObjectNo) @@ -117,6 +117,7 @@ export default function Estimate({}) { item.value = item.clRefChr1 item.label = item.clRefChr2 }) + // console.log(code2) setCableItemList(code2) } @@ -152,7 +153,7 @@ export default function Estimate({}) { } useEffect(() => { - console.log('🚀 ~ Estimate ~ selectedPlan:', selectedPlan) + // console.log('🚀 ~ Estimate ~ selectedPlan:', selectedPlan) if (selectedPlan) initEstimate(selectedPlan.planNo) }, [selectedPlan]) @@ -739,6 +740,18 @@ export default function Estimate({}) { setCableItem(value) } + /* 케이블 select 변경시 */ + const onChangeDisplayDoubleCableItem = (value, itemList) => { + itemList.map((item, index) => { + if (item.dispCableFlg === '1' && item.itemTpCd === 'M12') { + if (value !== '') { + onChangeDisplayItem(value, item.dispOrder, index, true) + } + } + }) + setCableDbItem(value) + } + // 아이템 자동완성 검색시 아이템 추가/변경시 const onChangeDisplayItem = (itemId, dispOrder, index, flag) => { const param = { @@ -1088,15 +1101,20 @@ export default function Estimate({}) { item.showSaleTotPrice = '0' } - if (item.dispCableFlg === '1' && item.itemTpCd !== 'M12') { + if (item.dispCableFlg === '1' ) { dispCableFlgCnt++ - setCableItem(item.itemId) + if(item.itemTpCd === 'M12') { + setCableDbItem(item.itemId) + }else{ + setCableItem(item.itemId) + } } } }) if (dispCableFlgCnt === 0) { setCableItem('100038') + setCableDbItem('100037') } let pkgAsp = estimateContextState.pkgAsp ? Number(estimateContextState.pkgAsp.replaceAll(',', '')) : 0 @@ -1159,14 +1177,20 @@ export default function Estimate({}) { dispCableFlgCnt++ } - if (item.dispCableFlg === '1' && item.itemTpCd !== 'M12') { - setCableItem(item.itemId) + if (item.dispCableFlg === '1'){ + + if(item.itemTpCd === 'M12') { + setCableDbItem(item.itemId) + }else{ + setCableItem(item.itemId) + } } } }) if (dispCableFlgCnt === 0) { setCableItem('100038') + setCableDbItem('100037') } totals.vatPrice = totals.supplyPrice * 0.1 @@ -1227,6 +1251,7 @@ export default function Estimate({}) { if (dispCableFlgCnt === 0) { setCableItem('100038') + setCableDbItem('100037') } } }, [estimateContextState?.itemList, cableItemList]) @@ -1831,6 +1856,7 @@ export default function Estimate({}) {
+
{getMessage('estimate.detail.header.singleCable')}
+
+
+
+
{getMessage('estimate.detail.header.doubleCable')}
+
+
@@ -1927,7 +1976,7 @@ export default function Estimate({}) { onChangeSelect(item.dispOrder)} checked={!!selection.has(item.dispOrder)} /> diff --git a/src/locales/ja.json b/src/locales/ja.json index 35f0b757..c59d0a16 100644 --- a/src/locales/ja.json +++ b/src/locales/ja.json @@ -944,6 +944,8 @@ "estimate.detail.sepcialEstimateProductInfo.pkgPrice": "PKG金額", "estimate.detail.header.showPrice": "価格表示", "estimate.detail.header.unitPrice": "定価", + "estimate.detail.header.singleCable": "片端ケーブル長さ", + "estimate.detail.header.doubleCable": "両端ケーブル長さ", "estimate.detail.showPrice.pricingBtn": "Pricing", "estimate.detail.showPrice.pricingBtn.noItemId": "Pricingが欠落しているアイテムがあります。 Pricingを進めてください。", "estimate.detail.showPrice.pricingBtn.confirm": "価格登録初期化されますがよろしいですか?", diff --git a/src/locales/ko.json b/src/locales/ko.json index daf9ad60..3374c1b9 100644 --- a/src/locales/ko.json +++ b/src/locales/ko.json @@ -945,6 +945,8 @@ "estimate.detail.sepcialEstimateProductInfo.pkgPrice": "PKG 금액", "estimate.detail.header.showPrice": "가격표시", "estimate.detail.header.unitPrice": "정가", + "estimate.detail.header.singleCable": "편당케이블 길이", + "estimate.detail.header.doubleCable": "양단케이블 길이", "estimate.detail.showPrice.pricingBtn": "Pricing", "estimate.detail.showPrice.pricingBtn.noItemId": "Pricing이 누락된 아이템이 있습니다. 제품 선택 후 Pricing을 진행해주세요.", "estimate.detail.showPrice.pricingBtn.confirm": "가격등록을 초기화 하시겠습니까?", -- 2.47.2 From 457253f0f6d617c245d7a0b8cd7faa439f210ad3 Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Wed, 14 May 2025 14:41:23 +0900 Subject: [PATCH 085/109] =?UTF-8?q?=EB=B2=94=EC=9C=84=EB=A1=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/roofcover/useAuxiliaryDrawing.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks/roofcover/useAuxiliaryDrawing.js b/src/hooks/roofcover/useAuxiliaryDrawing.js index d66014f8..78d0b883 100644 --- a/src/hooks/roofcover/useAuxiliaryDrawing.js +++ b/src/hooks/roofcover/useAuxiliaryDrawing.js @@ -675,7 +675,7 @@ export function useAuxiliaryDrawing(id, isUseEffect = true) { const distance1 = distanceBetweenPoints({ x: line1.x1, y: line1.y1 }, interSectionPointsWithRoofLines[0]) const distance2 = distanceBetweenPoints({ x: line1.x2, y: line1.y2 }, interSectionPointsWithRoofLines[0]) - if (!(distance1 === 0 || distance2 === 0)) { + if (!(distance1 < 1 || distance2 < 1)) { if (distance1 >= distance2) { const newLine = addLine([line1.x1, line1.y1, interSectionPointsWithRoofLines[0].x, interSectionPointsWithRoofLines[0].y], { stroke: 'black', -- 2.47.2 From 80bf9263fdd5f2ae85eee3a86163ba0399bac9ea Mon Sep 17 00:00:00 2001 From: ysCha Date: Wed, 14 May 2025 15:16:28 +0900 Subject: [PATCH 086/109] =?UTF-8?q?=3F=3F=20=3D>=20=3F=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/locales/ja.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/locales/ja.json b/src/locales/ja.json index c59d0a16..57953508 100644 --- a/src/locales/ja.json +++ b/src/locales/ja.json @@ -346,9 +346,9 @@ "modal.actual.size.setting.not.exist.size": "実際の寸法の長さを入力してください", "modal.actual.size.setting.plane.size.length": "廊下寸法の長さ", "modal.actual.size.setting.actual.size.length": "実寸長", - "plan.message.confirm.save": "プラン保存しますか??", - "plan.message.confirm.copy": "プランコピーしますか??", - "plan.message.confirm.delete": "プラン削除しますか??", + "plan.message.confirm.save": "プラン保存しますか?", + "plan.message.confirm.copy": "プランコピーしますか?", + "plan.message.confirm.delete": "プラン削除しますか?", "plan.message.save": "保存されました。", "plan.message.delete": "削除されました。", "plan.message.leave": "物件状況(リスト)に移動しますか? [はい]を選択した場合は保存して移動します。", -- 2.47.2 From 9609cad8f55fe5da7911ecaa74373337b30f4eda Mon Sep 17 00:00:00 2001 From: ysCha Date: Wed, 14 May 2025 15:18:50 +0900 Subject: [PATCH 087/109] =?UTF-8?q?=3F=3F=20=3D>=20=3F=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reviewed-on: https://git.hanasys.jp/qcast3/qcast-front/pulls/38 --- src/locales/ja.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/locales/ja.json b/src/locales/ja.json index c59d0a16..57953508 100644 --- a/src/locales/ja.json +++ b/src/locales/ja.json @@ -346,9 +346,9 @@ "modal.actual.size.setting.not.exist.size": "実際の寸法の長さを入力してください", "modal.actual.size.setting.plane.size.length": "廊下寸法の長さ", "modal.actual.size.setting.actual.size.length": "実寸長", - "plan.message.confirm.save": "プラン保存しますか??", - "plan.message.confirm.copy": "プランコピーしますか??", - "plan.message.confirm.delete": "プラン削除しますか??", + "plan.message.confirm.save": "プラン保存しますか?", + "plan.message.confirm.copy": "プランコピーしますか?", + "plan.message.confirm.delete": "プラン削除しますか?", "plan.message.save": "保存されました。", "plan.message.delete": "削除されました。", "plan.message.leave": "物件状況(リスト)に移動しますか? [はい]を選択した場合は保存して移動します。", -- 2.47.2 From e6da798e4300a6e9c3b4f1d351de4243e89efa2c Mon Sep 17 00:00:00 2001 From: yoosangwook Date: Thu, 15 May 2025 14:13:06 +0900 Subject: [PATCH 088/109] fix: update API server path in development environment to use secure HTTPS URL --- .env.development | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env.development b/.env.development index d3e1ba88..f35f6208 100644 --- a/.env.development +++ b/.env.development @@ -1,4 +1,4 @@ -NEXT_PUBLIC_API_SERVER_PATH="http://1.248.227.176:38080" +NEXT_PUBLIC_API_SERVER_PATH="https://dev-api.hanasys.jp" NEXT_PUBLIC_HOST_URL="//1.248.227.176:4000" -- 2.47.2 From 410da6f541032f14b63a8330b755c13bee4b5d65 Mon Sep 17 00:00:00 2001 From: Jaeyoung Lee Date: Thu, 15 May 2025 16:00:40 +0900 Subject: [PATCH 089/109] =?UTF-8?q?=EC=A7=80=EB=B6=95=20=EB=B3=B4=EC=A1=B0?= =?UTF-8?q?=EC=84=A0=20=EC=9E=91=EC=84=B1=20=EC=88=98=EC=A0=95=20=EC=A4=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/util/qpolygon-utils.js | 563 +++++++++++++++++++++++++++---------- 1 file changed, 413 insertions(+), 150 deletions(-) diff --git a/src/util/qpolygon-utils.js b/src/util/qpolygon-utils.js index 3082e682..bf5c8b97 100644 --- a/src/util/qpolygon-utils.js +++ b/src/util/qpolygon-utils.js @@ -879,6 +879,7 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { const currentLine = currentBaseLine.line const prevLine = prevBaseLine.line const nextLine = nextBaseLine.line + const currentAngle = calculateAngle(currentLine.startPoint, currentLine.endPoint) const prevAngle = calculateAngle(prevLine.startPoint, prevLine.endPoint) const nextAngle = calculateAngle(nextLine.startPoint, nextLine.endPoint) @@ -918,7 +919,6 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { drawEavesSecondLines.push({ currentBaseLine, prevBaseLine, nextBaseLine }) } } else if (gableType.includes(nextLine.attributes?.type) && gableType.includes(prevLine.attributes?.type)) { - console.log('currentLine :', currentBaseLine.size, 'prevAngle :', prevAngle, 'nextAngle :', nextAngle) if (Big(prevAngle).minus(Big(nextAngle)).abs().eq(180)) { const checkPoints = { x: currentMidX.plus(checkScale.times(Math.sign(xVector.toNumber()))).toNumber(), @@ -934,7 +934,9 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { drawGableRidgeSecond.push({ currentBaseLine, prevBaseLine, nextBaseLine }) // } } else { - drawGablePolygonSecond.push({ currentBaseLine, prevBaseLine, nextBaseLine }) + if (currentAngle !== prevAngle && currentAngle !== nextAngle) { + drawGablePolygonSecond.push({ currentBaseLine, prevBaseLine, nextBaseLine }) + } } } } @@ -1034,44 +1036,158 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { const nextVectorY = Math.sign(Big(nextLine.y2).minus(Big(nextLine.y1))) /** 현재라인의 기준점*/ - const currentMidX = Big(x1).plus(Big(x2)).div(2).plus(Big(prevVectorX).times(currentLine.attributes.offset)) - const currentMidY = Big(y1).plus(Big(y2)).div(2).plus(Big(prevVectorY).times(currentLine.attributes.offset)) + let currentMidX = Big(x1).plus(Big(x2)).div(2).plus(Big(prevVectorX).times(currentLine.attributes.offset)) + let currentMidY = Big(y1).plus(Big(y2)).div(2).plus(Big(prevVectorY).times(currentLine.attributes.offset)) /** 마루 반대 좌표*/ let oppositeMidX = currentMidX, oppositeMidY = currentMidY + /** 한개의 지붕선을 둘로 나누어서 처리 하는 경우 */ if (prevAngle === beforePrevAngle || nextAngle === afterNextAngle) { - console.log('지붕선 분할 : start') - const checkLine1 = new fabric.Line([prevLine.x1, prevLine.y1, prevLine.x2, prevLine.y2], { - stroke: 'red', - strokeWidth: 4, - parentId: roofId, - name: 'checkLine', - }) - const checkLine2 = new fabric.Line([nextLine.x1, nextLine.y1, nextLine.x2, nextLine.y2], { - stroke: 'blue', - strokeWidth: 4, - parentId: roofId, - name: 'checkLine', - }) - const checkPoint = new fabric.Circle({ - left: currentMidX.toNumber(), - top: currentMidY.toNumber(), - radius: 5, - fill: 'red', - parentId: roofId, - name: 'checkPoint', - }) - canvas.add(checkLine1) - canvas.add(checkLine2) - canvas.add(checkPoint) - canvas.renderAll() - if (currentVectorX === 0) { + const addLength = Big(currentLine.y1).minus(Big(currentLine.y2)).abs().div(2) + const ridgeVector = Math.sign(prevLine.x1 - currentLine.x1) + oppositeMidX = Big(prevLine.x1).plus(Big(addLength).times(ridgeVector)) + + const ridgeEdge = { + vertex1: { x: oppositeMidX.toNumber(), y: oppositeMidY.toNumber() }, + vertex2: { x: currentMidX.toNumber(), y: currentMidY.toNumber() }, + } + + const ridgeVectorX = Math.sign(ridgeEdge.vertex1.x - ridgeEdge.vertex2.x) + roof.lines + .filter((line) => line.x1 === line.x2) + .forEach((line) => { + const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } + const is = edgesIntersection(ridgeEdge, lineEdge) + if (is) { + const isVectorX = Math.sign(ridgeEdge.vertex1.x - is.x) + if ( + isVectorX === ridgeVectorX && + ((line.x1 <= currentMidX && line.x2 >= currentMidX) || (line.x2 <= currentMidX && line.x1 >= currentMidX)) + ) { + currentMidX = Big(is.x) + currentMidY = Big(is.y) + } + } + }) } else { + const addLength = Big(currentLine.x1).minus(Big(currentLine.x2)).abs().div(2) + const ridgeVector = Math.sign(prevLine.y1 - currentLine.y1) + oppositeMidY = Big(prevLine.y1).plus(addLength.times(ridgeVector)) + + const ridgeEdge = { + vertex1: { x: oppositeMidX.toNumber(), y: oppositeMidY.toNumber() }, + vertex2: { x: currentMidX.toNumber(), y: currentMidY.toNumber() }, + } + const ridgeVectorY = Math.sign(ridgeEdge.vertex1.y - ridgeEdge.vertex2.y) + roof.lines + .filter((line) => line.y1 === line.y2) + .forEach((line) => { + const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } + const is = edgesIntersection(ridgeEdge, lineEdge) + if (is) { + const isVectorY = Math.sign(ridgeEdge.vertex1.y - is.y) + if ( + isVectorY === ridgeVectorY && + ((line.x1 <= currentMidX && line.x2 >= currentMidX) || (line.x2 <= currentMidX && line.x1 >= currentMidX)) + ) { + currentMidX = Big(is.x) + currentMidY = Big(is.y) + } + } + }) } + const prevHipEdge = { vertex1: { x: oppositeMidX.toNumber(), y: oppositeMidY.toNumber() }, vertex2: { x: prevLine.x1, y: prevLine.y1 } } + const prevHipVectorX = Math.sign(prevHipEdge.vertex1.x - prevHipEdge.vertex2.x) + const prevHipVectorY = Math.sign(prevHipEdge.vertex1.y - prevHipEdge.vertex2.y) + const prevIsPoints = [] + roof.lines + .filter((line) => (Math.sign(prevLine.x1 - prevLine.x2) === 0 ? line.x1 === line.x2 : line.y1 === line.y2)) + .forEach((line) => { + const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } + const is = edgesIntersection(prevHipEdge, lineEdge) + if (is) { + const isVectorX = Math.sign(prevHipEdge.vertex1.x - is.x) + const isVectorY = Math.sign(prevHipEdge.vertex1.y - is.y) + if (isVectorX === prevHipVectorX && isVectorY === prevHipVectorY) { + const size = Big(prevHipEdge.vertex1.x) + .minus(Big(is.x)) + .abs() + .pow(2) + .plus(Big(prevHipEdge.vertex1.y).minus(Big(is.y)).abs().pow(2)) + .sqrt() + .toNumber() + prevIsPoints.push({ is, size }) + } + } + }) + + if (prevIsPoints.length > 0) { + const prevIs = prevIsPoints.sort((a, b) => a.size - b.size)[0].is + const prevHipLine = drawHipLine( + [prevIs.x, prevIs.y, oppositeMidX.toNumber(), oppositeMidY.toNumber()], + canvas, + roof, + textMode, + null, + prevDegree, + prevDegree, + ) + baseHipLines.push({ x1: prevLine.x1, y1: prevLine.y1, x2: oppositeMidX.toNumber(), y2: oppositeMidY.toNumber(), line: prevHipLine }) + } + + const nextHipEdge = { vertex1: { x: oppositeMidX.toNumber(), y: oppositeMidY.toNumber() }, vertex2: { x: nextLine.x2, y: nextLine.y2 } } + const nextHipVectorX = Math.sign(nextHipEdge.vertex1.x - nextHipEdge.vertex2.x) + const nextHipVectorY = Math.sign(nextHipEdge.vertex1.y - nextHipEdge.vertex2.y) + const nextIsPoints = [] + + roof.lines + .filter((line) => (Math.sign(nextLine.x1 - nextLine.x2) === 0 ? line.x1 === line.x2 : line.y1 === line.y2)) + .forEach((line) => { + const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } + const is = edgesIntersection(nextHipEdge, lineEdge) + if (is) { + const isVectorX = Math.sign(nextHipEdge.vertex1.x - is.x) + const isVectorY = Math.sign(nextHipEdge.vertex1.y - is.y) + if (isVectorX === nextHipVectorX && isVectorY === nextHipVectorY) { + const size = Big(nextHipEdge.vertex1.x) + .minus(Big(is.x)) + .abs() + .pow(2) + .plus(Big(nextHipEdge.vertex1.y).minus(Big(is.y)).abs().pow(2)) + .sqrt() + .toNumber() + nextIsPoints.push({ is, size }) + } + } + }) + if (nextIsPoints.length > 0) { + const nextIs = nextIsPoints.sort((a, b) => a.size - b.size)[0].is + const nextHipLine = drawHipLine( + [nextIs.x, nextIs.y, oppositeMidX.toNumber(), oppositeMidY.toNumber()], + canvas, + roof, + textMode, + null, + nextDegree, + nextDegree, + ) + baseHipLines.push({ x1: nextLine.x2, y1: nextLine.y2, x2: oppositeMidX.toNumber(), y2: oppositeMidY.toNumber(), line: nextHipLine }) + } + + if (baseRidgeCount < getMaxRidge(baseLines.length)) { + const ridgeLine = drawRidgeLine( + [currentMidX.toNumber(), currentMidY.toNumber(), oppositeMidX.toNumber(), oppositeMidY.toNumber()], + canvas, + roof, + textMode, + ) + baseGableRidgeLines.push(ridgeLine) + baseRidgeCount++ + } console.log('지붕선 분할 : end') } else { if (beforePrevBaseLine === afterNextBaseLine) { @@ -1236,7 +1352,10 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { y: currentMidY.plus(Big(nextVectorY).times(nextBaseLine.size)).toNumber(), }, } - let hipEdge = { vertex1: { x: nextLine.x2, y: nextLine.y2 }, vertex2: { x: nextEndPoint.x, y: nextEndPoint.y } } + let hipEdge = { + vertex1: { x: nextLine.x2, y: nextLine.y2 }, + vertex2: { x: nextEndPoint.x, y: nextEndPoint.y }, + } let intersection = edgesIntersection(ridgeEdge, hipEdge) if (intersection) { nextHipCoords = { x1: nextLine.x2, y1: nextLine.y2, x2: intersection.x, y2: intersection.y } @@ -1283,7 +1402,10 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { y: currentMidY.plus(Big(prevVectorY).times(prevBaseLine.size)).toNumber(), }, } - let hipEdge = { vertex1: { x: prevLine.x1, y: prevLine.y1 }, vertex2: { x: prevEndPoint.x, y: prevEndPoint.y } } + let hipEdge = { + vertex1: { x: prevLine.x1, y: prevLine.y1 }, + vertex2: { x: prevEndPoint.x, y: prevEndPoint.y }, + } let intersection = edgesIntersection(ridgeEdge, hipEdge) if (intersection) { prevHipCoords = { x1: prevLine.x1, y1: prevLine.y1, x2: intersection.x, y2: intersection.y } @@ -1299,135 +1421,268 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { prevOppositeMidY = currentMidY } } - const checkPrevSize = currentMidX.minus(prevOppositeMidX).pow(2).plus(currentMidY.minus(prevOppositeMidY).pow(2)).sqrt() - const checkNextSize = currentMidX.minus(nextOppositeMidX).pow(2).plus(currentMidY.minus(nextOppositeMidY).pow(2)).sqrt() - /** 두 포인트 중에 current와 가까운 포인트를 사용*/ - if (checkPrevSize.gt(checkNextSize)) { - if (nextHipCoords) { - let intersectPoints = [] - const hipEdge = { - vertex1: { x: nextHipCoords.x2, y: nextHipCoords.y2 }, - vertex2: { x: nextHipCoords.x1, y: nextHipCoords.y1 }, - } + const currentMidEdge = { + vertex1: { x: currentMidX.toNumber(), y: currentMidY.toNumber() }, + vertex2: { + x: currentVectorX === 0 ? nextLine.x2 : currentMidX.toNumber(), + y: currentVectorX === 0 ? currentMidY.toNumber() : nextLine.y2, + }, + } - /** 외벽선에서 라인 겹치는 경우에 대한 확인*/ - roof.lines.forEach((line) => { - const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } - const intersection = edgesIntersection(hipEdge, lineEdge) - if ( - intersection && - Math.sign(nextHipCoords.x2 - nextHipCoords.x1) === Math.sign(nextHipCoords.x2 - intersection.x) && - Math.sign(nextHipCoords.y2 - nextHipCoords.y1) === Math.sign(nextHipCoords.y2 - intersection.y) - ) { - const intersectEdge = { - vertex1: { x: nextHipCoords.x2, y: nextHipCoords.y2 }, - vertex2: { x: intersection.x, y: intersection.y }, - } - const is = edgesIntersection(intersectEdge, lineEdge) - if (!is.isIntersectionOutside) { - const intersectSize = Big(nextHipCoords.x2) - .minus(Big(intersection.x)) - .pow(2) - .plus(Big(nextHipCoords.y2).minus(Big(intersection.y)).pow(2)) - .abs() - .sqrt() - .toNumber() - - intersectPoints.push({ - intersection, - size: intersectSize, - line, - }) + let oppositeLines = [] + drawBaseLines + .filter((line, index) => { + const currentLine = line.line + const nextLine = drawBaseLines[(index + 1) % drawBaseLines.length].line + const prevLine = drawBaseLines[(index - 1 + drawBaseLines.length) % drawBaseLines.length].line + console.log('line.startPoint :', currentLine.startPoint, 'line.endPoint :', currentLine.endPoint) + const angle = calculateAngle(currentLine.startPoint, currentLine.endPoint) + const prevAngle = calculateAngle(prevLine.startPoint, prevLine.endPoint) + const nextAngle = calculateAngle(nextLine.startPoint, nextLine.endPoint) + if (angle === prevAngle || angle === nextAngle) { + const sameAngleLine = angle === prevAngle ? prevLine : nextLine + if (gableType.includes(currentLine.attributes.type) && !gableType.includes(sameAngleLine.attributes.type)) { + switch (currentAngle) { + case 90: + return angle === -90 + case -90: + return angle === 90 + case 0: + return angle === 180 + case 180: + return angle === 0 } } - }) - const intersect = intersectPoints.sort((a, b) => a.size - b.size)[0] - if (intersect) { - const degree = intersect.line.attributes.pitch > 0 ? getDegreeByChon(intersect.line.attributes.pitch) : intersect.line.attributes.degree - const hipLine = drawHipLine( - [intersect.intersection.x, intersect.intersection.y, nextOppositeMidX.toNumber(), nextOppositeMidY.toNumber()], - canvas, - roof, - textMode, - null, - degree, - degree, - ) - baseHipLines.push({ - x1: nextHipCoords.x1, - y1: nextHipCoords.y1, - x2: nextHipCoords.x2, - y2: nextHipCoords.y2, - line: hipLine, - }) } - } - oppositeMidY = nextOppositeMidY - oppositeMidX = nextOppositeMidX + return false + }) + .forEach((line) => { + const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } + const intersection = edgesIntersection(currentMidEdge, lineEdge) + if (intersection) { + if (line.x1 <= intersection.x && line.x2 >= intersection.x && line.y1 <= intersection.y && line.y2 >= intersection.y) { + oppositeLines.push({ + line, + intersection, + size: Big(intersection.x) + .minus(currentMidX) + .abs() + .pow(2) + .plus(Big(intersection.y).minus(currentMidY).abs().pow(2)) + .sqrt() + .toNumber(), + }) + } + } + }) + + if (oppositeLines.length > 0) { + const oppositePoint = oppositeLines.sort((a, b) => a.size - b.size)[0].intersection + const checkEdge = { vertex1: { x: currentMidX.toNumber(), y: currentMidY.toNumber() }, vertex2: { x: oppositePoint.x, y: oppositePoint.y } } + const oppositeRoofPoints = [] + roof.lines + .filter((line) => { + const angle = calculateAngle(line.startPoint, line.endPoint) + switch (currentAngle) { + case 90: + return angle === -90 + case -90: + return angle === 90 + case 0: + return angle === 180 + case 180: + return angle === 0 + } + }) + .forEach((line) => { + const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } + const intersection = edgesIntersection(checkEdge, lineEdge) + if ( + intersection && + ((line.x1 <= intersection.x && line.x2 >= intersection.x && line.y1 <= intersection.y && line.y2 >= intersection.y) || + (line.x1 >= intersection.x && line.x2 <= intersection.x && line.y1 >= intersection.y && line.y2 <= intersection.y)) + ) { + oppositeRoofPoints.push({ + line, + intersection, + size: Big(intersection.x) + .minus(currentMidX.toNumber()) + .abs() + .pow(2) + .plus(Big(intersection.y).minus(currentMidY.toNumber()).abs().pow(2)) + .sqrt() + .toNumber(), + }) + } + }) + const oppositeRoofPoint = oppositeRoofPoints.sort((a, b) => a.size - b.size)[0].intersection + oppositeMidX = Big(oppositeRoofPoint.x) + oppositeMidY = Big(oppositeRoofPoint.y) + + const currentRoofPoints = [] + roof.lines + .filter((line) => { + const angle = calculateAngle(line.startPoint, line.endPoint) + return currentAngle === angle + }) + .forEach((line) => { + const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } + const intersection = edgesIntersection(checkEdge, lineEdge) + if ( + intersection && + ((line.x1 <= intersection.x && line.x2 >= intersection.x && line.y1 <= intersection.y && line.y2 >= intersection.y) || + (line.x1 >= intersection.x && line.x2 <= intersection.x && line.y1 >= intersection.y && line.y2 <= intersection.y)) + ) { + currentRoofPoints.push({ + line, + intersection, + size: Big(intersection.x) + .minus(currentMidX.toNumber()) + .abs() + .pow(2) + .plus(Big(intersection.y).minus(currentMidY.toNumber()).abs().pow(2)) + .sqrt() + .toNumber(), + }) + } + }) + const currentRoofPoint = currentRoofPoints.sort((a, b) => a.size - b.size)[0].intersection + currentMidX = Big(currentRoofPoint.x) + currentMidY = Big(currentRoofPoint.y) } else { - if (prevHipCoords) { - let intersectPoints = [] - const hipEdge = { - vertex1: { x: prevHipCoords.x2, y: prevHipCoords.y2 }, - vertex2: { x: prevHipCoords.x1, y: prevHipCoords.y1 }, - } + const checkPrevSize = currentMidX.minus(prevOppositeMidX).pow(2).plus(currentMidY.minus(prevOppositeMidY).pow(2)).sqrt() + const checkNextSize = currentMidX.minus(nextOppositeMidX).pow(2).plus(currentMidY.minus(nextOppositeMidY).pow(2)).sqrt() - /** 외벽선에서 라인 겹치는 경우에 대한 확인*/ - roof.lines.forEach((line) => { - const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } - const intersection = edgesIntersection(hipEdge, lineEdge) - if ( - intersection && - Math.sign(prevHipCoords.x2 - prevHipCoords.x1) === Math.sign(prevHipCoords.x2 - intersection.x) && - Math.sign(prevHipCoords.y2 - prevHipCoords.y1) === Math.sign(prevHipCoords.y2 - intersection.y) - ) { - const intersectEdge = { - vertex1: { x: prevHipCoords.x2, y: prevHipCoords.y2 }, - vertex2: { x: intersection.x, y: intersection.y }, - } - const is = edgesIntersection(intersectEdge, lineEdge) - if (!is.isIntersectionOutside) { - const intersectSize = Big(prevHipCoords.x2) - .minus(Big(intersection.x)) - .pow(2) - .plus(Big(prevHipCoords.y2).minus(Big(intersection.y)).pow(2)) - .abs() - .sqrt() - .toNumber() - - intersectPoints.push({ - intersection, - size: intersectSize, - line, - }) - } + /** 두 포인트 중에 current와 가까운 포인트를 사용*/ + if (checkPrevSize.gt(checkNextSize)) { + if (nextHipCoords) { + let intersectPoints = [] + const hipEdge = { + vertex1: { x: nextHipCoords.x2, y: nextHipCoords.y2 }, + vertex2: { x: nextHipCoords.x1, y: nextHipCoords.y1 }, } - }) - const intersect = intersectPoints.sort((a, b) => a.size - b.size)[0] - if (intersect) { - const degree = intersect.line.attributes.pitch > 0 ? getDegreeByChon(intersect.line.attributes.pitch) : intersect.line.attributes.degree - const hipLine = drawHipLine( - [intersect.intersection.x, intersect.intersection.y, prevOppositeMidX.toNumber(), prevOppositeMidY.toNumber()], - canvas, - roof, - textMode, - null, - degree, - degree, - ) - baseHipLines.push({ - x1: prevHipCoords.x1, - y1: prevHipCoords.y1, - x2: prevHipCoords.x2, - y2: prevHipCoords.y2, - line: hipLine, + /** 외벽선에서 라인 겹치는 경우에 대한 확인*/ + roof.lines.forEach((line) => { + const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } + const intersection = edgesIntersection(hipEdge, lineEdge) + if ( + intersection && + Math.sign(nextHipCoords.x2 - nextHipCoords.x1) === Math.sign(nextHipCoords.x2 - intersection.x) && + Math.sign(nextHipCoords.y2 - nextHipCoords.y1) === Math.sign(nextHipCoords.y2 - intersection.y) + ) { + const intersectEdge = { + vertex1: { x: nextHipCoords.x2, y: nextHipCoords.y2 }, + vertex2: { x: intersection.x, y: intersection.y }, + } + const is = edgesIntersection(intersectEdge, lineEdge) + if (!is.isIntersectionOutside) { + const intersectSize = Big(nextHipCoords.x2) + .minus(Big(intersection.x)) + .pow(2) + .plus(Big(nextHipCoords.y2).minus(Big(intersection.y)).pow(2)) + .abs() + .sqrt() + .toNumber() + + intersectPoints.push({ + intersection, + size: intersectSize, + line, + }) + } + } }) + const intersect = intersectPoints.sort((a, b) => a.size - b.size)[0] + if (intersect) { + const degree = + intersect.line.attributes.pitch > 0 ? getDegreeByChon(intersect.line.attributes.pitch) : intersect.line.attributes.degree + const hipLine = drawHipLine( + [intersect.intersection.x, intersect.intersection.y, nextOppositeMidX.toNumber(), nextOppositeMidY.toNumber()], + canvas, + roof, + textMode, + null, + degree, + degree, + ) + baseHipLines.push({ + x1: nextHipCoords.x1, + y1: nextHipCoords.y1, + x2: nextHipCoords.x2, + y2: nextHipCoords.y2, + line: hipLine, + }) + } } + oppositeMidY = nextOppositeMidY + oppositeMidX = nextOppositeMidX + } else { + if (prevHipCoords) { + let intersectPoints = [] + const hipEdge = { + vertex1: { x: prevHipCoords.x2, y: prevHipCoords.y2 }, + vertex2: { x: prevHipCoords.x1, y: prevHipCoords.y1 }, + } + + /** 외벽선에서 라인 겹치는 경우에 대한 확인*/ + roof.lines.forEach((line) => { + const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } + const intersection = edgesIntersection(hipEdge, lineEdge) + if ( + intersection && + Math.sign(prevHipCoords.x2 - prevHipCoords.x1) === Math.sign(prevHipCoords.x2 - intersection.x) && + Math.sign(prevHipCoords.y2 - prevHipCoords.y1) === Math.sign(prevHipCoords.y2 - intersection.y) + ) { + const intersectEdge = { + vertex1: { x: prevHipCoords.x2, y: prevHipCoords.y2 }, + vertex2: { x: intersection.x, y: intersection.y }, + } + const is = edgesIntersection(intersectEdge, lineEdge) + if (!is.isIntersectionOutside) { + const intersectSize = Big(prevHipCoords.x2) + .minus(Big(intersection.x)) + .pow(2) + .plus(Big(prevHipCoords.y2).minus(Big(intersection.y)).pow(2)) + .abs() + .sqrt() + .toNumber() + + intersectPoints.push({ + intersection, + size: intersectSize, + line, + }) + } + } + }) + const intersect = intersectPoints.sort((a, b) => a.size - b.size)[0] + + if (intersect) { + const degree = + intersect.line.attributes.pitch > 0 ? getDegreeByChon(intersect.line.attributes.pitch) : intersect.line.attributes.degree + const hipLine = drawHipLine( + [intersect.intersection.x, intersect.intersection.y, prevOppositeMidX.toNumber(), prevOppositeMidY.toNumber()], + canvas, + roof, + textMode, + null, + degree, + degree, + ) + baseHipLines.push({ + x1: prevHipCoords.x1, + y1: prevHipCoords.y1, + x2: prevHipCoords.x2, + y2: prevHipCoords.y2, + line: hipLine, + }) + } + } + oppositeMidY = prevOppositeMidY + oppositeMidX = prevOppositeMidX } - oppositeMidY = prevOppositeMidY - oppositeMidX = prevOppositeMidX } } if (baseRidgeCount < getMaxRidge(baseLines.length)) { @@ -1459,7 +1714,6 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { } } }) - const ridgeLine = drawRidgeLine([currentMidX, currentMidY, oppositeMidX, oppositeMidY], canvas, roof, textMode) baseGableRidgeLines.push(ridgeLine) baseRidgeCount++ @@ -2190,6 +2444,15 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { let prevLineRidges = [], nextLineRidges = [] + const checkLine = new fabric.Line([x1, y1, x2, y2], { + stroke: 'red', + strokeWidth: 4, + parentId: roofId, + name: 'checkLine', + }) + canvas.add(checkLine) + canvas.renderAll() + const currentVectorX = Math.sign(currentLine.x2 - currentLine.x1) const currentVectorY = Math.sign(currentLine.y2 - currentLine.y1) const prevVectorX = Math.sign(prevLine.x2 - prevLine.x1) -- 2.47.2 From b5b2ac21047272197b507772393426038c4a8b1e Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Fri, 16 May 2025 10:09:22 +0900 Subject: [PATCH 090/109] =?UTF-8?q?=EB=B3=B4=EC=A1=B0=EC=84=A0=20,=20?= =?UTF-8?q?=EC=A7=80=EB=B6=95=EC=84=A0=20=EB=A7=8C=EB=82=98=EB=8A=94=20?= =?UTF-8?q?=EA=B2=BD=EC=9A=B0=20=EC=A7=80=EB=B6=95=EC=84=A0=EB=B6=80?= =?UTF-8?q?=ED=84=B0=20=EC=9E=91=EC=97=85=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/roofcover/useAuxiliaryDrawing.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/hooks/roofcover/useAuxiliaryDrawing.js b/src/hooks/roofcover/useAuxiliaryDrawing.js index 78d0b883..c854ae77 100644 --- a/src/hooks/roofcover/useAuxiliaryDrawing.js +++ b/src/hooks/roofcover/useAuxiliaryDrawing.js @@ -704,6 +704,7 @@ export function useAuxiliaryDrawing(id, isUseEffect = true) { removeLine(line1) } intersectionPoints.current.push(interSectionPointsWithRoofLines[0]) + return } } -- 2.47.2 From 59a55e4c881cc87d7f3d2d0ae8abd0aa16a62e95 Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Fri, 16 May 2025 10:31:23 +0900 Subject: [PATCH 091/109] =?UTF-8?q?961=20:=20=EC=99=B8=EB=B2=BD=EC=84=A0?= =?UTF-8?q?=20=EA=B7=B8=EB=A6=AC=EA=B8=B0=20=EB=B0=8F=20=EB=B0=B0=EC=B9=98?= =?UTF-8?q?=EB=A9=B4=20=EA=B7=B8=EB=A6=AC=EA=B8=B0=EC=97=90=20=EB=8C=80?= =?UTF-8?q?=ED=95=B4=EC=84=9C=E3=80=80=E5=A4=96=E5=A3=81=E7=B7=9A=E3=82=92?= =?UTF-8?q?=E6=8F=8F=E3=81=8F=E3=83=BB=E9=85=8D=E7=BD=AE=E9=9D=A2=E3=81=AE?= =?UTF-8?q?=E6=8F=8F=E7=94=BB=E3=81=AB=E3=81=A4=E3=81=84=E3=81=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. "외벽선 그리기", "배치면 그리기"에서 선을 그을때 우클릭으로 "undo" 할 수 있도록 해주세요. 2. 마찬가지로 "외벽선 그리기", "배치면 그리기"로 지붕면을 확정할 때 Enter Key로 지붕면을 확정할 수 있도록 해주시기 바랍니다. --- src/hooks/roofcover/useOuterLineWall.js | 7 +++++++ src/hooks/surface/usePlacementShapeDrawing.js | 19 ++++++++++++++++--- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/hooks/roofcover/useOuterLineWall.js b/src/hooks/roofcover/useOuterLineWall.js index 18a07cf8..9ca76ea5 100644 --- a/src/hooks/roofcover/useOuterLineWall.js +++ b/src/hooks/roofcover/useOuterLineWall.js @@ -96,6 +96,10 @@ export function useOuterLineWall(id, propertiesId) { } addCanvasMouseEventListener('mouse:down', mouseDown) + addDocumentEventListener('contextmenu', document, (e) => { + handleRollback() + }) + clear() return () => { initEvent() @@ -690,6 +694,9 @@ export function useOuterLineWall(id, propertiesId) { if (points.length === 0) { return } + if (e.key === 'Enter') { + handleFix() + } // 포커스가 length1에 있지 않으면 length1에 포커스를 줌 const activeElem = document.activeElement if (activeElem !== length1Ref.current) { diff --git a/src/hooks/surface/usePlacementShapeDrawing.js b/src/hooks/surface/usePlacementShapeDrawing.js index d53faa39..be23cd7c 100644 --- a/src/hooks/surface/usePlacementShapeDrawing.js +++ b/src/hooks/surface/usePlacementShapeDrawing.js @@ -92,6 +92,9 @@ export function usePlacementShapeDrawing(id) { } addCanvasMouseEventListener('mouse:down', mouseDown) + addDocumentEventListener('contextmenu', document, (e) => { + handleRollback() + }) clear() }, [verticalHorizontalMode, points, adsorptionPointAddMode, adsorptionPointMode, adsorptionRange, interval, tempGridMode]) @@ -175,7 +178,7 @@ export function usePlacementShapeDrawing(id) { } } -/* + /* mouseMove */ const roofs = canvas?.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF) @@ -184,7 +187,7 @@ mouseMove const { getAdsorptionPoints } = useAdsorptionPoint() const mouseMove = (e) => { - removeMouseLine(); + removeMouseLine() const pointer = canvas.getPointer(e.e) const roofsPoints = roofs.map((roof) => roof.points).flat() roofAdsorptionPoints.current = [...roofsPoints] @@ -246,7 +249,6 @@ mouseMove }) canvas?.add(horizontalLine, verticalLine) canvas?.renderAll() - } useEffect(() => { @@ -768,6 +770,7 @@ mouseMove if (points.length === 0) { return } + enterCheck(e) // 포커스가 length1에 있지 않으면 length1에 포커스를 줌 const activeElem = document.activeElement @@ -833,6 +836,7 @@ mouseMove if (points.length === 0) { return } + enterCheck(e) const key = e.key const activeElem = document.activeElement @@ -866,6 +870,7 @@ mouseMove if (points.length === 0) { return } + enterCheck(e) const key = e.key switch (key) { case 'Down': // IE/Edge에서 사용되는 값 @@ -891,6 +896,7 @@ mouseMove if (points.length === 0) { return } + enterCheck(e) const key = e.key switch (key) { case 'Enter': { @@ -915,6 +921,7 @@ mouseMove if (points.length === 0) { return } + enterCheck(e) const key = e.key switch (key) { @@ -979,6 +986,12 @@ mouseMove isFix.current = true } + const enterCheck = (e) => { + if (e.key === 'Enter') { + handleFix() + } + } + return { points, setPoints, -- 2.47.2 From 46f9385163be4096114cf7892bfe10c14c3068d8 Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Fri, 16 May 2025 10:37:30 +0900 Subject: [PATCH 092/109] =?UTF-8?q?enterCheck=20=EC=A0=84=EB=B6=80=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/roofcover/useOuterLineWall.js | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/hooks/roofcover/useOuterLineWall.js b/src/hooks/roofcover/useOuterLineWall.js index 9ca76ea5..302a4c9a 100644 --- a/src/hooks/roofcover/useOuterLineWall.js +++ b/src/hooks/roofcover/useOuterLineWall.js @@ -694,9 +694,7 @@ export function useOuterLineWall(id, propertiesId) { if (points.length === 0) { return } - if (e.key === 'Enter') { - handleFix() - } + enterCheck(e) // 포커스가 length1에 있지 않으면 length1에 포커스를 줌 const activeElem = document.activeElement if (activeElem !== length1Ref.current) { @@ -761,6 +759,7 @@ export function useOuterLineWall(id, propertiesId) { if (points.length === 0) { return } + enterCheck(e) const key = e.key const activeElem = document.activeElement @@ -794,6 +793,7 @@ export function useOuterLineWall(id, propertiesId) { if (points.length === 0) { return } + enterCheck(e) const key = e.key switch (key) { case 'Down': // IE/Edge에서 사용되는 값 @@ -819,6 +819,7 @@ export function useOuterLineWall(id, propertiesId) { if (points.length === 0) { return } + enterCheck(e) const key = e.key switch (key) { case 'Enter': { @@ -843,7 +844,7 @@ export function useOuterLineWall(id, propertiesId) { if (points.length === 0) { return } - + enterCheck(e) const key = e.key switch (key) { case 'Down': // IE/Edge에서 사용되는 값 @@ -909,6 +910,12 @@ export function useOuterLineWall(id, propertiesId) { isFix.current = true } + const enterCheck = (e) => { + if (e.key === 'Enter') { + handleFix() + } + } + return { points, setPoints, -- 2.47.2 From 79e33880acdeab9ca990da531516eef4e5d7e7fb Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Fri, 16 May 2025 14:39:11 +0900 Subject: [PATCH 093/109] =?UTF-8?q?direction=20=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?=EC=8B=9C=20default=EA=B0=92=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/usePolygon.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/hooks/usePolygon.js b/src/hooks/usePolygon.js index 577274ad..91562948 100644 --- a/src/hooks/usePolygon.js +++ b/src/hooks/usePolygon.js @@ -1127,6 +1127,9 @@ export const usePolygon = () => { case 'left': defense = 'north' break + default: + defense = 'south' + break } pitch = polygon.lines[index]?.attributes?.pitch ?? 0 -- 2.47.2 From 97d20321ed0ed9df6719e98e56ec0d0e4ad40f9b Mon Sep 17 00:00:00 2001 From: yjnoh Date: Mon, 19 May 2025 10:29:49 +0900 Subject: [PATCH 094/109] =?UTF-8?q?[1049]=20:=20[=E6=96=87=E5=AD=97?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [작업내용] : 번역 작업 진행 --- src/locales/ja.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/locales/ja.json b/src/locales/ja.json index 57953508..6eef1edc 100644 --- a/src/locales/ja.json +++ b/src/locales/ja.json @@ -125,9 +125,9 @@ "modal.module.basic.settting.module.error6": "垂木の間隔を入力してください。\n(屋根材: {0})", "modal.module.basic.settting.module.error7": "下在ビーチを入力してください。\n(屋根材: {0})", "modal.module.basic.settting.module.error8": "モジュール配置領域の値を入力してください。\n(屋根材: {0})", - "modal.module.basic.settting.module.error9": "軒側の値は{0} mm以上でなければなりません。\n(屋根材: {1})", - "modal.module.basic.settting.module.error10": "吊下側の値は{0} mm以上でなければなりません。\n(屋根材: {1})", - "modal.module.basic.settting.module.error11": "ケラバ側の値は{0} mm以上でなければなりません。\n(屋根材: {1})", + "modal.module.basic.settting.module.error9": "軒側の配置領域の値を{0} mm以上に変更してください。\n(屋根材: {1})", + "modal.module.basic.settting.module.error10": "棟側の配置領域の値を{0} mm上に変更してください。\n(屋根材: {1})", + "modal.module.basic.settting.module.error11": "ケラバ側の配置領域の値を{0} mm上に変更してください。\n(屋根材: {1})", "modal.module.basic.settting.module.error12": "施工方法を選択してください。\n(屋根材: {0})", "modal.module.basic.setting.module.placement": "モジュールの配置", "modal.module.basic.setting.module.placement.select.fitting.type": "設置形態を選択してください。", @@ -786,7 +786,7 @@ "stuff.search.schObjectNo": "物件番号", "stuff.search.schSaleStoreName": "販売代理店名", "stuff.search.schAddress": "商品アドレス", - "stuff.search.schObjectName": "商品名", + "stuff.search.schObjectName": "物件名", "stuff.search.schDispCompanyName": "見積先", "stuff.search.schSelSaleStoreId": "販売代理店選択", "stuff.search.schReceiveUser": "担当者", -- 2.47.2 From 98f87553e6f8dd598b55fb750e006390be51bb52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=AF=BC=EC=8B=9D?= <43837214+Minsiki@users.noreply.github.com> Date: Mon, 19 May 2025 10:30:15 +0900 Subject: [PATCH 095/109] =?UTF-8?q?[1049]=20:=20[=E3=80=90HANASYS=20DESIGN?= =?UTF-8?q?=E3=80=91=E6=96=87=E5=AD=97=E4=BF=AE=E6=AD=A3]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [작업내용] : 일본어 번역 수정 --- src/locales/ja.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/locales/ja.json b/src/locales/ja.json index e866e95e..b4b66991 100644 --- a/src/locales/ja.json +++ b/src/locales/ja.json @@ -125,9 +125,9 @@ "modal.module.basic.settting.module.error6": "垂木の間隔を入力してください。\n(屋根材: {0})", "modal.module.basic.settting.module.error7": "下在ビーチを入力してください。\n(屋根材: {0})", "modal.module.basic.settting.module.error8": "モジュール配置領域の値を入力してください。\n(屋根材: {0})", - "modal.module.basic.settting.module.error9": "軒側の値は{0} mm以上でなければなりません。\n(屋根材: {1})", - "modal.module.basic.settting.module.error10": "吊下側の値は{0} mm以上でなければなりません。\n(屋根材: {1})", - "modal.module.basic.settting.module.error11": "ケラバ側の値は{0} mm以上でなければなりません。\n(屋根材: {1})", + "modal.module.basic.settting.module.error9": "軒側の配置領域の値を{0} mm以上に変更してください。\n(屋根材: {1})", + "modal.module.basic.settting.module.error10": "棟側の配置領域の値を{0} mm以上に変更してください。\n(屋根材: {1})", + "modal.module.basic.settting.module.error11": "ケラバ側の配置領域の値を{0} mm以上に変更してください。\n(屋根材: {1})", "modal.module.basic.settting.module.error12": "施工方法を選択してください。\n(屋根材: {0})", "modal.module.basic.setting.module.placement": "モジュールの配置", "modal.module.basic.setting.module.placement.select.fitting.type": "設置形態を選択してください。", @@ -785,7 +785,7 @@ "stuff.search.schObjectNo": "物件番号", "stuff.search.schSaleStoreName": "販売代理店名", "stuff.search.schAddress": "商品アドレス", - "stuff.search.schObjectName": "商品名", + "stuff.search.schObjectName": "物件名", "stuff.search.schDispCompanyName": "見積先", "stuff.search.schSelSaleStoreId": "販売代理店選択", "stuff.search.schReceiveUser": "担当者", -- 2.47.2 From e482538dec5b1a18f92bd31fff9b19317a222224 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=AF=BC=EC=8B=9D?= <43837214+Minsiki@users.noreply.github.com> Date: Mon, 19 May 2025 13:50:39 +0900 Subject: [PATCH 096/109] =?UTF-8?q?[995]=20:=20[=E3=80=90HANASYS=20DESIGN?= =?UTF-8?q?=E3=80=91=E5=9B=9E=E8=B7=AF=E7=B5=84=E3=81=AB=E3=81=A4=E3=81=84?= =?UTF-8?q?=E3=81=A6]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [작업내용] : 수동 회로 할당 시 동일면/경사인 지붕면 일 경우 불가하게 수정 및 관련 다국어 메시지 추가 --- .../step/type/PassivityCircuitAllocation.jsx | 20 +++++++++++++++++++ src/locales/ja.json | 1 + src/locales/ko.json | 1 + 3 files changed, 22 insertions(+) diff --git a/src/components/floor-plan/modal/circuitTrestle/step/type/PassivityCircuitAllocation.jsx b/src/components/floor-plan/modal/circuitTrestle/step/type/PassivityCircuitAllocation.jsx index 4a4d72a2..281ae5a8 100644 --- a/src/components/floor-plan/modal/circuitTrestle/step/type/PassivityCircuitAllocation.jsx +++ b/src/components/floor-plan/modal/circuitTrestle/step/type/PassivityCircuitAllocation.jsx @@ -86,6 +86,26 @@ export default function PassivityCircuitAllocation(props) { .map((obj) => obj.circuitNumber), ), ] + + const surfaceList = targetModules.map((module) => { + return canvas.getObjects().filter((obj) => obj.id === canvas.getObjects().filter((obj) => obj.id === module)[0].surfaceId)[0] + }) + + if (surfaceList.length > 1) { + let surfaceType = {} + + surfaceList.forEach((surface) => { + surfaceType[`${surface.direction}-${surface.roofMaterial.pitch}`] = surface + }) + if (Object.keys(surfaceType).length > 1) { + swalFire({ + text: getMessage('module.circuit.fix.not.same.roof.error'), + type: 'alert', + icon: 'warning', + }) + return + } + } if (!circuitNumber || circuitNumber === 0) { swalFire({ text: getMessage('module.circuit.minimun.error'), diff --git a/src/locales/ja.json b/src/locales/ja.json index e4804401..4e0114ab 100644 --- a/src/locales/ja.json +++ b/src/locales/ja.json @@ -1050,6 +1050,7 @@ "batch.canvas.delete.all": "配置面の内容をすべて削除しますか?", "module.not.found": "インストールモジュールを選択してください。", "module.circuit.minimun.error": "回路番号は1以上の数値を入力してください。", + "module.circuit.fix.not.same.roof.error": "異なる屋根面のモジュールが選択されています。 モジュールの選択をや直してください。", "module.already.exist.error": "回路番号が同じで異なるパワーコンディショナのモジュールがあります。 別の回路番号を設定してください。", "construction.length.difference": "屋根面工法をすべて選択してください。", "menu.validation.canvas.roof": "パネルを配置するには、屋根面を入力する必要があります。", diff --git a/src/locales/ko.json b/src/locales/ko.json index 3374c1b9..e7ab9711 100644 --- a/src/locales/ko.json +++ b/src/locales/ko.json @@ -1052,6 +1052,7 @@ "module.not.found": "모듈을 선택하세요.", "module.circuit.minimun.error": "회로번호는 1 이상입력해주세요.", "module.already.exist.error": "회로번호가 같은 다른 파워 컨디셔너 모듈이 있습니다. 다른 회로번호를 설정하십시오.", + "module.circuit.fix.not.same.roof.error": "다른 지붕면의 모듈이 선택되어 있습니다. 모듈 선택을 다시 하세요.", "construction.length.difference": "지붕면 공법을 모두 선택하십시오.", "menu.validation.canvas.roof": "패널을 배치하려면 지붕면을 입력해야 합니다.", "batch.object.outside.roof": "오브젝트는 지붕내에 설치해야 합니다.", -- 2.47.2 From 70a4c85149ea14457613cb02213a0cf1eafc97a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=AF=BC=EC=8B=9D?= <43837214+Minsiki@users.noreply.github.com> Date: Mon, 19 May 2025 13:57:34 +0900 Subject: [PATCH 097/109] =?UTF-8?q?[1049]=20:=20[=E3=80=90HANASYS=20DESIGN?= =?UTF-8?q?=E3=80=91=E6=96=87=E5=AD=97=E4=BF=AE=E6=AD=A3]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [작업내용] : 다국어 수정 --- src/locales/ja.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/locales/ja.json b/src/locales/ja.json index 3fe4da71..7593b6b6 100644 --- a/src/locales/ja.json +++ b/src/locales/ja.json @@ -786,7 +786,7 @@ "stuff.search.schObjectNo": "物件番号", "stuff.search.schSaleStoreName": "販売代理店名", "stuff.search.schAddress": "商品アドレス", - "stuff.search.schObjectName": "商品名", + "stuff.search.schObjectName": "物件名", "stuff.search.schDispCompanyName": "見積先", "stuff.search.schSelSaleStoreId": "販売代理店選択", "stuff.search.schReceiveUser": "担当者", @@ -1050,7 +1050,6 @@ "batch.canvas.delete.all": "配置面の内容をすべて削除しますか?", "module.not.found": "インストールモジュールを選択してください。", "module.circuit.minimun.error": "回路番号は1以上の数値を入力してください。", - "module.circuit.fix.not.same.roof.error": "異なる屋根面のモジュールが選択されています。 モジュールの選択をや直してください。", "module.already.exist.error": "回路番号が同じで異なるパワーコンディショナのモジュールがあります。 別の回路番号を設定してください。", "construction.length.difference": "屋根面工法をすべて選択してください。", "menu.validation.canvas.roof": "パネルを配置するには、屋根面を入力する必要があります。", -- 2.47.2 From d4ae092ae5368cc4ec9fa0bf60fe7fcb627ba6a5 Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Tue, 20 May 2025 10:23:36 +0900 Subject: [PATCH 098/109] =?UTF-8?q?=ED=9D=A1=EC=B0=A9=EC=A0=90=20=EC=9E=91?= =?UTF-8?q?=EB=8F=99=20=EA=B4=80=EB=A0=A8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../modal/setting01/SecondOption.jsx | 10 +++--- src/hooks/option/useCanvasSetting.js | 6 ++-- src/hooks/useEvent.js | 35 ++++++++++++------- 3 files changed, 30 insertions(+), 21 deletions(-) diff --git a/src/components/floor-plan/modal/setting01/SecondOption.jsx b/src/components/floor-plan/modal/setting01/SecondOption.jsx index f264edb2..d0acddb2 100644 --- a/src/components/floor-plan/modal/setting01/SecondOption.jsx +++ b/src/components/floor-plan/modal/setting01/SecondOption.jsx @@ -185,7 +185,7 @@ export default function SecondOption(props) { const onClickOption = async (item) => { let option4Data = settingModalSecondOptions?.option4 - let adsorpPointData = adsorptionPointMode.adsorptionPoint + let adsorpPointData = adsorptionPointMode //흡착범위 설정(단 건 선택) if ( @@ -203,11 +203,9 @@ export default function SecondOption(props) { //흡착점 범위 setAdsorptionRange(item.range) - - setAdsorptionPointMode({ ...adsorptionPointMode, adsorptionPoint: adsorpPointData }) + setAdsorptionPointMode(adsorpPointData) } else if (item === 'adsorpPoint') { - setAdsorptionPointMode({ ...adsorptionPointMode, adsorptionPoint: !adsorpPointData }) - adsorpPointData = !adsorpPointData + setAdsorptionPointMode(!adsorpPointData) } setSettingsData({ ...settingsData, option4: [...option4Data], adsorptionPoint: adsorpPointData }) @@ -257,7 +255,7 @@ export default function SecondOption(props) { }} > {getMessage('modal.canvas.setting.font.plan.absorption.point')} - {adsorptionPointMode.adsorptionPoint ? 'ON' : 'OFF'} + {adsorptionPointMode ? 'ON' : 'OFF'} diff --git a/src/hooks/option/useCanvasSetting.js b/src/hooks/option/useCanvasSetting.js index 7bde5638..6cd11a2d 100644 --- a/src/hooks/option/useCanvasSetting.js +++ b/src/hooks/option/useCanvasSetting.js @@ -609,7 +609,7 @@ export function useCanvasSetting(executeEffect = true) { const optionData5 = settingModalFirstOptions.dimensionDisplay.map((item) => ({ ...item })) /** 흡착점 ON/OFF */ - setAdsorptionPointMode({ ...adsorptionPointMode, adsorptionPoint: res.adsorpPoint }) + setAdsorptionPointMode(res.adsorpPoint) /** 치수선 설정 */ setDimensionLineSettings({ ...dimensionLineSettings, pixel: res.originPixel, color: res.originColor }) @@ -695,7 +695,7 @@ export function useCanvasSetting(executeEffect = true) { /** 조회된 글꼴 데이터가 없는 경우 (데이터 초기화) */ /** 흡착점 ON/OFF */ - setAdsorptionPointMode({ ...adsorptionPointMode, adsorptionPoint: false }) + setAdsorptionPointMode(false) /** 치수선 설정 */ resetDimensionLineSettings() @@ -775,7 +775,7 @@ export function useCanvasSetting(executeEffect = true) { adsorpRangeMedium: dataToSend.secondOption2[2].selected, adsorpRangeLarge: dataToSend.secondOption2[3].selected, /** 흡착점 ON/OFF */ - adsorpPoint: adsorptionPointMode.adsorptionPoint, + adsorpPoint: adsorptionPointMode, //??: adsorptionRange, 사용여부 확인 필요 /** 문자 글꼴 설정 */ diff --git a/src/hooks/useEvent.js b/src/hooks/useEvent.js index 593fd76f..18e3907b 100644 --- a/src/hooks/useEvent.js +++ b/src/hooks/useEvent.js @@ -104,24 +104,35 @@ export function useEvent() { if (dotLineGridSetting.LINE || canvas.getObjects().filter((obj) => ['lineGrid', 'tempGrid'].includes(obj.name)).length > 0) { const closestLine = getClosestLineGrid(pointer) - const horizonLines = canvas.getObjects().filter((obj) => obj.name === 'lineGrid' && obj.direction === 'horizontal') - const verticalLines = canvas.getObjects().filter((obj) => obj.name === 'lineGrid' && obj.direction === 'vertical') + const horizonLines = canvas.getObjects().filter((obj) => ['lineGrid', 'tempGrid'].includes(obj.name) && obj.direction === 'horizontal') + const verticalLines = canvas.getObjects().filter((obj) => ['lineGrid', 'tempGrid'].includes(obj.name) && obj.direction === 'vertical') if (!horizonLines || !verticalLines) { return } - const closestHorizontalLine = horizonLines.reduce((prev, curr) => { - const prevDistance = calculateDistance(pointer, prev) - const currDistance = calculateDistance(pointer, curr) - return prevDistance < currDistance ? prev : curr - }) + let closestHorizontalLine = null + let closestVerticalLine = null - const closestVerticalLine = verticalLines.reduce((prev, curr) => { - const prevDistance = calculateDistance(pointer, prev) - const currDistance = calculateDistance(pointer, curr) - return prevDistance < currDistance ? prev : curr - }) + if (horizonLines && horizonLines.length > 0) { + closestHorizontalLine = horizonLines.reduce((prev, curr) => { + const prevDistance = calculateDistance(pointer, prev) + const currDistance = calculateDistance(pointer, curr) + return prevDistance < currDistance ? prev : curr + }) + } + + if (verticalLines && verticalLines.length > 0) { + closestVerticalLine = verticalLines.reduce((prev, curr) => { + const prevDistance = calculateDistance(pointer, prev) + const currDistance = calculateDistance(pointer, curr) + return prevDistance < currDistance ? prev : curr + }) + } + + if (!closestVerticalLine || !closestHorizontalLine) { + return + } const closestIntersectionPoint = calculateIntersection(closestHorizontalLine, closestVerticalLine) -- 2.47.2 From 46dc8123df05490bb7f9bd64a3cd2a9a33e2664a Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Tue, 20 May 2025 10:34:24 +0900 Subject: [PATCH 099/109] =?UTF-8?q?=EC=9E=84=EC=9D=98=EA=B7=B8=EB=A6=AC?= =?UTF-8?q?=EB=93=9C=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useEvent.js | 34 +++++++++++++++++++++++++++++++++- src/hooks/useTempGrid.js | 32 ++------------------------------ 2 files changed, 35 insertions(+), 31 deletions(-) diff --git a/src/hooks/useEvent.js b/src/hooks/useEvent.js index 18e3907b..184bbe46 100644 --- a/src/hooks/useEvent.js +++ b/src/hooks/useEvent.js @@ -6,6 +6,8 @@ import { calculateDistance, calculateDistancePoint, calculateIntersection, dista import { useAdsorptionPoint } from '@/hooks/useAdsorptionPoint' import { useDotLineGrid } from '@/hooks/useDotLineGrid' import { useTempGrid } from '@/hooks/useTempGrid' +import { gridColorState } from '@/store/gridAtom' +import { gridDisplaySelector } from '@/store/settingAtom' export function useEvent() { const canvas = useRecoilValue(canvasState) @@ -13,10 +15,12 @@ export function useEvent() { const documentEventListeners = useRef([]) const mouseEventListeners = useRef([]) const setCanvasZoom = useSetRecoilState(canvasZoomState) + const gridColor = useRecoilValue(gridColorState) + const isGridDisplay = useRecoilValue(gridDisplaySelector) const { adsorptionPointAddMode, adsorptionPointMode, adsorptionRange, getAdsorptionPoints, adsorptionPointAddModeStateEvent } = useAdsorptionPoint() const { dotLineGridSetting, interval, getClosestLineGrid } = useDotLineGrid() - const { tempGridModeStateLeftClickEvent, tempGridMode, tempGridRightClickEvent } = useTempGrid() + const { tempGridModeStateLeftClickEvent, tempGridMode } = useTempGrid() const textMode = useRecoilValue(textModeState) @@ -249,6 +253,34 @@ export function useEvent() { }) } + const tempGridRightClickEvent = (e) => { + e.preventDefault() + e.stopPropagation() + //임의 그리드 모드일 경우 + let pointer = { x: e.offsetX, y: e.offsetY } + + const tempGrid = new fabric.Line([-1500, pointer.y, 2500, pointer.y], { + stroke: gridColor, + strokeWidth: 1, + selectable: true, + lockMovementX: true, + lockMovementY: true, + lockRotation: true, + lockScalingX: true, + lockScalingY: true, + strokeDashArray: [5, 2], + opacity: 0.3, + padding: 5, + name: 'tempGrid', + visible: isGridDisplay, + direction: 'horizontal', + }) + + canvas.add(tempGrid) + + canvas.renderAll() + } + const defaultKeyboardEvent = (e) => { if (e.key === 'Escape') { console.log('defaultKeyboardEvent') diff --git a/src/hooks/useTempGrid.js b/src/hooks/useTempGrid.js index d43a19c0..c875bcb3 100644 --- a/src/hooks/useTempGrid.js +++ b/src/hooks/useTempGrid.js @@ -7,8 +7,9 @@ const GRID_PADDING = 5 export function useTempGrid() { const canvas = useRecoilValue(canvasState) const gridColor = useRecoilValue(gridColorState) - const [tempGridMode, setTempGridMode] = useRecoilState(tempGridModeState) const isGridDisplay = useRecoilValue(gridDisplaySelector) + const [tempGridMode, setTempGridMode] = useRecoilState(tempGridModeState) + const tempGridModeStateLeftClickEvent = (e) => { //임의 그리드 모드일 경우 let pointer = canvas.getPointer(e.e) @@ -35,37 +36,8 @@ export function useTempGrid() { canvas.renderAll() } - const tempGridRightClickEvent = (e) => { - e.preventDefault() - e.stopPropagation() - //임의 그리드 모드일 경우 - let pointer = { x: e.offsetX, y: e.offsetY } - - const tempGrid = new fabric.Line([-1500, pointer.y, 2500, pointer.y], { - stroke: gridColor, - strokeWidth: 1, - selectable: true, - lockMovementX: true, - lockMovementY: true, - lockRotation: true, - lockScalingX: true, - lockScalingY: true, - strokeDashArray: [5, 2], - opacity: 0.3, - padding: GRID_PADDING, - name: 'tempGrid', - visible: isGridDisplay, - direction: 'horizontal', - }) - - canvas.add(tempGrid) - - canvas.renderAll() - } - return { tempGridModeStateLeftClickEvent, - tempGridRightClickEvent, tempGridMode, setTempGridMode, } -- 2.47.2 From cec871fb70c2f7c11a71a78d3b90bd8f842d640f Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Tue, 20 May 2025 10:52:22 +0900 Subject: [PATCH 100/109] =?UTF-8?q?=EA=B7=B8=EB=A6=AC=EB=93=9C=20=EC=9D=B4?= =?UTF-8?q?=EB=8F=99=20=EB=8B=A8=EC=9C=84=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/floor-plan/modal/grid/GridCopy.jsx | 2 +- src/components/floor-plan/modal/grid/GridMove.jsx | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/floor-plan/modal/grid/GridCopy.jsx b/src/components/floor-plan/modal/grid/GridCopy.jsx index e1a6f9f9..189a5e79 100644 --- a/src/components/floor-plan/modal/grid/GridCopy.jsx +++ b/src/components/floor-plan/modal/grid/GridCopy.jsx @@ -17,7 +17,7 @@ export default function GridCopy(props) { const currentObject = useRecoilValue(currentObjectState) const { copy } = useGrid() const handleApply = () => { - copy(currentObject, ['↑', '←'].includes(arrow) ? +length * -1 : +length) + copy(currentObject, ['↑', '←'].includes(arrow) ? (+length * -1) / 10 : +length / 10) } return ( diff --git a/src/components/floor-plan/modal/grid/GridMove.jsx b/src/components/floor-plan/modal/grid/GridMove.jsx index 4aa27851..11ac20b8 100644 --- a/src/components/floor-plan/modal/grid/GridMove.jsx +++ b/src/components/floor-plan/modal/grid/GridMove.jsx @@ -54,15 +54,15 @@ export default function GridMove(props) { .forEach((grid) => { move( grid, - arrow2 === '←' ? Number(horizonSize) * -1 : Number(horizonSize), - arrow1 === '↑' ? Number(verticalSize) * -1 : Number(verticalSize), + arrow2 === '←' ? (Number(horizonSize) * -1) / 10 : Number(horizonSize) / 10, + arrow1 === '↑' ? (Number(verticalSize) * -1) / 10 : Number(verticalSize) / 10, ) }) } else { move( currentObject, - arrow2 === '←' ? Number(horizonSize) * -1 : Number(horizonSize), - arrow1 === '↑' ? Number(verticalSize) * -1 : Number(verticalSize), + arrow2 === '←' ? (Number(horizonSize) * -1) / 10 : Number(horizonSize) / 10, + arrow1 === '↑' ? (Number(verticalSize) * -1) / 10 : Number(verticalSize) / 10, ) } canvas.renderAll() -- 2.47.2 From afd59e580f56355797562ca22eabe078a4ce8966 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=AF=BC=EC=8B=9D?= <43837214+Minsiki@users.noreply.github.com> Date: Tue, 20 May 2025 15:36:20 +0900 Subject: [PATCH 101/109] =?UTF-8?q?=EA=B7=B8=EB=A6=AC=EB=93=9C=20=EC=9D=B4?= =?UTF-8?q?=EB=8F=99,=20=EB=B3=B5=EC=82=AC=20=EB=B2=84=EA=B7=B8=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../floor-plan/modal/grid/GridCopy.jsx | 38 +++++++++++++++++-- .../floor-plan/modal/grid/GridMove.jsx | 12 +++++- 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/src/components/floor-plan/modal/grid/GridCopy.jsx b/src/components/floor-plan/modal/grid/GridCopy.jsx index e1a6f9f9..e83e5f18 100644 --- a/src/components/floor-plan/modal/grid/GridCopy.jsx +++ b/src/components/floor-plan/modal/grid/GridCopy.jsx @@ -4,9 +4,11 @@ import { usePopup } from '@/hooks/usePopup' import { useRecoilValue } from 'recoil' import { contextPopupPositionState } from '@/store/popupAtom' import { useState } from 'react' -import { currentObjectState } from '@/store/canvasAtom' +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) const { id, pos = contextPopupPosition } = props @@ -15,10 +17,40 @@ export default function GridCopy(props) { const [length, setLength] = useState('0') const [arrow, setArrow] = useState(null) const currentObject = useRecoilValue(currentObjectState) - const { copy } = useGrid() + const canvas = useRecoilValue(canvasState) + const gridColor = useRecoilValue(gridColorState) + const isGridDisplay = useRecoilValue(gridDisplaySelector) const handleApply = () => { copy(currentObject, ['↑', '←'].includes(arrow) ? +length * -1 : +length) } + + 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 line = new fabric.Line([lineStartX, lineStartY, lineEndX, lineEndY], { + stroke: gridColor, + strokeWidth: 1, + selectable: true, + lockMovementX: true, + lockMovementY: true, + lockRotation: true, + lockScalingX: true, + lockScalingY: true, + strokeDashArray: [5, 2], + opacity: 0.3, + padding: GRID_PADDING, + direction: object.direction, + visible: isGridDisplay, + name: object.name, + }) + + canvas.add(line) + canvas.setActiveObject(line) + canvas.renderAll() + } return ( closePopup(id)} /> diff --git a/src/components/floor-plan/modal/grid/GridMove.jsx b/src/components/floor-plan/modal/grid/GridMove.jsx index 4aa27851..dadd7a6e 100644 --- a/src/components/floor-plan/modal/grid/GridMove.jsx +++ b/src/components/floor-plan/modal/grid/GridMove.jsx @@ -6,7 +6,6 @@ import { contextPopupPositionState } from '@/store/popupAtom' import { useCanvas } from '@/hooks/useCanvas' import { canvasState, currentObjectState } from '@/store/canvasAtom' import { useEffect, useState } from 'react' -import { useGrid } from '@/hooks/common/useGrid' import { useSwal } from '@/hooks/useSwal' import { set } from 'react-hook-form' @@ -17,7 +16,6 @@ export default function GridMove(props) { const { getMessage } = useMessage() const { closePopup } = usePopup() const { swalFire } = useSwal() - const { move } = useGrid() const [currentObject, setCurrentObject] = useRecoilState(currentObjectState) const [isAll, setIsAll] = useState(false) const [verticalSize, setVerticalSize] = useState('0') @@ -69,6 +67,16 @@ export default function GridMove(props) { handleClose() } + 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, + }) + } + const handleClose = () => { closePopup(id) } -- 2.47.2 From 4ec191dcb0bbc60c835260a927265a8088c88039 Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Tue, 20 May 2025 17:02:56 +0900 Subject: [PATCH 102/109] =?UTF-8?q?1.=20=EC=99=B8=EB=B2=BD=EC=84=A0?= =?UTF-8?q?=EA=B7=B8=EB=A6=AC=EA=B8=B0,=20=EB=B0=B0=EC=B9=98=EB=A9=B4?= =?UTF-8?q?=EA=B7=B8=EB=A6=AC=EA=B8=B0=20=EC=97=90=EC=84=9C=20=ED=9D=A1?= =?UTF-8?q?=EC=B0=A9=EC=A0=90=20=EB=8F=99=EC=9E=91=202.=20=EC=99=B8?= =?UTF-8?q?=EB=B2=BD=EC=84=A0=EA=B7=B8=EB=A6=AC=EA=B8=B0,=20=EB=B0=B0?= =?UTF-8?q?=EC=B9=98=EB=A9=B4=EA=B7=B8=EB=A6=AC=EA=B8=B0=20=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EA=B7=B8=EB=A6=AC=EB=93=9C,=20=ED=9D=A1=EC=B0=A9?= =?UTF-8?q?=EC=A0=90=20=EC=84=A0=ED=83=9D=20=EB=B6=88=EA=B0=80=203.=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99,=20=EB=B3=B5=EC=82=AC=20=EC=8B=9C=20?= =?UTF-8?q?=EA=B8=B8=EC=9D=B4=20=EC=A4=84=EC=96=B4=EB=93=AC=204.=20?= =?UTF-8?q?=EC=98=B5=EC=85=98=20=EC=84=A4=EC=A0=95=20=EB=8B=AB=EC=9D=84=20?= =?UTF-8?q?=EB=95=8C,=20=EC=9E=84=EC=9D=98=EA=B7=B8=EB=A6=AC=EB=93=9C,?= =?UTF-8?q?=ED=9D=A1=EC=B0=A9=EC=A0=90=20=EC=84=A0=ED=83=9D=20=20=ED=95=B4?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../floor-plan/modal/setting01/GridOption.jsx | 10 ++ .../modal/setting01/SettingModal01.jsx | 25 ++++- src/hooks/common/useGrid.js | 16 +-- src/hooks/roofcover/useOuterLineWall.js | 8 +- src/hooks/surface/usePlacementShapeDrawing.js | 102 +++--------------- src/hooks/useEvent.js | 46 +++++++- src/hooks/useObject.js | 14 ++- 7 files changed, 117 insertions(+), 104 deletions(-) diff --git a/src/components/floor-plan/modal/setting01/GridOption.jsx b/src/components/floor-plan/modal/setting01/GridOption.jsx index b5b61acc..5931b561 100644 --- a/src/components/floor-plan/modal/setting01/GridOption.jsx +++ b/src/components/floor-plan/modal/setting01/GridOption.jsx @@ -105,6 +105,16 @@ export default function GridOption(props) { initEvent() }, [gridOptions]) + useEffect(() => { + return () => { + setAdsorptionPointAddMode(false) + setTempGridMode(false) + setTimeout(() => { + initEvent() + }, 100) + } + }, []) + const dotLineGridProps = { id: dotLineId, setIsShow: setShowDotLineGridModal, diff --git a/src/components/floor-plan/modal/setting01/SettingModal01.jsx b/src/components/floor-plan/modal/setting01/SettingModal01.jsx index 4e6f91ea..71c19a83 100644 --- a/src/components/floor-plan/modal/setting01/SettingModal01.jsx +++ b/src/components/floor-plan/modal/setting01/SettingModal01.jsx @@ -6,16 +6,22 @@ import WithDraggable from '@/components/common/draggable/WithDraggable' import SecondOption from '@/components/floor-plan/modal/setting01/SecondOption' import { useMessage } from '@/hooks/useMessage' import GridOption from '@/components/floor-plan/modal/setting01/GridOption' -import { canGridOptionSeletor } from '@/store/canvasAtom' -import { useRecoilValue } from 'recoil' +import { adsorptionPointAddModeState, canGridOptionSeletor, tempGridModeState } from '@/store/canvasAtom' +import { useRecoilState, useRecoilValue } from 'recoil' import { usePopup } from '@/hooks/usePopup' import { useCanvasSetting } from '@/hooks/option/useCanvasSetting' +import { useTempGrid } from '@/hooks/useTempGrid' +import { settingModalGridOptionsState } from '@/store/settingAtom' +import { useEvent } from '@/hooks/useEvent' export default function SettingModal01(props) { const { id } = props const [buttonAct, setButtonAct] = useState(1) const { getMessage } = useMessage() const canGridOptionSeletorValue = useRecoilValue(canGridOptionSeletor) + const [gridOptions, setGridOptions] = useRecoilState(settingModalGridOptionsState) + const [tempGridMode, setTempGridMode] = useRecoilState(tempGridModeState) + const [adsorptionPointAddMode, setAdsorptionPointAddMode] = useRecoilState(adsorptionPointAddModeState) const { closePopup } = usePopup() const { @@ -71,9 +77,22 @@ export default function SettingModal01(props) { setButtonAct(num) } + const onClose = () => { + setTempGridMode(false) + setAdsorptionPointAddMode(false) + setGridOptions((prev) => { + const newSettingOptions = [...prev] + newSettingOptions[0].selected = false + newSettingOptions[2].selected = false + return [...newSettingOptions] + }) + + closePopup(id, true) + } + return ( - closePopup(id, true)} /> +