260 lines
7.4 KiB
JavaScript
260 lines
7.4 KiB
JavaScript
import { fabric } from 'fabric'
|
|
import { v4 as uuidv4 } from 'uuid'
|
|
import { getDirectionByPoint } from '@/util/canvas-util'
|
|
|
|
export const QLine = fabric.util.createClass(fabric.Line, {
|
|
type: 'QLine',
|
|
text: null,
|
|
id: null,
|
|
line: null,
|
|
length: 0,
|
|
direction: null,
|
|
idx: 0,
|
|
area: 0,
|
|
children: [],
|
|
padding: 5,
|
|
textVisible: true,
|
|
initialize: function (points, options, length = 0) {
|
|
// 소수점 전부 제거
|
|
|
|
points = points.map((point) => Number(point?.toFixed(1)))
|
|
|
|
this.callSuper('initialize', points, { ...options, selectable: options.selectable ?? true })
|
|
if (options.id) {
|
|
this.id = options.id
|
|
} else {
|
|
this.id = uuidv4()
|
|
}
|
|
this.line = this
|
|
|
|
this.idx = options.idx ?? 0
|
|
this.direction = options.direction ?? getDirectionByPoint({ x: this.x1, y: this.y1 }, { x: this.x2, y: this.y2 })
|
|
this.textMode = options.textMode ?? 'plane' // plane:복시도, actual:실측, none:표시안함
|
|
this.textVisible = options.textVisible ?? true
|
|
if (length !== 0) {
|
|
this.length = length
|
|
} else {
|
|
this.setLength()
|
|
}
|
|
|
|
this.startPoint = { x: this.x1, y: this.y1 }
|
|
this.endPoint = { x: this.x2, y: this.y2 }
|
|
},
|
|
|
|
init: function () {
|
|
if (!this.textVisible) {
|
|
return
|
|
}
|
|
this.addLengthText()
|
|
|
|
this.on('moving', () => {
|
|
this.addLengthText()
|
|
})
|
|
|
|
this.on('modified', (e) => {
|
|
this.startPoint = { x: this.x1, y: this.y1 }
|
|
this.endPoint = { x: this.x2, y: this.y2 }
|
|
this.addLengthText()
|
|
})
|
|
|
|
this.on('removed', () => {
|
|
const children = this.canvas.getObjects().filter((obj) => obj.parentId === this.id)
|
|
children.forEach((child) => {
|
|
this.canvas.remove(child)
|
|
})
|
|
})
|
|
},
|
|
|
|
setLength() {
|
|
if (this.attributes?.actualSize !== undefined && this.attributes?.planeSize !== undefined) {
|
|
if (this.textMode === 'plane') {
|
|
this.length = this.attributes.planeSize / 10
|
|
} else if (this.textMode === 'actual') {
|
|
this.length = this.attributes.actualSize / 10
|
|
}
|
|
} else {
|
|
const scaleX = this.scaleX
|
|
const scaleY = this.scaleY
|
|
const x1 = this.left
|
|
const y1 = this.top
|
|
const x2 = this.left + this.width * scaleX
|
|
const y2 = this.top + this.height * scaleY
|
|
const dx = x2 - x1
|
|
const dy = y2 - y1
|
|
this.length = Number(Math.sqrt(dx * dx + dy * dy).toFixed(1))
|
|
}
|
|
},
|
|
|
|
addLengthText() {
|
|
const thisText = this.canvas.getObjects().find((obj) => obj.name === 'lengthText' && obj.parentId === this.id)
|
|
|
|
if (this.textMode === 'none') {
|
|
if (thisText) {
|
|
this.canvas.remove(thisText)
|
|
}
|
|
} else {
|
|
this.setLength()
|
|
const scaleX = this.scaleX
|
|
const scaleY = this.scaleY
|
|
const x1 = this.left
|
|
const y1 = this.top
|
|
const x2 = this.left + this.width * scaleX
|
|
const y2 = this.top + this.height * scaleY
|
|
|
|
if (thisText) {
|
|
thisText.set({ text: this.getLength().toString(), left: (x1 + x2) / 2, top: (y1 + y2) / 2 })
|
|
this.text = thisText
|
|
return
|
|
}
|
|
let left, top
|
|
if (this.direction === 'left' || this.direction === 'right') {
|
|
left = (x1 + x2) / 2
|
|
top = (y1 + y2) / 2 + 10
|
|
} else if (this.direction === 'top' || this.direction === 'bottom') {
|
|
left = (x1 + x2) / 2 + 10
|
|
top = (y1 + y2) / 2
|
|
}
|
|
|
|
const minX = this.left
|
|
const maxX = this.left + this.width
|
|
const minY = this.top
|
|
const maxY = this.top + this.length
|
|
const degree = (Math.atan2(y2 - y1, x2 - x1) * 180) / Math.PI
|
|
|
|
const text = new fabric.Textbox(this.getLength().toString(), {
|
|
left: left,
|
|
top: top,
|
|
fontSize: this.fontSize,
|
|
minX,
|
|
maxX,
|
|
minY,
|
|
maxY,
|
|
parentDirection: this.direction,
|
|
parentDegree: degree,
|
|
parentId: this.id,
|
|
editable: false,
|
|
selectable: true,
|
|
lockRotation: true,
|
|
lockScalingX: true,
|
|
lockScalingY: true,
|
|
parent: this,
|
|
name: 'lengthText',
|
|
})
|
|
|
|
this.text = text
|
|
this.canvas.add(text)
|
|
}
|
|
},
|
|
setFontSize(fontSize) {
|
|
this.fontSize = fontSize
|
|
this.text.set({ fontSize })
|
|
},
|
|
_render: function (ctx) {
|
|
this.callSuper('_render', ctx)
|
|
this.init()
|
|
},
|
|
_set: function (key, value) {
|
|
this.callSuper('_set', key, value)
|
|
},
|
|
|
|
setCanvas(canvas) {
|
|
this.canvas = canvas
|
|
},
|
|
|
|
getLength() {
|
|
//10배 곱해진 값 return
|
|
return Number(this.length.toFixed(1)) * 10
|
|
},
|
|
|
|
setViewLengthText(bool) {
|
|
const thisText = this.canvas.getObjects().find((obj) => obj.name === 'lengthText' && obj.parentId === this.id)
|
|
if (thisText) {
|
|
thisText.set({ visible: bool })
|
|
}
|
|
return this
|
|
},
|
|
|
|
setLengthText(text) {
|
|
const thisText = this.canvas.getObjects().find((obj) => obj.name === 'lengthText' && obj.parentId === this.id)
|
|
if (thisText) {
|
|
thisText.set({ text: text.toString() })
|
|
this.canvas.renderAll()
|
|
}
|
|
return this
|
|
},
|
|
|
|
setCoords: function () {
|
|
// 부모 클래스의 setCoords 호출
|
|
this.callSuper('setCoords')
|
|
|
|
// QLine의 경우 추가 처리 - 항상 강제로 재계산
|
|
if (this.canvas) {
|
|
// 모든 좌표 관련 캐시 초기화
|
|
delete this.oCoords
|
|
delete this.aCoords
|
|
delete this.__corner
|
|
|
|
// 다시 부모 setCoords 호출
|
|
this.callSuper('setCoords')
|
|
|
|
// 한 번 더 강제로 bounding rect 재계산
|
|
this._clearCache && this._clearCache()
|
|
}
|
|
},
|
|
|
|
containsPoint: function (point) {
|
|
// 먼저 좌표 업데이트
|
|
this.setCoords()
|
|
|
|
// 캔버스 줌과 viewport transform 고려한 좌표 변환
|
|
let localPoint = point
|
|
if (this.canvas) {
|
|
const vpt = this.canvas.viewportTransform
|
|
if (vpt) {
|
|
// viewport transform 역변환
|
|
const inverted = fabric.util.invertTransform(vpt)
|
|
localPoint = fabric.util.transformPoint(point, inverted)
|
|
}
|
|
}
|
|
|
|
// 기본 boundingRect 사용하되 줌을 고려하여 선택 영역 조정
|
|
const boundingRect = this.getBoundingRect(true)
|
|
|
|
// 선의 방향 판단
|
|
const dx = Math.abs(this.x2 - this.x1)
|
|
const dy = Math.abs(this.y2 - this.y1)
|
|
const isVertical = dx < dy && dx < 10
|
|
const isDiagonal = dx > 10 && dy > 10
|
|
|
|
// 줌 레벨에 따른 선택 영역 조정
|
|
const zoom = this.canvas ? this.canvas.getZoom() : 1
|
|
const baseMultiplier = 1 // 줌이 클수록 선택 영역을 줄임
|
|
|
|
let reducedWidth, reducedHeight
|
|
|
|
if (isDiagonal) {
|
|
reducedWidth = Math.max(boundingRect.width / 2, 10 * baseMultiplier)
|
|
reducedHeight = Math.max(boundingRect.height / 2, 10 * baseMultiplier)
|
|
} else if (isVertical) {
|
|
reducedWidth = Math.max(boundingRect.width * 2, 20 * baseMultiplier)
|
|
reducedHeight = boundingRect.height
|
|
} else {
|
|
reducedWidth = boundingRect.width
|
|
reducedHeight = Math.max(boundingRect.height * 2, 20 * baseMultiplier)
|
|
}
|
|
|
|
// 축소된 영역의 중심점 계산
|
|
const centerX = boundingRect.left + boundingRect.width / 2
|
|
const centerY = boundingRect.top + boundingRect.height / 2
|
|
|
|
// 축소된 영역의 경계 계산
|
|
const left = centerX - reducedWidth / 2
|
|
const top = centerY - reducedHeight / 2
|
|
const right = centerX + reducedWidth / 2
|
|
const bottom = centerY + reducedHeight / 2
|
|
|
|
// 점이 축소된 영역 내에 있는지 확인
|
|
return localPoint.x >= left && localPoint.x <= right && localPoint.y >= top && localPoint.y <= bottom
|
|
},
|
|
})
|