작업중 dev와 merge 금지

This commit is contained in:
Jaeyoung Lee 2025-02-07 21:37:34 +09:00
parent 2159046e8f
commit 4101a3ede9
11 changed files with 960 additions and 498 deletions

View File

@ -13,6 +13,7 @@
"@nextui-org/react": "^2.4.2",
"ag-grid-react": "^32.0.2",
"axios": "^1.7.8",
"big.js": "^6.2.2",
"chart.js": "^4.4.6",
"dayjs": "^1.11.13",
"fabric": "^5.3.0",

View File

@ -28,6 +28,7 @@ export const QLine = fabric.util.createClass(fabric.Line, {
this.idx = options.idx ?? 0
this.direction = options.direction ?? getDirectionByPoint({ x: this.x1, y: this.y1 }, { x: this.x2, y: this.y2 })
this.textMode = options.textMode ?? 'plane' // plane:복시도, actual:실측, none:표시안함
if (length !== 0) {
this.length = length
} else {
@ -60,70 +61,84 @@ export const QLine = fabric.util.createClass(fabric.Line, {
},
setLength() {
const scaleX = this.scaleX
const scaleY = this.scaleY
const x1 = this.left
const y1 = this.top
const x2 = this.left + this.width * scaleX
const y2 = this.top + this.height * scaleY
const dx = x2 - x1
const dy = y2 - y1
this.length = Number(Math.sqrt(dx * dx + dy * dy).toFixed(1))
if (this.attributes?.actualSize !== undefined && this.attributes?.planeSize !== undefined) {
if (this.textMode === 'plane') {
this.length = this.attributes.planeSize / 10
} else if (this.textMode === 'actual') {
this.length = this.attributes.actualSize / 10
}
} else {
const scaleX = this.scaleX
const scaleY = this.scaleY
const x1 = this.left
const y1 = this.top
const x2 = this.left + this.width * scaleX
const y2 = this.top + this.height * scaleY
const dx = x2 - x1
const dy = y2 - y1
this.length = Number(Math.sqrt(dx * dx + dy * dy).toFixed(1))
}
},
addLengthText() {
const thisText = this.canvas.getObjects().find((obj) => obj.name === 'lengthText' && obj.parentId === this.id)
this.setLength()
const scaleX = this.scaleX
const scaleY = this.scaleY
const x1 = this.left
const y1 = this.top
const x2 = this.left + this.width * scaleX
const y2 = this.top + this.height * scaleY
if (this.textMode === 'none') {
if (thisText) {
this.canvas.remove(thisText)
}
} else {
this.setLength()
const scaleX = this.scaleX
const scaleY = this.scaleY
const x1 = this.left
const y1 = this.top
const x2 = this.left + this.width * scaleX
const y2 = this.top + this.height * scaleY
if (thisText) {
thisText.set({ text: this.getLength().toString(), left: (x1 + x2) / 2, top: (y1 + y2) / 2 })
this.text = thisText
return
if (thisText) {
thisText.set({ text: this.getLength().toString(), left: (x1 + x2) / 2, top: (y1 + y2) / 2 })
this.text = thisText
return
}
let left, top
if (this.direction === 'left' || this.direction === 'right') {
left = (x1 + x2) / 2
top = (y1 + y2) / 2 + 10
} else if (this.direction === 'top' || this.direction === 'bottom') {
left = (x1 + x2) / 2 + 10
top = (y1 + y2) / 2
}
const minX = this.left
const maxX = this.left + this.width
const minY = this.top
const maxY = this.top + this.length
const degree = (Math.atan2(y2 - y1, x2 - x1) * 180) / Math.PI
const text = new fabric.Textbox(this.getLength().toString(), {
left: left,
top: top,
fontSize: this.fontSize,
minX,
maxX,
minY,
maxY,
parentDirection: this.direction,
parentDegree: degree,
parentId: this.id,
editable: false,
selectable: true,
lockRotation: true,
lockScalingX: true,
lockScalingY: true,
parent: this,
name: 'lengthText',
})
this.text = text
this.canvas.add(text)
}
let left, top
if (this.direction === 'left' || this.direction === 'right') {
left = (x1 + x2) / 2
top = (y1 + y2) / 2 + 10
} else if (this.direction === 'top' || this.direction === 'bottom') {
left = (x1 + x2) / 2 + 10
top = (y1 + y2) / 2
}
const minX = this.left
const maxX = this.left + this.width
const minY = this.top
const maxY = this.top + this.length
const degree = (Math.atan2(y2 - y1, x2 - x1) * 180) / Math.PI
const text = new fabric.Textbox(this.getLength().toString(), {
left: left,
top: top,
fontSize: this.fontSize,
minX,
maxX,
minY,
maxY,
parentDirection: this.direction,
parentDegree: degree,
parentId: this.id,
editable: false,
selectable: true,
lockRotation: true,
lockScalingX: true,
lockScalingY: true,
parent: this,
name: 'lengthText',
})
this.text = text
this.canvas.add(text)
},
setFontSize(fontSize) {
this.fontSize = fontSize

View File

@ -5,6 +5,7 @@ import { distanceBetweenPoints, findTopTwoIndexesByDistance, getDirectionByPoint
import { calculateAngle, drawGabledRoof, drawRidgeRoof, drawShedRoof, inPolygon, toGeoJSON } from '@/util/qpolygon-utils'
import * as turf from '@turf/turf'
import { LINE_TYPE, POLYGON_TYPE } from '@/common/common'
import Big from 'big.js'
export const QPolygon = fabric.util.createClass(fabric.Polygon, {
type: 'QPolygon',
@ -188,7 +189,7 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
},
// 보조선 그리기
drawHelpLine() {
drawHelpLine(settingModalFirstOptions) {
const types = []
this.lines.forEach((line) => types.push(line.attributes.type))
@ -205,7 +206,7 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
(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)
drawGabledRoof(this.id, this.canvas, settingModalFirstOptions)
} else if (hasShed) {
const sheds = this.lines.filter((line) => line.attributes !== undefined && line.attributes.type === LINE_TYPE.WALLLINE.SHED)
const areLinesParallel = function (line1, line2) {
@ -232,18 +233,18 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
const gables = this.lines.filter((line) => sheds.includes(line) === false && eaves.includes(line) === false)
const isGable = gables.every((line) => gableType.includes(line.attributes.type))
if (isGable) {
drawShedRoof(this.id, this.canvas)
drawShedRoof(this.id, this.canvas, settingModalFirstOptions)
} else {
drawRidgeRoof(this.id, this.canvas)
drawRidgeRoof(this.id, this.canvas, settingModalFirstOptions)
}
} else {
drawRidgeRoof(this.id, this.canvas)
drawRidgeRoof(this.id, this.canvas, settingModalFirstOptions)
}
} else {
drawRidgeRoof(this.id, this.canvas)
drawRidgeRoof(this.id, this.canvas, settingModalFirstOptions)
}
} else {
drawRidgeRoof(this.id, this.canvas)
drawRidgeRoof(this.id, this.canvas, settingModalFirstOptions)
}
},
@ -262,15 +263,16 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
const end = points[(i + 1) % points.length]
// 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,
const dx = end.x - start.x
const dy = end.y - start.y
const length = Math.round(Math.sqrt(Math.pow(end.x - start.x, 2) + Math.pow(end.y - start.y, 2))) * 10
const dx = Big(end.x).minus(Big(start.x))
const dy = Big(end.y).minus(Big(start.y))
// const length = Math.round(Math.sqrt(Math.pow(end.x - start.x, 2) + Math.pow(end.y - start.y, 2))) * 10
const length = dx.pow(2).plus(dy.pow(2)).sqrt().times(10).round().toNumber()
let midPoint
midPoint = new fabric.Point((start.x + end.x) / 2, (start.y + end.y) / 2)
const degree = (Math.atan2(dy, dx) * 180) / Math.PI
const degree = Big(Math.atan2(dy.toNumber(), dx.toNumber())).times(180).div(Math.PI).toNumber()
// Create new text object if it doesn't exist
const text = new fabric.Text(length.toString(), {

View File

@ -10,6 +10,7 @@ import { outerLineFixState } from '@/store/outerLineAtom'
import { useSwal } from '@/hooks/useSwal'
import { usePopup } from '@/hooks/usePopup'
import { getChonByDegree } from '@/util/canvas-util'
import { settingModalFirstOptionsState } from '@/store/settingAtom'
// 처마.케라바 변경
export function useEavesGableEdit(id) {
@ -46,6 +47,8 @@ export function useEavesGableEdit(id) {
{ id: 4, name: getMessage('shed'), type: TYPES.SHED },
]
const settingModalFirstOptions = useRecoilValue(settingModalFirstOptionsState)
useEffect(() => {
const outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine')
if (!outerLineFix || outerLines.length === 0) {
@ -180,7 +183,7 @@ export function useEavesGableEdit(id) {
const roof = drawRoofPolygon(wallLine)
canvas?.renderAll()
roof.drawHelpLine()
roof.drawHelpLine(settingModalFirstOptions)
})
wallLines.forEach((wallLine) => {

View File

@ -31,6 +31,7 @@ import { fabric } from 'fabric'
import { outlineDisplaySelector } from '@/store/settingAtom'
import { usePopup } from '@/hooks/usePopup'
import PropertiesSetting from '@/components/floor-plan/modal/outerlinesetting/PropertiesSetting'
import Big from 'big.js'
//외벽선 그리기
export function useOuterLineWall(id, propertiesId) {
@ -141,6 +142,8 @@ 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])
@ -148,24 +151,30 @@ 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: pointer.x - points[points.length - 1].x,
y: pointer.y - points[points.length - 1].y,
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) // 기울기 계산
// 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
if (slope >= 1) {
// if (slope >= 1) {
if (slope.gte(1)) {
// 기울기가 1 이상이면 x축 방향으로 그림
scaledVector = {
x: 0,
y: vector.y >= 0 ? Number(length) : -Number(length),
// 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 >= 0 ? Number(length) : -Number(length),
x: vector.x.gte(0) ? Big(length).toNumber() : Big(length).neg().toNumber(),
y: 0,
}
}
@ -173,9 +182,11 @@ export function useOuterLineWall(id, propertiesId) {
const verticalLength = scaledVector.y
const horizontalLength = scaledVector.x
console.log('verticalLength', verticalLength, 'horizontalLength', horizontalLength)
newPoint = {
x: lastPoint.x + horizontalLength,
y: lastPoint.y + verticalLength,
x: Big(lastPoint.x).plus(horizontalLength).toNumber(),
y: Big(lastPoint.y).plus(verticalLength).toNumber(),
}
}
setPoints((prev) => [...prev, newPoint])
@ -865,6 +876,8 @@ export function useOuterLineWall(id, propertiesId) {
const firstPoint = points[0]
console.log('points 좌표 : ', points)
points.forEach((point, idx) => {
if (idx === 0 || !isAllRightAngle) {
return

View File

@ -1,12 +1,13 @@
import { useEffect, useRef } from 'react'
import { LINE_TYPE, POLYGON_TYPE } from '@/common/common'
import { useRecoilState, useRecoilValue, useResetRecoilState } from 'recoil'
import { useRecoilValue, useResetRecoilState } from 'recoil'
import { canvasState, currentObjectState } from '@/store/canvasAtom'
import { useMode } from '@/hooks/useMode'
import { usePolygon } from '@/hooks/usePolygon'
import { useLine } from '@/hooks/useLine'
import { outerLinePointsState } from '@/store/outerLineAtom'
import { usePopup } from '@/hooks/usePopup'
import { settingModalFirstOptionsState } from '@/store/settingAtom'
// 외벽선 속성 설정
export function usePropertiesSetting(id) {
@ -16,6 +17,7 @@ export function usePropertiesSetting(id) {
const { drawRoofPolygon } = useMode()
const setPoints = useResetRecoilState(outerLinePointsState)
const settingModalFirstOptions = useRecoilValue(settingModalFirstOptionsState)
const { addPolygonByLines } = usePolygon()
const { removeLine, hideLine } = useLine()
@ -156,7 +158,7 @@ export function usePropertiesSetting(id) {
setPoints([])
canvas.renderAll()
roof.drawHelpLine()
roof.drawHelpLine(settingModalFirstOptions)
closePopup(id)
return

View File

@ -10,6 +10,7 @@ import { outerLineFixState } from '@/store/outerLineAtom'
import { useSwal } from '@/hooks/useSwal'
import { usePopup } from '@/hooks/usePopup'
import { getChonByDegree } from '@/util/canvas-util'
import { settingModalFirstOptionsState } from '@/store/settingAtom'
// 지붕형상 설정
export function useRoofShapeSetting(id) {
@ -48,6 +49,8 @@ export function useRoofShapeSetting(id) {
const history = useRef([])
const { closePopup } = usePopup()
const settingModalFirstOptions = useRecoilValue(settingModalFirstOptionsState)
useEffect(() => {
pitchRef.current = currentAngleType === ANGLE_TYPE.SLOPE ? pitch : getChonByDegree(pitch)
}, [pitch])
@ -423,7 +426,7 @@ export function useRoofShapeSetting(id) {
const roof = drawRoofPolygon(polygon)
canvas?.renderAll()
roof.drawHelpLine()
roof.drawHelpLine(settingModalFirstOptions)
isFixRef.current = true
closePopup(id)
}

View File

@ -37,6 +37,8 @@ import offsetPolygon 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'
import Big from 'big.js'
import { settingModalFirstOptionsState } from '@/store/settingAtom'
export function useMode() {
const [mode, setMode] = useRecoilState(modeState)
@ -78,6 +80,8 @@ export function useMode() {
const [objectPlacementMode, setObjectPlacementModeState] = useRecoilState(objectPlacementModeState)
const settingModalFirstOptions = useRecoilValue(settingModalFirstOptionsState)
useEffect(() => {
// if (!canvas) {
// canvas?.setZoom(0.8)
@ -1554,7 +1558,7 @@ export function useMode() {
})
const roof = drawRoofPolygon(polygon) //지붕 그리기
roof.drawHelpLine()
roof.drawHelpLine(settingModalFirstOptions)
// roof.divideLine()
}
@ -1664,11 +1668,18 @@ export function useMode() {
}
}
/**
* polygon 기준으로 margin polygon 작성한다.
* @param polygon
* @param lines
* @param arcSegments
* @returns {{vertices, edges: *[], minX: *, minY: *, maxX: *, maxY: *}}
*/
function createMarginPolygon(polygon, lines, arcSegments = 0) {
const offsetEdges = []
polygon.edges.forEach((edge, i) => {
const offset = lines[i % lines.length].attributes.offset === 0 ? 1 : lines[i % lines.length].attributes.offset
const offset = lines[i % lines.length].attributes.offset === 0 ? 0.1 : lines[i % lines.length].attributes.offset
const dx = edge.outwardNormal.x * offset
const dy = edge.outwardNormal.y * offset
offsetEdges.push(createOffsetEdge(edge, dx, dy))
@ -1692,6 +1703,13 @@ export function useMode() {
return marginPolygon
}
/**
* polygon 기준으로 padding polygon 작성한다.
* @param polygon
* @param lines
* @param arcSegments
* @returns {{vertices, edges: *[], minX: *, minY: *, maxX: *, maxY: *}}
*/
function createPaddingPolygon(polygon, lines, arcSegments = 0) {
const offsetEdges = []
@ -1720,7 +1738,13 @@ export function useMode() {
return paddingPolygon
}
/**
* 외벽선을 기준으로 지붕을 그린다.
* @param wall
* @returns {*}
*/
const drawRoofPolygon = (wall) => {
//외벽선의 순서를 최좌측 선을 기준으로 반시계방향으로 정리한다. 지붕선과 순서를 맞추기 위함.
const startLine = wall.lines
.filter((line) => line.x1 === Math.min(...wall.lines.map((line) => line.x1)))
.reduce((prev, current) => {
@ -1740,12 +1764,15 @@ export function useMode() {
wall.lines = afterLine.concat(beforeLine)
//외벽선을 기준으로 polygon을 생성한다. 지붕선의 기준이 됨.
const polygon = createRoofPolygon(wall.points)
const originPolygon = new QPolygon(wall.points, { fontSize: 0 })
originPolygon.setViewLengthText(false)
let offsetPolygon
let result = createMarginPolygon(polygon, wall.lines).vertices
//margin polygon 의 point가 기준 polygon의 밖에 있는지 판단한다.
const allPointsOutside = result.every((point) => !originPolygon.inPolygon(point))
if (allPointsOutside) {
@ -1759,6 +1786,7 @@ export function useMode() {
return { x1: point.x, y1: point.y }
}),
)
if (wall.direction) {
roof.direction = wall.direction
}
@ -1769,9 +1797,11 @@ export function useMode() {
roof.setWall(wall)
roof.lines.forEach((line, index) => {
const lineLength = Math.sqrt(
Math.pow(Math.round(Math.abs(line.x1 - line.x2) * 10), 2) + Math.pow(Math.round(Math.abs(line.y1 - line.y2) * 10), 2),
)
const x1 = Big(line.x1)
const x2 = Big(line.x2)
const y1 = Big(line.y1)
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,
planeSize: lineLength,
@ -1789,14 +1819,18 @@ export function useMode() {
roofId: roof.id,
}
//외벽선 삭제
canvas
.getObjects()
.filter((line) => line.attributes?.wallId === wall.id)
.forEach((line) => canvas.remove(line))
wall.lines.forEach((line, index) => {
const lineLength = Math.sqrt(
Math.pow(Math.round(Math.abs(line.x1 - line.x2) * 10), 2) + Math.pow(Math.round(Math.abs(line.y1 - line.y2) * 10), 2),
)
const x1 = Big(line.x1)
const x2 = Big(line.x2)
const y1 = Big(line.y1)
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.planeSize = lineLength
@ -1829,6 +1863,8 @@ export function useMode() {
wallStrokeWidth = 4
break
}
//외벽선의 색깔 표시를 위해 라인을 추가한다.
const wallLine = new fabric.Line([line.x1, line.y1, line.x2, line.y2], {
parentId: wall.id,
name: 'wallLine',

View File

@ -1,12 +1,12 @@
import { useRecoilValue } from 'recoil'
import { canvasState } from '@/store/canvasAtom'
import { calculateIntersection, getInterSectionLineNotOverCoordinate } from '@/util/canvas-util'
import { getInterSectionLineNotOverCoordinate } from '@/util/canvas-util'
export function useMouse() {
const canvas = useRecoilValue(canvasState)
//가로선, 세로선의 교차점을 return
const getIntersectMousePoint = (e) => {
/*const getIntersectMousePoint = (e) => {
let pointer = canvas.getPointer(e.e)
const mouseLines = canvas.getObjects().filter((obj) => obj.name === 'mouseLine')
@ -15,6 +15,17 @@ export function useMouse() {
}
return getInterSectionLineNotOverCoordinate(mouseLines[0], mouseLines[1]) || pointer
}*/
//가로선, 세로선의 교차점을 return
const getIntersectMousePoint = (e) => {
let pointer = canvas.getPointer(e.e)
const mouseLines = canvas.getObjects().filter((obj) => obj.name === 'mouseLine')
if (mouseLines.length < 2) {
return { x: Math.round(pointer.x), y: Math.round(pointer.y) }
}
return getInterSectionLineNotOverCoordinate(mouseLines[0], mouseLines[1]) || { x: Math.round(pointer.x), y: Math.round(pointer.y) }
}
return {

View File

@ -1,5 +1,6 @@
import { intersect } from 'mathjs'
import * as turf from '@turf/turf'
import Big from 'big.js'
/**
* Collection of function to use on canvas
@ -128,9 +129,9 @@ export const formattedWithComma = (value, unit = 'mm') => {
}
export const distanceBetweenPoints = (point1, point2) => {
const dx = point2.x - point1.x
const dy = point2.y - point1.y
return Math.sqrt(dx * dx + dy * dy)
const dx = Big(point2.x).minus(Big(point1.x))
const dy = Big(point2.y).minus(Big(point1.y))
return dx.pow(2).plus(dy.pow(2)).sqrt().round(1).toNumber()
}
/**

File diff suppressed because it is too large Load Diff