This commit is contained in:
yjnoh 2024-12-13 13:25:02 +09:00
commit f65c34a7ad
19 changed files with 327 additions and 255 deletions

View File

@ -3,7 +3,6 @@
import { createContext, useEffect, useState } from 'react' import { createContext, useEffect, useState } from 'react'
import { ErrorBoundary } from 'next/dist/client/components/error-boundary' import { ErrorBoundary } from 'next/dist/client/components/error-boundary'
import { useCommonCode } from '@/hooks/common/useCommonCode' import { useCommonCode } from '@/hooks/common/useCommonCode'
import { usePlan } from '@/hooks/usePlan'
import ServerError from './error' import ServerError from './error'
import '@/styles/common.scss' import '@/styles/common.scss'
@ -19,7 +18,6 @@ export const QcastContext = createContext({
export const QcastProvider = ({ children }) => { export const QcastProvider = ({ children }) => {
const [planSave, setPlanSave] = useState(false) const [planSave, setPlanSave] = useState(false)
const [isGlobalLoading, setIsGlobalLoading] = useState(false) const [isGlobalLoading, setIsGlobalLoading] = useState(false)
const { currentCanvasPlan, modifiedPlans, checkUnsavedCanvasPlan } = usePlan()
const { commonCode, findCommonCode } = useCommonCode() const { commonCode, findCommonCode } = useCommonCode()
const [qcastState, setQcastState] = useState({ const [qcastState, setQcastState] = useState({
@ -30,16 +28,6 @@ export const QcastProvider = ({ children }) => {
businessChargerMail: null, businessChargerMail: null,
}) })
useEffect(() => {
const targetElement = document.getElementById('canvas')
if (!targetElement && currentCanvasPlan?.id && planSave) {
setPlanSave((prev) => !prev)
checkUnsavedCanvasPlan()
} else if (targetElement && currentCanvasPlan?.id) {
setPlanSave(true)
}
}, [modifiedPlans])
// useEffect(() => { // useEffect(() => {
// console.log('commonCode', commonCode) // console.log('commonCode', commonCode)
// console.log(findCommonCode(113600)) // console.log(findCommonCode(113600))

View File

@ -36,7 +36,7 @@ export default function Playground() {
const converterUrl = process.env.NEXT_PUBLIC_CONVERTER_API_URL const converterUrl = process.env.NEXT_PUBLIC_CONVERTER_API_URL
const { getMessage } = useMessage() const { getMessage } = useMessage()
const { swalFire } = useSwal() const { swalFire } = useSwal()
const { getRoofMaterialList, getModuleTypeItemList } = useMasterController() const { getRoofMaterialList, getModuleTypeItemList, getTrestleList, getConstructionList, getTrestleDetailList } = useMasterController()
const [color, setColor] = useState('#ff0000') const [color, setColor] = useState('#ff0000')
@ -154,6 +154,41 @@ export default function Playground() {
console.log('users:', users) console.log('users:', users)
}, [users]) }, [users])
const trestleData = [{ moduleTpCd: '1', roofMatlCd: '2', raftBaseCd: '', trestleMkrCd: '4', constMthdCd: '', roofBaseCd: '6' }]
const constructionData = [
{
moduleTpCd: '',
roofMatlCd: '',
trestleMkrCd: '',
constMthdCd: '',
roofBaseCd: '',
illuminationTp: '',
instHt: '',
stdWindSpeed: '',
stdSnowLd: '',
inclCd: '',
raftBaseCd: '',
roofPitch: 0,
},
]
const trestleDetailData = [
{
moduleTpCd: '',
roofMatlCd: '',
trestleMkrCd: '',
constMthdCd: '',
roofBaseCd: '',
illuminationTp: '',
instHt: '',
stdWindSpeed: '',
stdSnowLd: '',
inclCd: '',
constTp: '',
mixMatlNo: 0,
roofPitch: 0,
},
]
return ( return (
<> <>
<div className="container mx-auto p-4 m-4 border"> <div className="container mx-auto p-4 m-4 border">
@ -166,7 +201,7 @@ export default function Playground() {
}} }}
> >
지붕재 목록 조회 API 호출 지붕재 목록 조회 API 호출
</button> </button>{' '}
<button <button
className="btn-frame deepgray" className="btn-frame deepgray"
onClick={() => { onClick={() => {
@ -174,8 +209,33 @@ export default function Playground() {
}} }}
> >
모듈 타입별 아이템 목록 조회 API 호출 모듈 타입별 아이템 목록 조회 API 호출
</button>{' '}
<button
className="btn-frame deepgray"
onClick={() => {
getTrestleList(trestleData)
}}
>
가대 목록 조회 API 호출
</button>{' '}
<button
className="btn-frame deepgray"
onClick={() => {
getConstructionList(constructionData)
}}
>
시공법 목록 조회 API 호출
</button>{' '}
<button
className="btn-frame deepgray"
onClick={() => {
getTrestleDetailList(trestleDetailData)
}}
>
가대 상세 조회 API 호출
</button> </button>
</div> </div>
<div className="m-2"> <div className="m-2">
<button <button
className="btn-frame deepgray" className="btn-frame deepgray"

View File

@ -188,7 +188,6 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
// 보조선 그리기 // 보조선 그리기
drawHelpLine() { drawHelpLine() {
// drawHelpLineInHexagon(this, pitch)
const types = [] const types = []
this.lines.forEach((line) => types.push(line.attributes.type)) this.lines.forEach((line) => types.push(line.attributes.type))

View File

@ -22,7 +22,7 @@ export default function CanvasFrame() {
const { canvasLoadInit, gridInit } = useCanvasConfigInitialize() const { canvasLoadInit, gridInit } = useCanvasConfigInitialize()
const currentMenu = useRecoilValue(currentMenuState) const currentMenu = useRecoilValue(currentMenuState)
const { contextMenu, handleClick } = useContextMenu() const { contextMenu, handleClick } = useContextMenu()
const { selectedPlan, modifiedPlanFlag, checkCanvasObjectEvent, resetModifiedPlans, currentCanvasPlan } = usePlan() const { selectedPlan, currentCanvasPlan } = usePlan()
const totalDisplay = useRecoilValue(totalDisplaySelector) // const totalDisplay = useRecoilValue(totalDisplaySelector) //
// useEvent() // useEvent()
// const { initEvent } = useContext(EventContext) // const { initEvent } = useContext(EventContext)
@ -41,21 +41,8 @@ export default function CanvasFrame() {
} }
} }
useEffect(() => {
if (modifiedPlanFlag && selectedPlan?.id) {
checkCanvasObjectEvent(selectedPlan.id)
}
}, [modifiedPlanFlag])
useEffect(() => {
return () => {
resetModifiedPlans()
}
}, [])
useEffect(() => { useEffect(() => {
loadCanvas() loadCanvas()
resetModifiedPlans()
}, [selectedPlan, canvas]) }, [selectedPlan, canvas])
return ( return (

View File

@ -20,7 +20,7 @@ export default function CanvasLayout({ children }) {
const { getMessage } = useMessage() const { getMessage } = useMessage()
const { swalFire } = useSwal() const { swalFire } = useSwal()
const { plans, modifiedPlans, loadCanvasPlanData, handleCurrentPlan, handleAddPlan, handleDeletePlan } = usePlan() const { plans, loadCanvasPlanData, handleCurrentPlan, handleAddPlan, handleDeletePlan } = usePlan()
useEffect(() => { useEffect(() => {
loadCanvasPlanData(session.userId, objectNo, pid) loadCanvasPlanData(session.userId, objectNo, pid)
@ -36,10 +36,7 @@ export default function CanvasLayout({ children }) {
className={`canvas-page-box ${plan.isCurrent === true ? 'on' : ''}`} className={`canvas-page-box ${plan.isCurrent === true ? 'on' : ''}`}
onClick={() => handleCurrentPlan(plan.id)} onClick={() => handleCurrentPlan(plan.id)}
> >
<span> <span>{`Plan ${plan.ordering}`}</span>
{`Plan ${plan.ordering}`}
{modifiedPlans.some((modifiedPlan) => modifiedPlan === plan.id) && ' [ M ]'}
</span>
<i <i
className="close" className="close"
onClick={(e) => onClick={(e) =>
@ -56,7 +53,12 @@ export default function CanvasLayout({ children }) {
))} ))}
</div> </div>
{plans.length < 10 && ( {plans.length < 10 && (
<button className="plane-add" onClick={() => handleAddPlan(session.userId, objectNo)}> <button
className="plane-add"
onClick={async () => {
await handleAddPlan(session.userId, objectNo)
}}
>
<span></span> <span></span>
</button> </button>
)} )}

View File

@ -41,6 +41,7 @@ import { pwrGnrSimTypeState } from '@/store/simulatorAtom'
import { useAxios } from '@/hooks/useAxios' import { useAxios } from '@/hooks/useAxios'
import { useModuleBasicSetting } from '@/hooks/module/useModuleBasicSetting' import { useModuleBasicSetting } from '@/hooks/module/useModuleBasicSetting'
import { isObjectNotEmpty } from '@/util/common-utils'
export default function CanvasMenu(props) { export default function CanvasMenu(props) {
const { menuNumber, setMenuNumber } = props const { menuNumber, setMenuNumber } = props
@ -78,6 +79,9 @@ export default function CanvasMenu(props) {
const { floorPlanState, setFloorPlanState } = useContext(FloorPlanContext) const { floorPlanState, setFloorPlanState } = useContext(FloorPlanContext)
const { restoreModuleInstArea } = useModuleBasicSetting() const { restoreModuleInstArea } = useModuleBasicSetting()
//
const [buttonStyle, setButtonStyle] = useState('')
const onClickNav = (menu) => { const onClickNav = (menu) => {
setMenuNumber(menu.index) setMenuNumber(menu.index)
setCurrentMenu(menu.title) setCurrentMenu(menu.title)
@ -241,6 +245,16 @@ export default function CanvasMenu(props) {
}) })
} }
useEffect(() => {
if (isObjectNotEmpty(estimateRecoilState)) {
if (estimateRecoilState?.createUser === 'T01') {
if (sessionState.userId !== 'T01') {
setButtonStyle('none')
}
}
}
}, [estimateRecoilState])
return ( return (
<div className={`canvas-menu-wrap ${[2, 3, 4].some((num) => num === menuNumber) ? 'active' : ''}`}> <div className={`canvas-menu-wrap ${[2, 3, 4].some((num) => num === menuNumber) ? 'active' : ''}`}>
<div className="canvas-menu-inner"> <div className="canvas-menu-inner">
@ -320,11 +334,12 @@ export default function CanvasMenu(props) {
<span className="ico ico01"></span> <span className="ico ico01"></span>
<span className="name">{getMessage('plan.menu.estimate.docDown')}</span> <span className="name">{getMessage('plan.menu.estimate.docDown')}</span>
</button> </button>
<button className="btn-frame gray ico-flx" onClick={handleEstimateSubmit}> <button style={{ display: buttonStyle }} className="btn-frame gray ico-flx" onClick={handleEstimateSubmit}>
<span className="ico ico02"></span> <span className="ico ico02"></span>
<span className="name">{getMessage('plan.menu.estimate.save')}</span> <span className="name">{getMessage('plan.menu.estimate.save')}</span>
</button> </button>
<button <button
style={{ display: buttonStyle }}
className="btn-frame gray ico-flx" className="btn-frame gray ico-flx"
onClick={() => { onClick={() => {
handleEstimateReset() handleEstimateReset()
@ -345,7 +360,7 @@ export default function CanvasMenu(props) {
<span className="name">{getMessage('plan.menu.estimate.copy')}</span> <span className="name">{getMessage('plan.menu.estimate.copy')}</span>
</button> </button>
)} )}
<button className="btn-frame gray ico-flx"> <button style={{ display: buttonStyle }} className="btn-frame gray ico-flx">
<span className="ico ico05"></span> <span className="ico ico05"></span>
<span className="name">{getMessage('plan.menu.estimate.unLock')}</span> <span className="name">{getMessage('plan.menu.estimate.unLock')}</span>
</button> </button>

View File

@ -10,7 +10,7 @@ import { globalLocaleStore } from '@/store/localeAtom'
import { isEmptyArray, isNotEmptyArray, isObjectNotEmpty, queryStringFormatter } from '@/util/common-utils' import { isEmptyArray, isNotEmptyArray, isObjectNotEmpty, queryStringFormatter } from '@/util/common-utils'
import { useMessage } from '@/hooks/useMessage' import { useMessage } from '@/hooks/useMessage'
import { useForm } from 'react-hook-form' import { useForm } from 'react-hook-form'
import { useRecoilValue, useSetRecoilState, useResetRecoilState, useRecoilState } from 'recoil' import { useRecoilValue, useSetRecoilState, useResetRecoilState } from 'recoil'
import { SessionContext } from '@/app/SessionProvider' import { SessionContext } from '@/app/SessionProvider'
import FindAddressPop from './popup/FindAddressPop' import FindAddressPop from './popup/FindAddressPop'
import PlanRequestPop from './popup/PlanRequestPop' import PlanRequestPop from './popup/PlanRequestPop'
@ -41,7 +41,6 @@ export default function StuffDetail() {
const { session } = useContext(SessionContext) const { session } = useContext(SessionContext)
const router = useRouter() const router = useRouter()
const pathname = usePathname()
const searchParams = useSearchParams() const searchParams = useSearchParams()
const { getMessage } = useMessage() const { getMessage } = useMessage()
const globalLocaleState = useRecoilValue(globalLocaleStore) const globalLocaleState = useRecoilValue(globalLocaleStore)

View File

@ -1,6 +1,6 @@
'use client' 'use client'
import { useContext, useEffect } from 'react' import { useState, useContext, useEffect } from 'react'
import Link from 'next/link' import Link from 'next/link'
import Image from 'next/image' import Image from 'next/image'
@ -11,20 +11,37 @@ import { useSetRecoilState } from 'recoil'
import { QcastContext } from '@/app/QcastProvider' import { QcastContext } from '@/app/QcastProvider'
import { useMessage } from '@/hooks/useMessage' import { useMessage } from '@/hooks/useMessage'
import { floorPlanObjectState } from '@/store/floorPlanObjectAtom' import { floorPlanObjectState } from '@/store/floorPlanObjectAtom'
import { queryStringFormatter } from '@/util/common-utils' import { isObjectNotEmpty, queryStringFormatter } from '@/util/common-utils'
import { ManagementContext } from '@/app/management/ManagementProvider'
import { SessionContext } from '@/app/SessionProvider'
export default function StuffSubHeader({ type }) { export default function StuffSubHeader({ type }) {
const { getMessage } = useMessage() const { getMessage } = useMessage()
const router = useRouter() const router = useRouter()
const { session } = useContext(SessionContext)
const setFloorPlanObjectNo = useSetRecoilState(floorPlanObjectState) const setFloorPlanObjectNo = useSetRecoilState(floorPlanObjectState)
const { isGlobalLoading } = useContext(QcastContext) const { isGlobalLoading } = useContext(QcastContext)
const { managementState } = useContext(ManagementContext)
const [buttonStyle, setButtonStyle] = useState('')
useEffect(() => { useEffect(() => {
window.scrollTo(0, 0) window.scrollTo(0, 0)
}, []) }, [])
useEffect(() => {
if (isObjectNotEmpty(managementState)) {
if (managementState.createUser === 'T01') {
if (session.userId !== 'T01') {
setButtonStyle('none')
}
}
}
}, [managementState])
const searchParams = useSearchParams() const searchParams = useSearchParams()
const objectNo = searchParams.get('objectNo') //url set const objectNo = searchParams.get('objectNo') //url set
@ -98,7 +115,7 @@ export default function StuffSubHeader({ type }) {
{getMessage('stuff.temp.subTitle')} {getMessage('stuff.temp.subTitle')}
</Link> </Link>
</li> </li>
<li className="title-item"> <li className="title-item" style={{ display: buttonStyle }}>
<a className="sub-header-title" onClick={moveFloorPlan}> <a className="sub-header-title" onClick={moveFloorPlan}>
<span className="icon drawing"></span> <span className="icon drawing"></span>
{getMessage('stuff.temp.subTitle2')} {getMessage('stuff.temp.subTitle2')}

View File

@ -39,10 +39,6 @@ export const useCommonCode = () => {
return resultCodes return resultCodes
} }
useEffect(() => {
findCommonCode()
}, [globalLocale])
useEffect(() => { useEffect(() => {
const getCommonCode = async () => { const getCommonCode = async () => {
await promiseGet({ url: '/api/commcode/qc-comm-code' }).then((res) => { await promiseGet({ url: '/api/commcode/qc-comm-code' }).then((res) => {

View File

@ -1,5 +1,7 @@
import { useAxios } from '@/hooks/useAxios' import { useAxios } from '@/hooks/useAxios'
import axios from 'axios' import { useMessage } from '@/hooks/useMessage'
import { useSwal } from '@/hooks/useSwal'
import { getQueryString } from '@/util/common-utils'
/** /**
* 마스터 컨트롤러 * 마스터 컨트롤러
@ -7,6 +9,8 @@ import axios from 'axios'
*/ */
export function useMasterController() { export function useMasterController() {
const { get } = useAxios() const { get } = useAxios()
const { getMessage } = useMessage()
const { swalFire } = useSwal()
/** /**
* 지붕재 목록 조회 * 지붕재 목록 조회
@ -21,11 +25,17 @@ export function useMasterController() {
/** /**
* 모듈 타입별 아이템 목록 조회 * 모듈 타입별 아이템 목록 조회
* @param {지붕재 코드} roofMaterialCd * @param {지붕재 코드} roofMatlCd
* @returns * @returns
*/ */
const getModuleTypeItemList = async (roofMaterialCd) => { const getModuleTypeItemList = async (roofMatlCd) => {
return await get({ url: `/api/v1/master/getModuleTypeItemList/${roofMaterialCd}` }).then((res) => { if (!roofMatlCd || roofMatlCd.trim() === '') {
swalFire({ text: getMessage('master.moduletypeitem.message.error'), type: 'alert', icon: 'error' })
return null
}
const param = { roofMatlCd: roofMatlCd }
const paramString = getQueryString(param)
return await get({ url: `/api/v1/master/getModuleTypeItemList${paramString}` }).then((res) => {
console.log('🚀🚀 ~ getModuleTypeItemList ~ res:', res) console.log('🚀🚀 ~ getModuleTypeItemList ~ res:', res)
return res return res
}) })
@ -33,11 +43,25 @@ export function useMasterController() {
/** /**
* 가대 목록 조회 * 가대 목록 조회
* @param * @param {모듈타입코드} moduleTpCd
* @param {지붕재코드} roofMatlCd
* @param {서까래기초코드} raftBaseCd
* @param {가대메이커코드} trestleMkrCd
* @param {공법코드} constMthdCd
* @param {지붕기초코드} roofBaseCd
* @returns * @returns
*/ */
const getTrestleList = async (params) => { const getTrestleList = async (params) => {
return await get({ url: `/api/v1/master/getTrestleList/${params}` }).then((res) => { console.log('🚀🚀 ~ getTrestleList ~ params:', params)
params = getQueryString({
moduleTpCd: params.moduleTpCd ? params.moduleTpCd : '',
roofMatlCd: params.roofMatlCd ? params.roofMatlCd : '',
raftBaseCd: params.raftBaseCd ? params.raftBaseCd : '',
trestleMkrCd: params.trestleMkrCd ? params.trestleMkrCd : '',
constMthdCd: params.constMthdCd ? params.constMthdCd : '',
roofBaseCd: params.roofBaseCd ? params.roofBaseCd : '',
})
return await get({ url: '/api/v1/master/getTrestleList' + params }).then((res) => {
console.log('🚀🚀 ~ getTrestleList ~ res:', res) console.log('🚀🚀 ~ getTrestleList ~ res:', res)
return res return res
}) })
@ -45,11 +69,38 @@ export function useMasterController() {
/** /**
* 모듈 시공법 목록 조회 * 모듈 시공법 목록 조회
* @param * @param {모듈타입코드} moduleTpCd
* @param {지붕재코드} roofMatlCd
* @param {가대메이커코드} trestleMkrCd
* @param {공법코드} constMthdCd
* @param {지붕기초코드} roofBaseCd
* @param {면조도} illuminationTp
* @param {설치높이} instHt
* @param {풍속} stdWindSpeed
* @param {적설량} stdSnowLd
* @param {경사도코드} inclCd
* @param {서까래기초코드} raftBaseCd
* @param {하제(망둥어)피치} roofPitch
*
* @returns * @returns
*/ */
const getConstructionList = async (params) => { const getConstructionList = async (params) => {
return await get({ url: `/api/v1/master/getConstructionList/${params}` }).then((res) => { console.log('🚀🚀 ~ getConstructionList ~ params:', params)
params = getQueryString({
moduleTpCd: params.moduleTpCd ? params.moduleTpCd : '',
roofMatlCd: params.roofMatlCd ? params.roofMatlCd : '',
trestleMkrCd: params.trestleMkrCd ? params.trestleMkrCd : '',
constMthdCd: params.constMthdCd ? params.constMthdCd : '',
roofBaseCd: params.roofBaseCd ? params.roofBaseCd : '',
illuminationTp: params.illuminationTp ? params.illuminationTp : '',
instHt: params.instHt ? params.instHt : '',
stdWindSpeed: params.stdWindSpeed ? params.stdWindSpeed : '',
stdSnowLd: params.stdSnowLd ? params.stdSnowLd : '',
inclCd: params.inclCd ? params.inclCd : '',
raftBaseCd: params.raftBaseCd ? params.raftBaseCd : '',
roofPitch: params.roofPitch ? params.roofPitch : 0,
})
return await get({ url: '/api/v1/master/getConstructionList' + params }).then((res) => {
console.log('🚀🚀 ~ getConstructionList ~ res:', res) console.log('🚀🚀 ~ getConstructionList ~ res:', res)
return res return res
}) })
@ -57,11 +108,39 @@ export function useMasterController() {
/** /**
* 가대 상세 조회 * 가대 상세 조회
* @param * @param {모듈타입코드} moduleTpCd
* @param {지붕재코드} roofMatlCd
* @param {가대메이커코드} trestleMkrCd
* @param {공법코드} constMthdCd
* @param {지붕기초코드} roofBaseCd
* @param {면조도} illuminationTp
* @param {설치높이} instHt
* @param {풍속} stdWindSpeed
* @param {적설량} stdSnowLd
* @param {경사도코드} inclCd
* @param {시공법} constTp
* @param {혼합모듈번호} mixMatlNo
* @param {하제(망둥어)피치}roofPitch
* @returns * @returns
*/ */
const getTrestleDetailList = async (params) => { const getTrestleDetailList = async (params) => {
return await get({ url: `/api/v1/master/getTrestleDetailList/${params}` }).then((res) => { console.log('🚀🚀 ~ getConstructionList ~ params:', params)
params = getQueryString({
moduleTpCd: params.moduleTpCd ? params.moduleTpCd : '',
roofMatlCd: params.roofMatlCd ? params.roofMatlCd : '',
trestleMkrCd: params.trestleMkrCd ? params.trestleMkrCd : '',
constMthdCd: params.constMthdCd ? params.constMthdCd : '',
roofBaseCd: params.roofBaseCd ? params.roofBaseCd : '',
illuminationTp: params.illuminationTp ? params.illuminationTp : '',
instHt: params.instHt ? params.instHt : '',
stdWindSpeed: params.stdWindSpeed ? params.stdWindSpeed : '',
stdSnowLd: params.stdSnowLd ? params.stdSnowLd : '',
inclCd: params.inclCd ? params.inclCd : '',
constTp: params.constTp ? params.constTp : '',
mixMatlNo: params.mixMatlNo ? params.mixMatlNo : 0,
roofPitch: params.roofPitch ? params.roofPitch : 0,
})
return await get({ url: '/api/v1/master/getTrestleDetailList' + params }).then((res) => {
console.log('🚀🚀 ~ getTrestleDetailList ~ res:', res) console.log('🚀🚀 ~ getTrestleDetailList ~ res:', res)
return res return res
}) })

View File

@ -5,6 +5,7 @@ import { useMessage } from '@/hooks/useMessage'
import { useEffect, useRef, useState } from 'react' import { useEffect, useRef, useState } from 'react'
import { useEvent } from '@/hooks/useEvent' import { useEvent } from '@/hooks/useEvent'
import { LINE_TYPE, POLYGON_TYPE } from '@/common/common' import { LINE_TYPE, POLYGON_TYPE } from '@/common/common'
import { getDegreeByChon } from '@/util/canvas-util'
//동선이동 형 올림 내림 //동선이동 형 올림 내림
export function useMovementSetting(id) { export function useMovementSetting(id) {
@ -227,6 +228,20 @@ export function useMovementSetting(id) {
} }
}) })
polygon.set({ points: newPoints }) polygon.set({ points: newPoints })
polygon.setCoords()
polygon.addLengthText()
polygon.initLines()
const slope = (line) => (line.x2 - line.x1 === 0 ? Infinity : (line.y2 - line.y1) / (line.x2 - line.x1))
const currentDegree = polygon.attributes.pitch > 0 ? getDegreeByChon(polygon.attributes.pitch) : polygon.attributes.degree
polygon.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)
if (currentDegree > 0 && slope(line) !== slope(currentObject)) {
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
}
})
}) })
} else { } else {
roof.innerLines roof.innerLines
@ -239,7 +254,7 @@ export function useMovementSetting(id) {
(line.x2 === currentX2 && line.y2 === currentY2), (line.x2 === currentX2 && line.y2 === currentY2),
) )
.forEach((line) => { .forEach((line) => {
console.log('line : ', line) const lineDegree = 90 - Math.asin(line.attributes.planeSize / line.attributes.actualSize) * (180 / Math.PI)
if (line.x1 === currentX1 && line.y1 === currentY1) { if (line.x1 === currentX1 && line.y1 === currentY1) {
line.set({ x1: newPoint[0], y1: newPoint[1] }) line.set({ x1: newPoint[0], y1: newPoint[1] })
} else if (line.x2 === currentX1 && line.y2 === currentY1) { } else if (line.x2 === currentX1 && line.y2 === currentY1) {
@ -250,20 +265,18 @@ export function useMovementSetting(id) {
line.set({ x2: newPoint[2], y2: newPoint[3] }) line.set({ x2: newPoint[2], y2: newPoint[3] })
} }
line.setCoords() 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.set({ x1: cloned.x1, y1: cloned.y1, x2: cloned.x2, y2: cloned.y2 })
currentObject.setCoords() currentObject.setCoords()
console.log('currentObject : ', currentObject.x1, currentObject.y1, currentObject.x2, currentObject.y2)
canvas.renderAll() canvas.renderAll()
canvas.discardActiveObject() canvas.discardActiveObject()
if (roof.separatePolygon.length > 0) {
console.log('roof.separatePolygon : ', roof.separatePolygon)
}
} }
//형 올림내림 마우스 클릭 이벤트 //형 올림내림 마우스 클릭 이벤트

View File

@ -3,7 +3,6 @@ import { useRecoilState, useRecoilValue } from 'recoil'
import { v4 as uuidv4 } from 'uuid' import { v4 as uuidv4 } from 'uuid'
import { canvasSizeState, canvasState, canvasZoomState, currentObjectState } from '@/store/canvasAtom' import { canvasSizeState, canvasState, canvasZoomState, currentObjectState } from '@/store/canvasAtom'
import { QPolygon } from '@/components/fabric/QPolygon' import { QPolygon } from '@/components/fabric/QPolygon'
import { usePlan } from '@/hooks/usePlan'
import { fontSelector } from '@/store/fontAtom' import { fontSelector } from '@/store/fontAtom'
// 캔버스에 필요한 이벤트 // 캔버스에 필요한 이벤트
@ -14,7 +13,6 @@ export function useCanvasEvent() {
const canvasSize = useRecoilValue(canvasSizeState) const canvasSize = useRecoilValue(canvasSizeState)
const [canvasZoom, setCanvasZoom] = useRecoilState(canvasZoomState) const [canvasZoom, setCanvasZoom] = useRecoilState(canvasZoomState)
const lengthTextOption = useRecoilValue(fontSelector('lengthText')) const lengthTextOption = useRecoilValue(fontSelector('lengthText'))
const { modifiedPlanFlag, setModifiedPlanFlag } = usePlan()
useEffect(() => { useEffect(() => {
canvas?.setZoom(canvasZoom / 100) canvas?.setZoom(canvasZoom / 100)
@ -40,10 +38,6 @@ export function useCanvasEvent() {
onChange: (e) => { onChange: (e) => {
const target = e.target const target = e.target
if (target.name !== 'mouseLine' && !modifiedPlanFlag) {
setModifiedPlanFlag((prev) => !prev)
}
if (target) { if (target) {
// settleDown(target) // settleDown(target)
} }
@ -58,10 +52,6 @@ export function useCanvasEvent() {
target.uuid = uuidv4() target.uuid = uuidv4()
} }
if (target.name !== 'mouseLine' && !modifiedPlanFlag) {
setModifiedPlanFlag((prev) => !prev)
}
if (target.type === 'QPolygon' || target.type === 'QLine') { if (target.type === 'QPolygon' || target.type === 'QLine') {
const textObjs = canvas?.getObjects().filter((obj) => obj.name === 'lengthText') const textObjs = canvas?.getObjects().filter((obj) => obj.name === 'lengthText')
textObjs.forEach((obj) => { textObjs.forEach((obj) => {
@ -164,9 +154,6 @@ export function useCanvasEvent() {
target.on('moving', (e) => { target.on('moving', (e) => {
target.uuid = uuidv4() target.uuid = uuidv4()
if (!modifiedPlanFlag) {
setModifiedPlanFlag((prev) => !prev)
}
if (target.parentDirection === 'left' || target.parentDirection === 'right') { if (target.parentDirection === 'left' || target.parentDirection === 'right') {
const minX = target.minX const minX = target.minX

View File

@ -1,7 +1,7 @@
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { useRecoilState } from 'recoil' import { useRecoilState } from 'recoil'
import { v4 as uuidv4 } from 'uuid' import { v4 as uuidv4 } from 'uuid'
import { canvasState, currentCanvasPlanState, plansState, modifiedPlansState, modifiedPlanFlagState } from '@/store/canvasAtom' import { canvasState, currentCanvasPlanState, plansState } from '@/store/canvasAtom'
import { useAxios } from '@/hooks/useAxios' import { useAxios } from '@/hooks/useAxios'
import { useMessage } from '@/hooks/useMessage' import { useMessage } from '@/hooks/useMessage'
import { useSwal } from '@/hooks/useSwal' import { useSwal } from '@/hooks/useSwal'
@ -12,14 +12,11 @@ import { useCanvas } from '@/hooks/useCanvas'
export function usePlan() { export function usePlan() {
const [planNum, setPlanNum] = useState(0) const [planNum, setPlanNum] = useState(0)
const [selectedPlan, setSelectedPlan] = useState(null) const [selectedPlan, setSelectedPlan] = useState(null)
const [currentCanvasStatus, setCurrentCanvasStatus] = useState(null)
const [canvas, setCanvas] = useRecoilState(canvasState) const [canvas, setCanvas] = useRecoilState(canvasState)
const [currentCanvasPlan, setCurrentCanvasPlan] = useRecoilState(currentCanvasPlanState) const [currentCanvasPlan, setCurrentCanvasPlan] = useRecoilState(currentCanvasPlanState)
const [plans, setPlans] = useRecoilState(plansState) // 전체 plan const [plans, setPlans] = useRecoilState(plansState) // 전체 plan
const [modifiedPlans, setModifiedPlans] = useRecoilState(modifiedPlansState) // 변경된 canvas plan
const [modifiedPlanFlag, setModifiedPlanFlag] = useRecoilState(modifiedPlanFlagState) // 캔버스 실시간 오브젝트 이벤트 감지 flag
const { swalFire } = useSwal() const { swalFire } = useSwal()
const { getMessage } = useMessage() const { getMessage } = useMessage()
@ -95,64 +92,6 @@ export function usePlan() {
return addCanvas() return addCanvas()
} }
/**
* 캔버스에서 발생하는 실시간 오브젝트 이벤트를 감지하여 수정 여부를 확인 관리
*/
const checkCanvasObjectEvent = (planId) => {
setCurrentCanvasStatus(currentCanvasData())
if (!modifiedPlans.some((modifiedPlan) => modifiedPlan === planId) && checkModifiedCanvasPlan(planId)) {
setModifiedPlans((prev) => [...prev, planId])
setModifiedPlanFlag(false)
}
}
useEffect(() => {
if (currentCanvasStatus) {
setCurrentCanvasPlan((prev) => ({ ...prev, canvasStatus: currentCanvasStatus }))
}
}, [currentCanvasStatus])
/**
* 현재 캔버스 상태와 DB에 저장된 캔버스 상태를 비교하여 수정 여부를 판단
*/
const checkModifiedCanvasPlan = (planId) => {
const planData = plans.find((plan) => plan.id === planId)
if (planData.canvasStatus === '') {
// 빈 상태로 저장된 캔버스
return true
}
// 각각 object들의 uuid 목록을 추출하여 비교
const canvasStatus = currentCanvasData()
const canvasObjsUuids = getObjectUuids(JSON.parse(canvasStatus).objects)
const dbObjsUuids = getObjectUuids(JSON.parse(planData.canvasStatus).objects)
return canvasObjsUuids.length !== dbObjsUuids.length || !canvasObjsUuids.every((uuid, index) => uuid === dbObjsUuids[index])
}
const getObjectUuids = (objects) => {
return objects
.filter((obj) => obj.hasOwnProperty('uuid'))
.map((obj) => obj.uuid)
.sort()
}
const resetModifiedPlans = () => {
setModifiedPlans([])
setModifiedPlanFlag(false)
}
/**
* 캔버스에 저장되지 않은 변경사항이 있을때 저장 여부를 확인 저장
*/
const checkUnsavedCanvasPlan = async () => {
swalFire({
text: `Plan ${currentCanvasPlan.ordering} ` + getMessage('plan.message.confirm.save.modified'),
type: 'confirm',
confirmFn: async () => {
await putCanvasStatus(currentCanvasPlan.canvasStatus)
},
})
resetModifiedPlans()
}
/** /**
* DB에 저장된 데이터를 canvas에서 사용할 있도록 포맷화 * DB에 저장된 데이터를 canvas에서 사용할 있도록 포맷화
*/ */
@ -206,8 +145,8 @@ export function usePlan() {
} }
await promisePost({ url: '/api/canvas-management/canvas-statuses', data: planData }) await promisePost({ url: '/api/canvas-management/canvas-statuses', data: planData })
.then((res) => { .then((res) => {
setPlans([...plans, { id: res.data, objectNo: objectNo, userId: userId, canvasStatus: canvasStatus, ordering: planNum + 1 }]) setPlans((plans) => [...plans, { id: res.data, objectNo: objectNo, userId: userId, canvasStatus: canvasStatus, ordering: planNum + 1 }])
handleCurrentPlan(res.data) updateCurrentPlan(res.data)
setPlanNum(planNum + 1) setPlanNum(planNum + 1)
}) })
.catch((error) => { .catch((error) => {
@ -228,7 +167,6 @@ export function usePlan() {
await promisePut({ url: '/api/canvas-management/canvas-statuses', data: planData }) await promisePut({ url: '/api/canvas-management/canvas-statuses', data: planData })
.then((res) => { .then((res) => {
setPlans((plans) => plans.map((plan) => (plan.id === currentCanvasPlan.id ? { ...plan, canvasStatus: canvasStatus } : plan))) setPlans((plans) => plans.map((plan) => (plan.id === currentCanvasPlan.id ? { ...plan, canvasStatus: canvasStatus } : plan)))
setModifiedPlans((modifiedPlans) => modifiedPlans.filter((planId) => planId !== currentCanvasPlan.id))
}) })
.catch((error) => { .catch((error) => {
swalFire({ text: error.message, icon: 'error' }) swalFire({ text: error.message, icon: 'error' })
@ -251,13 +189,11 @@ export function usePlan() {
/** /**
* plan 이동 * plan 이동
* 현재 plan의 작업상태를 확인, 저장 이동 * 현재 plan의 작업상태를 저장 이동
*/ */
const handleCurrentPlan = async (newCurrentId) => { const handleCurrentPlan = async (newCurrentId) => {
if (!currentCanvasPlan || currentCanvasPlan.id !== newCurrentId) { if (!currentCanvasPlan || currentCanvasPlan.id !== newCurrentId) {
if (currentCanvasPlan?.id && modifiedPlans.some((modifiedPlan) => modifiedPlan === currentCanvasPlan.id)) { await saveCanvas()
await saveCanvas()
}
updateCurrentPlan(newCurrentId) updateCurrentPlan(newCurrentId)
} }
} }
@ -281,9 +217,12 @@ export function usePlan() {
/** /**
* 새로운 plan 생성 * 새로운 plan 생성
* 현재 plan의 데이터가 있을 경우 복제 여부를 확인 * 현재 plan의 데이터가 있을 경우 현재 plan 저장 복제 여부를 확인
*/ */
const handleAddPlan = async (userId, objectNo) => { const handleAddPlan = async (userId, objectNo) => {
if (currentCanvasPlan?.id) {
await saveCanvas()
}
JSON.parse(currentCanvasData()).objects.length > 0 JSON.parse(currentCanvasData()).objects.length > 0
? swalFire({ ? swalFire({
text: `Plan ${currentCanvasPlan.ordering} ` + getMessage('plan.message.confirm.copy'), text: `Plan ${currentCanvasPlan.ordering} ` + getMessage('plan.message.confirm.copy'),
@ -324,7 +263,6 @@ export function usePlan() {
await delCanvasById(id) await delCanvasById(id)
.then((res) => { .then((res) => {
setPlans((plans) => plans.filter((plan) => plan.id !== id)) setPlans((plans) => plans.filter((plan) => plan.id !== id))
setModifiedPlans((modifiedPlans) => modifiedPlans.filter((planId) => planId !== currentCanvasPlan.id))
removeImage(currentCanvasPlan.id) removeImage(currentCanvasPlan.id)
swalFire({ text: getMessage('plan.message.delete') }) swalFire({ text: getMessage('plan.message.delete') })
}) })
@ -362,14 +300,6 @@ export function usePlan() {
canvas, canvas,
plans, plans,
selectedPlan, selectedPlan,
currentCanvasPlan,
setCurrentCanvasPlan,
modifiedPlans,
modifiedPlanFlag,
setModifiedPlanFlag,
checkCanvasObjectEvent,
checkUnsavedCanvasPlan,
resetModifiedPlans,
saveCanvas, saveCanvas,
handleCurrentPlan, handleCurrentPlan,
handleAddPlan, handleAddPlan,

View File

@ -295,7 +295,6 @@
"modal.actual.size.setting.plane.size.length": "廊下の寸法の長さ", "modal.actual.size.setting.plane.size.length": "廊下の寸法の長さ",
"modal.actual.size.setting.actual.size.length": "実寸長", "modal.actual.size.setting.actual.size.length": "実寸長",
"plan.message.confirm.save": "PLAN을 저장하시겠습니까?", "plan.message.confirm.save": "PLAN을 저장하시겠습니까?",
"plan.message.confirm.save.modified": "PLAN의 변경사항을 저장하시겠습니까?",
"plan.message.confirm.copy": "PLAN을 복사하시겠습니까?", "plan.message.confirm.copy": "PLAN을 복사하시겠습니까?",
"plan.message.confirm.delete": "PLAN을 삭제하시겠습니까?", "plan.message.confirm.delete": "PLAN을 삭제하시겠습니까?",
"plan.message.save": "저장되었습니다.", "plan.message.save": "저장되었습니다.",
@ -932,5 +931,6 @@
"simulator.table.sub8": "台", "simulator.table.sub8": "台",
"simulator.table.sub9": "予測発電量 (kWh)", "simulator.table.sub9": "予測発電量 (kWh)",
"simulator.notice.sub1": "Hanwha Japan 年間発電量", "simulator.notice.sub1": "Hanwha Japan 年間発電量",
"simulator.notice.sub2": "シミュレーション案内事項" "simulator.notice.sub2": "シミュレーション案内事項",
"master.moduletypeitem.message.error": "지붕재 코드를 입력하세요."
} }

View File

@ -300,7 +300,6 @@
"modal.actual.size.setting.plane.size.length": "복도치수 길이", "modal.actual.size.setting.plane.size.length": "복도치수 길이",
"modal.actual.size.setting.actual.size.length": "실제치수 길이", "modal.actual.size.setting.actual.size.length": "실제치수 길이",
"plan.message.confirm.save": "PLAN을 저장하시겠습니까?", "plan.message.confirm.save": "PLAN을 저장하시겠습니까?",
"plan.message.confirm.save.modified": "PLAN의 변경사항을 저장하시겠습니까?",
"plan.message.confirm.copy": "PLAN을 복사하시겠습니까?", "plan.message.confirm.copy": "PLAN을 복사하시겠습니까?",
"plan.message.confirm.delete": "PLAN을 삭제하시겠습니까?", "plan.message.confirm.delete": "PLAN을 삭제하시겠습니까?",
"plan.message.save": "저장되었습니다.", "plan.message.save": "저장되었습니다.",
@ -942,5 +941,6 @@
"simulator.table.sub8": "대", "simulator.table.sub8": "대",
"simulator.table.sub9": "예측발전량 (kWh)", "simulator.table.sub9": "예측발전량 (kWh)",
"simulator.notice.sub1": "Hanwha Japan 연간 발전량", "simulator.notice.sub1": "Hanwha Japan 연간 발전량",
"simulator.notice.sub2": "시뮬레이션 안내사항" "simulator.notice.sub2": "시뮬레이션 안내사항",
"master.moduletypeitem.message.error": "지붕재 코드를 입력하세요."
} }

View File

@ -272,17 +272,6 @@ export const plansState = atom({
default: [], default: [],
}) })
// 변경된 canvas plan 목록
export const modifiedPlansState = atom({
key: 'modifiedPlansState',
default: [],
})
// 변경감지 flag
export const modifiedPlanFlagState = atom({
key: 'modifiedPlanFlagState',
default: false,
})
export const tempGridModeState = atom({ export const tempGridModeState = atom({
key: 'tempGridModeState', key: 'tempGridModeState',
default: false, default: false,

View File

@ -1,84 +1,84 @@
.test { .test {
background-color: #797979; background-color: #797979;
font: 30px sans-serif; font: 30px sans-serif;
font-style: italic; font-style: italic;
color: white; color: white;
} }
.grid-container2 { .grid-container2 {
display: grid; display: grid;
grid-template-columns: repeat(2, 1fr); /* 2개의 열 */ grid-template-columns: repeat(2, 1fr); /* 2개의 열 */
grid-template-rows: repeat(6, 30px); /* 6개의 행 */ grid-template-rows: repeat(6, 30px); /* 6개의 행 */
justify-items: center; /* 각 그리드 아이템을 수평 가운데 정렬 */ justify-items: center; /* 각 그리드 아이템을 수평 가운데 정렬 */
align-items: center; /* 각 그리드 아이템을 수직 가운데 정렬 */ align-items: center; /* 각 그리드 아이템을 수직 가운데 정렬 */
gap: 5px; /* 그리드 아이템 사이의 간격 */ gap: 5px; /* 그리드 아이템 사이의 간격 */
} }
.grid-container3 { .grid-container3 {
display: grid; display: grid;
grid-template-columns: repeat(3, 1fr); /* 3개의 열 */ grid-template-columns: repeat(3, 1fr); /* 3개의 열 */
grid-template-rows: repeat(6, 30px); /* 6개의 행 */ grid-template-rows: repeat(6, 30px); /* 6개의 행 */
justify-items: center; /* 각 그리드 아이템을 수평 가운데 정렬 */ justify-items: center; /* 각 그리드 아이템을 수평 가운데 정렬 */
align-items: center; /* 각 그리드 아이템을 수직 가운데 정렬 */ align-items: center; /* 각 그리드 아이템을 수직 가운데 정렬 */
gap: 5px; /* 그리드 아이템 사이의 간격 */ gap: 5px; /* 그리드 아이템 사이의 간격 */
} }
.grid-container4 { .grid-container4 {
display: grid; display: grid;
grid-template-columns: repeat(4, 1fr); /* 4개의 열 */ grid-template-columns: repeat(4, 1fr); /* 4개의 열 */
grid-template-rows: repeat(6, 30px); /* 6개의 행 */ grid-template-rows: repeat(6, 30px); /* 6개의 행 */
justify-items: center; /* 각 그리드 아이템을 수평 가운데 정렬 */ justify-items: center; /* 각 그리드 아이템을 수평 가운데 정렬 */
align-items: center; /* 각 그리드 아이템을 수직 가운데 정렬 */ align-items: center; /* 각 그리드 아이템을 수직 가운데 정렬 */
gap: 5px; /* 그리드 아이템 사이의 간격 */ gap: 5px; /* 그리드 아이템 사이의 간격 */
} }
.grid-container5 { .grid-container5 {
display: grid; display: grid;
grid-template-columns: repeat(5, 1fr); /* 5개의 열 */ grid-template-columns: repeat(5, 1fr); /* 5개의 열 */
grid-template-rows: repeat(5, 30px); /* 5개의 행 */ grid-template-rows: repeat(5, 30px); /* 5개의 행 */
justify-items: center; /* 각 그리드 아이템을 수평 가운데 정렬 */ justify-items: center; /* 각 그리드 아이템을 수평 가운데 정렬 */
align-items: center; /* 각 그리드 아이템을 수직 가운데 정렬 */ align-items: center; /* 각 그리드 아이템을 수직 가운데 정렬 */
gap: 0px; /* 그리드 아이템 사이의 간격 */ gap: 0px; /* 그리드 아이템 사이의 간격 */
} }
.grid-item { .grid-item {
width: 100%; width: 100%;
height: 100%; height: 100%;
border: 1px solid black; /* 그리드 외각선 */ border: 1px solid black; /* 그리드 외각선 */
text-align: center; /* 그리드 내 가운데 정렬 */ text-align: center; /* 그리드 내 가운데 정렬 */
} }
.grid-item2 { .grid-item2 {
padding: 20px; padding: 20px;
text-align: center; text-align: center;
cursor: pointer; cursor: pointer;
border: 1px solid #000; border: 1px solid #000;
} }
.grid-item3 { .grid-item3 {
padding: 20px; padding: 20px;
text-align: center; text-align: center;
cursor: pointer; cursor: pointer;
border: 1px solid #000; border: 1px solid #000;
transition: background-color 0.3s ease; transition: background-color 0.3s ease;
} }
.grid-item.Y { .grid-item.Y {
background-color: #d3d0d0; background-color: #d3d0d0;
color: black; color: black;
} }
.grid-item.N { .grid-item.N {
background-color: white; background-color: white;
color: black; color: black;
} }
.grid-item.selected { .grid-item.selected {
background-color: #d3d0d0; background-color: #d3d0d0;
color: black; color: black;
} }
.grid-item.unselected { .grid-item.unselected {
background-color: white; background-color: white;
color: black; color: black;
} }

View File

@ -117,3 +117,15 @@ export const calculateFlowDirection = (canvasAngle) => {
right: -90 - canvasAngle < -180 ? -90 - canvasAngle + 360 : -90 - canvasAngle, right: -90 - canvasAngle < -180 ? -90 - canvasAngle + 360 : -90 - canvasAngle,
} }
} }
/**
* 자바스크립트 객체로 쿼리스트링 생성
* @param {javascript object} o 쿼리스트링 생성할 객체
* @returns {string} 쿼리스트링
*/
export const getQueryString = (o) => {
const queryString = Object.keys(o)
.map((key) => `${key}=${o[key]}`)
.join('&')
return `?${queryString}`
}

View File

@ -1109,7 +1109,6 @@ const drawHips = (roof, canvas) => {
const vectorX2 = ridgeCoordinate.x1 - currentRoof.x2 const vectorX2 = ridgeCoordinate.x1 - currentRoof.x2
const vectorY2 = ridgeCoordinate.y1 - currentRoof.y2 const vectorY2 = ridgeCoordinate.y1 - currentRoof.y2
const angle2 = Math.atan2(vectorY2, vectorX2) * (180 / Math.PI) const angle2 = Math.atan2(vectorY2, vectorX2) * (180 / Math.PI)
console.log('angle2', Math.abs(Math.round(angle2)) % 45)
if (Math.abs(Math.round(angle2)) % 45 === 0) { if (Math.abs(Math.round(angle2)) % 45 === 0) {
const hip2 = new QLine([currentRoof.x2, currentRoof.y2, ridgeCoordinate.x1, ridgeCoordinate.y1], { const hip2 = new QLine([currentRoof.x2, currentRoof.y2, ridgeCoordinate.x1, ridgeCoordinate.y1], {
fontSize: roof.fontSize, fontSize: roof.fontSize,