dev #608
@ -17,7 +17,7 @@ import {
|
|||||||
import { calculateVisibleModuleHeight, getDegreeByChon, polygonToTurfPolygon, rectToPolygon, toFixedWithoutRounding } from '@/util/canvas-util'
|
import { calculateVisibleModuleHeight, getDegreeByChon, polygonToTurfPolygon, rectToPolygon, toFixedWithoutRounding } from '@/util/canvas-util'
|
||||||
import '@/util/fabric-extensions' // fabric 객체들에 getCurrentPoints 메서드 추가
|
import '@/util/fabric-extensions' // fabric 객체들에 getCurrentPoints 메서드 추가
|
||||||
import { basicSettingState, roofDisplaySelector } from '@/store/settingAtom'
|
import { basicSettingState, roofDisplaySelector } from '@/store/settingAtom'
|
||||||
import offsetPolygon, { calculateAngle, createLinesFromPolygon } from '@/util/qpolygon-utils'
|
import offsetPolygon, { calculateAngle, createLinesFromPolygon, cleanSelfIntersectingPolygon } from '@/util/qpolygon-utils'
|
||||||
import { QPolygon } from '@/components/fabric/QPolygon'
|
import { QPolygon } from '@/components/fabric/QPolygon'
|
||||||
import { useEvent } from '@/hooks/useEvent'
|
import { useEvent } from '@/hooks/useEvent'
|
||||||
import { BATCH_TYPE, LINE_TYPE, MODULE_SETUP_TYPE, POLYGON_TYPE } from '@/common/common'
|
import { BATCH_TYPE, LINE_TYPE, MODULE_SETUP_TYPE, POLYGON_TYPE } from '@/common/common'
|
||||||
@ -384,6 +384,8 @@ export function useModuleBasicSetting(tabNum) {
|
|||||||
} else {
|
} else {
|
||||||
offsetPoints = createPaddingPolygon(polygon, roof.lines).vertices
|
offsetPoints = createPaddingPolygon(polygon, roof.lines).vertices
|
||||||
}
|
}
|
||||||
|
// 자기교차(꼬임) 제거
|
||||||
|
offsetPoints = cleanSelfIntersectingPolygon(offsetPoints)
|
||||||
}
|
}
|
||||||
|
|
||||||
//모듈설치영역?? 생성
|
//모듈설치영역?? 생성
|
||||||
|
|||||||
@ -75,13 +75,14 @@ export function useAuxiliaryDrawing(id, isUseEffect = true) {
|
|||||||
}, [arrow2])
|
}, [arrow2])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
typeRef.current = type
|
|
||||||
clear()
|
|
||||||
if (type === null) {
|
if (type === null) {
|
||||||
initEvent()
|
initEvent()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
initEvent()
|
initEvent()
|
||||||
|
typeRef.current = type
|
||||||
|
clear()
|
||||||
addCanvasMouseEventListener('mouse:move', mouseMove)
|
addCanvasMouseEventListener('mouse:move', mouseMove)
|
||||||
addCanvasMouseEventListener('mouse:down', mouseDown)
|
addCanvasMouseEventListener('mouse:down', mouseDown)
|
||||||
addDocumentEventListener('keydown', document, keydown[type])
|
addDocumentEventListener('keydown', document, keydown[type])
|
||||||
@ -96,6 +97,10 @@ export function useAuxiliaryDrawing(id, isUseEffect = true) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (type === null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// 지붕의 각 꼭지점을 흡착점으로 설정
|
// 지붕의 각 꼭지점을 흡착점으로 설정
|
||||||
const roofsPoints = roofs.map((roof) => roof.points).flat()
|
const roofsPoints = roofs.map((roof) => roof.points).flat()
|
||||||
roofAdsorptionPoints.current = [...roofsPoints]
|
roofAdsorptionPoints.current = [...roofsPoints]
|
||||||
@ -113,6 +118,9 @@ export function useAuxiliaryDrawing(id, isUseEffect = true) {
|
|||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (type === null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
const roofs = canvas?.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF)
|
const roofs = canvas?.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF)
|
||||||
if (roofs.length === 0) {
|
if (roofs.length === 0) {
|
||||||
return
|
return
|
||||||
|
|||||||
@ -48,9 +48,9 @@ export function useRoofShapePassivitySetting(id) {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine')
|
const outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine')
|
||||||
if (!canvas.outerLineFix || outerLines.length === 0) {
|
if (!canvas.outerLineFix || outerLines.length === 0) {
|
||||||
swalFire({ text: getMessage('wall.line.not.found') })
|
swalFire({ text: getMessage('wall.line.not.found'),icon: 'warning' })
|
||||||
closePopup(id)
|
closePopup(id)
|
||||||
return
|
//return
|
||||||
}
|
}
|
||||||
setIsLoading(true)
|
setIsLoading(true)
|
||||||
}, [])
|
}, [])
|
||||||
@ -142,8 +142,9 @@ export function useRoofShapePassivitySetting(id) {
|
|||||||
|
|
||||||
const handleConfirm = () => {
|
const handleConfirm = () => {
|
||||||
if (!currentLineRef.current) {
|
if (!currentLineRef.current) {
|
||||||
alert('선택된 외곽선이 없습니다.')
|
//alert('선택된 외곽선이 없습니다.')
|
||||||
return
|
swalFire({ text: getMessage('wall.line.not.selected'), icon: 'warning' })
|
||||||
|
//return
|
||||||
}
|
}
|
||||||
let attributes
|
let attributes
|
||||||
const offset = Number(offsetRef.current.value) / 10
|
const offset = Number(offsetRef.current.value) / 10
|
||||||
@ -210,8 +211,8 @@ export function useRoofShapePassivitySetting(id) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
if (!checkedAllSetting) {
|
if (!checkedAllSetting) {
|
||||||
swalFire({ text: '설정이 완료되지 않은 외벽선이 있습니다.', icon: 'warning' })
|
swalFire({ text: getMessage('modal.canvas.setting.roofline.properties.setting.not.setting'), icon: 'warning' })
|
||||||
return
|
//return
|
||||||
}
|
}
|
||||||
|
|
||||||
exceptObjs.forEach((obj) => {
|
exceptObjs.forEach((obj) => {
|
||||||
|
|||||||
@ -1576,6 +1576,7 @@ export const usePolygon = () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
roofLines.forEach((line) => {
|
roofLines.forEach((line) => {
|
||||||
|
//console.log("::::::::::",line);
|
||||||
roof.lines.forEach((roofLine) => {
|
roof.lines.forEach((roofLine) => {
|
||||||
if (
|
if (
|
||||||
(isSamePoint(line.startPoint, roofLine.startPoint) && isSamePoint(line.endPoint, roofLine.endPoint)) ||
|
(isSamePoint(line.startPoint, roofLine.startPoint) && isSamePoint(line.endPoint, roofLine.endPoint)) ||
|
||||||
@ -1952,7 +1953,16 @@ export const usePolygon = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
polygon.lines.forEach((line) => {
|
polygon.lines.forEach((line, index) => {
|
||||||
|
//text 와 planSize 및 actualSize가 안맞는 문제
|
||||||
|
const nextText = polygon?.texts?.[index]?.text
|
||||||
|
const nextPlaneSize = Number(nextText)
|
||||||
|
if (nextText != null && nextText !== '' && Number.isFinite(nextPlaneSize) ) {
|
||||||
|
if(line.attributes.actualSize !== nextPlaneSize && line.attributes.planeSize !== nextPlaneSize) {
|
||||||
|
line.attributes.planeSize = nextPlaneSize
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
setActualSize(line, polygon.direction, +polygon.roofMaterial?.pitch)
|
setActualSize(line, polygon.direction, +polygon.roofMaterial?.pitch)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -1111,6 +1111,7 @@
|
|||||||
"module.layout.setup.has.zero.value": "モジュールの列数、段数を入力して下さい。",
|
"module.layout.setup.has.zero.value": "モジュールの列数、段数を入力して下さい。",
|
||||||
"modal.placement.initial.setting.plan.drawing.only.number": "(※数字は[半角]入力のみ可能です。)",
|
"modal.placement.initial.setting.plan.drawing.only.number": "(※数字は[半角]入力のみ可能です。)",
|
||||||
"wall.line.not.found": "外壁がありません",
|
"wall.line.not.found": "外壁がありません",
|
||||||
|
"wall.line.not.selected": "選択された外郭線がありません。",
|
||||||
"roof.line.not.found": "屋根形状がありません",
|
"roof.line.not.found": "屋根形状がありません",
|
||||||
"roof.material.can.not.delete": "割り当てられた配置面があります。",
|
"roof.material.can.not.delete": "割り当てられた配置面があります。",
|
||||||
"chidory.can.not.install": "千鳥配置できない工法です。",
|
"chidory.can.not.install": "千鳥配置できない工法です。",
|
||||||
|
|||||||
@ -1111,6 +1111,7 @@
|
|||||||
"module.layout.setup.has.zero.value": "모듈의 열수, 단수를 입력해 주세요.",
|
"module.layout.setup.has.zero.value": "모듈의 열수, 단수를 입력해 주세요.",
|
||||||
"modal.placement.initial.setting.plan.drawing.only.number": "(※ 숫자는 [반각]입력만 가능합니다.)",
|
"modal.placement.initial.setting.plan.drawing.only.number": "(※ 숫자는 [반각]입력만 가능합니다.)",
|
||||||
"wall.line.not.found": "외벽선이 없습니다.",
|
"wall.line.not.found": "외벽선이 없습니다.",
|
||||||
|
"wall.line.not.selected": "선택된 외곽선이 없습니다.",
|
||||||
"roof.line.not.found": "지붕형상이 없습니다.",
|
"roof.line.not.found": "지붕형상이 없습니다.",
|
||||||
"roof.material.can.not.delete": "할당된 배치면이 있습니다.",
|
"roof.material.can.not.delete": "할당된 배치면이 있습니다.",
|
||||||
"chidory.can.not.install": "치조 불가 공법입니다.",
|
"chidory.can.not.install": "치조 불가 공법입니다.",
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import { getAdjacent, getDegreeByChon, isPointOnLine, isPointOnLineNew } from '@
|
|||||||
import { QPolygon } from '@/components/fabric/QPolygon'
|
import { QPolygon } from '@/components/fabric/QPolygon'
|
||||||
import { LINE_TYPE, POLYGON_TYPE } from '@/common/common'
|
import { LINE_TYPE, POLYGON_TYPE } from '@/common/common'
|
||||||
import Big from 'big.js'
|
import Big from 'big.js'
|
||||||
|
import * as turf from '@turf/turf'
|
||||||
|
|
||||||
const TWO_PI = Math.PI * 2
|
const TWO_PI = Math.PI * 2
|
||||||
const EPSILON = 1e-10 //좌표계산 시 최소 차이값
|
const EPSILON = 1e-10 //좌표계산 시 최소 차이값
|
||||||
@ -248,6 +249,47 @@ function createPaddingPolygon(polygon, offset, arcSegments = 0) {
|
|||||||
return paddingPolygon
|
return paddingPolygon
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 자기교차(self-intersection)가 있는 폴리곤을 정리하는 함수
|
||||||
|
* turf.js의 unkinkPolygon을 사용하여 꼬인 부분을 제거하고 가장 큰 폴리곤을 반환
|
||||||
|
* @param {Array} vertices - [{x, y}, ...] 형태의 점 배열
|
||||||
|
* @returns {Array} 정리된 점 배열
|
||||||
|
*/
|
||||||
|
export function cleanSelfIntersectingPolygon(vertices) {
|
||||||
|
if (!vertices || vertices.length < 3) return vertices
|
||||||
|
|
||||||
|
try {
|
||||||
|
// vertices를 GeoJSON 폴리곤으로 변환
|
||||||
|
const coords = vertices.map((p) => [p.x, p.y])
|
||||||
|
coords.push([vertices[0].x, vertices[0].y]) // ring 닫기
|
||||||
|
|
||||||
|
const turfPoly = turf.polygon([coords])
|
||||||
|
|
||||||
|
// 자기교차 검사
|
||||||
|
const kinked = turf.kinks(turfPoly)
|
||||||
|
|
||||||
|
if (kinked.features.length === 0) {
|
||||||
|
return vertices // 꼬임 없음
|
||||||
|
}
|
||||||
|
|
||||||
|
// 꼬인 폴리곤을 분리
|
||||||
|
const unkinked = turf.unkinkPolygon(turfPoly)
|
||||||
|
|
||||||
|
if (unkinked.features.length > 0) {
|
||||||
|
// 가장 큰 면적의 폴리곤 선택
|
||||||
|
const largest = unkinked.features.reduce((max, f) => (turf.area(f) > turf.area(max) ? f : max))
|
||||||
|
|
||||||
|
// GeoJSON 좌표를 다시 {x, y} 형태로 변환
|
||||||
|
const cleanedCoords = largest.geometry.coordinates[0]
|
||||||
|
return cleanedCoords.slice(0, -1).map((c) => ({ x: c[0], y: c[1] }))
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('Failed to clean self-intersecting polygon:', e)
|
||||||
|
}
|
||||||
|
|
||||||
|
return vertices
|
||||||
|
}
|
||||||
|
|
||||||
export default function offsetPolygon(vertices, offset) {
|
export default function offsetPolygon(vertices, offset) {
|
||||||
const polygon = createPolygon(vertices)
|
const polygon = createPolygon(vertices)
|
||||||
const arcSegments = 0
|
const arcSegments = 0
|
||||||
@ -255,25 +297,29 @@ export default function offsetPolygon(vertices, offset) {
|
|||||||
const originPolygon = new QPolygon(vertices, { fontSize: 0 })
|
const originPolygon = new QPolygon(vertices, { fontSize: 0 })
|
||||||
originPolygon.setViewLengthText(false)
|
originPolygon.setViewLengthText(false)
|
||||||
|
|
||||||
|
let result
|
||||||
if (offset > 0) {
|
if (offset > 0) {
|
||||||
let result = createMarginPolygon(polygon, offset, arcSegments).vertices
|
let marginResult = createMarginPolygon(polygon, offset, arcSegments).vertices
|
||||||
const allPointsOutside = result.every((point) => !originPolygon.inPolygon(point))
|
const allPointsOutside = marginResult.every((point) => !originPolygon.inPolygon(point))
|
||||||
|
|
||||||
if (allPointsOutside) {
|
if (allPointsOutside) {
|
||||||
return createMarginPolygon(polygon, offset, arcSegments).vertices
|
result = createMarginPolygon(polygon, offset, arcSegments).vertices
|
||||||
} else {
|
} else {
|
||||||
return createPaddingPolygon(polygon, offset, arcSegments).vertices
|
result = createPaddingPolygon(polygon, offset, arcSegments).vertices
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let result = createPaddingPolygon(polygon, offset, arcSegments).vertices
|
let paddingResult = createPaddingPolygon(polygon, offset, arcSegments).vertices
|
||||||
const allPointsInside = result.every((point) => originPolygon.inPolygon(point))
|
const allPointsInside = paddingResult.every((point) => originPolygon.inPolygon(point))
|
||||||
|
|
||||||
if (allPointsInside) {
|
if (allPointsInside) {
|
||||||
return createPaddingPolygon(polygon, offset, arcSegments).vertices
|
result = createPaddingPolygon(polygon, offset, arcSegments).vertices
|
||||||
} else {
|
} else {
|
||||||
return createMarginPolygon(polygon, offset, arcSegments).vertices
|
result = createMarginPolygon(polygon, offset, arcSegments).vertices
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 자기교차(꼬임) 제거
|
||||||
|
return cleanSelfIntersectingPolygon(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizePoint(point) {
|
function normalizePoint(point) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user