Merge remote-tracking branch 'origin/dev' into feature/skeleton_ysCha
# Conflicts: # src/components/fabric/QPolygon.js # src/util/skeleton-utils.js
This commit is contained in:
commit
f6a5e5db16
@ -3,7 +3,7 @@ import { createCalculator } from '@/util/calc-utils'
|
|||||||
import '@/styles/calc.scss'
|
import '@/styles/calc.scss'
|
||||||
|
|
||||||
export const CalculatorInput = forwardRef(
|
export const CalculatorInput = forwardRef(
|
||||||
({ value, onChange, label, options = {}, id, className = 'calculator-input', readOnly = false, placeholder }, ref) => {
|
({ value, onChange, label, options = {}, id, className = 'calculator-input', readOnly = false, placeholder, name='', disabled = false }, ref) => {
|
||||||
const [showKeypad, setShowKeypad] = useState(false)
|
const [showKeypad, setShowKeypad] = useState(false)
|
||||||
const [displayValue, setDisplayValue] = useState(value || '0')
|
const [displayValue, setDisplayValue] = useState(value || '0')
|
||||||
const [hasOperation, setHasOperation] = useState(false)
|
const [hasOperation, setHasOperation] = useState(false)
|
||||||
@ -353,6 +353,7 @@ export const CalculatorInput = forwardRef(
|
|||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
type="text"
|
type="text"
|
||||||
id={id}
|
id={id}
|
||||||
|
name={name}
|
||||||
value={displayValue}
|
value={displayValue}
|
||||||
readOnly={readOnly}
|
readOnly={readOnly}
|
||||||
className={className}
|
className={className}
|
||||||
@ -363,6 +364,7 @@ export const CalculatorInput = forwardRef(
|
|||||||
tabIndex={readOnly ? -1 : 0}
|
tabIndex={readOnly ? -1 : 0}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
autoComplete={'off'}
|
autoComplete={'off'}
|
||||||
|
disabled={disabled}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{showKeypad && !readOnly && (
|
{showKeypad && !readOnly && (
|
||||||
|
|||||||
@ -25,6 +25,7 @@ export default function QnaDetailModal({ qnaNo, setOpen, qnaType }) {
|
|||||||
compCd : 5200,
|
compCd : 5200,
|
||||||
loginId : sessionState.userId,
|
loginId : sessionState.userId,
|
||||||
langCd : 'JA',
|
langCd : 'JA',
|
||||||
|
siteTpCd : 'QC',
|
||||||
})
|
})
|
||||||
const apiUrl = `${url}?${params.toString()}`
|
const apiUrl = `${url}?${params.toString()}`
|
||||||
|
|
||||||
|
|||||||
@ -22,7 +22,8 @@ export default function QnaRegModal({ setOpen, setReload, searchValue, selectPag
|
|||||||
const [sessionState, setSessionState] = useRecoilState(sessionStore)
|
const [sessionState, setSessionState] = useRecoilState(sessionStore)
|
||||||
const globalLocaleState = useRecoilValue(globalLocaleStore)
|
const globalLocaleState = useRecoilValue(globalLocaleStore)
|
||||||
const [files, setFiles] = useState([])
|
const [files, setFiles] = useState([])
|
||||||
const [qnaData, setQnaData] = useState([])
|
//const [qnaData, setQnaData] = useState([])
|
||||||
|
const [qnaData, setQnaData] = useState({})
|
||||||
const [closeMdFlg, setCloseMdFlg] = useState(true)
|
const [closeMdFlg, setCloseMdFlg] = useState(true)
|
||||||
const [closeSmFlg, setCloseSmFlg] = useState(true)
|
const [closeSmFlg, setCloseSmFlg] = useState(true)
|
||||||
const [hideSmFlg, setHideSmFlg] = useState(false)
|
const [hideSmFlg, setHideSmFlg] = useState(false)
|
||||||
@ -44,6 +45,10 @@ export default function QnaRegModal({ setOpen, setReload, searchValue, selectPag
|
|||||||
const [isBtnDisable, setIsBtnDisable] = useState(false);
|
const [isBtnDisable, setIsBtnDisable] = useState(false);
|
||||||
const { promiseGet, post, promisePost } = useAxios(globalLocaleState)
|
const { promiseGet, post, promisePost } = useAxios(globalLocaleState)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
console.log('qnaData updated:', qnaData);
|
||||||
|
}, [qnaData]);
|
||||||
|
|
||||||
let fileCheck = false;
|
let fileCheck = false;
|
||||||
const regPhoneNumber = (e) => {
|
const regPhoneNumber = (e) => {
|
||||||
const result = e.target.value
|
const result = e.target.value
|
||||||
@ -80,14 +85,16 @@ let fileCheck = false;
|
|||||||
//setQnaData([])
|
//setQnaData([])
|
||||||
|
|
||||||
setQnaData({
|
setQnaData({
|
||||||
...qnaData,
|
|
||||||
compCd: "5200",
|
compCd: "5200",
|
||||||
siteTpCd: "QC",
|
siteTpCd: "QC",
|
||||||
schNoticeClsCd: "QNA",
|
schNoticeClsCd: "QNA",
|
||||||
regId: sessionState.userId,
|
regId: sessionState?.userId || '',
|
||||||
storeId: sessionState.userId,
|
storeId: sessionState?.userId || '',
|
||||||
qstMail : sessionState.email
|
qstMail: sessionState?.email || '',
|
||||||
})
|
qnaClsLrgCd: '',
|
||||||
|
qnaClsMidCd: '',
|
||||||
|
qnaClsSmlCd: ''
|
||||||
|
});
|
||||||
|
|
||||||
const codeL = findCommonCode(204200)
|
const codeL = findCommonCode(204200)
|
||||||
if (codeL != null) {
|
if (codeL != null) {
|
||||||
@ -119,41 +126,42 @@ let fileCheck = false;
|
|||||||
|
|
||||||
}
|
}
|
||||||
const onChangeQnaTypeM = (e) => {
|
const onChangeQnaTypeM = (e) => {
|
||||||
|
if (!e?.clCode) return;
|
||||||
|
|
||||||
if(e === undefined || e === null) return;
|
// 중분류 코드 업데이트
|
||||||
const codeS = findCommonCode(204400)
|
setQnaData(prevState => ({
|
||||||
if (codeS != null) {
|
...prevState,
|
||||||
|
qnaClsMidCd: e.clCode,
|
||||||
let codeList = []
|
// 소분류는 초기화 (새로 선택하도록)
|
||||||
|
qnaClsSmlCd: ''
|
||||||
codeS.map((item) => {
|
}));
|
||||||
|
|
||||||
if (item.clRefChr1 === e.clCode) {
|
|
||||||
codeList.push(item);
|
|
||||||
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
setQnaData({ ...qnaData, qnaClsMidCd: e.clCode })
|
|
||||||
setCloseSmFlg(false)
|
|
||||||
setQnaTypeSmCodeList(codeList)
|
|
||||||
qnaTypeSmCodeRef.current?.setValue();
|
|
||||||
|
|
||||||
if(codeList.length > 0) {
|
|
||||||
setHideSmFlg(false)
|
|
||||||
}else{
|
|
||||||
setHideSmFlg(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
// 소분류 코드 목록 설정
|
||||||
|
const codeS = findCommonCode(204400);
|
||||||
|
if (codeS) {
|
||||||
|
const filteredCodeList = codeS.filter(item => item.clRefChr1 === e.clCode);
|
||||||
|
setQnaTypeSmCodeList(filteredCodeList);
|
||||||
|
|
||||||
|
// 소분류가 있으면 초기화, 없으면 숨김
|
||||||
|
const hasSubCategories = filteredCodeList.length > 0;
|
||||||
|
setCloseSmFlg(!hasSubCategories);
|
||||||
|
setHideSmFlg(!hasSubCategories);
|
||||||
|
} else {
|
||||||
|
setHideSmFlg(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
// 소분류 선택기 초기화
|
||||||
|
qnaTypeSmCodeRef.current?.setValue();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
const onChangeQnaTypeS = (e) => {
|
const onChangeQnaTypeS = (e) => {
|
||||||
if(e === undefined || e === null) return;
|
if (!e?.clCode) return;
|
||||||
setQnaData({ ...qnaData, qnaClsSmlCd:e.clCode})
|
|
||||||
|
setQnaData(prevState => ({
|
||||||
|
...prevState,
|
||||||
|
qnaClsSmlCd: e.clCode
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
const onFileSave = () => {
|
const onFileSave = () => {
|
||||||
|
|||||||
@ -138,7 +138,27 @@ export default function Estimate({}) {
|
|||||||
updatedRes = [...res]
|
updatedRes = [...res]
|
||||||
}
|
}
|
||||||
|
|
||||||
setOriginDisplayItemList(res)
|
const groupByItemGroup = (items) => {
|
||||||
|
const grouped = items.reduce((acc, item) => {
|
||||||
|
const group = item.itemGroup || '기타';
|
||||||
|
if (!acc[group]) {
|
||||||
|
acc[group] = {
|
||||||
|
label: group,
|
||||||
|
options: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
acc[group].options.push({
|
||||||
|
value: item.itemId,
|
||||||
|
label: `${item.itemNo} - ${item.itemName}`,
|
||||||
|
...item
|
||||||
|
});
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
return Object.values(grouped);
|
||||||
|
};
|
||||||
|
const groupedItems = groupByItemGroup(res);
|
||||||
|
setOriginDisplayItemList(groupedItems)
|
||||||
setDisplayItemList(updatedRes)
|
setDisplayItemList(updatedRes)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -153,6 +173,19 @@ export default function Estimate({}) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const groupStyles = {
|
||||||
|
groupHeading: (provided) => ({
|
||||||
|
...provided,
|
||||||
|
fontSize: '14px',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
color: '#333',
|
||||||
|
backgroundColor: '#f5f5f5',
|
||||||
|
padding: '8px 12px',
|
||||||
|
marginBottom: '4px',
|
||||||
|
borderBottom: '2px solid #ddd'
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// console.log('🚀 ~ Estimate ~ selectedPlan:', selectedPlan)
|
// console.log('🚀 ~ Estimate ~ selectedPlan:', selectedPlan)
|
||||||
if (selectedPlan) initEstimate(selectedPlan?.planNo?? currentPid)
|
if (selectedPlan) initEstimate(selectedPlan?.planNo?? currentPid)
|
||||||
@ -1998,6 +2031,7 @@ export default function Estimate({}) {
|
|||||||
classNamePrefix="custom"
|
classNamePrefix="custom"
|
||||||
placeholder="Select"
|
placeholder="Select"
|
||||||
options={originDisplayItemList}
|
options={originDisplayItemList}
|
||||||
|
styles={groupStyles}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
if (isObjectNotEmpty(e)) {
|
if (isObjectNotEmpty(e)) {
|
||||||
onChangeDisplayItem(e.itemId, item.dispOrder, index, false)
|
onChangeDisplayItem(e.itemId, item.dispOrder, index, false)
|
||||||
|
|||||||
@ -336,8 +336,8 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
|
|||||||
if (types.every((type) => type === LINE_TYPE.WALLLINE.EAVES)) {
|
if (types.every((type) => type === LINE_TYPE.WALLLINE.EAVES)) {
|
||||||
// 용마루 -- straight-skeleton
|
// 용마루 -- straight-skeleton
|
||||||
console.log('용마루 지붕')
|
console.log('용마루 지붕')
|
||||||
//drawRidgeRoof(this.id, this.canvas, textMode)
|
drawRidgeRoof(this.id, this.canvas, textMode)
|
||||||
drawSkeletonRidgeRoof(this.id, this.canvas, textMode);
|
//drawSkeletonRidgeRoof(this.id, this.canvas, textMode);
|
||||||
} else if (isGableRoof(types)) {
|
} else if (isGableRoof(types)) {
|
||||||
// A형, B형 박공 지붕
|
// A형, B형 박공 지붕
|
||||||
console.log('패턴 지붕')
|
console.log('패턴 지붕')
|
||||||
|
|||||||
@ -346,6 +346,7 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla
|
|||||||
/> */}
|
/> */}
|
||||||
<CalculatorInput
|
<CalculatorInput
|
||||||
id=""
|
id=""
|
||||||
|
name=""
|
||||||
label=""
|
label=""
|
||||||
className="input-origin block"
|
className="input-origin block"
|
||||||
readOnly={currentRoof?.roofAngleSet !== item.value}
|
readOnly={currentRoof?.roofAngleSet !== item.value}
|
||||||
@ -412,15 +413,33 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla
|
|||||||
<div className="flex-ment">
|
<div className="flex-ment">
|
||||||
<span>W</span>
|
<span>W</span>
|
||||||
<div className="input-grid" style={{ width: '84px' }}>
|
<div className="input-grid" style={{ width: '84px' }}>
|
||||||
<input
|
{/*<input*/}
|
||||||
type="text"
|
{/* type="text"*/}
|
||||||
|
{/* className="input-origin block"*/}
|
||||||
|
{/* name={`width`}*/}
|
||||||
|
{/* ref={roofRef.width}*/}
|
||||||
|
{/* value={parseInt(currentRoof?.width)}*/}
|
||||||
|
{/* onChange={(e) => changeInput(normalizeDigits(e.target.value), e)}*/}
|
||||||
|
{/* readOnly={currentRoof?.widAuth === 'R'}*/}
|
||||||
|
{/* disabled={currentRoof?.roofSizeSet === '3'}*/}
|
||||||
|
{/*/>*/}
|
||||||
|
|
||||||
|
<CalculatorInput
|
||||||
|
id=""
|
||||||
|
name={'width'}
|
||||||
|
label=""
|
||||||
className="input-origin block"
|
className="input-origin block"
|
||||||
name={`width`}
|
|
||||||
ref={roofRef.width}
|
ref={roofRef.width}
|
||||||
value={parseInt(currentRoof?.width)}
|
value={currentRoof?.width||0}
|
||||||
onChange={(e) => changeInput(normalizeDigits(e.target.value), e)}
|
onChange={(value) => {
|
||||||
|
setCurrentRoof({ ...currentRoof, value })
|
||||||
|
}}
|
||||||
readOnly={currentRoof?.widAuth === 'R'}
|
readOnly={currentRoof?.widAuth === 'R'}
|
||||||
disabled={currentRoof?.roofSizeSet === '3'}
|
disabled={currentRoof?.roofSizeSet === '3'}
|
||||||
|
options={{
|
||||||
|
allowNegative: false,
|
||||||
|
allowDecimal: false //(index !== 0),
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -429,15 +448,33 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla
|
|||||||
<div className="flex-ment">
|
<div className="flex-ment">
|
||||||
<span>L</span>
|
<span>L</span>
|
||||||
<div className="input-grid" style={{ width: '84px' }}>
|
<div className="input-grid" style={{ width: '84px' }}>
|
||||||
<input
|
{/*<input*/}
|
||||||
type="text"
|
{/* type="text"*/}
|
||||||
|
{/* className="input-origin block"*/}
|
||||||
|
{/* name={`length`}*/}
|
||||||
|
{/* ref={roofRef.length}*/}
|
||||||
|
{/* value={parseInt(currentRoof?.length)}*/}
|
||||||
|
{/* onChange={(e) => changeInput(normalizeDigits(e.target.value), e)}*/}
|
||||||
|
{/* readOnly={currentRoof?.lenAuth === 'R'}*/}
|
||||||
|
{/* disabled={currentRoof?.roofSizeSet === '3'}*/}
|
||||||
|
{/*/>*/}
|
||||||
|
|
||||||
|
<CalculatorInput
|
||||||
|
id=""
|
||||||
|
name={'length'}
|
||||||
|
label=""
|
||||||
className="input-origin block"
|
className="input-origin block"
|
||||||
name={`length`}
|
|
||||||
ref={roofRef.length}
|
ref={roofRef.length}
|
||||||
value={parseInt(currentRoof?.length)}
|
value={currentRoof?.length||0}
|
||||||
onChange={(e) => changeInput(normalizeDigits(e.target.value), e)}
|
onChange={(value) => {
|
||||||
|
setCurrentRoof({ ...currentRoof, value })
|
||||||
|
}}
|
||||||
readOnly={currentRoof?.lenAuth === 'R'}
|
readOnly={currentRoof?.lenAuth === 'R'}
|
||||||
disabled={currentRoof?.roofSizeSet === '3'}
|
disabled={currentRoof?.roofSizeSet === '3'}
|
||||||
|
options={{
|
||||||
|
allowNegative: false,
|
||||||
|
allowDecimal: false //(index !== 0),
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -465,16 +502,34 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla
|
|||||||
<div className="flex-ment">
|
<div className="flex-ment">
|
||||||
<span>{getMessage('hajebichi')}</span>
|
<span>{getMessage('hajebichi')}</span>
|
||||||
<div className="input-grid" style={{ width: '84px' }}>
|
<div className="input-grid" style={{ width: '84px' }}>
|
||||||
<input
|
{/*<input*/}
|
||||||
type="text"
|
{/* type="text"*/}
|
||||||
|
{/* className="input-origin block"*/}
|
||||||
|
{/* name={`hajebichi`}*/}
|
||||||
|
{/* ref={roofRef.hajebichi}*/}
|
||||||
|
{/* value={parseInt(currentRoof?.hajebichi)}*/}
|
||||||
|
{/* onChange={(e) => changeInput(normalizeDigits(e.target.value), e)}*/}
|
||||||
|
{/* readOnly={currentRoof?.roofPchAuth === 'R'}*/}
|
||||||
|
{/* disabled={currentRoof?.roofSizeSet === '3'}*/}
|
||||||
|
{/*/>*/}
|
||||||
|
<CalculatorInput
|
||||||
|
id=""
|
||||||
|
name={'hajebichi'}
|
||||||
|
label=""
|
||||||
className="input-origin block"
|
className="input-origin block"
|
||||||
name={`hajebichi`}
|
|
||||||
ref={roofRef.hajebichi}
|
ref={roofRef.hajebichi}
|
||||||
value={parseInt(currentRoof?.hajebichi)}
|
value={currentRoof?.hajebichi||0}
|
||||||
onChange={(e) => changeInput(normalizeDigits(e.target.value), e)}
|
onChange={(value) => {
|
||||||
|
setCurrentRoof({ ...currentRoof, value })
|
||||||
|
}}
|
||||||
readOnly={currentRoof?.roofPchAuth === 'R'}
|
readOnly={currentRoof?.roofPchAuth === 'R'}
|
||||||
disabled={currentRoof?.roofSizeSet === '3'}
|
disabled={currentRoof?.roofSizeSet === '3'}
|
||||||
|
options={{
|
||||||
|
allowNegative: false,
|
||||||
|
allowDecimal: false //(index !== 0),
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -632,9 +632,12 @@ export function useCanvasSetting(executeEffect = true) {
|
|||||||
originHorizon: res.originHorizon,
|
originHorizon: res.originHorizon,
|
||||||
originVertical: res.originVertical,
|
originVertical: res.originVertical,
|
||||||
})
|
})
|
||||||
canvas.setWidth(res.originHorizon)
|
|
||||||
canvas.setHeight(res.originVertical)
|
if (canvas) {
|
||||||
canvas.renderAll()
|
canvas.setWidth(res.originHorizon)
|
||||||
|
canvas.setHeight(res.originVertical)
|
||||||
|
canvas.renderAll()
|
||||||
|
}
|
||||||
|
|
||||||
/** 데이터 설정 */
|
/** 데이터 설정 */
|
||||||
setSettingModalFirstOptions({
|
setSettingModalFirstOptions({
|
||||||
|
|||||||
@ -319,6 +319,14 @@ export function useMovementSetting(id) {
|
|||||||
|
|
||||||
const roofId = target.attributes.roofId
|
const roofId = target.attributes.roofId
|
||||||
const roof = canvas.getObjects().find((obj) => obj.id === roofId)
|
const roof = canvas.getObjects().find((obj) => obj.id === roofId)
|
||||||
|
|
||||||
|
// 현이동, 동이동 추가
|
||||||
|
const moveFlowLine = typeRef.current === TYPE.FLOW_LINE ? FLOW_LINE_REF.POINTER_INPUT_REF.current.value : 0
|
||||||
|
const moveUpDown = typeRef.current === TYPE.UP_DOWN ? UP_DOWN_REF.POINTER_INPUT_REF.current.value : 0
|
||||||
|
roof.moveFlowLine = parseInt(moveFlowLine, 10) || 0;
|
||||||
|
roof.moveUpDown = parseInt(moveUpDown, 10) || 0;
|
||||||
|
roof.moveDirect = "";
|
||||||
|
roof.moveSelectLine = target;
|
||||||
const wall = canvas.getObjects().find((obj) => obj.name === POLYGON_TYPE.WALL && obj.attributes.roofId === roofId)
|
const wall = canvas.getObjects().find((obj) => obj.name === POLYGON_TYPE.WALL && obj.attributes.roofId === roofId)
|
||||||
const baseLines = wall.baseLines
|
const baseLines = wall.baseLines
|
||||||
let targetBaseLines = []
|
let targetBaseLines = []
|
||||||
@ -348,6 +356,7 @@ export function useMovementSetting(id) {
|
|||||||
? 'right'
|
? 'right'
|
||||||
: 'left'
|
: 'left'
|
||||||
let checkBaseLines, currentBaseLines
|
let checkBaseLines, currentBaseLines
|
||||||
|
roof.moveDirect = lineVector
|
||||||
switch (lineVector) {
|
switch (lineVector) {
|
||||||
case 'up':
|
case 'up':
|
||||||
checkBaseLines = baseLines.filter((line) => line.y1 === line.y2 && line.y1 < target.y1)
|
checkBaseLines = baseLines.filter((line) => line.y1 === line.y2 && line.y1 < target.y1)
|
||||||
@ -442,10 +451,17 @@ export function useMovementSetting(id) {
|
|||||||
|
|
||||||
let value
|
let value
|
||||||
if (typeRef.current === TYPE.FLOW_LINE) {
|
if (typeRef.current === TYPE.FLOW_LINE) {
|
||||||
value =
|
value = (() => {
|
||||||
FLOW_LINE_REF.FILLED_INPUT_REF.current.value !== ''
|
const filledValue = FLOW_LINE_REF.FILLED_INPUT_REF.current?.value;
|
||||||
? Big(FLOW_LINE_REF.FILLED_INPUT_REF.current.value).times(2)
|
const pointerValue = FLOW_LINE_REF.POINTER_INPUT_REF.current?.value;
|
||||||
: Big(FLOW_LINE_REF.POINTER_INPUT_REF.current.value).times(2)
|
|
||||||
|
if (filledValue && !isNaN(filledValue) && filledValue.trim() !== '') {
|
||||||
|
return Big(filledValue).times(2);
|
||||||
|
} else if (pointerValue && !isNaN(pointerValue) && pointerValue.trim() !== '') {
|
||||||
|
return Big(pointerValue).times(2);
|
||||||
|
}
|
||||||
|
return Big(0); // 기본값으로 0 반환 또는 다른 적절한 기본값
|
||||||
|
})();
|
||||||
if (target.y1 === target.y2) {
|
if (target.y1 === target.y2) {
|
||||||
value = value.neg()
|
value = value.neg()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -252,6 +252,7 @@ export function useOuterLineWall(id, propertiesId) {
|
|||||||
canvas?.renderAll()
|
canvas?.renderAll()
|
||||||
setOuterLineFix(true)
|
setOuterLineFix(true)
|
||||||
closePopup(id)
|
closePopup(id)
|
||||||
|
ccwCheck()
|
||||||
addPopup(propertiesId, 1, <RoofShapeSetting id={propertiesId} pos={{ x: 50, y: 230 }} />)
|
addPopup(propertiesId, 1, <RoofShapeSetting id={propertiesId} pos={{ x: 50, y: 230 }} />)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -905,6 +906,51 @@ export function useOuterLineWall(id, propertiesId) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 시계방향으로 그려진 경우 반시게방향으로 변경
|
||||||
|
const ccwCheck = () => {
|
||||||
|
let outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine')
|
||||||
|
|
||||||
|
if (outerLines.length < 2) {
|
||||||
|
swalFire({ text: getMessage('wall.line.not.found') })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 외벽선이 시계방향인지 시계반대 방향인지 확인
|
||||||
|
*/
|
||||||
|
const outerLinePoints = outerLines.map((line) => ({ x: line.x1, y: line.y1 }))
|
||||||
|
let counterClockwise = true
|
||||||
|
let signedArea = 0
|
||||||
|
|
||||||
|
outerLinePoints.forEach((point, index) => {
|
||||||
|
const nextPoint = outerLinePoints[(index + 1) % outerLinePoints.length]
|
||||||
|
signedArea += point.x * nextPoint.y - point.y * nextPoint.x
|
||||||
|
})
|
||||||
|
|
||||||
|
if (signedArea > 0) {
|
||||||
|
counterClockwise = false
|
||||||
|
}
|
||||||
|
/** 시계 방향일 경우 외벽선 reverse*/
|
||||||
|
if (!counterClockwise) {
|
||||||
|
outerLines.reverse().forEach((line, index) => {
|
||||||
|
addLine([line.x2, line.y2, line.x1, line.y1], {
|
||||||
|
stroke: line.stroke,
|
||||||
|
strokeWidth: line.strokeWidth,
|
||||||
|
idx: index,
|
||||||
|
selectable: line.selectable,
|
||||||
|
name: 'outerLine',
|
||||||
|
x1: line.x2,
|
||||||
|
y1: line.y2,
|
||||||
|
x2: line.x1,
|
||||||
|
y2: line.y1,
|
||||||
|
visible: line.visible,
|
||||||
|
})
|
||||||
|
canvas.remove(line)
|
||||||
|
})
|
||||||
|
canvas.renderAll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
points,
|
points,
|
||||||
setPoints,
|
setPoints,
|
||||||
|
|||||||
@ -179,46 +179,6 @@ export function useRoofShapeSetting(id) {
|
|||||||
let outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine')
|
let outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine')
|
||||||
let direction
|
let direction
|
||||||
|
|
||||||
if (outerLines.length < 2) {
|
|
||||||
swalFire({ text: getMessage('wall.line.not.found') })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 외벽선이 시계방향인지 시계반대 방향인지 확인
|
|
||||||
*/
|
|
||||||
const outerLinePoints = outerLines.map((line) => ({ x: line.x1, y: line.y1 }))
|
|
||||||
let counterClockwise = true
|
|
||||||
let signedArea = 0
|
|
||||||
|
|
||||||
outerLinePoints.forEach((point, index) => {
|
|
||||||
const nextPoint = outerLinePoints[(index + 1) % outerLinePoints.length]
|
|
||||||
signedArea += point.x * nextPoint.y - point.y * nextPoint.x
|
|
||||||
})
|
|
||||||
|
|
||||||
if (signedArea > 0) {
|
|
||||||
counterClockwise = false
|
|
||||||
}
|
|
||||||
/** 시계 방향일 경우 외벽선 reverse*/
|
|
||||||
if (!counterClockwise) {
|
|
||||||
outerLines.reverse().forEach((line, index) => {
|
|
||||||
addLine([line.x2, line.y2, line.x1, line.y1], {
|
|
||||||
stroke: line.stroke,
|
|
||||||
strokeWidth: line.strokeWidth,
|
|
||||||
idx: index,
|
|
||||||
selectable: line.selectable,
|
|
||||||
name: 'outerLine',
|
|
||||||
x1: line.x2,
|
|
||||||
y1: line.y2,
|
|
||||||
x2: line.x1,
|
|
||||||
y2: line.y1,
|
|
||||||
visible: line.visible,
|
|
||||||
})
|
|
||||||
canvas.remove(line)
|
|
||||||
})
|
|
||||||
canvas.renderAll()
|
|
||||||
}
|
|
||||||
|
|
||||||
if ([1, 2, 3, 5, 6, 7, 8].includes(shapeNum)) {
|
if ([1, 2, 3, 5, 6, 7, 8].includes(shapeNum)) {
|
||||||
// 변별로 설정이 아닌 경우 경사를 지붕재에 적용해주어야함
|
// 변별로 설정이 아닌 경우 경사를 지붕재에 적용해주어야함
|
||||||
setRoofPitch()
|
setRoofPitch()
|
||||||
@ -507,7 +467,7 @@ export function useRoofShapeSetting(id) {
|
|||||||
originX: 'center',
|
originX: 'center',
|
||||||
originY: 'center',
|
originY: 'center',
|
||||||
})
|
})
|
||||||
polygon.setViewLengthText(false)
|
// polygon.setViewLengthText(false)
|
||||||
polygon.lines = [...outerLines]
|
polygon.lines = [...outerLines]
|
||||||
|
|
||||||
addPitchTextsByOuterLines()
|
addPitchTextsByOuterLines()
|
||||||
|
|||||||
@ -1451,6 +1451,50 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) {
|
|||||||
// 그룹화할 객체들 배열 (currentObject + relatedObjects)
|
// 그룹화할 객체들 배열 (currentObject + relatedObjects)
|
||||||
const objectsToGroup = [currentObject, ...relatedObjects]
|
const objectsToGroup = [currentObject, ...relatedObjects]
|
||||||
|
|
||||||
|
// 회전 카운트 초기화 및 최초 상태 저장
|
||||||
|
if (!currentObject.rotationCount) {
|
||||||
|
currentObject.rotationCount = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// 최초 회전일 때 (rotationCount === 0) 원본 상태 저장
|
||||||
|
if (currentObject.rotationCount === 0) {
|
||||||
|
objectsToGroup.forEach((obj) => {
|
||||||
|
if (!obj.originalState) {
|
||||||
|
obj.originalState = {
|
||||||
|
left: obj.left,
|
||||||
|
top: obj.top,
|
||||||
|
angle: obj.angle || 0,
|
||||||
|
points: obj.type === 'QPolygon' ? JSON.parse(JSON.stringify(obj.points)) : null,
|
||||||
|
scaleX: obj.scaleX || 1,
|
||||||
|
scaleY: obj.scaleY || 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 회전 카운트 증가 (먼저 증가시켜서 목표 각도 계산)
|
||||||
|
currentObject.rotationCount = (currentObject.rotationCount + 1) % 4
|
||||||
|
|
||||||
|
// 목표 회전 각도 계산 (원본 기준)
|
||||||
|
const targetAngle = currentObject.rotationCount * 90
|
||||||
|
|
||||||
|
// 원본 상태로 먼저 복원한 후 목표 각도만큼 회전
|
||||||
|
objectsToGroup.forEach((obj) => {
|
||||||
|
if (obj.originalState) {
|
||||||
|
// 원본 상태로 복원
|
||||||
|
obj.set({
|
||||||
|
left: obj.originalState.left,
|
||||||
|
top: obj.originalState.top,
|
||||||
|
angle: obj.originalState.angle,
|
||||||
|
scaleX: obj.originalState.scaleX,
|
||||||
|
scaleY: obj.originalState.scaleY,
|
||||||
|
})
|
||||||
|
if (obj.originalState.points && obj.type === 'QPolygon') {
|
||||||
|
obj.set({ points: JSON.parse(JSON.stringify(obj.originalState.points)) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// 기존 객체들을 캔버스에서 제거
|
// 기존 객체들을 캔버스에서 제거
|
||||||
objectsToGroup.forEach((obj) => canvas.remove(obj))
|
objectsToGroup.forEach((obj) => canvas.remove(obj))
|
||||||
|
|
||||||
@ -1463,12 +1507,8 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) {
|
|||||||
// 그룹을 캔버스에 추가
|
// 그룹을 캔버스에 추가
|
||||||
canvas.add(group)
|
canvas.add(group)
|
||||||
|
|
||||||
// 현재 회전값에 90도 추가
|
// 목표 각도로 회전 (원본 기준)
|
||||||
const currentAngle = group.angle || 0
|
group.rotate(targetAngle)
|
||||||
const newAngle = (currentAngle + 90) % 360
|
|
||||||
|
|
||||||
// 그룹 전체를 회전
|
|
||||||
group.rotate(newAngle)
|
|
||||||
group.setCoords()
|
group.setCoords()
|
||||||
|
|
||||||
// 그룹을 해제하고 개별 객체로 복원
|
// 그룹을 해제하고 개별 객체로 복원
|
||||||
|
|||||||
@ -1820,7 +1820,13 @@ export function useMode() {
|
|||||||
x: xDiff.eq(0) ? offsetCurrentPoint.x : nextWall.x1,
|
x: xDiff.eq(0) ? offsetCurrentPoint.x : nextWall.x1,
|
||||||
y: yDiff.eq(0) ? offsetCurrentPoint.y : nextWall.y1,
|
y: yDiff.eq(0) ? offsetCurrentPoint.y : nextWall.y1,
|
||||||
}
|
}
|
||||||
const diffOffset = Big(nextWall.attributes.offset).minus(Big(currentWall.attributes.offset))
|
let diffOffset
|
||||||
|
if (nextWall.index > currentWall.index) {
|
||||||
|
diffOffset = Big(nextWall.attributes.offset).minus(Big(currentWall.attributes.offset)).abs()
|
||||||
|
} else {
|
||||||
|
diffOffset = Big(currentWall.attributes.offset).minus(Big(nextWall.attributes.offset))
|
||||||
|
}
|
||||||
|
|
||||||
const offsetPoint2 = {
|
const offsetPoint2 = {
|
||||||
x: yDiff.eq(0) ? offsetPoint1.x : Big(offsetPoint1.x).plus(diffOffset).toNumber(),
|
x: yDiff.eq(0) ? offsetPoint1.x : Big(offsetPoint1.x).plus(diffOffset).toNumber(),
|
||||||
y: xDiff.eq(0) ? offsetPoint1.y : Big(offsetPoint1.y).plus(diffOffset).toNumber(),
|
y: xDiff.eq(0) ? offsetPoint1.y : Big(offsetPoint1.y).plus(diffOffset).toNumber(),
|
||||||
|
|||||||
@ -614,7 +614,7 @@
|
|||||||
"qna.sub.title": "お問合せリスト",
|
"qna.sub.title": "お問合せリスト",
|
||||||
"qna.reg.header.regDt": "お問い合わせ登録日",
|
"qna.reg.header.regDt": "お問い合わせ登録日",
|
||||||
"qna.reg.header.regUserNm": "名前",
|
"qna.reg.header.regUserNm": "名前",
|
||||||
"qna.reg.header.regUserTelNo": "お問い合わせ",
|
"qna.reg.header.regUserTelNo": "電話番号",
|
||||||
"qna.reg.header.type": "お問い合わせ区分",
|
"qna.reg.header.type": "お問い合わせ区分",
|
||||||
"qna.reg.header.title": "お問い合わせタイトル",
|
"qna.reg.header.title": "お問い合わせタイトル",
|
||||||
"qna.reg.header.contents": "お問い合わせ内容",
|
"qna.reg.header.contents": "お問い合わせ内容",
|
||||||
|
|||||||
@ -260,7 +260,7 @@ export const getDegreeByChon = (chon) => {
|
|||||||
// tan(theta) = height / base
|
// tan(theta) = height / base
|
||||||
const radians = Math.atan(chon / 10)
|
const radians = Math.atan(chon / 10)
|
||||||
// 라디안을 도 단위로 변환
|
// 라디안을 도 단위로 변환
|
||||||
return Number((radians * (180 / Math.PI)).toFixed(1))
|
return Number((radians * (180 / Math.PI)).toFixed(2))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -7579,7 +7579,12 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => {
|
|||||||
|
|
||||||
hipBasePoint = { x1: line.x1, y1: line.y1, x2: line.x2, y2: line.y2 }
|
hipBasePoint = { x1: line.x1, y1: line.y1, x2: line.x2, y2: line.y2 }
|
||||||
point = [mergePoint[0].x, mergePoint[0].y, mergePoint[3].x, mergePoint[3].y]
|
point = [mergePoint[0].x, mergePoint[0].y, mergePoint[3].x, mergePoint[3].y]
|
||||||
const theta = Big(Math.acos(Big(line.line.attributes.planeSize).div(line.line.attributes.actualSize)))
|
const theta = Big(Math.acos(Big(line.line.attributes.planeSize).div(
|
||||||
|
line.line.attributes.actualSize === 0 ||
|
||||||
|
line.line.attributes.actualSize === '' ||
|
||||||
|
line.line.attributes.actualSize === undefined ?
|
||||||
|
line.line.attributes.planeSize : line.line.attributes.actualSize
|
||||||
|
)))
|
||||||
.times(180)
|
.times(180)
|
||||||
.div(Math.PI)
|
.div(Math.PI)
|
||||||
.round(1)
|
.round(1)
|
||||||
@ -7660,7 +7665,11 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => {
|
|||||||
.filter((line) => (line.x2 === ridge.x1 && line.y2 === ridge.y1) || (line.x2 === ridge.x2 && line.y2 === ridge.y2))
|
.filter((line) => (line.x2 === ridge.x1 && line.y2 === ridge.y1) || (line.x2 === ridge.x2 && line.y2 === ridge.y2))
|
||||||
.filter((line) => baseLines.filter((baseLine) => baseLine.x1 === line.x1 && baseLine.y1 === line.y1).length > 0)
|
.filter((line) => baseLines.filter((baseLine) => baseLine.x1 === line.x1 && baseLine.y1 === line.y1).length > 0)
|
||||||
basePoints.sort((a, b) => a.line.attributes.planeSize - b.line.attributes.planeSize)
|
basePoints.sort((a, b) => a.line.attributes.planeSize - b.line.attributes.planeSize)
|
||||||
hipSize = Big(basePoints[0].line.attributes.planeSize)
|
if (basePoints.length > 0 && basePoints[0].line) {
|
||||||
|
hipSize = Big(basePoints[0].line.attributes.planeSize)
|
||||||
|
} else {
|
||||||
|
hipSize = Big(0) // 또는 기본값 설정
|
||||||
|
}
|
||||||
}
|
}
|
||||||
hipSize = hipSize.pow(2).div(2).sqrt().round().div(10).toNumber()
|
hipSize = hipSize.pow(2).div(2).sqrt().round().div(10).toNumber()
|
||||||
|
|
||||||
@ -9223,7 +9232,11 @@ const getSortedPoint = (points, lines) => {
|
|||||||
const reCalculateSize = (line) => {
|
const reCalculateSize = (line) => {
|
||||||
const oldPlaneSize = line.attributes.planeSize
|
const oldPlaneSize = line.attributes.planeSize
|
||||||
const oldActualSize = line.attributes.actualSize
|
const oldActualSize = line.attributes.actualSize
|
||||||
const theta = Big(Math.acos(Big(oldPlaneSize).div(oldActualSize)))
|
const theta = Big(Math.acos(Big(oldPlaneSize).div(
|
||||||
|
oldActualSize === 0 || oldActualSize === '' || oldActualSize === undefined ?
|
||||||
|
oldPlaneSize :
|
||||||
|
oldActualSize
|
||||||
|
)))
|
||||||
.times(180)
|
.times(180)
|
||||||
.div(Math.PI)
|
.div(Math.PI)
|
||||||
const planeSize = calcLinePlaneSize({
|
const planeSize = calcLinePlaneSize({
|
||||||
|
|||||||
@ -4,7 +4,6 @@ import { calcLineActualSize, calcLinePlaneSize, toGeoJSON } from '@/util/qpolygo
|
|||||||
import { QLine } from '@/components/fabric/QLine'
|
import { QLine } from '@/components/fabric/QLine'
|
||||||
import { getDegreeByChon } from '@/util/canvas-util'
|
import { getDegreeByChon } from '@/util/canvas-util'
|
||||||
import Big from 'big.js'
|
import Big from 'big.js'
|
||||||
import { line } from 'framer-motion/m'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 지붕 폴리곤의 스켈레톤(중심선)을 생성하고 캔버스에 그립니다.
|
* 지붕 폴리곤의 스켈레톤(중심선)을 생성하고 캔버스에 그립니다.
|
||||||
@ -16,144 +15,33 @@ import { line } from 'framer-motion/m'
|
|||||||
|
|
||||||
|
|
||||||
export const drawSkeletonRidgeRoof = (roofId, canvas, textMode) => {
|
export const drawSkeletonRidgeRoof = (roofId, canvas, textMode) => {
|
||||||
|
|
||||||
// 2. 스켈레톤 생성 및 그리기
|
|
||||||
skeletonBuilder(roofId, canvas, textMode)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const movingRidgeFromSkeleton = (roofId, canvas) => {
|
|
||||||
|
|
||||||
let roof = canvas?.getObjects().find((object) => object.id === roofId)
|
let roof = canvas?.getObjects().find((object) => object.id === roofId)
|
||||||
let moveDirection = roof.moveDirect;
|
const wall = canvas.getObjects().find((object) => object.name === POLYGON_TYPE.WALL && object.attributes.roofId === roofId)
|
||||||
let moveFlowLine = roof.moveFlowLine??0;
|
|
||||||
const selectLine = roof.moveSelectLine;
|
|
||||||
|
|
||||||
const startPoint = selectLine.startPoint
|
const hasNonParallelLines = roof.lines.filter((line) => Big(line.x1).minus(Big(line.x2)).gt(1) && Big(line.y1).minus(Big(line.y2)).gt(1))
|
||||||
const endPoint = selectLine.endPoint
|
if (hasNonParallelLines.length > 0) {
|
||||||
const oldPoints = canvas?.movePoints?.points ?? roof.points
|
return
|
||||||
const oppositeLine = findOppositeLine(canvas.skeleton.Edges, startPoint, endPoint, oldPoints);
|
|
||||||
|
|
||||||
if (oppositeLine) {
|
|
||||||
console.log('Opposite line found:', oppositeLine);
|
|
||||||
} else {
|
|
||||||
console.log('No opposite line found');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return oldPoints.map((point) => {
|
const eavesType = [LINE_TYPE.WALLLINE.EAVES, LINE_TYPE.WALLLINE.HIPANDGABLE]
|
||||||
const newPoint = { ...point };
|
const gableType = [LINE_TYPE.WALLLINE.GABLE, LINE_TYPE.WALLLINE.JERKINHEAD]
|
||||||
const absMove = Big(moveFlowLine).abs().times(2).div(10);
|
|
||||||
//console.log('absMove:', absMove);
|
|
||||||
|
|
||||||
const skeletonLines = canvas.skeletonLines;
|
/** 외벽선 */
|
||||||
|
const baseLines = wall.baseLines.filter((line) => line.attributes.planeSize > 0)
|
||||||
console.log('skeleton line:', canvas.skeletonLines);
|
|
||||||
const changeSkeletonLine = (canvas, oldPoint, newPoint, str) => {
|
|
||||||
for (const line of canvas.skeletonLines) {
|
|
||||||
if (str === 'start' && isSamePoint(line.startPoint, oldPoint)) {
|
|
||||||
// Fabric.js 객체의 set 메서드로 속성 업데이트
|
|
||||||
line.set({
|
|
||||||
x1: newPoint.x,
|
|
||||||
y1: newPoint.y,
|
|
||||||
x2: line.x2 || line.endPoint?.x,
|
|
||||||
y2: line.y2 || line.endPoint?.y
|
|
||||||
});
|
|
||||||
line.startPoint = newPoint; // 참조 업데이트
|
|
||||||
}
|
|
||||||
else if (str === 'end' && isSamePoint(line.endPoint, oldPoint)) {
|
|
||||||
line.set({
|
|
||||||
x1: line.x1 || line.startPoint?.x,
|
|
||||||
y1: line.y1 || line.startPoint?.y,
|
|
||||||
x2: newPoint.x,
|
|
||||||
y2: newPoint.y
|
|
||||||
});
|
|
||||||
line.endPoint = newPoint; // 참조 업데이트
|
|
||||||
}
|
|
||||||
}
|
|
||||||
canvas.requestRenderAll();
|
|
||||||
console.log('skeleton line:', canvas.skeletonLines);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if(moveFlowLine > 0) {
|
//const skeletonLines = [];
|
||||||
if(moveDirection === 'down'){
|
// 1. 지붕 폴리곤 좌표 전처리
|
||||||
moveDirection = 'up';
|
const coordinates = preprocessPolygonCoordinates(roof.points);
|
||||||
}else if(moveDirection === 'left'){
|
if (coordinates.length < 3) {
|
||||||
moveDirection = 'right';
|
console.warn("Polygon has less than 3 unique points. Cannot generate skeleton.");
|
||||||
}
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('skeletonBuilder moveDirection:', moveDirection);
|
|
||||||
|
|
||||||
switch (moveDirection) {
|
// 2. 스켈레톤 생성 및 그리기
|
||||||
case 'left':
|
skeletonBuilder(roofId, canvas, textMode, roof, baseLines)
|
||||||
// Move left: decrease X
|
|
||||||
for (const line of oppositeLine) {
|
|
||||||
if (line.position === 'left') {
|
|
||||||
if (isSamePoint(newPoint, line.start)) {
|
|
||||||
newPoint.x = Big(line.start.x).minus(absMove).toNumber();
|
|
||||||
//changeSkeletonLine(canvas, line.start, newPoint, 'start')
|
|
||||||
} else if (isSamePoint(newPoint, line.end)) {
|
|
||||||
newPoint.x = Big(line.end.x).minus(absMove).toNumber();
|
|
||||||
//changeSkeletonLine(canvas, line.end, newPoint, 'end')
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
case 'right':
|
|
||||||
for (const line of oppositeLine) {
|
|
||||||
if (line.position === 'right') {
|
|
||||||
if (isSamePoint(newPoint, line.start)) {
|
|
||||||
newPoint.x = Big(line.start.x).plus(absMove).toNumber();
|
|
||||||
//changeSkeletonLine(canvas, line.start, newPoint, 'start')
|
|
||||||
} else if (isSamePoint(newPoint, line.end)) {
|
|
||||||
newPoint.x = Big(line.end.x).plus(absMove).toNumber();
|
|
||||||
//changeSkeletonLine(canvas, line.end, newPoint, 'end')
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
case 'up':
|
|
||||||
// Move up: decrease Y (toward top of screen)
|
|
||||||
for (const line of oppositeLine) {
|
|
||||||
if (line.position === 'top') {
|
|
||||||
if (isSamePoint(newPoint, line.start)) {
|
|
||||||
newPoint.y = Big(line.start.y).minus(absMove).toNumber();
|
|
||||||
//changeSkeletonLine(canvas, line.start, newPoint, 'start')
|
|
||||||
} else if (isSamePoint(newPoint, line.end)) {
|
|
||||||
newPoint.y = Big(line.end.y).minus(absMove).toNumber();
|
|
||||||
//changeSkeletonLine(canvas, line.end, newPoint, 'end')
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
case 'down':
|
|
||||||
// Move down: increase Y (toward bottom of screen)
|
|
||||||
for (const line of oppositeLine) {
|
|
||||||
if (line.position === 'bottom') {
|
|
||||||
if (isSamePoint(newPoint, line.start)) {
|
|
||||||
newPoint.y = Big(line.start.y).plus(absMove).toNumber();
|
|
||||||
//changeSkeletonLine(canvas, line.start, newPoint, 'start')
|
|
||||||
|
|
||||||
} else if (isSamePoint(newPoint, line.end)) {
|
|
||||||
newPoint.y = Big(line.end.y).plus(absMove).toNumber();
|
|
||||||
//changeSkeletonLine(canvas, line.end, newPoint, 'end')
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return newPoint;
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -164,65 +52,18 @@ const movingRidgeFromSkeleton = (roofId, canvas) => {
|
|||||||
* @param {fabric.Object} roof - 지붕 객체
|
* @param {fabric.Object} roof - 지붕 객체
|
||||||
* @param baseLines
|
* @param baseLines
|
||||||
*/
|
*/
|
||||||
export const skeletonBuilder = (roofId, canvas, textMode) => {
|
export const skeletonBuilder = (roofId, canvas, textMode, roof, baseLines) => {
|
||||||
|
const geoJSONPolygon = toGeoJSON(roof.points)
|
||||||
//처마
|
|
||||||
let roof = canvas?.getObjects().find((object) => object.id === roofId)
|
|
||||||
//벽
|
|
||||||
const wall = canvas.getObjects().find((object) => object.name === POLYGON_TYPE.WALL && object.attributes.roofId === roofId)
|
|
||||||
|
|
||||||
// const hasNonParallelLines = roof.lines.filter((line) => Big(line.x1).minus(Big(line.x2)).gt(1) && Big(line.y1).minus(Big(line.y2)).gt(1))
|
|
||||||
// if (hasNonParallelLines.length > 0) {
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|
||||||
const eavesType = [LINE_TYPE.WALLLINE.EAVES, LINE_TYPE.WALLLINE.HIPANDGABLE]
|
|
||||||
const gableType = [LINE_TYPE.WALLLINE.GABLE, LINE_TYPE.WALLLINE.JERKINHEAD]
|
|
||||||
|
|
||||||
/** 외벽선 */
|
|
||||||
const baseLines = wall.baseLines.filter((line) => line.attributes.planeSize > 0)
|
|
||||||
|
|
||||||
//const skeletonLines = [];
|
|
||||||
// 1. 지붕 폴리곤 좌표 전처리
|
|
||||||
const coordinates = preprocessPolygonCoordinates(roof.points);
|
|
||||||
if (coordinates.length < 3) {
|
|
||||||
console.warn("Polygon has less than 3 unique points. Cannot generate skeleton.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const moveFlowLine = roof.moveFlowLine || 0; // Provide a default value
|
|
||||||
const moveUpDown = roof.moveUpDown || 0; // Provide a default value
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
let points = roof.points;
|
|
||||||
|
|
||||||
//마루이동
|
|
||||||
if (moveFlowLine !== 0) {
|
|
||||||
points = movingRidgeFromSkeleton(roofId, canvas)
|
|
||||||
|
|
||||||
const movePoints = {
|
|
||||||
points: points,
|
|
||||||
roofId: roofId,
|
|
||||||
}
|
|
||||||
canvas.set("movePoints", movePoints)
|
|
||||||
|
|
||||||
}
|
|
||||||
//처마
|
|
||||||
if(moveUpDown !== 0) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
const geoJSONPolygon = toGeoJSON(points)
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// SkeletonBuilder는 닫히지 않은 폴리곤을 기대하므로 마지막 점 제거
|
// SkeletonBuilder는 닫히지 않은 폴리곤을 기대하므로 마지막 점 제거
|
||||||
geoJSONPolygon.pop()
|
geoJSONPolygon.pop()
|
||||||
const skeleton = SkeletonBuilder.BuildFromGeoJSON([[geoJSONPolygon]])
|
const skeleton = SkeletonBuilder.BuildFromGeoJSON([[geoJSONPolygon]])
|
||||||
|
|
||||||
|
console.log(`지붕 형태: ${skeleton.roof_type}`, skeleton.edge_analysis)
|
||||||
|
|
||||||
// 스켈레톤 데이터를 기반으로 내부선 생성
|
// 스켈레톤 데이터를 기반으로 내부선 생성
|
||||||
roof.innerLines = roof.innerLines || [];
|
roof.innerLines = createInnerLinesFromSkeleton(skeleton, canvas, textMode, roof, baseLines)
|
||||||
roof.innerLines = createInnerLinesFromSkeleton(roofId, canvas, skeleton, textMode)
|
|
||||||
|
|
||||||
// 캔버스에 스켈레톤 상태 저장
|
// 캔버스에 스켈레톤 상태 저장
|
||||||
if (!canvas.skeletonStates) {
|
if (!canvas.skeletonStates) {
|
||||||
@ -230,35 +71,12 @@ export const skeletonBuilder = (roofId, canvas, textMode) => {
|
|||||||
canvas.skeletonLines = []
|
canvas.skeletonLines = []
|
||||||
}
|
}
|
||||||
canvas.skeletonStates[roofId] = true
|
canvas.skeletonStates[roofId] = true
|
||||||
canvas.skeletonLines = [];
|
|
||||||
canvas.skeletonLines.push(...roof.innerLines)
|
|
||||||
canvas.set("skeletonLines", canvas.skeletonLines)
|
|
||||||
|
|
||||||
const cleanSkeleton = {
|
|
||||||
Edges: skeleton.Edges.map(edge => ({
|
|
||||||
X1: edge.Edge.Begin.X,
|
|
||||||
Y1: edge.Edge.Begin.Y,
|
|
||||||
X2: edge.Edge.End.X,
|
|
||||||
Y2: edge.Edge.End.Y,
|
|
||||||
Polygon: edge.Polygon,
|
|
||||||
|
|
||||||
// Add other necessary properties, but skip circular references
|
|
||||||
})),
|
|
||||||
roofId: roofId,
|
|
||||||
// Add other necessary top-level properties
|
|
||||||
};
|
|
||||||
canvas.skeleton = [];
|
|
||||||
canvas.skeleton = cleanSkeleton
|
|
||||||
|
|
||||||
canvas.set("skeleton", cleanSkeleton);
|
|
||||||
|
|
||||||
canvas.renderAll()
|
canvas.renderAll()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('스켈레톤 생성 중 오류 발생:', e)
|
console.error('스켈레톤 생성 중 오류 발생:', e)
|
||||||
if (canvas.skeletonStates) {
|
if (canvas.skeletonStates) {
|
||||||
canvas.skeletonStates[roofId] = false
|
canvas.skeletonStates[roofId] = false
|
||||||
canvas.skeletonStates = {}
|
|
||||||
canvas.skeletonLines = []
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -272,36 +90,16 @@ export const skeletonBuilder = (roofId, canvas, textMode) => {
|
|||||||
* @param {string} textMode - 텍스트 표시 모드 ('plane', 'actual', 'none')
|
* @param {string} textMode - 텍스트 표시 모드 ('plane', 'actual', 'none')
|
||||||
* @param {Array<QLine>} baseLines - 원본 외벽선 QLine 객체 배열
|
* @param {Array<QLine>} baseLines - 원본 외벽선 QLine 객체 배열
|
||||||
*/
|
*/
|
||||||
const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
|
const createInnerLinesFromSkeleton = (skeleton,canvas, textMode, roof, baseLines) => {
|
||||||
if (!skeleton?.Edges) return []
|
if (!skeleton?.Edges) return []
|
||||||
let roof = canvas?.getObjects().find((object) => object.id === roofId)
|
|
||||||
const skeletonLines = []
|
const skeletonLines = []
|
||||||
const processedInnerEdges = new Set()
|
const processedInnerEdges = new Set()
|
||||||
|
|
||||||
// 1. 모든 Edge를 순회하며 기본 스켈레톤 선(용마루)을 수집합니다.
|
// 1. 모든 Edge를 순회하며 기본 스켈레톤 선(용마루)을 수집합니다.
|
||||||
|
|
||||||
skeleton.Edges.forEach((edgeResult, index) => {
|
skeleton.Edges.forEach((edgeResult, index) => {
|
||||||
// const { Begin, End } = edgeResult.Edge;
|
processEavesEdge(edgeResult, processedInnerEdges, roof, skeletonLines, baseLines[index].attributes.pitch);
|
||||||
// let outerLine = roof.lines.find(line =>
|
|
||||||
// line.attributes.type === 'eaves' && isSameLine(Begin.X, Begin.Y, End.X, End.Y, line)
|
|
||||||
// );
|
|
||||||
// if(!outerLine){
|
|
||||||
//
|
|
||||||
// for (const line of canvas.skeletonLines) {
|
|
||||||
// if (line.lineName === 'hip' && line.attributes.hipIndex === index)
|
|
||||||
// {
|
|
||||||
// outerLine = line;
|
|
||||||
// break; // Found the matching line, exit the loop
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
// const pitch = outerLine.attributes?.pitch??0
|
|
||||||
// console.log("pitch", pitch)
|
|
||||||
processEavesEdge(roofId, canvas, skeleton, edgeResult, skeletonLines);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
// 2. 케라바(Gable) 속성을 가진 외벽선에 해당하는 스켈레톤을 후처리합니다.
|
// 2. 케라바(Gable) 속성을 가진 외벽선에 해당하는 스켈레톤을 후처리합니다.
|
||||||
|
|
||||||
@ -363,45 +161,24 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
|
|||||||
|
|
||||||
// 3. 최종적으로 정리된 스켈레톤 선들을 QLine 객체로 변환하여 캔버스에 추가합니다.
|
// 3. 최종적으로 정리된 스켈레톤 선들을 QLine 객체로 변환하여 캔버스에 추가합니다.
|
||||||
const innerLines = [];
|
const innerLines = [];
|
||||||
const existingLines = new Set(); // 이미 추가된 라인을 추적하기 위한 Set
|
|
||||||
|
|
||||||
skeletonLines.forEach(line => {
|
skeletonLines.forEach(line => {
|
||||||
const { p1, p2, attributes, lineStyle } = line;
|
const { p1, p2, attributes, lineStyle } = line;
|
||||||
|
|
||||||
// 라인을 고유하게 식별할 수 있는 키 생성 (정규화된 좌표로 정렬하여 비교)
|
|
||||||
const lineKey = [
|
|
||||||
[p1.x, p1.y].sort().join(','),
|
|
||||||
[p2.x, p2.y].sort().join(',')
|
|
||||||
].sort().join('|');
|
|
||||||
|
|
||||||
// 이미 추가된 라인인지 확인
|
|
||||||
if (existingLines.has(lineKey)) {
|
|
||||||
return; // 이미 있는 라인이면 스킵
|
|
||||||
}
|
|
||||||
|
|
||||||
const innerLine = new QLine([p1.x, p1.y, p2.x, p2.y], {
|
const innerLine = new QLine([p1.x, p1.y, p2.x, p2.y], {
|
||||||
parentId: roof.id,
|
parentId: roof.id,
|
||||||
fontSize: roof.fontSize,
|
fontSize: roof.fontSize,
|
||||||
stroke: lineStyle.color,
|
stroke: lineStyle.color,
|
||||||
strokeWidth: lineStyle.width,
|
strokeWidth: lineStyle.width,
|
||||||
name: (line.attributes.isOuterEdge)?'eaves': attributes.type,
|
name: attributes.type,
|
||||||
|
textMode: textMode,
|
||||||
attributes: attributes,
|
attributes: attributes,
|
||||||
isBaseLine: line.attributes.isOuterEdge,
|
|
||||||
lineName: (line.attributes.isOuterEdge)?'outerLine': attributes.type,
|
|
||||||
selectable:(!line.attributes.isOuterEdge),
|
|
||||||
roofId: roofId
|
|
||||||
});
|
});
|
||||||
|
|
||||||
//skeleton 라인에서 처마선은 삭제
|
canvas.add(innerLine);
|
||||||
if(innerLine.lineName !== 'outerLine'){
|
innerLine.bringToFront();
|
||||||
canvas.add(innerLine);
|
innerLines.push(innerLine);
|
||||||
innerLine.bringToFront();
|
|
||||||
existingLines.add(lineKey); // 추가된 라인을 추적
|
|
||||||
}
|
|
||||||
innerLines.push(innerLine)
|
|
||||||
canvas.renderAll();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
canvas.renderAll();
|
||||||
return innerLines;
|
return innerLines;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -413,65 +190,22 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
|
|||||||
* @param roof
|
* @param roof
|
||||||
* @param pitch
|
* @param pitch
|
||||||
*/
|
*/
|
||||||
function processEavesEdge(roofId, canvas, skeleton, edgeResult, skeletonLines) {
|
function processEavesEdge(edgeResult, processedInnerEdges, roof, skeletonLines, pitch) {
|
||||||
let roof = canvas?.getObjects().find((object) => object.id === roofId)
|
|
||||||
const polygonPoints = edgeResult.Polygon.map(p => ({ x: p.X, y: p.Y }));
|
const polygonPoints = edgeResult.Polygon.map(p => ({ x: p.X, y: p.Y }));
|
||||||
|
|
||||||
//처마선인지 확인하고 pitch 대입 각 처마선마다 pitch가 다를수 있음
|
const currentDegree = getDegreeByChon(pitch)
|
||||||
const { Begin, End } = edgeResult.Edge;
|
|
||||||
let outerLine = roof.lines.find(line =>
|
|
||||||
line.attributes.type === 'eaves' && isSameLine(Begin.X, Begin.Y, End.X, End.Y, line)
|
|
||||||
);
|
|
||||||
if(!outerLine) {
|
|
||||||
outerLine = findMatchingLine(edgeResult.Polygon, roof, roof.points);
|
|
||||||
console.log('Has matching line:', outerLine);
|
|
||||||
}
|
|
||||||
let pitch = outerLine?.attributes?.pitch??0
|
|
||||||
|
|
||||||
|
|
||||||
let eavesLines = []
|
let eavesLines = []
|
||||||
for (let i = 0; i < polygonPoints.length; i++) {
|
for (let i = 0; i < polygonPoints.length; i++) {
|
||||||
const p1 = polygonPoints[i];
|
const p1 = polygonPoints[i];
|
||||||
const p2 = polygonPoints[(i + 1) % polygonPoints.length];
|
const p2 = polygonPoints[(i + 1) % polygonPoints.length];
|
||||||
|
|
||||||
// 외벽선에 해당하는 스켈레톤 선은 제외하고 내부선만 추가
|
// 외벽선에 해당하는 스켈레톤 선은 제외하고 내부선만 추가
|
||||||
// if (!isOuterEdge(p1, p2, [edgeResult.Edge])) {
|
if (!isOuterEdge(p1, p2, [edgeResult.Edge])) {
|
||||||
//외벽선 밖으로 나간 선을 정리한다(roof.line의 교점까지 정리한다)
|
addRawLine(roof.id, skeletonLines, processedInnerEdges, p1, p2, 'RIDGE', '#FF0000', 3, currentDegree);
|
||||||
// 지붕 경계선과 교차 확인 및 클리핑
|
|
||||||
const clippedLine = clipLineToRoofBoundary(p1, p2, roof.lines);
|
|
||||||
console.log('clipped line', clippedLine.p1, clippedLine.p2);
|
|
||||||
const isOuterLine = isOuterEdge(p1, p2, [edgeResult.Edge])
|
|
||||||
addRawLine(roof.id, skeletonLines, p1, p2, 'ridge', '#FF0000', 3, pitch, isOuterLine);
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function findMatchingLine(edgePolygon, roof, roofPoints) {
|
|
||||||
const edgePoints = edgePolygon.map(p => ({ x: p.X, y: p.Y }));
|
|
||||||
|
|
||||||
for (let i = 0; i < edgePoints.length; i++) {
|
|
||||||
const p1 = edgePoints[i];
|
|
||||||
const p2 = edgePoints[(i + 1) % edgePoints.length];
|
|
||||||
|
|
||||||
for (let j = 0; j < roofPoints.length; j++) {
|
|
||||||
const rp1 = roofPoints[j];
|
|
||||||
const rp2 = roofPoints[(j + 1) % roofPoints.length];
|
|
||||||
|
|
||||||
if ((isSamePoint(p1, rp1) && isSamePoint(p2, rp2)) ||
|
|
||||||
(isSamePoint(p1, rp2) && isSamePoint(p2, rp1))) {
|
|
||||||
// 매칭되는 라인을 찾아서 반환
|
|
||||||
return roof.lines.find(line =>
|
|
||||||
(isSamePoint(line.p1, rp1) && isSamePoint(line.p2, rp2)) ||
|
|
||||||
(isSamePoint(line.p1, rp2) && isSamePoint(line.p2, rp1))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GABLE(케라바) Edge를 처리하여 스켈레톤 선을 정리하고 연장합니다.
|
* GABLE(케라바) Edge를 처리하여 스켈레톤 선을 정리하고 연장합니다.
|
||||||
* @param {object} edgeResult - 스켈레톤 Edge 데이터
|
* @param {object} edgeResult - 스켈레톤 Edge 데이터
|
||||||
@ -483,7 +217,7 @@ function findMatchingLine(edgePolygon, roof, roofPoints) {
|
|||||||
function processGableEdge(edgeResult, baseLines, skeletonLines, selectBaseLine, lastSkeletonLines) {
|
function processGableEdge(edgeResult, baseLines, skeletonLines, selectBaseLine, lastSkeletonLines) {
|
||||||
const edgePoints = edgeResult.Polygon.map(p => ({ x: p.X, y: p.Y }));
|
const edgePoints = edgeResult.Polygon.map(p => ({ x: p.X, y: p.Y }));
|
||||||
//const polygons = createPolygonsFromSkeletonLines(skeletonLines, selectBaseLine);
|
//const polygons = createPolygonsFromSkeletonLines(skeletonLines, selectBaseLine);
|
||||||
//console.log("edgePoints::::::", edgePoints)
|
console.log("edgePoints::::::", edgePoints)
|
||||||
// 1. Initialize processedLines with a deep copy of lastSkeletonLines
|
// 1. Initialize processedLines with a deep copy of lastSkeletonLines
|
||||||
let processedLines = []
|
let processedLines = []
|
||||||
// 1. 케라바 면과 관련된 불필요한 스켈레톤 선을 제거합니다.
|
// 1. 케라바 면과 관련된 불필요한 스켈레톤 선을 제거합니다.
|
||||||
@ -498,8 +232,8 @@ function processGableEdge(edgeResult, baseLines, skeletonLines, selectBaseLine,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//console.log("skeletonLines::::::", skeletonLines)
|
console.log("skeletonLines::::::", skeletonLines)
|
||||||
//console.log("lastSkeletonLines", lastSkeletonLines)
|
console.log("lastSkeletonLines", lastSkeletonLines)
|
||||||
|
|
||||||
// 2. Find common lines between skeletonLines and lastSkeletonLines
|
// 2. Find common lines between skeletonLines and lastSkeletonLines
|
||||||
skeletonLines.forEach(line => {
|
skeletonLines.forEach(line => {
|
||||||
@ -525,9 +259,9 @@ function processGableEdge(edgeResult, baseLines, skeletonLines, selectBaseLine,
|
|||||||
// return !isEdgeLine;
|
// return !isEdgeLine;
|
||||||
// });
|
// });
|
||||||
|
|
||||||
//console.log("skeletonLines::::::", skeletonLines);
|
console.log("skeletonLines::::::", skeletonLines);
|
||||||
//console.log("lastSkeletonLines", lastSkeletonLines);
|
console.log("lastSkeletonLines", lastSkeletonLines);
|
||||||
//console.log("processedLines after filtering", processedLines);
|
console.log("processedLines after filtering", processedLines);
|
||||||
|
|
||||||
return processedLines;
|
return processedLines;
|
||||||
|
|
||||||
@ -566,50 +300,42 @@ function isOuterEdge(p1, p2, edges) {
|
|||||||
* @param {number} width - 두께
|
* @param {number} width - 두께
|
||||||
* @param currentDegree
|
* @param currentDegree
|
||||||
*/
|
*/
|
||||||
function addRawLine(id, skeletonLines, p1, p2, lineType, color, width, pitch, isOuterLine) {
|
function addRawLine(id, skeletonLines, processedInnerEdges, p1, p2, lineType, color, width, currentDegree) {
|
||||||
// const edgeKey = [`${p1.x.toFixed(1)},${p1.y.toFixed(1)}`, `${p2.x.toFixed(1)},${p2.y.toFixed(1)}`].sort().join('|');
|
const edgeKey = [`${p1.x.toFixed(1)},${p1.y.toFixed(1)}`, `${p2.x.toFixed(1)},${p2.y.toFixed(1)}`].sort().join('|');
|
||||||
// if (processedInnerEdges.has(edgeKey)) return;
|
if (processedInnerEdges.has(edgeKey)) return;
|
||||||
// processedInnerEdges.add(edgeKey);
|
processedInnerEdges.add(edgeKey);
|
||||||
const currentDegree = getDegreeByChon(pitch)
|
|
||||||
const dx = Math.abs(p2.x - p1.x);
|
const dx = Math.abs(p2.x - p1.x);
|
||||||
const dy = Math.abs(p2.y - p1.y);
|
const dy = Math.abs(p2.y - p1.y);
|
||||||
const isDiagonal = dx > 0.1 && dy > 0.1;
|
const isDiagonal = dx > 0.1 && dy > 0.1;
|
||||||
const normalizedType = isDiagonal ? LINE_TYPE.SUBLINE.HIP : lineType;
|
const normalizedType = isDiagonal ? LINE_TYPE.SUBLINE.HIP : lineType;
|
||||||
|
const rawLines = []
|
||||||
|
|
||||||
// Count existing HIP lines
|
skeletonLines.push({
|
||||||
const existingEavesCount = skeletonLines.filter(line =>
|
|
||||||
line.lineName === LINE_TYPE.SUBLINE.RIDGE
|
|
||||||
).length;
|
|
||||||
|
|
||||||
// If this is a HIP line, its index will be the existing count
|
|
||||||
const eavesIndex = normalizedType === LINE_TYPE.SUBLINE.RIDGE ? existingEavesCount : undefined;
|
|
||||||
|
|
||||||
const newLine = {
|
|
||||||
p1,
|
p1,
|
||||||
p2,
|
p2,
|
||||||
attributes: {
|
attributes: {
|
||||||
roofId: id,
|
roofId:id,
|
||||||
|
|
||||||
actualSize: (isDiagonal) ? calcLineActualSize(
|
actualSize: (isDiagonal) ? calcLineActualSize(
|
||||||
{
|
{
|
||||||
x1: p1.x,
|
x1: p1.x,
|
||||||
y1: p1.y,
|
y1: p1.y,
|
||||||
x2: p2.x,
|
x2: p2.x,
|
||||||
y2: p2.y
|
y2: p2.y
|
||||||
},
|
},
|
||||||
currentDegree
|
currentDegree
|
||||||
) : calcLinePlaneSize({ x1: p1.x, y1: p1.y, x2: p2.x, y2: p2.y }),
|
) : calcLinePlaneSize({ x1: p1.x, y1: p1.y, x2: p2.x, y2: p2.y }),
|
||||||
|
|
||||||
type: normalizedType,
|
type: normalizedType,
|
||||||
planeSize: calcLinePlaneSize({ x1: p1.x, y1: p1.y, x2: p2.x, y2: p2.y }),
|
planeSize: calcLinePlaneSize({ x1: p1.x, y1: p1.y, x2: p2.x, y2: p2.y }),
|
||||||
isRidge: normalizedType === LINE_TYPE.SUBLINE.RIDGE,
|
isRidge: normalizedType === LINE_TYPE.SUBLINE.RIDGE,
|
||||||
isOuterEdge: isOuterLine,
|
|
||||||
pitch: pitch,
|
|
||||||
...(eavesIndex !== undefined && { eavesIndex })
|
|
||||||
},
|
},
|
||||||
lineStyle: { color, width },
|
lineStyle: { color, width },
|
||||||
};
|
});
|
||||||
|
|
||||||
|
console.log('skeletonLines', skeletonLines);
|
||||||
|
|
||||||
skeletonLines.push(newLine);
|
|
||||||
//console.log('skeletonLines', skeletonLines);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1086,8 +812,6 @@ const isPointOnSegment = (point, segStart, segEnd) => {
|
|||||||
return dotProduct >= 0 && dotProduct <= squaredLength;
|
return dotProduct >= 0 && dotProduct <= squaredLength;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Export all necessary functions
|
// Export all necessary functions
|
||||||
export {
|
export {
|
||||||
findAllIntersections,
|
findAllIntersections,
|
||||||
@ -1095,306 +819,3 @@ export {
|
|||||||
createPolygonsFromSkeletonLines
|
createPolygonsFromSkeletonLines
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Finds lines in the roof that match certain criteria based on the given points
|
|
||||||
* @param {Array} lines - The roof lines to search through
|
|
||||||
* @param {Object} startPoint - The start point of the reference line
|
|
||||||
* @param {Object} endPoint - The end point of the reference line
|
|
||||||
* @param {Array} oldPoints - The old points to compare against
|
|
||||||
* @returns {Array} Array of matching line objects with their properties
|
|
||||||
*/
|
|
||||||
function findMatchingRoofLines(lines, startPoint, endPoint, oldPoints) {
|
|
||||||
const result = [];
|
|
||||||
|
|
||||||
// If no lines provided, return empty array
|
|
||||||
if (!lines || !lines.length) return result;
|
|
||||||
|
|
||||||
// Process each line in the roof
|
|
||||||
for (const line of lines) {
|
|
||||||
// Get the start and end points of the current line
|
|
||||||
const p1 = { x: line.x1, y: line.y1 };
|
|
||||||
const p2 = { x: line.x2, y: line.y2 };
|
|
||||||
|
|
||||||
// Check if both points exist in the oldPoints array
|
|
||||||
const p1Exists = oldPoints.some(p =>
|
|
||||||
Math.abs(p.x - p1.x) < 0.0001 && Math.abs(p.y - p1.y) < 0.0001
|
|
||||||
);
|
|
||||||
|
|
||||||
const p2Exists = oldPoints.some(p =>
|
|
||||||
Math.abs(p.x - p2.x) < 0.0001 && Math.abs(p.y - p2.y) < 0.0001
|
|
||||||
);
|
|
||||||
|
|
||||||
// If both points exist in oldPoints, add to results
|
|
||||||
if (p1Exists && p2Exists) {
|
|
||||||
// Calculate line position relative to the reference line
|
|
||||||
const position = getLinePosition(
|
|
||||||
{ start: p1, end: p2 },
|
|
||||||
{ start: startPoint, end: endPoint }
|
|
||||||
);
|
|
||||||
|
|
||||||
result.push({
|
|
||||||
start: p1,
|
|
||||||
end: p2,
|
|
||||||
position: position,
|
|
||||||
line: line
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Finds the opposite line in a polygon based on the given line
|
|
||||||
* @param {Array} edges - The polygon edges from canvas.skeleton.Edges
|
|
||||||
* @param {Object} startPoint - The start point of the line to find opposite for
|
|
||||||
* @param {Object} endPoint - The end point of the line to find opposite for
|
|
||||||
* @param targetPosition
|
|
||||||
* @returns {Object|null} The opposite line if found, null otherwise
|
|
||||||
*/
|
|
||||||
function findOppositeLine(edges, startPoint, endPoint, points) {
|
|
||||||
const result = [];
|
|
||||||
// 1. 다각형 찾기
|
|
||||||
const polygons = findPolygonsContainingLine(edges, startPoint, endPoint);
|
|
||||||
if (polygons.length === 0) return null;
|
|
||||||
|
|
||||||
const referenceSlope = calculateSlope(startPoint, endPoint);
|
|
||||||
|
|
||||||
// 각 다각형에 대해 처리
|
|
||||||
for (const polygon of polygons) {
|
|
||||||
// 2. 기준 선분의 인덱스 찾기
|
|
||||||
|
|
||||||
let baseIndex = -1;
|
|
||||||
for (let i = 0; i < polygon.length; i++) {
|
|
||||||
const p1 = { x: polygon[i].X, y: polygon[i].Y };
|
|
||||||
const p2 = {
|
|
||||||
x: polygon[(i + 1) % polygon.length].X,
|
|
||||||
y: polygon[(i + 1) % polygon.length].Y
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if ((isSamePoint(p1, startPoint) && isSamePoint(p2, endPoint)) ||
|
|
||||||
(isSamePoint(p1, endPoint) && isSamePoint(p2, startPoint))) {
|
|
||||||
baseIndex = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (baseIndex === -1) continue; // 현재 다각형에서 기준 선분을 찾지 못한 경우
|
|
||||||
|
|
||||||
// 3. 다각형의 각 선분을 순회하면서 평행한 선분 찾기
|
|
||||||
const polyLength = polygon.length;
|
|
||||||
for (let i = 0; i < polyLength; i++) {
|
|
||||||
if (i === baseIndex) continue; // 기준 선분은 제외
|
|
||||||
|
|
||||||
const p1 = { x: polygon[i].X, y: polygon[i].Y };
|
|
||||||
const p2 = {
|
|
||||||
x: polygon[(i + 1) % polyLength].X,
|
|
||||||
y: polygon[(i + 1) % polyLength].Y
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
const p1Exist = points.some(p =>
|
|
||||||
Math.abs(p.x - p1.x) < 0.0001 && Math.abs(p.y - p1.y) < 0.0001
|
|
||||||
);
|
|
||||||
|
|
||||||
const p2Exist = points.some(p =>
|
|
||||||
Math.abs(p.x - p2.x) < 0.0001 && Math.abs(p.y - p2.y) < 0.0001
|
|
||||||
);
|
|
||||||
|
|
||||||
if(p1Exist && p2Exist){
|
|
||||||
const position = getLinePosition(
|
|
||||||
{ start: p1, end: p2 },
|
|
||||||
{ start: startPoint, end: endPoint }
|
|
||||||
);
|
|
||||||
result.push({
|
|
||||||
start: p1,
|
|
||||||
end: p2,
|
|
||||||
position: position,
|
|
||||||
polygon: polygon
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// // 현재 선분의 기울기 계산
|
|
||||||
// const currentSlope = calculateSlope(p1, p2);
|
|
||||||
//
|
|
||||||
// // 기울기가 같은지 확인 (평행한 선분)
|
|
||||||
// if (areLinesParallel(referenceSlope, currentSlope)) {
|
|
||||||
// // 동일한 선분이 아닌지 확인
|
|
||||||
// if (!areSameLine(p1, p2, startPoint, endPoint)) {
|
|
||||||
// const position = getLinePosition(
|
|
||||||
// { start: p1, end: p2 },
|
|
||||||
// { start: startPoint, end: endPoint }
|
|
||||||
// );
|
|
||||||
//
|
|
||||||
// const lineMid = {
|
|
||||||
// x: (p1.x + p2.x) / 2,
|
|
||||||
// y: (p1.y + p2.y) / 2
|
|
||||||
// };
|
|
||||||
//
|
|
||||||
// const baseMid = {
|
|
||||||
// x: (startPoint.x + endPoint.x) / 2,
|
|
||||||
// y: (startPoint.y + endPoint.y) / 2
|
|
||||||
// };
|
|
||||||
// const distance = Math.sqrt(
|
|
||||||
// Math.pow(lineMid.x - baseMid.x, 2) +
|
|
||||||
// Math.pow(lineMid.y - baseMid.y, 2)
|
|
||||||
// );
|
|
||||||
//
|
|
||||||
// const existingIndex = result.findIndex(line => line.position === position);
|
|
||||||
//
|
|
||||||
// if (existingIndex === -1) {
|
|
||||||
// // If no line with this position exists, add it
|
|
||||||
// result.push({
|
|
||||||
// start: p1,
|
|
||||||
// end: p2,
|
|
||||||
// position: position,
|
|
||||||
// polygon: polygon,
|
|
||||||
// distance: distance
|
|
||||||
// });
|
|
||||||
// } else if (distance > result[existingIndex].distance) {
|
|
||||||
// // If a line with this position exists but is closer, replace it
|
|
||||||
// result[existingIndex] = {
|
|
||||||
// start: p1,
|
|
||||||
// end: p2,
|
|
||||||
// position: position,
|
|
||||||
// polygon: polygon,
|
|
||||||
// distance: distance
|
|
||||||
// };
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result.length > 0 ? result:[];
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function getLinePosition(line, referenceLine) {
|
|
||||||
const lineMidX = (line.start.x + line.end.x) / 2;
|
|
||||||
const lineMidY = (line.start.y + line.end.y) / 2;
|
|
||||||
const refMidX = (referenceLine.start.x + referenceLine.end.x) / 2;
|
|
||||||
const refMidY = (referenceLine.start.y + referenceLine.end.y) / 2;
|
|
||||||
|
|
||||||
// Y축 차이가 더 크면 위/아래로 판단
|
|
||||||
// Y축 차이가 더 크면 위/아래로 판단
|
|
||||||
if (Math.abs(lineMidY - refMidY) > Math.abs(lineMidX - refMidX)) {
|
|
||||||
return lineMidY > refMidY ? 'bottom' : 'top';
|
|
||||||
}
|
|
||||||
// X축 차이가 더 크면 왼쪽/오른쪽으로 판단
|
|
||||||
else {
|
|
||||||
return lineMidX > refMidX ? 'right' : 'left';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper function to find if two points are the same within a tolerance
|
|
||||||
*/
|
|
||||||
function isSamePoint(p1, p2, tolerance = 0.1) {
|
|
||||||
return Math.abs(p1.x - p2.x) < tolerance && Math.abs(p1.y - p2.y) < tolerance;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 두 점을 지나는 직선의 기울기 계산
|
|
||||||
function calculateSlope(p1, p2) {
|
|
||||||
// 수직선인 경우 (기울기 무한대)
|
|
||||||
if (p1.x === p2.x) return Infinity;
|
|
||||||
return (p2.y - p1.y) / (p2.x - p1.x);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 두 직선이 평행한지 확인
|
|
||||||
// function areLinesParallel(slope1, slope2) {
|
|
||||||
// // 두 직선 모두 수직선인 경우
|
|
||||||
// if (slope1 === Infinity && slope2 === Infinity) return true;
|
|
||||||
//
|
|
||||||
// // 기울기의 차이가 매우 작으면 평행한 것으로 간주
|
|
||||||
// const epsilon = 0.0001;
|
|
||||||
// return Math.abs(slope1 - slope2) < epsilon;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// 두 선분이 동일한지 확인
|
|
||||||
// function areSameLine(p1, p2, p3, p4) {
|
|
||||||
// return (
|
|
||||||
// (isSamePoint(p1, p3) && isSamePoint(p2, p4)) ||
|
|
||||||
// (isSamePoint(p1, p4) && isSamePoint(p2, p3))
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
/**
|
|
||||||
* Helper function to find the polygon containing the given line
|
|
||||||
*/
|
|
||||||
function findPolygonsContainingLine(edges, p1, p2) {
|
|
||||||
const polygons = [];
|
|
||||||
for (const edge of edges) {
|
|
||||||
const polygon = edge.Polygon;
|
|
||||||
for (let i = 0; i < polygon.length; i++) {
|
|
||||||
const ep1 = { x: polygon[i].X, y: polygon[i].Y };
|
|
||||||
const ep2 = {
|
|
||||||
x: polygon[(i + 1) % polygon.length].X,
|
|
||||||
y: polygon[(i + 1) % polygon.length].Y
|
|
||||||
};
|
|
||||||
|
|
||||||
if ((isSamePoint(ep1, p1) && isSamePoint(ep2, p2)) ||
|
|
||||||
(isSamePoint(ep1, p2) && isSamePoint(ep2, p1))) {
|
|
||||||
polygons.push(polygon);
|
|
||||||
break; // 이 다각형에 대한 검사 완료
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return polygons; // 일치하는 모든 다각형 반환
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* roof.lines와 교차하는 선분(p1, p2)을 찾아 교차점에서 자릅니다.
|
|
||||||
* @param {Object} p1 - 선분의 시작점 {x, y}
|
|
||||||
* @param {Object} p2 - 선분의 끝점 {x, y}
|
|
||||||
* @param {Array} roofLines - 지붕 경계선 배열 (QLine 객체의 배열)
|
|
||||||
* @returns {Object} {p1: {x, y}, p2: {x, y}} - 교차점에서 자른 선분 또는 원래 선분
|
|
||||||
*/
|
|
||||||
function clipLineToRoofBoundary(p1, p2, roofLines) {
|
|
||||||
if (!roofLines || !roofLines.length) return { p1, p2 };
|
|
||||||
|
|
||||||
let closestIntersection = null;
|
|
||||||
let minDistSq = Infinity;
|
|
||||||
const originalP1 = { ...p1 };
|
|
||||||
const originalP2 = { ...p2 };
|
|
||||||
|
|
||||||
// 모든 지붕 경계선과의 교차점을 찾음
|
|
||||||
for (const line of roofLines) {
|
|
||||||
const lineP1 = { x: line.x1, y: line.y1 };
|
|
||||||
const lineP2 = { x: line.x2, y: line.y2 };
|
|
||||||
|
|
||||||
const intersection = getLineIntersection(
|
|
||||||
p1, p2,
|
|
||||||
lineP1, lineP2
|
|
||||||
);
|
|
||||||
|
|
||||||
if (intersection) {
|
|
||||||
// 교차점과 p1 사이의 거리 계산
|
|
||||||
const dx = intersection.x - p1.x;
|
|
||||||
const dy = intersection.y - p1.y;
|
|
||||||
const distSq = dx * dx + dy * dy;
|
|
||||||
|
|
||||||
// p1에 가장 가까운 교차점 찾기
|
|
||||||
if (distSq < minDistSq) {
|
|
||||||
minDistSq = distSq;
|
|
||||||
closestIntersection = intersection;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 교차점이 있으면 p2를 가장 가까운 교차점으로 업데이트
|
|
||||||
if (closestIntersection) {
|
|
||||||
return {
|
|
||||||
p1: originalP1,
|
|
||||||
p2: closestIntersection
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// 교차점이 없으면 원래 선분 반환
|
|
||||||
return { p1: originalP1, p2: originalP2 };
|
|
||||||
}
|
|
||||||
|
|
||||||
// 기존 getLineIntersection 함수를 사용하거나, 없으면 아래 구현 사용
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user