feat: 한쪽흐름(shed) 외곽 처마선 편집 + 처마선 치수 중복 정리

[작업내용] :
- splitRoofLinesToInnerLines: degreeOf 옵션 추가 — 한쪽흐름 경사변(gable) plane/cos 실제길이 보정
- drawShedRoof: 외곽 처마선을 innerLine 으로 split(degreeOf 주입)해 편집 가능화
- removeRoofLengthText 추가: 처마선 innerLine 이 치수 전담 → roof 원본 변 치수(lengthText) 제거 (박공/split 공용)
- drawGableRoof: removeRoofLengthText 호출로 박공 roof 원본 치수 중복 제거
- commonDeleteText: 보조선(QLine) 삭제 시 parentId 매칭으로 lengthText 동반 제거 (치수 잔존 방지)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jaeyoung Lee 2026-06-15 13:10:10 +09:00
parent 0944aa3a8c
commit 07f787670d
2 changed files with 46 additions and 6 deletions

View File

@ -556,6 +556,12 @@ export function useCommonUtils() {
if (object.id) { if (object.id) {
const group = canvas.getObjects().filter((obj) => obj.id === object.id) const group = canvas.getObjects().filter((obj) => obj.id === object.id)
group.forEach((obj) => canvas?.remove(obj)) group.forEach((obj) => canvas?.remove(obj))
// QLine 보조선은 lengthText 를 this.text(단수)에 저장해 아래 texts(배열) 제거에 안 걸린다.
// lengthText 는 parentId === line.id 로 연결되므로 parentId 매칭으로 함께 제거 (삭제 시 text 잔존 방지).
canvas
.getObjects()
.filter((obj) => obj.parentId === object.id && obj.name === 'lengthText')
.forEach((obj) => canvas?.remove(obj))
} }
if (object.type === 'group') { if (object.type === 'group') {

View File

@ -1628,9 +1628,10 @@ export const drawGableRoof = (roofId, canvas, textMode) => {
drawRoofPlane(backward) drawRoofPlane(backward)
}) })
roof.innerLines.push(...ridgeLines, ...innerLines) roof.innerLines.push(...ridgeLines, ...innerLines)
// 처마선 innerLine 이 외곽선을 전담하므로 roof 원본 외곽(stroke)은 숨긴다. // 처마선 innerLine 이 외곽선/치수를 전담하므로 roof 원본 외곽(stroke) 숨김 + 지붕선 치수(roof lengthText) 제거.
// (박공은 split 공용 함수를 안 거치므로 여기서 직접 처리 — usePolygon.js 의 roof 기본 stroke:'black') // 외벽선(wall) 치수는 유지. (박공은 split 공용 함수를 안 거치므로 여기서 직접 처리)
roof.set({ stroke: 'transparent' }) roof.set({ stroke: 'transparent' })
removeRoofLengthText(roof, canvas)
canvas canvas
.getObjects() .getObjects()
.filter((obj) => obj.name === 'check') .filter((obj) => obj.name === 'check')
@ -1722,6 +1723,16 @@ export const drawShedRoof = (roofId, canvas, textMode) => {
const maxLine = pitchSizeLines.reduce((prev, current) => (prev.length > current.length ? prev : current), pitchSizeLines[0]) const maxLine = pitchSizeLines.reduce((prev, current) => (prev.length > current.length ? prev : current), pitchSizeLines[0])
canvas.add(maxLine) canvas.add(maxLine)
// [docs §8] 한쪽흐름 외곽 처마선 — 내부 보조선이 없어 각 처마변이 통짜 innerLine 으로 그려진다.
// 경사변(gable)은 평면길이≠실제길이 라서 degreeOf 로 shedDegree 를 주입 → drawHipLine(plane/cos) 으로 보정.
// roof 원본 외곽(stroke) 숨김은 splitRoofLinesToInnerLines 가 담당.
roof.innerLines = roof.innerLines || []
splitRoofLinesToInnerLines(roof, roof.innerLines, canvas, textMode, {
degreeOf: (parentLine) => (parentLine.attributes?.type === LINE_TYPE.WALLLINE.GABLE ? shedDegree : 0),
})
// 지붕선 치수(roof lengthText) 제거는 split 내부 removeRoofLengthText 가 담당. 외벽선(wall) 치수는 유지.
canvas.renderAll() canvas.renderAll()
} }
@ -5445,6 +5456,18 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => {
canvas.renderAll() canvas.renderAll()
} }
/**
* 지붕선(roof.lines) 원본 길이 text(lengthText) 제거한다.
* 지붕선 치수는 위에 그려진 보조선(처마선 innerLine) 치수로 대체되므로 roof.lines 자체 치수는 숨긴다.
* 외벽선(wall) 치수는 유지한다. (변별/박공/용마루/한쪽흐름 공통)
*/
export const removeRoofLengthText = (roof, canvas) => {
canvas
?.getObjects()
.filter((o) => o.name === 'lengthText' && o.parentId === roof.id)
.forEach((o) => canvas.remove(o))
}
/** /**
* roof.lines(처마 외곽선) innerLine 끝점 기준으로 split 편집 가능한 innerLine(hip/roofLine)으로 만들고, * roof.lines(처마 외곽선) innerLine 끝점 기준으로 split 편집 가능한 innerLine(hip/roofLine)으로 만들고,
* 처마선 innerLine 외곽선을 전담하므로 roof 원본 외곽선(stroke) 숨긴다(transparent). * 처마선 innerLine 외곽선을 전담하므로 roof 원본 외곽선(stroke) 숨긴다(transparent).
@ -5454,9 +5477,13 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => {
* @param {Array<QLine>} innerLines - 내부선 배열 (split 결과가 여기에 in-place 추가됨) * @param {Array<QLine>} innerLines - 내부선 배열 (split 결과가 여기에 in-place 추가됨)
* @param {fabric.Canvas} canvas * @param {fabric.Canvas} canvas
* @param {string} textMode * @param {string} textMode
* @param {{degreeOf?: (parentLine) => number}} [opts] - degreeOf: 부모 외곽변 경사각().
* 한쪽흐름 경사변(gable)처럼 평면길이실제길이인 변을 통짜로 그릴 drawHipLine 으로 실제길이 보정(plane/cos) docs §8.
* 미전달 기존 동작(분할점 인접 pitch 기반).
* @returns {Array<QLine>} innerLines * @returns {Array<QLine>} innerLines
*/ */
export const splitRoofLinesToInnerLines = (roof, innerLines, canvas, textMode) => { export const splitRoofLinesToInnerLines = (roof, innerLines, canvas, textMode, opts = {}) => {
const { degreeOf } = opts
// 분할점이 처마변 끝점(코너)과 이 거리(px) 이내면 분할에서 제외. // 분할점이 처마변 끝점(코너)과 이 거리(px) 이내면 분할에서 제외.
// SK 추녀 연장이 처마 코너에 닿을 때 ray cast 부동소수 오차(관측 0.02px)로 코너와 미세하게 // SK 추녀 연장이 처마 코너에 닿을 때 ray cast 부동소수 오차(관측 0.02px)로 코너와 미세하게
// 어긋나면, 기존 almostEqual(1e-10)은 "끝점 아님"으로 오판 → 0 길이 조각 생성. END_EPS 로 흡수. // 어긋나면, 기존 almostEqual(1e-10)은 "끝점 아님"으로 오판 → 0 길이 조각 생성. END_EPS 로 흡수.
@ -5521,13 +5548,20 @@ export const splitRoofLinesToInnerLines = (roof, innerLines, canvas, textMode) =
innerLines.push(drawHipLine([startPoint.x, startPoint.y, currentLine.x2, currentLine.y2], canvas, roof, textMode, nextDegree, nextDegree)) innerLines.push(drawHipLine([startPoint.x, startPoint.y, currentLine.x2, currentLine.y2], canvas, roof, textMode, nextDegree, nextDegree))
} }
} else { } else {
innerLines.push(drawRoofLine([currentLine.x1, currentLine.y1, currentLine.x2, currentLine.y2], canvas, roof, textMode)) // 분할점 없는 통짜 변. degreeOf 가 주어지면(한쪽흐름) 경사변은 drawHipLine(plane/cos) 으로 실제길이 보정.
const deg = degreeOf ? degreeOf(currentLine) : 0
if (deg > 0) {
innerLines.push(drawHipLine([currentLine.x1, currentLine.y1, currentLine.x2, currentLine.y2], canvas, roof, textMode, deg, deg))
} else {
innerLines.push(drawRoofLine([currentLine.x1, currentLine.y1, currentLine.x2, currentLine.y2], canvas, roof, textMode))
}
} }
}) })
// 처마선 innerLine 이 외곽선을 전담하므로 roof 원본 외곽(stroke)은 숨긴다. // 처마선 innerLine 이 외곽선/치수를 전담하므로 roof 원본 외곽(stroke) 숨김 + 지붕선 치수(roof lengthText) 제거.
// (안 숨기면 처마선 삭제 시 roof 의 기본 stroke:'black'(usePolygon.js) 이 노출됨) // (안 숨기면 처마선 삭제 시 roof 기본 stroke:'black'(usePolygon.js) 노출. 외벽선 wall 치수는 유지)
roof.set({ stroke: 'transparent' }) roof.set({ stroke: 'transparent' })
removeRoofLengthText(roof, canvas)
return innerLines return innerLines
} }