Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into feature/skeleton-dev

# Conflicts:
#	src/util/skeleton-utils.js
This commit is contained in:
ysCha 2025-12-22 09:09:22 +09:00
commit 56371807ab
7 changed files with 1371 additions and 1649 deletions

View File

@ -11,6 +11,7 @@ import { moduleSelectionDataState } from '@/store/selectedModuleOptions'
import { useCanvasPopupStatusController } from '@/hooks/common/useCanvasPopupStatusController' import { useCanvasPopupStatusController } from '@/hooks/common/useCanvasPopupStatusController'
import { isObjectNotEmpty } from '@/util/common-utils' import { isObjectNotEmpty } from '@/util/common-utils'
import { normalizeDecimal} from '@/util/input-utils' import { normalizeDecimal} from '@/util/input-utils'
import { CalculatorInput } from '@/components/common/input/CalcInput'
export default function Module({ setTabNum }) { export default function Module({ setTabNum }) {
const { getMessage } = useMessage() const { getMessage } = useMessage()
@ -185,11 +186,23 @@ export default function Module({ setTabNum }) {
<div className="eaves-keraba-td"> <div className="eaves-keraba-td">
<div className="outline-form"> <div className="outline-form">
<div className="grid-select mr10"> <div className="grid-select mr10">
<input {/*<input*/}
type="text" {/* type="text"*/}
{/* className="input-origin block"*/}
{/* value={inputInstallHeight}*/}
{/* onChange={(e) => setInputInstallHeight(normalizeDecimal(e.target.value))}*/}
{/*/>*/}
<CalculatorInput
id=""
name=""
label=""
className="input-origin block" className="input-origin block"
value={inputInstallHeight} value={inputInstallHeight}
onChange={(e) => setInputInstallHeight(normalizeDecimal(e.target.value))} onChange={(value) => setInputInstallHeight(value)}
options={{
allowNegative: false,
allowDecimal: false
}}
/> />
</div> </div>
<span className="thin">m</span> <span className="thin">m</span>

View File

@ -10,6 +10,7 @@ import { useModuleBasicSetting } from '@/hooks/module/useModuleBasicSetting'
import { useCommonCode } from '@/hooks/common/useCommonCode' import { useCommonCode } from '@/hooks/common/useCommonCode'
import Swal from 'sweetalert2' import Swal from 'sweetalert2'
import { normalizeDecimal} from '@/util/input-utils' import { normalizeDecimal} from '@/util/input-utils'
import { CalculatorInput } from '@/components/common/input/CalcInput'
export const Orientation = forwardRef((props, ref) => { export const Orientation = forwardRef((props, ref) => {
const { getMessage } = useMessage() const { getMessage } = useMessage()
@ -436,13 +437,26 @@ export const Orientation = forwardRef((props, ref) => {
<label htmlFor="ch99">{getMessage('modal.module.basic.setting.orientation.setting.angle.passivity')}</label> <label htmlFor="ch99">{getMessage('modal.module.basic.setting.orientation.setting.angle.passivity')}</label>
</div> </div>
<div className="input-grid mr10" style={{ width: '60px' }}> <div className="input-grid mr10" style={{ width: '60px' }}>
<input {/*<input*/}
type="text" {/* type="text"*/}
{/* className="input-origin block"*/}
{/* value={inputCompasDeg}*/}
{/* readOnly={!hasAnglePassivity}*/}
{/* placeholder={0}*/}
{/* onChange={(e) => checkDegree(e.target.value)}*/}
{/*/>*/}
<CalculatorInput
id=""
name=""
label=""
className="input-origin block" className="input-origin block"
value={inputCompasDeg} value={inputCompasDeg}
readOnly={!hasAnglePassivity} readOnly={!hasAnglePassivity}
placeholder={0} onChange={(value) => setInputCompasDeg(value)}
onChange={(e) => checkDegree(e.target.value)} options={{
allowNegative: true,
allowDecimal: false
}}
/> />
</div> </div>
<span className="thin">°</span> <span className="thin">°</span>
@ -533,7 +547,19 @@ export const Orientation = forwardRef((props, ref) => {
<div className="outline-form mt15"> <div className="outline-form mt15">
<span>{getMessage('modal.module.basic.setting.module.placement.area')}</span> <span>{getMessage('modal.module.basic.setting.module.placement.area')}</span>
<div className="input-grid mr10" style={{ width: '60px' }}> <div className="input-grid mr10" style={{ width: '60px' }}>
<input type="text" className="input-origin block" value={inputMargin} onChange={(e) => setInputMargin(normalizeDecimal(e.target.value))} /> {/*<input type="text" className="input-origin block" value={inputMargin} onChange={(e) => setInputMargin(normalizeDecimal(e.target.value))} />*/}
<CalculatorInput
id=""
name=""
label=""
className="input-origin block"
value={inputMargin}
onChange={(value) => setInputMargin(value)}
options={{
allowNegative: false,
allowDecimal: false
}}
/>
</div> </div>
<span className="thin">m</span> <span className="thin">m</span>
</div> </div>
@ -561,11 +587,23 @@ export const Orientation = forwardRef((props, ref) => {
<div className="outline-form"> <div className="outline-form">
<span>{getMessage('modal.module.basic.setting.module.fitting.height')}</span> <span>{getMessage('modal.module.basic.setting.module.fitting.height')}</span>
<div className="input-grid mr10"> <div className="input-grid mr10">
<input {/*<input*/}
type="text" {/* type="text"*/}
{/* className="input-origin block"*/}
{/* value={inputInstallHeight}*/}
{/* onChange={(e) => handleChangeInstallHeight(normalizeDecimal(e.target.value))}*/}
{/*/>*/}
<CalculatorInput
id=""
name=""
label=""
className="input-origin block" className="input-origin block"
value={inputInstallHeight} value={inputInstallHeight}
onChange={(e) => handleChangeInstallHeight(normalizeDecimal(e.target.value))} onChange={(value) => handleChangeInstallHeight(value)}
options={{
allowNegative: false,
allowDecimal: false
}}
/> />
</div> </div>
<span className="thin">m</span> <span className="thin">m</span>
@ -589,11 +627,23 @@ export const Orientation = forwardRef((props, ref) => {
<div className="outline-form"> <div className="outline-form">
<span>{getMessage('modal.module.basic.setting.module.standard.snowfall.amount')}</span> <span>{getMessage('modal.module.basic.setting.module.standard.snowfall.amount')}</span>
<div className="input-grid mr10"> <div className="input-grid mr10">
<input {/*<input*/}
type="text" {/* type="text"*/}
{/* className="input-origin block"*/}
{/* value={inputVerticalSnowCover}*/}
{/* onChange={(e) => handleChangeVerticalSnowCover(normalizeDecimal(e.target.value))}*/}
{/*/>*/}
<CalculatorInput
id=""
name=""
label=""
className="input-origin block" className="input-origin block"
value={inputVerticalSnowCover} value={inputInstallHeight}
onChange={(e) => handleChangeVerticalSnowCover(normalizeDecimal(e.target.value))} onChange={(value) => handleChangeVerticalSnowCover(value)}
options={{
allowNegative: false,
allowDecimal: false
}}
/> />
</div> </div>
<span className="thin">cm</span> <span className="thin">cm</span>

View File

@ -10,6 +10,7 @@ import { forwardRef, useContext, useEffect, useImperativeHandle, useRef, useStat
import { useRecoilState, useRecoilValue } from 'recoil' import { useRecoilState, useRecoilValue } from 'recoil'
import Swal from 'sweetalert2' import Swal from 'sweetalert2'
import { normalizeDigits } from '@/util/input-utils' import { normalizeDigits } from '@/util/input-utils'
import { CalculatorInput } from '@/components/common/input/CalcInput'
const Trestle = forwardRef((props, ref) => { const Trestle = forwardRef((props, ref) => {
const { tabNum, setTabNum, trestleTrigger, roofs, setRoofs, moduleSelectionData, setModuleSelectionData, setRoofsStore } = props const { tabNum, setTabNum, trestleTrigger, roofs, setRoofs, moduleSelectionData, setModuleSelectionData, setRoofsStore } = props
@ -885,12 +886,24 @@ const Trestle = forwardRef((props, ref) => {
<div className="outline-form mr15"> <div className="outline-form mr15">
<span>{getMessage('modal.module.basic.setting.module.placement.area.eaves')}</span> <span>{getMessage('modal.module.basic.setting.module.placement.area.eaves')}</span>
<div className="input-grid mr10"> <div className="input-grid mr10">
<input {/*<input*/}
type="number" {/* type="number"*/}
{/* className="input-origin block"*/}
{/* value={eavesMargin ?? 0}*/}
{/* // onChange={(e) => dispatch({ type: 'SET_TRESTLE_DETAIL', roof: { ...trestleState, eavesMargin: e.target.value } })}*/}
{/* onChange={(e) => setEavesMargin(+e.target.value)}*/}
{/*/>*/}
<CalculatorInput
id=""
name=""
label=""
className="input-origin block" className="input-origin block"
value={eavesMargin ?? 0} value={eavesMargin ?? 0}
// onChange={(e) => dispatch({ type: 'SET_TRESTLE_DETAIL', roof: { ...trestleState, eavesMargin: e.target.value } })} onChange={(value) => setEavesMargin(value)}
onChange={(e) => setEavesMargin(+e.target.value)} options={{
allowNegative: false,
allowDecimal: false
}}
/> />
</div> </div>
<span className="thin">mm</span> <span className="thin">mm</span>
@ -898,12 +911,24 @@ const Trestle = forwardRef((props, ref) => {
<div className="outline-form mr15"> <div className="outline-form mr15">
<span>{getMessage('modal.module.basic.setting.module.placement.area.ridge')}</span> <span>{getMessage('modal.module.basic.setting.module.placement.area.ridge')}</span>
<div className="input-grid mr10"> <div className="input-grid mr10">
<input {/*<input*/}
type="number" {/* type="number"*/}
{/* className="input-origin block"*/}
{/* value={ridgeMargin ?? 0}*/}
{/* // onChange={(e) => dispatch({ type: 'SET_TRESTLE_DETAIL', roof: { ...trestleState, ridgeMargin: e.target.value } })}*/}
{/* onChange={(e) => setRidgeMargin(+e.target.value)}*/}
{/*/>*/}
<CalculatorInput
id=""
name=""
label=""
className="input-origin block" className="input-origin block"
value={ridgeMargin ?? 0} value={ridgeMargin ?? 0}
// onChange={(e) => dispatch({ type: 'SET_TRESTLE_DETAIL', roof: { ...trestleState, ridgeMargin: e.target.value } })} onChange={(value) => setRidgeMargin(value)}
onChange={(e) => setRidgeMargin(+e.target.value)} options={{
allowNegative: false,
allowDecimal: false
}}
/> />
</div> </div>
<span className="thin">mm</span> <span className="thin">mm</span>
@ -911,12 +936,24 @@ const Trestle = forwardRef((props, ref) => {
<div className="outline-form "> <div className="outline-form ">
<span>{getMessage('modal.module.basic.setting.module.placement.area.keraba')}</span> <span>{getMessage('modal.module.basic.setting.module.placement.area.keraba')}</span>
<div className="input-grid mr10"> <div className="input-grid mr10">
<input {/*<input*/}
type="number" {/* type="number"*/}
{/* className="input-origin block"*/}
{/* value={kerabaMargin ?? 0}*/}
{/* // onChange={(e) => dispatch({ type: 'SET_TRESTLE_DETAIL', roof: { ...trestleState, kerabaMargin: e.target.value } })}*/}
{/* onChange={(e) => setKerabaMargin(+e.target.value)}*/}
{/*/>*/}
<CalculatorInput
id=""
name=""
label=""
className="input-origin block" className="input-origin block"
value={kerabaMargin ?? 0} value={kerabaMargin ?? 0}
// onChange={(e) => dispatch({ type: 'SET_TRESTLE_DETAIL', roof: { ...trestleState, kerabaMargin: e.target.value } })} onChange={(value) => setKerabaMargin(value)}
onChange={(e) => setKerabaMargin(+e.target.value)} options={{
allowNegative: false,
allowDecimal: false
}}
/> />
</div> </div>
<span className="thin">mm</span> <span className="thin">mm</span>

View File

@ -631,7 +631,7 @@ export function useMovementSetting(id) {
} }
return Big(0); // 기본값으로 0 반환 또는 다른 적절한 기본값 return Big(0); // 기본값으로 0 반환 또는 다른 적절한 기본값
})(); })();
if (target.y1 === target.y2) { if (Math.abs(target.y1 - target.y2) < 0.5) {
value = value.neg() value = value.neg()
} }
} else { } else {

View File

@ -851,12 +851,15 @@ export const usePolygon = () => {
// innerLines와 polygonLines의 겹침을 확인하고 type 변경 // innerLines와 polygonLines의 겹침을 확인하고 type 변경
innerLines.forEach((innerLine) => { innerLines.forEach((innerLine) => {
polygonLines.forEach((polygonLine) => { polygonLines.forEach((polygonLine) => {
if (polygonLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) {
return
}
if (checkLineOverlap(innerLine, polygonLine)) { if (checkLineOverlap(innerLine, polygonLine)) {
// innerLine의 type을 polygonLine의 type으로 변경 // innerLine의 type을 polygonLine의 type으로 변경
if (innerLine.attributes && polygonLine.attributes.type) { if (innerLine.attributes && polygonLine.attributes.type) {
// innerLine이 polygonLine보다 긴 경우 polygonLine.need를 false로 변경 // innerLine이 polygonLine보다 긴 경우 polygonLine.need를 false로 변경
if (polygonLine.length < innerLine.length) { if (polygonLine.length < innerLine.length) {
if (polygonLine.lineName !== 'eaveHelpLine') { if (polygonLine.lineName !== 'eaveHelpLine' || polygonLine.lineName !== 'eaveHelpLine') {
polygonLine.need = false polygonLine.need = false
} }
} }
@ -1014,6 +1017,7 @@ export const usePolygon = () => {
canvas.add(line) canvas.add(line)
}) })
canvas.renderAll()*/ canvas.renderAll()*/
polygonLines = polygonLines.filter((line) => line.need) polygonLines = polygonLines.filter((line) => line.need)
polygonLines.forEach((line) => { polygonLines.forEach((line) => {
@ -1377,7 +1381,6 @@ export const usePolygon = () => {
let newRoofs = getSplitRoofsPoints(allLines) let newRoofs = getSplitRoofsPoints(allLines)
newRoofs = newRoofs.filter((roof) => roof.length !== 0) newRoofs = newRoofs.filter((roof) => roof.length !== 0)
newRoofs.forEach((roofPoint, index) => { newRoofs.forEach((roofPoint, index) => {
let defense, pitch let defense, pitch
@ -1411,6 +1414,124 @@ export const usePolygon = () => {
} }
}) })
// representLines가 없다면 A,B타입중 하나임
if (representLines.length === 0) {
// 1. roofPoint로 폴리곤의 라인들을 생성
const roofPolygonLines = []
for (let i = 0; i < roofPoint.length; i++) {
const nextIndex = (i + 1) % roofPoint.length
const startPt = roofPoint[i]
const endPt = roofPoint[nextIndex]
roofPolygonLines.push({
x1: startPt.x,
y1: startPt.y,
x2: endPt.x,
y2: endPt.y,
startPoint: startPt,
endPoint: endPt,
})
}
// 3. 평행 여부 확인 함수
const checkParallel = (line1, line2) => {
const v1x = line1.x2 - line1.x1
const v1y = line1.y2 - line1.y1
const v2x = line2.x2 - line2.x1
const v2y = line2.y2 - line2.y1
const length1 = Math.sqrt(v1x ** 2 + v1y ** 2)
const length2 = Math.sqrt(v2x ** 2 + v2y ** 2)
if (length1 === 0 || length2 === 0) return false
const norm1x = v1x / length1
const norm1y = v1y / length1
const norm2x = v2x / length2
const norm2y = v2y / length2
const EPSILON = 0.01
const crossProduct = Math.abs(norm1x * norm2y - norm1y * norm2x)
const dotProduct = norm1x * norm2x + norm1y * norm2y
return crossProduct < EPSILON || Math.abs(Math.abs(dotProduct) - 1) < EPSILON
}
// 4. 점에서 라인까지의 거리 계산 함수
const getDistanceFromPointToLine = (point, lineP1, lineP2) => {
const A = point.x - lineP1.x
const B = point.y - lineP1.y
const C = lineP2.x - lineP1.x
const D = lineP2.y - lineP1.y
const dot = A * C + B * D
const lenSq = C * C + D * D
let param = -1
if (lenSq !== 0) {
param = dot / lenSq
}
let xx, yy
if (param < 0) {
xx = lineP1.x
yy = lineP1.y
} else if (param > 1) {
xx = lineP2.x
yy = lineP2.y
} else {
xx = lineP1.x + param * C
yy = lineP1.y + param * D
}
const dx = point.x - xx
const dy = point.y - yy
return Math.sqrt(dx * dx + dy * dy)
}
// 5. 두 평행한 라인 사이의 거리 계산 (한 라인의 중점에서 다른 라인까지의 거리)
const getDistanceBetweenParallelLines = (line1, line2) => {
const midPoint = {
x: (line1.x1 + line1.x2) / 2,
y: (line1.y1 + line1.y2) / 2,
}
return getDistanceFromPointToLine(midPoint, { x: line2.x1, y: line2.y1 }, { x: line2.x2, y: line2.y2 })
}
// 6. roofPolygonLines의 모든 라인에서 평행하면서 가장 가까운 EAVES 라인 찾기
let closestLine = null
let minDistance = Infinity
roofPolygonLines.forEach((roofLine) => {
;[...polygonLines, ...innerLines].forEach((line) => {
// EAVES 타입만 필터링
if (line.attributes?.type !== LINE_TYPE.WALLLINE.EAVES && line.attributes?.type !== LINE_TYPE.WALLLINE.EAVE_HELP_LINE) {
return
}
const lineObj = {
x1: line.startPoint.x,
y1: line.startPoint.y,
x2: line.endPoint.x,
y2: line.endPoint.y,
}
if (checkParallel(roofLine, lineObj)) {
const distance = getDistanceBetweenParallelLines(roofLine, lineObj)
if (distance < minDistance && distance > 0) {
minDistance = distance
closestLine = line
}
}
})
})
if (closestLine) {
representLines.push(closestLine)
}
}
// representLines중 가장 긴 line을 찾는다. // representLines중 가장 긴 line을 찾는다.
representLines.forEach((line) => { representLines.forEach((line) => {
if (!representLine) { if (!representLine) {

File diff suppressed because it is too large Load Diff

View File

@ -494,29 +494,7 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
} }
}); });
/*
//2. 연결이 끊어진 스켈레톤 선을 찾아 연장합니다.
const { disconnectedLines } = findDisconnectedSkeletonLines(skeletonLines, roof.lines);
if(disconnectedLines.length > 0) {
disconnectedLines.forEach(dLine => {
const { index, extendedLine, p1Connected, p2Connected } = dLine;
const newPoint = extendedLine?.point;
if (!newPoint) return;
// p1이 끊어졌으면 p1을, p2가 끊어졌으면 p2를 연장된 지점으로 업데이트
if (p1Connected) { //p2 연장
skeletonLines[index].p2 = { ...skeletonLines[index].p2, x: newPoint.x, y: newPoint.y };
} else if (p2Connected) {//p1 연장
skeletonLines[index].p1 = { ...skeletonLines[index].p1, x: newPoint.x, y: newPoint.y };
}
});
//2-1 확장된 스켈레톤 선이 연장되다가 서로 만나면 만난점(접점)에서 멈추어야 된다.
trimIntersectingExtendedLines(skeletonLines, disconnectedLines);
}
*/
//2. 연결이 끊어진 라인이 있을경우 찾아서 추가한다(동 이동일때) //2. 연결이 끊어진 라인이 있을경우 찾아서 추가한다(동 이동일때)
@ -549,17 +527,6 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
{ x: sktLine.p2.x, y: sktLine.p2.y } { x: sktLine.p2.x, y: sktLine.p2.y }
); );
//그림을 그릴때 idx 가 필요함 roof는 왼쪽부터 시작됨 - 그림그리는 순서가 필요함
// roofLines.forEach((roofLine) => {
//
// if (isSameLine(p1.x, p1.y, p2.x, p2.y, roofLine) || isSameLine(p2.x, p2.y, p1.x, p1.y, roofLine)) {
// roofIdx = roofLine.idx;
// console.log("roofIdx::::::", roofIdx)
// return false; // forEach 중단
// }
// });
const skeletonLine = new QLine([p1.x, p1.y, p2.x, p2.y], { const skeletonLine = new QLine([p1.x, p1.y, p2.x, p2.y], {
@ -579,7 +546,7 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
//visible: (!sktLine.attributes.isOuterEdge), //visible: (!sktLine.attributes.isOuterEdge),
}); });
coordinateText(skeletonLine) //coordinateText(skeletonLine)
canvas.add(skeletonLine); canvas.add(skeletonLine);
skeletonLine.bringToFront(); skeletonLine.bringToFront();
existingLines.add(lineKey); // 추가된 라인을 추적 existingLines.add(lineKey); // 추가된 라인을 추적
@ -613,7 +580,6 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
const currentRoofLines = canvas.getObjects().filter((obj) => obj.lineName === 'roofLine' && obj.attributes.roofId === roofId) const currentRoofLines = canvas.getObjects().filter((obj) => obj.lineName === 'roofLine' && obj.attributes.roofId === roofId)
let roofLineRects = canvas.getObjects().filter((obj) => obj.name === 'roofLineRect' && obj.roofId === roofId) let roofLineRects = canvas.getObjects().filter((obj) => obj.name === 'roofLineRect' && obj.roofId === roofId)
roofLineRects.forEach((roofLineRect) => { roofLineRects.forEach((roofLineRect) => {
canvas.remove(roofLineRect) canvas.remove(roofLineRect)
canvas.renderAll() canvas.renderAll()
@ -629,69 +595,64 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
return [...lines].sort((a, b) => { return [...lines].sort((a, b) => {
// Get all coordinates in a consistent order // Get all coordinates in a consistent order
const getCoords = (line) => { const getCoords = (line) => {
const x1 = line.x1 ?? line.get('x1'); const x1 = line.x1 ?? line.get('x1')
const y1 = line.y1 ?? line.get('y1'); const y1 = line.y1 ?? line.get('y1')
const x2 = line.x2 ?? line.get('x2'); const x2 = line.x2 ?? line.get('x2')
const y2 = line.y2 ?? line.get('y2'); const y2 = line.y2 ?? line.get('y2')
// Sort points left-to-right, then top-to-bottom // Sort points left-to-right, then top-to-bottom
return x1 < x2 || (x1 === x2 && y1 < y2) return x1 < x2 || (x1 === x2 && y1 < y2) ? [x1, y1, x2, y2] : [x2, y2, x1, y1]
? [x1, y1, x2, y2] }
: [x2, y2, x1, y1];
};
const aCoords = getCoords(a); const aCoords = getCoords(a)
const bCoords = getCoords(b); const bCoords = getCoords(b)
// Compare each coordinate in order // Compare each coordinate in order
for (let i = 0; i < 4; i++) { for (let i = 0; i < 4; i++) {
if (Math.abs(aCoords[i] - bCoords[i]) > 0.1) { if (Math.abs(aCoords[i] - bCoords[i]) > 0.1) {
return aCoords[i] - bCoords[i]; return aCoords[i] - bCoords[i]
} }
} }
return 0; return 0
}); })
} }
// 각 라인 집합 정렬 // 각 라인 집합 정렬
const sortWallLines = ensureCounterClockwiseLines(wallLines)
const sortWallBaseLines = ensureCounterClockwiseLines(wall.baseLines)
const sortRoofLines = ensureCounterClockwiseLines(roofLines)
// roofLines의 방향에 맞춰 currentRoofLines의 방향을 조정 // roofLines의 방향에 맞춰 currentRoofLines의 방향을 조정
const alignLineDirection = (sourceLines, targetLines) => { const alignLineDirection = (sourceLines, targetLines) => {
return sourceLines.map(sourceLine => { return sourceLines.map((sourceLine) => {
// 가장 가까운 targetLine 찾기 // 가장 가까운 targetLine 찾기
const nearestTarget = targetLines.reduce((nearest, targetLine) => { const nearestTarget = targetLines.reduce((nearest, targetLine) => {
const sourceCenter = { const sourceCenter = {
x: (sourceLine.x1 + sourceLine.x2) / 2, x: (sourceLine.x1 + sourceLine.x2) / 2,
y: (sourceLine.y1 + sourceLine.y2) / 2 y: (sourceLine.y1 + sourceLine.y2) / 2,
}; }
const targetCenter = { const targetCenter = {
x: (targetLine.x1 + targetLine.x2) / 2, x: (targetLine.x1 + targetLine.x2) / 2,
y: (targetLine.y1 + targetLine.y2) / 2 y: (targetLine.y1 + targetLine.y2) / 2,
}; }
const distance = Math.hypot( const distance = Math.hypot(sourceCenter.x - targetCenter.x, sourceCenter.y - targetCenter.y)
sourceCenter.x - targetCenter.x,
sourceCenter.y - targetCenter.y
);
return !nearest || distance < nearest.distance return !nearest || distance < nearest.distance ? { line: targetLine, distance } : nearest
? { line: targetLine, distance } }, null)?.line
: nearest;
}, null)?.line;
if (!nearestTarget) return sourceLine; if (!nearestTarget) return sourceLine
// 방향이 반대인지 확인 (벡터 내적을 사용) // 방향이 반대인지 확인 (벡터 내적을 사용)
const sourceVec = { const sourceVec = {
x: sourceLine.x2 - sourceLine.x1, x: sourceLine.x2 - sourceLine.x1,
y: sourceLine.y2 - sourceLine.y1 y: sourceLine.y2 - sourceLine.y1,
}; }
const targetVec = { const targetVec = {
x: nearestTarget.x2 - nearestTarget.x1, x: nearestTarget.x2 - nearestTarget.x1,
y: nearestTarget.y2 - nearestTarget.y1 y: nearestTarget.y2 - nearestTarget.y1,
}; }
const dotProduct = sourceVec.x * targetVec.x + sourceVec.y * targetVec.y; const dotProduct = sourceVec.x * targetVec.x + sourceVec.y * targetVec.y
// 내적이 음수이면 방향이 반대이므로 뒤집기 // 내적이 음수이면 방향이 반대이므로 뒤집기
if (dotProduct < 0) { if (dotProduct < 0) {
@ -700,81 +661,43 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
x1: sourceLine.x2, x1: sourceLine.x2,
y1: sourceLine.y2, y1: sourceLine.y2,
x2: sourceLine.x1, x2: sourceLine.x1,
y2: sourceLine.y1 y2: sourceLine.y1,
}; }
} }
return sourceLine; return sourceLine
}); })
}; }
console.log("wallBaseLines", wall.baseLines) console.log('wallBaseLines', wall.baseLines)
// const sortedWallLines = sortCurrentRoofLines(wall.lines);
// roofLines의 방향에 맞춰 currentRoofLines 조정 후 정렬
const alignedCurrentRoofLines = alignLineDirection(currentRoofLines, roofLines);
const sortedCurrentRoofLines = sortCurrentRoofLines(alignedCurrentRoofLines)
// const sortedRoofLines = sortCurrentRoofLines(roofLines);
const sortedWallBaseLines = sortCurrentRoofLines(wall.baseLines)
// const sortedBaseLines = sortBaseLinesByWallLines(wall.baseLines, wallLines);
const sortRoofLines = sortBaseLinesByWallLines(roofLines, wallLines);
// 원본 wallLines를 복사하여 사용
const sortedWallLines = [...wallLines];
const sortedBaseLines = sortBaseLinesByWallLines(wall.baseLines, sortedWallLines);
const sortedRoofLines = sortBaseLinesByWallLines(roofLines, sortedWallLines);
//wall.lines 는 기본 벽 라인
//wall.baseLine은 움직인라인 //wall.baseLine은 움직인라인
const movedLines = [] let movedLines = []
// 조건에 맞는 라인들만 필터링 // 조건에 맞는 라인들만 필터링
const validWallLines = [...wallLines] const validWallLines = [...wallLines].sort((a, b) => a.idx - b.idx).filter((wallLine, index) => wallLine.idx - 1 === index)
.sort((a, b) => a.idx - b.idx)
.filter((wallLine, index) => wallLine.idx - 1 === index);
wallLines.length > 3 && wallLines.forEach((wallLine, index) => { console.log('', sortRoofLines, sortWallLines, sortWallBaseLines)
const originalIndex = wallLines.indexOf(wallLine) sortWallLines.length > 3 &&
const roofLine = sortRoofLines[originalIndex] sortWallLines.forEach((wallLine, index) => {
const currentRoofLine = currentRoofLines[originalIndex]
const moveLine = wall.baseLines[originalIndex]
const wallBaseLine = wall.baseLines[originalIndex]
// const roofLine = sortRoofLines[index]; const roofLine = sortRoofLines[index]
const wallBaseLine = sortWallBaseLines[index]
// if (roofLine.attributes.wallLine !== wallLine.id || (roofLine.idx - 1) !== index) {
// console.log("wallLine2::::", wallLine.id)
// console.log('roofLine:::', roofLine.attributes.wallLine)
// console.log("w:::", wallLine.startPoint, wallLine.endPoint)
// console.log("R:::", roofLine.startPoint, roofLine.endPoint)
// console.log("not matching roofLine", roofLine);
// return false
// }//roofLines.find(line => line.attributes.wallLineId === wallLine.attributes.wallId);
// const currentRoofLine = currentRoofLines[index];
// const moveLine = wall.baseLines[index]
// const wallBaseLine = wall.baseLines[index]
//console.log("wallBaseLine", wallBaseLine);
//roofline 외곽선 설정 //roofline 외곽선 설정
const origin = moveLine.attributes?.originPoint
if (!origin) return
console.log('index::::', index) console.log('index::::', index)
console.log("", roofLines, wallLines, wall.baseLines)
console.log('roofLine:', roofLine.x1, roofLine.y1, roofLine.x2, roofLine.y2) console.log('roofLine:', roofLine.x1, roofLine.y1, roofLine.x2, roofLine.y2)
// console.log('moveLine:', moveLine.x1, moveLine.y1, moveLine.x2, moveLine.y2)
console.log('wallLine:', wallLine.x1, wallLine.y1, wallLine.x2, wallLine.y2) console.log('wallLine:', wallLine.x1, wallLine.y1, wallLine.x2, wallLine.y2)
console.log('wallBaseLine:', wallBaseLine.x1, wallBaseLine.y1, wallBaseLine.x2, wallBaseLine.y2) console.log('wallBaseLine:', wallBaseLine.x1, wallBaseLine.y1, wallBaseLine.x2, wallBaseLine.y2)
console.log('isSamePoint result:', isSameLine2(wallBaseLine, wallLine))
if (isSameLine2(wallBaseLine, wallLine)) {
console.log('isSamePoint result:', isSameLine2(moveLine, wallLine))
if (isSameLine2(moveLine, wallLine)) {
return return
} }
const movedStart = Math.abs(moveLine.x1 - wallLine.x1) > EPSILON || Math.abs(moveLine.y1 - origin.y1) > EPSILON const movedStart = Math.abs(wallBaseLine.x1 - wallLine.x1) > EPSILON || Math.abs(wallBaseLine.y1 - wallLine.y1) > EPSILON
const movedEnd = Math.abs(moveLine.x2 - wallLine.x2) > EPSILON || Math.abs(moveLine.y2 - origin.y2) > EPSILON const movedEnd = Math.abs(wallBaseLine.x2 - wallLine.x2) > EPSILON || Math.abs(wallBaseLine.y2 - wallLine.y2) > EPSILON
const fullyMoved = movedStart && movedEnd const fullyMoved = movedStart && movedEnd
@ -787,13 +710,11 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
const getAddLine = (p1, p2, stroke = '') => { const getAddLine = (p1, p2, stroke = '') => {
movedLines.push({ index, p1, p2 }) movedLines.push({ index, p1, p2 })
// Usage:
// let mergeLines = mergeMovedLines(movedLines);
//console.log("mergeLines:::::::", mergeLines); //console.log("mergeLines:::::::", mergeLines);
const line = new QLine([p1.x, p1.y, p2.x, p2.y], { const line = new QLine([p1.x, p1.y, p2.x, p2.y], {
parentId: roof.id, parentId: roof.id,
fontSize: roof.fontSize, fontSize: roof.fontSize,
stroke: stroke, stroke: 'black',
strokeWidth: 4, strokeWidth: 4,
name: 'eaveHelpLine', name: 'eaveHelpLine',
lineName: 'eaveHelpLine', lineName: 'eaveHelpLine',
@ -846,7 +767,7 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
const positionType = isInPosition ? 'in' : 'out' const positionType = isInPosition ? 'in' : 'out'
const condition = `${mLine.position}_${positionType}` const condition = `${mLine.position}_${positionType}`
let isStartEnd = findInteriorPoint(wallBaseLine, sortedBaseLines) let isStartEnd = findInteriorPoint(wallBaseLine, sortWallBaseLines)
let sPoint, ePoint let sPoint, ePoint
if (condition === 'left_in') { if (condition === 'left_in') {
isIn = true isIn = true
@ -863,8 +784,12 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
const newPointX = Big(roofLine.x1).plus(moveDist).abs().toNumber() const newPointX = Big(roofLine.x1).plus(moveDist).abs().toNumber()
const pDist = Big(wallLine.x1).minus(roofLine.x1).toNumber() const pDist = Big(wallLine.x1).minus(roofLine.x1).toNumber()
const pLineY = Big(roofLine.y1).minus(0).abs().toNumber() const pLineY = Big(roofLine.y1).minus(0).abs().toNumber()
let idx = 0 > index - 1 ? roofLines.length : index // let idx = 0 > index - 1 ? sortRoofLines.length : index
const pLineX = roofLines[idx - 1].x1 // const pLineX = sortRoofLines[idx - 1].x1
const prevIndex = (index - 1 + sortRoofLines.length) % sortRoofLines.length
const nextIndex = (index + 1) % sortRoofLines.length
const pLineX = sortRoofLines[prevIndex].x1
getAddLine({ x: newPStart.x, y: newPStart.y }, { x: ePoint.x, y: ePoint.y }, 'blue') getAddLine({ x: newPStart.x, y: newPStart.y }, { x: ePoint.x, y: ePoint.y }, 'blue')
getAddLine({ x: roofLine.x2, y: roofLine.y2 }, { x: newPointX, y: roofLine.y2 }, 'orange') getAddLine({ x: roofLine.x2, y: roofLine.y2 }, { x: newPointX, y: roofLine.y2 }, 'orange')
@ -887,8 +812,12 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
const newPointX = Big(roofLine.x1).plus(moveDist).toNumber() const newPointX = Big(roofLine.x1).plus(moveDist).toNumber()
const pDist = Big(wallLine.x1).minus(roofLine.x1).abs().toNumber() const pDist = Big(wallLine.x1).minus(roofLine.x1).abs().toNumber()
const pLineY = Big(roofLine.y2).minus(0).toNumber() const pLineY = Big(roofLine.y2).minus(0).toNumber()
let idx = roofLines.length < index + 1 ? 0 : index // let idx = sortRoofLines.length < index + 1 ? 0 : index
const pLineX = roofLines[idx + 1].x2 // const pLineX = sortRoofLines[idx + 1].x2
const prevIndex = (index - 1 + sortRoofLines.length) % sortRoofLines.length
const nextIndex = (index + 1) % sortRoofLines.length
const pLineX = sortRoofLines[nextIndex].x2
getAddLine({ x: newPEnd.x, y: newPEnd.y }, { x: ePoint.x, y: ePoint.y }, 'blue') getAddLine({ x: newPEnd.x, y: newPEnd.y }, { x: ePoint.x, y: ePoint.y }, 'blue')
getAddLine({ x: roofLine.x1, y: roofLine.y1 }, { x: newPointX, y: roofLine.y1 }, 'orange') getAddLine({ x: roofLine.x1, y: roofLine.y1 }, { x: newPointX, y: roofLine.y1 }, 'orange')
@ -910,8 +839,12 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
const eLineY = Big(bStartY).minus(wallLine.y1).abs().toNumber() const eLineY = Big(bStartY).minus(wallLine.y1).abs().toNumber()
newPStart.y = aStartY newPStart.y = aStartY
newPEnd.y = roofLine.y2 //Big(roofLine.y2).minus(eLineY).toNumber() newPEnd.y = roofLine.y2 //Big(roofLine.y2).minus(eLineY).toNumber()
let idx = 0 >= index - 1 ? roofLines.length : index // let idx = 0 >= index - 1 ? sortRoofLines.length : index
const newLine = roofLines[idx - 1] // const newLine = sortRoofLines[idx - 1]
const prevIndex = (index - 1 + sortRoofLines.length) % sortRoofLines.length
const nextIndex = (index + 1) % sortRoofLines.length
const newLine = sortRoofLines[prevIndex]
if (Math.abs(wallBaseLine.y1 - wallLine.y1) < 0.1) { if (Math.abs(wallBaseLine.y1 - wallLine.y1) < 0.1) {
if (inLine) { if (inLine) {
@ -960,8 +893,13 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
const eLineY = Big(bStartY).minus(wallLine.y2).abs().toNumber() const eLineY = Big(bStartY).minus(wallLine.y2).abs().toNumber()
newPEnd.y = aStartY newPEnd.y = aStartY
newPStart.y = roofLine.y1 //Big(roofLine.y1).plus(eLineY).toNumber() newPStart.y = roofLine.y1 //Big(roofLine.y1).plus(eLineY).toNumber()
let idx = roofLines.length < index + 1 ? 0 : index // let idx = sortRoofLines.length < index + 1 ? 0 : index
const newLine = roofLines[idx + 1] // const newLine = sortRoofLines[idx + 1]
const prevIndex = (index - 1 + sortRoofLines.length) % sortRoofLines.length;
const nextIndex = (index + 1) % sortRoofLines.length;
const newLine = sortRoofLines[nextIndex]
if (Math.abs(wallBaseLine.y2 - wallLine.y2) < 0.1) { if (Math.abs(wallBaseLine.y2 - wallLine.y2) < 0.1) {
if (inLine) { if (inLine) {
@ -1015,8 +953,12 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
const newPointX = Big(roofLine.x1).minus(moveDist).abs().toNumber() const newPointX = Big(roofLine.x1).minus(moveDist).abs().toNumber()
const pDist = Big(wallLine.x1).minus(roofLine.x1).abs().toNumber() const pDist = Big(wallLine.x1).minus(roofLine.x1).abs().toNumber()
const pLineY = Big(roofLine.y1).minus(0).abs().toNumber() const pLineY = Big(roofLine.y1).minus(0).abs().toNumber()
let idx = 0 >= index - 1 ? roofLines.length : index // let idx = 0 >= index - 1 ? sortRoofLines.length : index
const pLineX = roofLines[idx - 1].x1 // const pLineX = sortRoofLines[idx - 1].x1
const prevIndex = (index - 1 + sortRoofLines.length) % sortRoofLines.length
const nextIndex = (index + 1) % sortRoofLines.length
const pLineX = sortRoofLines[prevIndex].x1
getAddLine({ x: newPStart.x, y: newPStart.y }, { x: ePoint.x, y: ePoint.y }, 'blue') getAddLine({ x: newPStart.x, y: newPStart.y }, { x: ePoint.x, y: ePoint.y }, 'blue')
//getAddLine({ x: roofLine.x2, y: roofLine.y2 }, { x: newPointX, y: roofLine.y2 }, 'orange') //getAddLine({ x: roofLine.x2, y: roofLine.y2 }, { x: newPointX, y: roofLine.y2 }, 'orange')
@ -1039,8 +981,12 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
const newPointX = Big(roofLine.x1).minus(moveDist).toNumber() const newPointX = Big(roofLine.x1).minus(moveDist).toNumber()
const pDist = Big(wallLine.x1).minus(roofLine.x1).abs().toNumber() const pDist = Big(wallLine.x1).minus(roofLine.x1).abs().toNumber()
const pLineY = Big(roofLine.y2).minus(0).abs().toNumber() const pLineY = Big(roofLine.y2).minus(0).abs().toNumber()
let idx = roofLines.length < index + 1 ? 0 : index // let idx = sortRoofLines.length < index + 1 ? 0 : index
const pLineX = roofLines[idx + 1].x2 // const pLineX = sortRoofLines[idx + 1].x2
const prevIndex = (index - 1 + sortRoofLines.length) % sortRoofLines.length
const nextIndex = (index + 1) % sortRoofLines.length
const pLineX = sortRoofLines[nextIndex].x2
getAddLine({ x: newPEnd.x, y: newPEnd.y }, { x: ePoint.x, y: ePoint.y }, 'blue') getAddLine({ x: newPEnd.x, y: newPEnd.y }, { x: ePoint.x, y: ePoint.y }, 'blue')
getAddLine({ x: roofLine.x1, y: roofLine.y1 }, { x: newPointX, y: roofLine.y1 }, 'orange') getAddLine({ x: roofLine.x1, y: roofLine.y1 }, { x: newPointX, y: roofLine.y1 }, 'orange')
@ -1063,8 +1009,12 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
const eLineY = Big(bStartY).minus(wallLine.y1).abs().toNumber() const eLineY = Big(bStartY).minus(wallLine.y1).abs().toNumber()
newPStart.y = aStartY newPStart.y = aStartY
newPEnd.y = roofLine.y2 //Big(roofLine.y2).plus(eLineY).toNumber() newPEnd.y = roofLine.y2 //Big(roofLine.y2).plus(eLineY).toNumber()
let idx = 0 >= index - 1 ? roofLines.length : index // let idx = 0 >= index - 1 ? sortRoofLines.length : index
const newLine = roofLines[idx - 1] // const newLine = sortRoofLines[idx - 1]
const prevIndex = (index - 1 + sortRoofLines.length) % sortRoofLines.length
const nextIndex = (index + 1) % sortRoofLines.length
const newLine = sortRoofLines[prevIndex]
if (Math.abs(wallBaseLine.y1 - wallLine.y1) < 0.1) { if (Math.abs(wallBaseLine.y1 - wallLine.y1) < 0.1) {
if (inLine) { if (inLine) {
@ -1113,8 +1063,13 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
const eLineY = Big(bStartY).minus(wallLine.y2).abs().toNumber() const eLineY = Big(bStartY).minus(wallLine.y2).abs().toNumber()
newPEnd.y = aStartY newPEnd.y = aStartY
newPStart.y = roofLine.y1 //Big(roofLine.y1).minus(eLineY).toNumber() newPStart.y = roofLine.y1 //Big(roofLine.y1).minus(eLineY).toNumber()
let idx = roofLines.length < index + 1 ? 0 : index // let idx = sortRoofLines.length < index + 1 ? 0 : index
const newLine = roofLines[idx + 1] // const newLine = sortRoofLines[idx + 1]
const prevIndex = (index - 1 + sortRoofLines.length) % sortRoofLines.length;
const nextIndex = (index + 1) % sortRoofLines.length;
const newLine = sortRoofLines[nextIndex]
if (inLine) { if (inLine) {
if (inLine.x2 < inLine.x1) { if (inLine.x2 < inLine.x1) {
getAddLine({ y: bStartY, x: wallLine.x1 }, { y: inLine.y2, x: inLine.x2 }, 'pink') getAddLine({ y: bStartY, x: wallLine.x1 }, { y: inLine.y2, x: inLine.x2 }, 'pink')
@ -1172,7 +1127,7 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
const positionType = isInPosition ? 'in' : 'out' const positionType = isInPosition ? 'in' : 'out'
const condition = `${mLine.position}_${positionType}` const condition = `${mLine.position}_${positionType}`
let isStartEnd = findInteriorPoint(wallBaseLine, sortedBaseLines) let isStartEnd = findInteriorPoint(wallBaseLine, sortWallBaseLines)
let sPoint, ePoint let sPoint, ePoint
@ -1186,8 +1141,14 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
const pDist = Big(wallLine.y2).minus(roofLine.y2).abs().toNumber() const pDist = Big(wallLine.y2).minus(roofLine.y2).abs().toNumber()
const pLineX = Big(roofLine.x1).minus(0).toNumber() const pLineX = Big(roofLine.x1).minus(0).toNumber()
let idx = 0 >= index - 1 ? roofLines.length : index // let idx = 0 >= index - 1 ? sortRoofLines.length : index
const pLineY = roofLines[idx - 1].y1 // const pLineY = sortRoofLines[idx - 1].y1
const prevIndex = (index - 1 + sortRoofLines.length) % sortRoofLines.length
const nextIndex = (index + 1) % sortRoofLines.length
const pLineY = sortRoofLines[prevIndex].y1
getAddLine({ x: newPStart.x, y: newPStart.y }, { x: sPoint.x, y: sPoint.y }, 'blue') getAddLine({ x: newPStart.x, y: newPStart.y }, { x: sPoint.x, y: sPoint.y }, 'blue')
findPoints.push({ x: sPoint.x, y: sPoint.y, position: 'top_in_start' }) findPoints.push({ x: sPoint.x, y: sPoint.y, position: 'top_in_start' })
@ -1207,8 +1168,14 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
const pDist = Big(wallLine.y1).minus(roofLine.y1).abs().toNumber() const pDist = Big(wallLine.y1).minus(roofLine.y1).abs().toNumber()
const pLineX = Big(roofLine.x2).minus(0).abs().toNumber() const pLineX = Big(roofLine.x2).minus(0).abs().toNumber()
let idx = roofLines.length < index + 1 ? 0 : index
const pLineY = roofLines[idx + 1].y2 // let idx = sortRoofLines.length < index + 1 ? 0 : index
// const pLineY = sortRoofLines[idx + 1].y2
const prevIndex = (index - 1 + sortRoofLines.length) % sortRoofLines.length
const nextIndex = (index + 1) % sortRoofLines.length
const pLineY = sortRoofLines[nextIndex].y2
getAddLine({ x: newPEnd.x, y: newPEnd.y }, { x: sPoint.x, y: sPoint.y }, 'blue') getAddLine({ x: newPEnd.x, y: newPEnd.y }, { x: sPoint.x, y: sPoint.y }, 'blue')
findPoints.push({ x: sPoint.x, y: sPoint.y, position: 'top_in_end' }) findPoints.push({ x: sPoint.x, y: sPoint.y, position: 'top_in_end' })
@ -1231,8 +1198,12 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
const eLineX = Big(bStartX).minus(wallLine.x1).abs().toNumber() const eLineX = Big(bStartX).minus(wallLine.x1).abs().toNumber()
newPEnd.x = roofLine.x2 //Big(newPEnd.x).plus(eLineX).toNumber() newPEnd.x = roofLine.x2 //Big(newPEnd.x).plus(eLineX).toNumber()
newPStart.x = aStartX newPStart.x = aStartX
let idx = 0 > index - 1 ? roofLines.length : index // let idx = 0 > index - 1 ? sortRoofLines.length : index
const newLine = roofLines[idx - 1] // const newLine = sortRoofLines[idx - 1]
const prevIndex = (index - 1 + sortRoofLines.length) % sortRoofLines.length;
const nextIndex = (index + 1) % sortRoofLines.length;
const newLine = sortRoofLines[prevIndex]
if (Math.abs(wallBaseLine.x1 - wallLine.x1) < 0.1) { if (Math.abs(wallBaseLine.x1 - wallLine.x1) < 0.1) {
if (inLine) { if (inLine) {
@ -1279,8 +1250,12 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
const eLineX = Big(bStartX).minus(wallLine.x2).abs().toNumber() const eLineX = Big(bStartX).minus(wallLine.x2).abs().toNumber()
newPStart.x = roofLine.x1 //Big(newPStart.x).minus(eLineX).abs().toNumber() newPStart.x = roofLine.x1 //Big(newPStart.x).minus(eLineX).abs().toNumber()
newPEnd.x = aStartX newPEnd.x = aStartX
let idx = roofLines.length < index + 1 ? 0 : index // let idx = sortRoofLines.length < index + 1 ? 0 : index
const newLine = roofLines[idx + 1] // const newLine = sortRoofLines[idx + 1]
const prevIndex = (index - 1 + sortRoofLines.length) % sortRoofLines.length;
const nextIndex = (index + 1) % sortRoofLines.length;
const newLine = sortRoofLines[nextIndex]
if (Math.abs(wallBaseLine.x2 - wallLine.x2) < 0.1) { if (Math.abs(wallBaseLine.x2 - wallLine.x2) < 0.1) {
if (inLine) { if (inLine) {
@ -1329,8 +1304,14 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
const pDist = Big(wallLine.y2).minus(roofLine.y2).abs().toNumber() const pDist = Big(wallLine.y2).minus(roofLine.y2).abs().toNumber()
const pLineX = Big(roofLine.x1).minus(0).abs().toNumber() const pLineX = Big(roofLine.x1).minus(0).abs().toNumber()
let idx = 0 > index - 1 ? roofLines.length : index
const pLineY = roofLines[idx - 1].y1 // let idx = 0 > index - 1 ? sortRoofLines.length : index
// const pLineY = sortRoofLines[idx - 1].y1
const prevIndex = (index - 1 + sortRoofLines.length) % sortRoofLines.length
const nextIndex = (index + 1) % sortRoofLines.length
const pLineY = sortRoofLines[prevIndex].y1
getAddLine({ x: newPStart.x, y: newPStart.y }, { x: sPoint.x, y: sPoint.y }, 'blue') getAddLine({ x: newPStart.x, y: newPStart.y }, { x: sPoint.x, y: sPoint.y }, 'blue')
findPoints.push({ x: sPoint.x, y: sPoint.y, position: 'bottom_in_start' }) findPoints.push({ x: sPoint.x, y: sPoint.y, position: 'bottom_in_start' })
@ -1350,8 +1331,15 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
const pDist = Big(wallLine.y1).minus(roofLine.y1).abs().toNumber() const pDist = Big(wallLine.y1).minus(roofLine.y1).abs().toNumber()
const pLineX = Big(roofLine.x2).minus(0).abs().toNumber() const pLineX = Big(roofLine.x2).minus(0).abs().toNumber()
let idx = roofLines.length < index + 1 ? 0 : index
const pLineY = roofLines[idx + 1].y2 // let idx = sortRoofLines.length < index + 1 ? 0 : index
// const pLineY = sortRoofLines[idx + 1].y2
const prevIndex = (index - 1 + sortRoofLines.length) % sortRoofLines.length
const nextIndex = (index + 1) % sortRoofLines.length
const pLineY = sortRoofLines[nextIndex].y2
getAddLine({ x: newPEnd.x, y: newPEnd.y }, { x: sPoint.x, y: sPoint.y }, 'blue') getAddLine({ x: newPEnd.x, y: newPEnd.y }, { x: sPoint.x, y: sPoint.y }, 'blue')
findPoints.push({ x: sPoint.x, y: sPoint.y, position: 'bottom_in_end' }) findPoints.push({ x: sPoint.x, y: sPoint.y, position: 'bottom_in_end' })
@ -1372,8 +1360,12 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
const eLineX = Big(bStartX).minus(wallLine.x1).abs().toNumber() const eLineX = Big(bStartX).minus(wallLine.x1).abs().toNumber()
newPEnd.x = roofLine.x2 //Big(roofLine.x2).minus(eLineX).toNumber() newPEnd.x = roofLine.x2 //Big(roofLine.x2).minus(eLineX).toNumber()
newPStart.x = aStartX newPStart.x = aStartX
let idx = 0 > index - 1 ? roofLines.length : index // let idx = 0 > index - 1 ? sortRoofLines.length : index
const newLine = roofLines[idx - 1] // const newLine = sortRoofLines[idx - 1]
const prevIndex = (index - 1 + sortRoofLines.length) % sortRoofLines.length;
const nextIndex = (index + 1) % sortRoofLines.length;
const newLine = sortRoofLines[prevIndex]
if (Math.abs(wallBaseLine.x1 - wallLine.x1) < 0.1) { if (Math.abs(wallBaseLine.x1 - wallLine.x1) < 0.1) {
if (inLine) { if (inLine) {
@ -1422,8 +1414,12 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
const eLineX = Big(bStartX).minus(wallLine.x2).abs().toNumber() const eLineX = Big(bStartX).minus(wallLine.x2).abs().toNumber()
newPEnd.x = aStartX newPEnd.x = aStartX
newPStart.x = roofLine.x1 //Big(roofLine.x1).plus(eLineX).toNumber() newPStart.x = roofLine.x1 //Big(roofLine.x1).plus(eLineX).toNumber()
let idx = roofLines.length < index + 1 ? 0 : index // let idx = sortRoofLines.length < index + 1 ? 0 : index
const newLine = roofLines[idx + 1] // const newLine = sortRoofLines[idx + 1]
const prevIndex = (index - 1 + sortRoofLines.length) % sortRoofLines.length;
const nextIndex = (index + 1) % sortRoofLines.length;
const newLine = sortRoofLines[nextIndex]
if (Math.abs(wallBaseLine.x2 - wallLine.x2) < 0.1) { if (Math.abs(wallBaseLine.x2 - wallLine.x2) < 0.1) {
if (inLine) { if (inLine) {
@ -1473,7 +1469,7 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
} }
canvas.renderAll() canvas.renderAll()
}); })
} }
getMoveUpDownLine() getMoveUpDownLine()
@ -1766,6 +1762,115 @@ const isSameLine = (edgeStartX, edgeStartY, edgeEndX, edgeEndY, baseLine) => {
// --- Disconnected Line Processing --- // --- Disconnected Line Processing ---
/**
* 라인들이 반시계 방향이 되도록 정렬하고, 왼쪽 상단에서 시작하는 배열 반환
* @param {Array} lines - x1, y1, x2, y2 속성을 가진 라인 객체 배열
* @returns {Array} 반시계 방향으로 정렬된 라인 배열
*/
export function ensureCounterClockwiseLines(lines) {
if (!lines || lines.length < 3) return [...(lines || [])];
// 1. 모든 점을 연결 그래프로 구성
const graph = new Map();
// 각 점에서 연결된 점들을 저장
lines.forEach(line => {
const p1 = `${line.x1},${line.y1}`;
const p2 = `${line.x2},${line.y2}`;
if (!graph.has(p1)) graph.set(p1, []);
if (!graph.has(p2)) graph.set(p2, []);
// 양방향 연결
graph.get(p1).push({ x: line.x2, y: line.y2, line });
graph.get(p2).push({ x: line.x1, y: line.y1, line });
});
// 2. 왼쪽 상단 점 찾기
let startPoint = null;
let minY = Infinity;
let minX = Infinity;
for (const [pointStr] of graph) {
const [x, y] = pointStr.split(',').map(Number);
if (y < minY || (y === minY && x < minX)) {
minY = y;
minX = x;
startPoint = { x, y };
}
}
if (!startPoint) return [...lines];
// 3. 점들을 순회하며 라인 구성
const visited = new Set();
const result = [];
let current = `${startPoint.x},${startPoint.y}`;
let prev = null;
while (true) {
if (visited.has(current)) break;
visited.add(current);
const neighbors = graph.get(current) || [];
if (neighbors.length === 0) break;
// 이전 점 제외
const nextPoints = neighbors.filter(n =>
!prev || `${n.x},${n.y}` !== `${prev.x},${prev.y}`
);
if (nextPoints.length === 0) break;
// 각도가 가장 작은(반시계 방향) 이웃 선택
const [cx, cy] = current.split(',').map(Number);
const next = nextPoints.reduce((best, curr) => {
const angleBest = Math.atan2(best.y - cy, best.x - cx);
const angleCurr = Math.atan2(curr.y - cy, curr.x - cx);
return angleCurr > angleBest ? curr : best;
}, nextPoints[0]);
// 라인 추가 (방향 유지)
const line = next.line;
const isReversed = (line.x1 !== next.x || line.y1 !== next.y);
result.push({
...line,
x1: isReversed ? line.x2 : line.x1,
y1: isReversed ? line.y2 : line.y1,
x2: isReversed ? line.x1 : line.x2,
y2: isReversed ? line.y1 : line.y2,
idx: result.length
});
prev = { x: cx, y: cy };
current = `${next.x},${next.y}`;
}
// 4. 시계 방향이면 뒤집기
let area = 0;
for (let i = 0; i < result.length; i++) {
const current = result[i];
const next = result[(i + 1) % result.length];
area += (next.x1 - current.x1) * (next.y1 + current.y1);
}
if (area > 0) {
return result.reverse().map((line, idx) => ({
...line,
x1: line.x2,
y1: line.y2,
x2: line.x1,
y2: line.y1,
idx
}));
}
return result;
}
/** /**
* 점을 선분에 투영한 점의 좌표를 반환합니다. * 점을 선분에 투영한 점의 좌표를 반환합니다.
* @param {object} point - 투영할 {x, y} * @param {object} point - 투영할 {x, y}
@ -2992,17 +3097,32 @@ function pointToLineDistance(point, lineP1, lineP2) {
const getOrientation = (line, eps = 0.1) => { const getOrientation = (line, eps = 0.1) => {
const x1 = line.get('x1') if (!line) {
const y1 = line.get('y1') console.error('line 객체가 유효하지 않습니다:', line);
const x2 = line.get('x2') return null; // 또는 적절한 기본값 반환
const y2 = line.get('y2') }
const dx = Math.abs(x2 - x1)
const dy = Math.abs(y2 - y1)
if (dx < eps && dy >= eps) return 'vertical' // get 메서드가 있으면 사용하고, 없으면 직접 프로퍼티에 접근
if (dy < eps && dx >= eps) return 'horizontal' const getValue = (obj, key) =>
if (dx < eps && dy < eps) return 'point' obj && typeof obj.get === 'function' ? obj.get(key) : obj[key];
return 'diagonal'
try {
const x1 = getValue(line, 'x1');
const y1 = getValue(line, 'y1');
const x2 = getValue(line, 'x2');
const y2 = getValue(line, 'y2');
const dx = Math.abs(x2 - x1);
const dy = Math.abs(y2 - y1);
if (dx < eps && dy >= eps) return 'vertical';
if (dy < eps && dx >= eps) return 'horizontal';
if (dx < eps && dy < eps) return 'point';
return 'diagonal';
} catch (e) {
console.error('방향 계산 중 오류 발생:', e);
return null;
}
} }
@ -3371,102 +3491,3 @@ function findInteriorPoint(line, polygonLines) {
}; };
} }
/**
* baseLines의 순서를 wallLines의 순서와 일치시킵니다.
* 1순위: 공통 ID(id, matchingId, parentId ) 이용한 직접 매칭
* 2순위: 기하학적 유사성(기울기, 길이, 위치) 점수화하여 매칭
*
* @param {Array} baseLines - 정렬할 원본 baseLine 배열
* @param {Array} wallLines - 기준이 되는 wallLine 배열
* @returns {Array} wallLines 순서에 맞춰 정렬된 baseLines
*/
export const sortBaseLinesByWallLines = (baseLines, wallLines) => {
if (!baseLines || !wallLines || baseLines.length === 0 || wallLines.length === 0) {
return baseLines;
}
const sortedBaseLines = new Array(wallLines.length).fill(null);
const usedBaseIndices = new Set();
// [1단계] ID 매칭 (기존 로직 유지 - 혹시 ID가 있는 경우를 대비)
// ... (ID 매칭 코드는 생략하거나 유지) ...
// [2단계] 'originPoint' 또는 좌표 일치성을 이용한 강력한 기하학적 매칭
wallLines.forEach((wLine, wIndex) => {
if (sortedBaseLines[wIndex]) return;
// 비교할 기준 좌표 설정 (originPoint가 있으면 그것을, 없으면 현재 좌표 사용)
const wStart = wLine.attributes?.originPoint
? { x: wLine.attributes.originPoint.x1, y: wLine.attributes.originPoint.y1 }
: { x: wLine.x1, y: wLine.y1 };
const wEnd = wLine.attributes?.originPoint
? { x: wLine.attributes.originPoint.x2, y: wLine.attributes.originPoint.y2 }
: { x: wLine.x2, y: wLine.y2 };
// 수직/수평 여부 판단
const isVertical = Math.abs(wStart.x - wEnd.x) < 0.1;
const isHorizontal = Math.abs(wStart.y - wEnd.y) < 0.1;
let bestMatchIndex = -1;
let minDiff = Infinity;
baseLines.forEach((bLine, bIndex) => {
if (usedBaseIndices.has(bIndex)) return;
let diff = Infinity;
// 1. 수직선인 경우: X좌표가 일치해야 함 (예: 230.8 == 230.8)
if (isVertical) {
// bLine도 수직선인지 확인 (x1, x2 차이가 거의 없어야 함)
if (Math.abs(bLine.x1 - bLine.x2) < 1.0) {
// X좌표 차이를 오차(diff)로 계산
diff = Math.abs(wStart.x - bLine.x1);
}
}
// 2. 수평선인 경우: Y좌표가 일치해야 함
else if (isHorizontal) {
// bLine도 수평선인지 확인
if (Math.abs(bLine.y1 - bLine.y2) < 1.0) {
diff = Math.abs(wStart.y - bLine.y1);
}
}
// 3. 대각선인 경우: 기울기와 절편 비교 (복잡하므로 거리로 대체)
else {
// 중점 간 거리 + 기울기 차이
// (이전 답변의 로직 사용 가능)
}
// 오차가 매우 작으면(예: 1px 미만) 같은 라인으로 간주
if (diff < 1.0 && diff < minDiff) {
minDiff = diff;
bestMatchIndex = bIndex;
}
});
if (bestMatchIndex !== -1) {
sortedBaseLines[wIndex] = baseLines[bestMatchIndex];
usedBaseIndices.add(bestMatchIndex);
}
});
// [3단계] 남은 라인 처리 (Fallback)
// 매칭되지 않은 wallLine들에 대해 남은 baseLines를 순서대로 배정하거나
// 거리 기반 근사 매칭을 수행
// ... (기존 fallback 로직) ...
// 빈 구멍 채우기 (null 방지)
for(let i=0; i<sortedBaseLines.length; i++) {
if(!sortedBaseLines[i]) {
const unused = baseLines.findIndex((_, idx) => !usedBaseIndices.has(idx));
if(unused !== -1) {
sortedBaseLines[i] = baseLines[unused];
usedBaseIndices.add(unused);
} else {
sortedBaseLines[i] = baseLines[0]; // 최후의 수단
}
}
}
return sortedBaseLines;
};