build-dxf 0.0.13 → 0.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/package.json +3 -2
- package/src/build.js +716 -217
- package/src/index2.js +2 -2
- package/src/utils/DxfSystem/components/Dxf.d.ts +27 -8
- package/src/utils/DxfSystem/components/LineAnalysis.d.ts +8 -0
- package/src/utils/DxfSystem/plugin/ModelDataPlugin/components/WhiteModel.d.ts +31 -4
- package/src/utils/Quadtree/LineSegment.d.ts +38 -0
- package/src/utils/Quadtree/Point.d.ts +8 -1
- package/src/utils/Quadtree/Quadtree.d.ts +6 -0
- package/src/utils/Quadtree/Rectangle.d.ts +1 -0
- package/src/utils/include.d.ts +1 -0
package/src/build.js
CHANGED
|
@@ -273,20 +273,32 @@ class Point {
|
|
|
273
273
|
dot(point) {
|
|
274
274
|
return this.x * point.x + this.y * point.y;
|
|
275
275
|
}
|
|
276
|
+
/** 求两个点叉积
|
|
277
|
+
* @description 如果叉积大于 0,a 到 b 为逆时针方向。
|
|
278
|
+
* @description 如果叉积小于 0,a 到 b 为顺时针方向。
|
|
279
|
+
* @param point
|
|
280
|
+
* @returns
|
|
281
|
+
*/
|
|
282
|
+
cross(point) {
|
|
283
|
+
return this.x * point.y - this.y * this.x;
|
|
284
|
+
}
|
|
276
285
|
/** 计算两个向量夹角
|
|
277
286
|
* @description 公式:a · b = |a| × |b| × cosθ
|
|
278
287
|
* @description 结果为0(0度),两个向量方向一致,结果为3.1415926(180度, PI),两个向量方向相反
|
|
279
288
|
* @param point
|
|
280
289
|
* @returns
|
|
281
290
|
*/
|
|
282
|
-
angleBetween(point) {
|
|
291
|
+
angleBetween(point, type = "radian", angle = "180") {
|
|
283
292
|
const dotProduct = this.dot(point);
|
|
284
293
|
const magnitude1 = this.magnitude();
|
|
285
294
|
const magnitude2 = point.magnitude();
|
|
286
295
|
if (magnitude1 === 0 || magnitude2 === 0) return 0;
|
|
287
296
|
const cosTheta = dotProduct / (magnitude1 * magnitude2);
|
|
288
297
|
const clampedCosTheta = Math.max(-1, Math.min(1, cosTheta));
|
|
289
|
-
return Math.acos(clampedCosTheta);
|
|
298
|
+
if (type === "radian") return Math.acos(clampedCosTheta);
|
|
299
|
+
if (type === "cos") return clampedCosTheta;
|
|
300
|
+
if (angle === "180" || this.cross(point) < 0) return Math.acos(clampedCosTheta) / (Math.PI / 180);
|
|
301
|
+
return 360 - Math.acos(clampedCosTheta) / (Math.PI / 180);
|
|
290
302
|
}
|
|
291
303
|
/** 获取向量长度
|
|
292
304
|
*/
|
|
@@ -578,6 +590,148 @@ class Box2 {
|
|
|
578
590
|
);
|
|
579
591
|
}
|
|
580
592
|
}
|
|
593
|
+
class Rectangle {
|
|
594
|
+
points;
|
|
595
|
+
get path() {
|
|
596
|
+
return this.points.flatMap((p, i) => {
|
|
597
|
+
const np = this.points[(i + 1) % this.points.length];
|
|
598
|
+
return [p.x, p.y, 0, np.x, np.y, 0];
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
path2D(callback) {
|
|
602
|
+
return this.points.flatMap((p, i) => {
|
|
603
|
+
const np = this.points[(i + 1) % this.points.length];
|
|
604
|
+
callback && callback(new Point(p.x, p.y), new Point(np.x, np.y));
|
|
605
|
+
return [p.x, p.y, np.x, np.y];
|
|
606
|
+
});
|
|
607
|
+
}
|
|
608
|
+
constructor(points) {
|
|
609
|
+
if (points.length !== 4) {
|
|
610
|
+
throw new Error("Rectangle must be defined by exactly 4 points");
|
|
611
|
+
}
|
|
612
|
+
this.points = points;
|
|
613
|
+
}
|
|
614
|
+
/**
|
|
615
|
+
* 判断线段是否与矩形相交
|
|
616
|
+
* @param line 线段
|
|
617
|
+
* @returns 是否与矩形相交
|
|
618
|
+
*/
|
|
619
|
+
intersectLineSegment(line) {
|
|
620
|
+
if (line.points.length !== 2) {
|
|
621
|
+
throw new Error("LineSegment must have exactly 2 points");
|
|
622
|
+
}
|
|
623
|
+
const [p1, p2] = line.points;
|
|
624
|
+
const doIntersect = (l1p1, l1p2, l2p1, l2p2) => {
|
|
625
|
+
const orientation = (p, q, r) => {
|
|
626
|
+
const val = (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y);
|
|
627
|
+
if (val === 0) return 0;
|
|
628
|
+
return val > 0 ? 1 : 2;
|
|
629
|
+
};
|
|
630
|
+
const onSegment = (p, q, r) => {
|
|
631
|
+
return Math.min(p.x, r.x) <= q.x && q.x <= Math.max(p.x, r.x) && Math.min(p.y, r.y) <= q.y && q.y <= Math.max(p.y, r.y);
|
|
632
|
+
};
|
|
633
|
+
const o1 = orientation(l1p1, l1p2, l2p1);
|
|
634
|
+
const o2 = orientation(l1p1, l1p2, l2p2);
|
|
635
|
+
const o3 = orientation(l2p1, l2p2, l1p1);
|
|
636
|
+
const o4 = orientation(l2p1, l2p2, l1p2);
|
|
637
|
+
if (o1 !== o2 && o3 !== o4) return true;
|
|
638
|
+
if (o1 === 0 && onSegment(l1p1, l2p1, l1p2)) return true;
|
|
639
|
+
if (o2 === 0 && onSegment(l1p1, l2p2, l1p2)) return true;
|
|
640
|
+
if (o3 === 0 && onSegment(l2p1, l1p1, l2p2)) return true;
|
|
641
|
+
if (o4 === 0 && onSegment(l2p1, l1p2, l2p2)) return true;
|
|
642
|
+
return false;
|
|
643
|
+
};
|
|
644
|
+
for (let i = 0; i < 4; i++) {
|
|
645
|
+
const rectP1 = this.points[i];
|
|
646
|
+
const rectP2 = this.points[(i + 1) % 4];
|
|
647
|
+
if (doIntersect(p1, p2, rectP1, rectP2)) {
|
|
648
|
+
return true;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
if (this.containsLineSegment(line)) {
|
|
652
|
+
return true;
|
|
653
|
+
}
|
|
654
|
+
return false;
|
|
655
|
+
}
|
|
656
|
+
/**
|
|
657
|
+
* 判断线段是否完全位于矩形内部
|
|
658
|
+
* @param line 线段
|
|
659
|
+
* @returns 是否完全在矩形内部
|
|
660
|
+
*/
|
|
661
|
+
containsLineSegment(line) {
|
|
662
|
+
if (line.points.length !== 2) {
|
|
663
|
+
throw new Error("LineSegment must have exactly 2 points");
|
|
664
|
+
}
|
|
665
|
+
const isPointInRectangle = (point) => {
|
|
666
|
+
let sign = 0;
|
|
667
|
+
for (let i = 0; i < 4; i++) {
|
|
668
|
+
const p1 = this.points[i];
|
|
669
|
+
const p2 = this.points[(i + 1) % 4];
|
|
670
|
+
const edge = { x: p2.x - p1.x, y: p2.y - p1.y };
|
|
671
|
+
const toPoint = { x: point.x - p1.x, y: point.y - p1.y };
|
|
672
|
+
const cross = edge.x * toPoint.y - edge.y * toPoint.x;
|
|
673
|
+
if (cross === 0) {
|
|
674
|
+
const t = edge.x !== 0 ? (point.x - p1.x) / edge.x : (point.y - p1.y) / edge.y;
|
|
675
|
+
if (t >= 0 && t <= 1) return true;
|
|
676
|
+
} else {
|
|
677
|
+
const currentSign = cross > 0 ? 1 : -1;
|
|
678
|
+
if (sign === 0) sign = currentSign;
|
|
679
|
+
if (sign !== currentSign) return false;
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
return true;
|
|
683
|
+
};
|
|
684
|
+
return isPointInRectangle(line.points[0]) && isPointInRectangle(line.points[1]);
|
|
685
|
+
}
|
|
686
|
+
/**
|
|
687
|
+
* 判断点是否完全位于矩形内部
|
|
688
|
+
* @param point
|
|
689
|
+
*/
|
|
690
|
+
containsPoint(point) {
|
|
691
|
+
let positiveCount = 0;
|
|
692
|
+
let negativeCount = 0;
|
|
693
|
+
for (let i = 0; i < 4; i++) {
|
|
694
|
+
const p1 = this.points[i];
|
|
695
|
+
const p2 = this.points[(i + 1) % 4];
|
|
696
|
+
const cross = (p2.x - p1.x) * (point.y - p1.y) - (p2.y - p1.y) * (point.x - p1.x);
|
|
697
|
+
if (cross > 0) positiveCount++;
|
|
698
|
+
else if (cross < 0) negativeCount++;
|
|
699
|
+
else return false;
|
|
700
|
+
}
|
|
701
|
+
return positiveCount === 4 || negativeCount === 4;
|
|
702
|
+
}
|
|
703
|
+
/**
|
|
704
|
+
*
|
|
705
|
+
* @returns
|
|
706
|
+
*/
|
|
707
|
+
toBox() {
|
|
708
|
+
let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
|
|
709
|
+
this.points.forEach((p) => {
|
|
710
|
+
maxX = Math.max(p.x, maxX);
|
|
711
|
+
minX = Math.min(p.x, minX);
|
|
712
|
+
maxY = Math.max(p.x, maxY);
|
|
713
|
+
minY = Math.min(p.x, minY);
|
|
714
|
+
});
|
|
715
|
+
return new Box2(minX, maxX, minY, maxY);
|
|
716
|
+
}
|
|
717
|
+
/**
|
|
718
|
+
*
|
|
719
|
+
* @param line
|
|
720
|
+
* @param width
|
|
721
|
+
* @returns
|
|
722
|
+
*/
|
|
723
|
+
static fromByLineSegment(line, width = 0.1, horizontal = false, hScale = 0.5) {
|
|
724
|
+
const p1 = line.points[0], p2 = line.points[1], normal = p2.normal(p1), pDirect = horizontal ? p2.direction(p1).mutiplyScalar(width * hScale) : Point.zero(), nDirect = horizontal ? p1.direction(p2).mutiplyScalar(width * hScale) : Point.zero();
|
|
725
|
+
const offsetX = normal.x * width * 0.5;
|
|
726
|
+
const offsetY = normal.y * width * 0.5;
|
|
727
|
+
return new Rectangle([
|
|
728
|
+
new Point(p1.x + offsetX, p1.y + offsetY).add(nDirect),
|
|
729
|
+
new Point(p2.x + offsetX, p2.y + offsetY).add(pDirect),
|
|
730
|
+
new Point(p2.x - offsetX, p2.y - offsetY).add(pDirect),
|
|
731
|
+
new Point(p1.x - offsetX, p1.y - offsetY).add(nDirect)
|
|
732
|
+
]);
|
|
733
|
+
}
|
|
734
|
+
}
|
|
581
735
|
class LineSegment {
|
|
582
736
|
points = [new Point(), new Point()];
|
|
583
737
|
userData;
|
|
@@ -596,6 +750,69 @@ class LineSegment {
|
|
|
596
750
|
constructor(p1 = new Point(), p2 = new Point()) {
|
|
597
751
|
this.points = [p1, p2];
|
|
598
752
|
}
|
|
753
|
+
/** 膨胀
|
|
754
|
+
* @description 向线段的两个端点分别膨胀 width
|
|
755
|
+
* @param width
|
|
756
|
+
*/
|
|
757
|
+
expansion(width, direction = "all") {
|
|
758
|
+
const step = this.direction().multiplyScalar(width);
|
|
759
|
+
if (direction === "end" || direction === "all") this.end.add(step);
|
|
760
|
+
if (direction === "start" || direction === "all") this.start.add(step.multiplyScalar(-1));
|
|
761
|
+
return this;
|
|
762
|
+
}
|
|
763
|
+
/** 向前
|
|
764
|
+
* @description 向前移动 width
|
|
765
|
+
* @param width
|
|
766
|
+
*/
|
|
767
|
+
forward(width) {
|
|
768
|
+
const step = this.direction().multiplyScalar(width);
|
|
769
|
+
this.start.add(step);
|
|
770
|
+
this.end.add(step);
|
|
771
|
+
return this;
|
|
772
|
+
}
|
|
773
|
+
/** 向前
|
|
774
|
+
* @description 向前移动 width
|
|
775
|
+
* @param width
|
|
776
|
+
*/
|
|
777
|
+
backward(width) {
|
|
778
|
+
const step = this.direction().multiplyScalar(-width);
|
|
779
|
+
this.start.add(step);
|
|
780
|
+
this.end.add(step);
|
|
781
|
+
return this;
|
|
782
|
+
}
|
|
783
|
+
/**
|
|
784
|
+
* 向指定方向平移
|
|
785
|
+
* @param direct
|
|
786
|
+
* @param size
|
|
787
|
+
*/
|
|
788
|
+
directionMove(direct, size) {
|
|
789
|
+
const step = direct.clone().multiplyScalar(size);
|
|
790
|
+
this.start.add(step);
|
|
791
|
+
this.end.add(step);
|
|
792
|
+
return this;
|
|
793
|
+
}
|
|
794
|
+
/** 膨胀为矩形
|
|
795
|
+
*
|
|
796
|
+
* @param width
|
|
797
|
+
* @returns {Rectangle}
|
|
798
|
+
*/
|
|
799
|
+
expandToRectangle(width = 0.1) {
|
|
800
|
+
const p1 = this.start, p2 = this.end;
|
|
801
|
+
const normal = p2.normal(p1);
|
|
802
|
+
const pDirect = p2.direction(p1).mutiplyScalar(width * 0.5);
|
|
803
|
+
const nDirect = p1.direction(p2).mutiplyScalar(width * 0.5);
|
|
804
|
+
const offsetX = normal.x * width * 0.5;
|
|
805
|
+
const offsetY = normal.y * width * 0.5;
|
|
806
|
+
const point = [
|
|
807
|
+
// 第一条线
|
|
808
|
+
new Point(p1.x + offsetX, p1.y + offsetY).add(nDirect),
|
|
809
|
+
new Point(p2.x + offsetX, p2.y + offsetY).add(pDirect),
|
|
810
|
+
// 第二条线
|
|
811
|
+
new Point(p1.x - offsetX, p1.y - offsetY).add(nDirect),
|
|
812
|
+
new Point(p2.x - offsetX, p2.y - offsetY).add(pDirect)
|
|
813
|
+
];
|
|
814
|
+
return new Rectangle([0, 1, 3, 2].map((i) => point[i]));
|
|
815
|
+
}
|
|
599
816
|
/**
|
|
600
817
|
* 计算线段的长度
|
|
601
818
|
* @returns 线段的长度
|
|
@@ -610,6 +827,12 @@ class LineSegment {
|
|
|
610
827
|
direction() {
|
|
611
828
|
return this.points[1].direction(this.points[0]);
|
|
612
829
|
}
|
|
830
|
+
/**
|
|
831
|
+
* 获取发向量
|
|
832
|
+
*/
|
|
833
|
+
normal() {
|
|
834
|
+
return this.points[1].normal(this.points[0]);
|
|
835
|
+
}
|
|
613
836
|
/**
|
|
614
837
|
* 线段长度
|
|
615
838
|
* @returns
|
|
@@ -667,6 +890,38 @@ class LineSegment {
|
|
|
667
890
|
}
|
|
668
891
|
return new LineSegment(projP1, projP2);
|
|
669
892
|
}
|
|
893
|
+
/**
|
|
894
|
+
* 计算一条线段在另一条直线上的投影,并裁剪超出目标线段的部分
|
|
895
|
+
* @param line 要投影的线段
|
|
896
|
+
* @returns 投影并裁剪后的线段
|
|
897
|
+
*/
|
|
898
|
+
projectPoint(p1) {
|
|
899
|
+
const [q1, q2] = this.points;
|
|
900
|
+
const dir = new Point(q2.x - q1.x, q2.y - q1.y);
|
|
901
|
+
if (dir.x === 0 && dir.y === 0) {
|
|
902
|
+
throw new Error("投影目标线段的两个点不能重合");
|
|
903
|
+
}
|
|
904
|
+
const projectPoint = (point) => {
|
|
905
|
+
const pq = new Point(point.x - q1.x, point.y - q1.y);
|
|
906
|
+
const dirLengthSquared = dir.x * dir.x + dir.y * dir.y;
|
|
907
|
+
const dotProduct = pq.x * dir.x + pq.y * dir.y;
|
|
908
|
+
const t = dotProduct / dirLengthSquared;
|
|
909
|
+
const projX = q1.x + t * dir.x;
|
|
910
|
+
const projY = q1.y + t * dir.y;
|
|
911
|
+
return new Point(projX, projY);
|
|
912
|
+
};
|
|
913
|
+
let projP1 = projectPoint(p1);
|
|
914
|
+
const getT = (point) => {
|
|
915
|
+
const pq = new Point(point.x - q1.x, point.y - q1.y);
|
|
916
|
+
const dirLengthSquared = dir.x * dir.x + dir.y * dir.y;
|
|
917
|
+
return (pq.x * dir.x + pq.y * dir.y) / dirLengthSquared;
|
|
918
|
+
};
|
|
919
|
+
let t1 = getT(projP1);
|
|
920
|
+
if (t1 < 0 || t1 > 1) {
|
|
921
|
+
return null;
|
|
922
|
+
}
|
|
923
|
+
return projP1;
|
|
924
|
+
}
|
|
670
925
|
/**
|
|
671
926
|
* 判断线段是与线段相交
|
|
672
927
|
* @param line
|
|
@@ -757,6 +1012,18 @@ class LineSegment {
|
|
|
757
1012
|
);
|
|
758
1013
|
}
|
|
759
1014
|
}
|
|
1015
|
+
async function include(path, exportDefault = true) {
|
|
1016
|
+
if (typeof global !== "undefined" && typeof require !== "undefined") {
|
|
1017
|
+
return require(path);
|
|
1018
|
+
} else {
|
|
1019
|
+
let pack = await import(
|
|
1020
|
+
/* @vite-ignore */
|
|
1021
|
+
path
|
|
1022
|
+
);
|
|
1023
|
+
if (exportDefault) pack = pack.default;
|
|
1024
|
+
return pack;
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
760
1027
|
const units = {
|
|
761
1028
|
Unitless: 1,
|
|
762
1029
|
// 无单位,1米 = 1(无单位)
|
|
@@ -805,8 +1072,8 @@ function pathToLines(path) {
|
|
|
805
1072
|
const lineSegments = [];
|
|
806
1073
|
for (let i = 0; i < path.length; i++) {
|
|
807
1074
|
lineSegments.push(new LineSegment(
|
|
808
|
-
path[i],
|
|
809
|
-
path[(i + 1) % path.length]
|
|
1075
|
+
path[i].clone(),
|
|
1076
|
+
path[(i + 1) % path.length].clone()
|
|
810
1077
|
));
|
|
811
1078
|
}
|
|
812
1079
|
return lineSegments;
|
|
@@ -829,6 +1096,7 @@ class Dxf extends Component {
|
|
|
829
1096
|
pointsGroups = [];
|
|
830
1097
|
wallsGroup = [];
|
|
831
1098
|
doors = [];
|
|
1099
|
+
doorLineSegment = [];
|
|
832
1100
|
lineSegments = [];
|
|
833
1101
|
originalZAverage = 0;
|
|
834
1102
|
static EndType = {
|
|
@@ -857,7 +1125,7 @@ class Dxf extends Component {
|
|
|
857
1125
|
super();
|
|
858
1126
|
this.width = width;
|
|
859
1127
|
this.scale = scale;
|
|
860
|
-
this.shortLine = width * 0.
|
|
1128
|
+
this.shortLine = width * 0.4;
|
|
861
1129
|
}
|
|
862
1130
|
/**
|
|
863
1131
|
* 设置
|
|
@@ -991,6 +1259,28 @@ class Dxf extends Component {
|
|
|
991
1259
|
}
|
|
992
1260
|
return linePaths;
|
|
993
1261
|
}
|
|
1262
|
+
/** 合并方向相同的线段
|
|
1263
|
+
* @param lines
|
|
1264
|
+
* @param errAngle
|
|
1265
|
+
*/
|
|
1266
|
+
mergeSameDirectionLine(lines, errAngle = 3) {
|
|
1267
|
+
if (lines[0].includedAngle(lines[lines.length - 1]) < 0.1) {
|
|
1268
|
+
const line = lines.pop();
|
|
1269
|
+
line.end.copy(lines[0].end);
|
|
1270
|
+
lines.splice(0, 1, line);
|
|
1271
|
+
}
|
|
1272
|
+
const filterLines = [lines[0]];
|
|
1273
|
+
for (let i = 1; i < lines.length; i++) {
|
|
1274
|
+
const line = lines[i];
|
|
1275
|
+
const preLine = lines[i - 1];
|
|
1276
|
+
if (preLine.includedAngle(line) < errAngle) {
|
|
1277
|
+
preLine.end.copy(line.end);
|
|
1278
|
+
} else {
|
|
1279
|
+
filterLines.push(line);
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
return filterLines;
|
|
1283
|
+
}
|
|
994
1284
|
/** etOpenRound 去除毛刺
|
|
995
1285
|
* @description 检查连续的短线段数量,去除合并后产生的毛刺
|
|
996
1286
|
*/
|
|
@@ -1027,44 +1317,22 @@ class Dxf extends Component {
|
|
|
1027
1317
|
}
|
|
1028
1318
|
return filterLines;
|
|
1029
1319
|
}
|
|
1030
|
-
epsilon = 1e-6;
|
|
1031
|
-
// 距离阈值,用于比较点
|
|
1032
1320
|
/**
|
|
1033
|
-
*
|
|
1034
|
-
* @
|
|
1035
|
-
* @
|
|
1321
|
+
* 线段矫直, 线段中心突刺
|
|
1322
|
+
* @description 突变长度小于墙体宽度,该线段可能为突起线段,
|
|
1323
|
+
* @description 判断后续第2线段与上一条线段是否方向相同,相同就为突刺
|
|
1036
1324
|
*/
|
|
1037
|
-
|
|
1325
|
+
lineSegmentStraightening(path) {
|
|
1038
1326
|
for (let i = 0; i < path.length; i++) {
|
|
1039
1327
|
const p1 = path[i];
|
|
1040
1328
|
const p2 = path[(i + 1) % path.length];
|
|
1041
1329
|
if (p1.distance(p2) > this.shortLine) {
|
|
1042
|
-
path.push(...path.slice(0, i));
|
|
1043
|
-
path.splice(0, i);
|
|
1330
|
+
path.push(...path.slice(0, i + 1));
|
|
1331
|
+
path.splice(0, i + 1);
|
|
1044
1332
|
break;
|
|
1045
1333
|
}
|
|
1046
1334
|
}
|
|
1047
|
-
|
|
1048
|
-
const cleanedPath = [];
|
|
1049
|
-
const n = path.length;
|
|
1050
|
-
for (let i = 0; i < n; i++) {
|
|
1051
|
-
const p1 = path[i];
|
|
1052
|
-
const p2 = path[(i + 1) % n];
|
|
1053
|
-
const p3 = path[(i + 2) % n];
|
|
1054
|
-
const cross = (p2.X - p1.X) * (p3.Y - p2.Y) - (p2.Y - p1.Y) * (p3.X - p2.X);
|
|
1055
|
-
if (Math.abs(cross) > this.epsilon) {
|
|
1056
|
-
cleanedPath.push(p1);
|
|
1057
|
-
}
|
|
1058
|
-
}
|
|
1059
|
-
return cleanedPath;
|
|
1060
|
-
}
|
|
1061
|
-
/**
|
|
1062
|
-
* 线段矫直, 线段中心突刺
|
|
1063
|
-
* @description 突变长度小于墙体宽度,该线段可能为突起线段,
|
|
1064
|
-
* @description 判断后续第2线段与上一条线段是否方向相同,相同就为突刺
|
|
1065
|
-
*/
|
|
1066
|
-
lineSegmentStraightening(path) {
|
|
1067
|
-
const lines = pathToLines(path), filterLines = [lines[0]];
|
|
1335
|
+
const lines = this.mergeSameDirectionLine(pathToLines(path)), filterLines = [lines[0]];
|
|
1068
1336
|
for (let i = 1; i < lines.length; i++) {
|
|
1069
1337
|
const line = lines[i];
|
|
1070
1338
|
const preLine = lines[(lines.length + i - 1) % lines.length];
|
|
@@ -1080,15 +1348,16 @@ class Dxf extends Component {
|
|
|
1080
1348
|
continue;
|
|
1081
1349
|
}
|
|
1082
1350
|
const line2 = lines[i + 2];
|
|
1083
|
-
if (line2 && preLine.includedAngle(line2) <
|
|
1351
|
+
if (line2 && preLine.includedAngle(line2) < 2) {
|
|
1084
1352
|
i = i + 2;
|
|
1085
1353
|
filterLines.push(line2);
|
|
1086
1354
|
} else filterLines.push(line);
|
|
1087
1355
|
}
|
|
1088
|
-
return filterLines.length > 3 ? linesToPath(filterLines) : [];
|
|
1356
|
+
return filterLines.length > 3 ? linesToPath(this.mergeSameDirectionLine(filterLines)) : [];
|
|
1089
1357
|
}
|
|
1090
1358
|
/**
|
|
1091
1359
|
* 移除短线段
|
|
1360
|
+
* @todo 根据线段两端线段长度,选取参照物_|▔▔
|
|
1092
1361
|
* @param path
|
|
1093
1362
|
*/
|
|
1094
1363
|
removeShortLine(path, shortLine = this.shortLine) {
|
|
@@ -1141,9 +1410,9 @@ class Dxf extends Component {
|
|
|
1141
1410
|
offset.Execute(solutions, this.width / 2 * scale);
|
|
1142
1411
|
this.wallsGroup = solutions.map((ps) => {
|
|
1143
1412
|
let path = ps.map((p) => Point.from(p).divisionScalar(scale));
|
|
1144
|
-
path = this.removeCollinearPoints(path);
|
|
1145
1413
|
path = this.lineSegmentStraightening(path);
|
|
1146
1414
|
if (endType == Dxf.EndType.etOpenSquare) path = this.squareRemoveBurr(path);
|
|
1415
|
+
path = this.removeShortLine(path);
|
|
1147
1416
|
return path;
|
|
1148
1417
|
});
|
|
1149
1418
|
this.dispatchEvent({
|
|
@@ -1167,6 +1436,32 @@ class Dxf extends Component {
|
|
|
1167
1436
|
});
|
|
1168
1437
|
return new Float32Array(array);
|
|
1169
1438
|
}
|
|
1439
|
+
/** 获取角度范围
|
|
1440
|
+
* @param center
|
|
1441
|
+
* @param p1
|
|
1442
|
+
* @param p2
|
|
1443
|
+
* @returns
|
|
1444
|
+
*/
|
|
1445
|
+
getArcAngleRange(center, p1, p2) {
|
|
1446
|
+
const x1 = p1.x - center.x;
|
|
1447
|
+
const y1 = p1.y - center.y;
|
|
1448
|
+
const x2 = p2.x - center.x;
|
|
1449
|
+
const y2 = p2.y - center.y;
|
|
1450
|
+
let angle1 = Math.atan2(y1, x1);
|
|
1451
|
+
let angle2 = Math.atan2(y2, x2);
|
|
1452
|
+
angle1 = angle1 < 0 ? angle1 + 2 * Math.PI : angle1;
|
|
1453
|
+
angle2 = angle2 < 0 ? angle2 + 2 * Math.PI : angle2;
|
|
1454
|
+
let r1, r2;
|
|
1455
|
+
const diff = Math.abs(angle2 - angle1);
|
|
1456
|
+
if (diff <= Math.PI) {
|
|
1457
|
+
r1 = Math.min(angle1, angle2);
|
|
1458
|
+
r2 = Math.max(angle1, angle2);
|
|
1459
|
+
} else {
|
|
1460
|
+
r1 = Math.max(angle1, angle2);
|
|
1461
|
+
r2 = Math.min(angle1, angle2) + 2 * Math.PI;
|
|
1462
|
+
}
|
|
1463
|
+
return [r1 / (Math.PI / 180), r2 / (Math.PI / 180)];
|
|
1464
|
+
}
|
|
1170
1465
|
/**
|
|
1171
1466
|
* 将点云结构转换为string
|
|
1172
1467
|
*/
|
|
@@ -1174,12 +1469,37 @@ class Dxf extends Component {
|
|
|
1174
1469
|
const d = new Drawing();
|
|
1175
1470
|
d.setUnits("Millimeters");
|
|
1176
1471
|
const s = units[unit];
|
|
1472
|
+
function drawLine(p1, p2) {
|
|
1473
|
+
d.drawLine(p1.X * s, p1.Y * s, p2.X * s, p2.Y * s);
|
|
1474
|
+
}
|
|
1177
1475
|
this.wallsGroup.forEach((points) => {
|
|
1178
1476
|
for (let i = 0; i < points.length; i++) {
|
|
1179
1477
|
const point1 = points[i];
|
|
1180
1478
|
const nextIndex = i === points.length - 1 ? 0 : i + 1;
|
|
1181
1479
|
const point2 = points[nextIndex];
|
|
1182
|
-
|
|
1480
|
+
drawLine(point1, point2);
|
|
1481
|
+
}
|
|
1482
|
+
});
|
|
1483
|
+
const doorThickness = this.width * 0.2;
|
|
1484
|
+
d.addLayer("l_yellow", Drawing.ACI.CYAN, "DOTTED");
|
|
1485
|
+
this.doorLineSegment.forEach((lineSegment) => {
|
|
1486
|
+
if (lineSegment.length() < 0.4) return;
|
|
1487
|
+
const line = lineSegment.clone().expansion(-this.width * 0.5);
|
|
1488
|
+
d.setActiveLayer("l_yellow");
|
|
1489
|
+
if (line.length() < 1.2) {
|
|
1490
|
+
line.expansion(-doorThickness * 0.5);
|
|
1491
|
+
const normal = lineSegment.normal();
|
|
1492
|
+
const door = new LineSegment(
|
|
1493
|
+
line.start.clone(),
|
|
1494
|
+
line.start.clone().add(normal.clone().multiplyScalar(line.length()))
|
|
1495
|
+
);
|
|
1496
|
+
door.expansion(-doorThickness * 0.5).expandToRectangle(this.width * 0.2).path2D((p1, p2) => drawLine(p1, p2));
|
|
1497
|
+
const a = line.length(), b = door.length(), r = (a ** 2 + b ** 2) / (2 * b), center = door.end.clone().add(door.direction().multiplyScalar(-r)), [startAngle, endAngle] = this.getArcAngleRange(center, line.end, door.end);
|
|
1498
|
+
d.drawArc(center.x * s, center.y * s, r * s, Math.min(startAngle, endAngle), Math.max(startAngle, endAngle));
|
|
1499
|
+
} else {
|
|
1500
|
+
line.clone().expansion(-this.width * 0.5).expandToRectangle(this.width).path2D((p1, p2) => drawLine(p1, p2));
|
|
1501
|
+
line.clone().directionMove(line.normal(), doorThickness * 0.5).expansion(-line.length() * 0.4, "end").forward(doorThickness * 0.5).expandToRectangle(doorThickness).path2D((p1, p2) => drawLine(p1, p2));
|
|
1502
|
+
line.clone().directionMove(line.normal(), -doorThickness * 0.5).expansion(-line.length() * 0.4, "start").forward(-doorThickness * 0.5).expandToRectangle(doorThickness).path2D((p1, p2) => drawLine(p1, p2));
|
|
1183
1503
|
}
|
|
1184
1504
|
});
|
|
1185
1505
|
return d.toDxfString();
|
|
@@ -1204,11 +1524,7 @@ class Dxf extends Component {
|
|
|
1204
1524
|
a.download = filename + ".dxf";
|
|
1205
1525
|
a.click();
|
|
1206
1526
|
} else if (typeof global !== "undefined") {
|
|
1207
|
-
const
|
|
1208
|
-
const { default: fs } = await import(
|
|
1209
|
-
/* @vite-ignore */
|
|
1210
|
-
packageName
|
|
1211
|
-
);
|
|
1527
|
+
const fs = await include("fs", false);
|
|
1212
1528
|
fs.writeFileSync(filename, this.toDxfString(unit));
|
|
1213
1529
|
}
|
|
1214
1530
|
}
|
|
@@ -1283,141 +1599,6 @@ class Variable extends Component {
|
|
|
1283
1599
|
if (key in this) return this[key];
|
|
1284
1600
|
}
|
|
1285
1601
|
}
|
|
1286
|
-
class Rectangle {
|
|
1287
|
-
points;
|
|
1288
|
-
get path() {
|
|
1289
|
-
return this.points.flatMap((p, i) => {
|
|
1290
|
-
const np = this.points[(i + 1) % this.points.length];
|
|
1291
|
-
return [p.x, p.y, 0, np.x, np.y, 0];
|
|
1292
|
-
});
|
|
1293
|
-
}
|
|
1294
|
-
constructor(points) {
|
|
1295
|
-
if (points.length !== 4) {
|
|
1296
|
-
throw new Error("Rectangle must be defined by exactly 4 points");
|
|
1297
|
-
}
|
|
1298
|
-
this.points = points;
|
|
1299
|
-
}
|
|
1300
|
-
/**
|
|
1301
|
-
* 判断线段是否与矩形相交
|
|
1302
|
-
* @param line 线段
|
|
1303
|
-
* @returns 是否与矩形相交
|
|
1304
|
-
*/
|
|
1305
|
-
intersectLineSegment(line) {
|
|
1306
|
-
if (line.points.length !== 2) {
|
|
1307
|
-
throw new Error("LineSegment must have exactly 2 points");
|
|
1308
|
-
}
|
|
1309
|
-
const [p1, p2] = line.points;
|
|
1310
|
-
const doIntersect = (l1p1, l1p2, l2p1, l2p2) => {
|
|
1311
|
-
const orientation = (p, q, r) => {
|
|
1312
|
-
const val = (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y);
|
|
1313
|
-
if (val === 0) return 0;
|
|
1314
|
-
return val > 0 ? 1 : 2;
|
|
1315
|
-
};
|
|
1316
|
-
const onSegment = (p, q, r) => {
|
|
1317
|
-
return Math.min(p.x, r.x) <= q.x && q.x <= Math.max(p.x, r.x) && Math.min(p.y, r.y) <= q.y && q.y <= Math.max(p.y, r.y);
|
|
1318
|
-
};
|
|
1319
|
-
const o1 = orientation(l1p1, l1p2, l2p1);
|
|
1320
|
-
const o2 = orientation(l1p1, l1p2, l2p2);
|
|
1321
|
-
const o3 = orientation(l2p1, l2p2, l1p1);
|
|
1322
|
-
const o4 = orientation(l2p1, l2p2, l1p2);
|
|
1323
|
-
if (o1 !== o2 && o3 !== o4) return true;
|
|
1324
|
-
if (o1 === 0 && onSegment(l1p1, l2p1, l1p2)) return true;
|
|
1325
|
-
if (o2 === 0 && onSegment(l1p1, l2p2, l1p2)) return true;
|
|
1326
|
-
if (o3 === 0 && onSegment(l2p1, l1p1, l2p2)) return true;
|
|
1327
|
-
if (o4 === 0 && onSegment(l2p1, l1p2, l2p2)) return true;
|
|
1328
|
-
return false;
|
|
1329
|
-
};
|
|
1330
|
-
for (let i = 0; i < 4; i++) {
|
|
1331
|
-
const rectP1 = this.points[i];
|
|
1332
|
-
const rectP2 = this.points[(i + 1) % 4];
|
|
1333
|
-
if (doIntersect(p1, p2, rectP1, rectP2)) {
|
|
1334
|
-
return true;
|
|
1335
|
-
}
|
|
1336
|
-
}
|
|
1337
|
-
if (this.containsLineSegment(line)) {
|
|
1338
|
-
return true;
|
|
1339
|
-
}
|
|
1340
|
-
return false;
|
|
1341
|
-
}
|
|
1342
|
-
/**
|
|
1343
|
-
* 判断线段是否完全位于矩形内部
|
|
1344
|
-
* @param line 线段
|
|
1345
|
-
* @returns 是否完全在矩形内部
|
|
1346
|
-
*/
|
|
1347
|
-
containsLineSegment(line) {
|
|
1348
|
-
if (line.points.length !== 2) {
|
|
1349
|
-
throw new Error("LineSegment must have exactly 2 points");
|
|
1350
|
-
}
|
|
1351
|
-
const isPointInRectangle = (point) => {
|
|
1352
|
-
let sign = 0;
|
|
1353
|
-
for (let i = 0; i < 4; i++) {
|
|
1354
|
-
const p1 = this.points[i];
|
|
1355
|
-
const p2 = this.points[(i + 1) % 4];
|
|
1356
|
-
const edge = { x: p2.x - p1.x, y: p2.y - p1.y };
|
|
1357
|
-
const toPoint = { x: point.x - p1.x, y: point.y - p1.y };
|
|
1358
|
-
const cross = edge.x * toPoint.y - edge.y * toPoint.x;
|
|
1359
|
-
if (cross === 0) {
|
|
1360
|
-
const t = edge.x !== 0 ? (point.x - p1.x) / edge.x : (point.y - p1.y) / edge.y;
|
|
1361
|
-
if (t >= 0 && t <= 1) return true;
|
|
1362
|
-
} else {
|
|
1363
|
-
const currentSign = cross > 0 ? 1 : -1;
|
|
1364
|
-
if (sign === 0) sign = currentSign;
|
|
1365
|
-
if (sign !== currentSign) return false;
|
|
1366
|
-
}
|
|
1367
|
-
}
|
|
1368
|
-
return true;
|
|
1369
|
-
};
|
|
1370
|
-
return isPointInRectangle(line.points[0]) && isPointInRectangle(line.points[1]);
|
|
1371
|
-
}
|
|
1372
|
-
/**
|
|
1373
|
-
* 判断点是否完全位于矩形内部
|
|
1374
|
-
* @param point
|
|
1375
|
-
*/
|
|
1376
|
-
containsPoint(point) {
|
|
1377
|
-
let positiveCount = 0;
|
|
1378
|
-
let negativeCount = 0;
|
|
1379
|
-
for (let i = 0; i < 4; i++) {
|
|
1380
|
-
const p1 = this.points[i];
|
|
1381
|
-
const p2 = this.points[(i + 1) % 4];
|
|
1382
|
-
const cross = (p2.x - p1.x) * (point.y - p1.y) - (p2.y - p1.y) * (point.x - p1.x);
|
|
1383
|
-
if (cross > 0) positiveCount++;
|
|
1384
|
-
else if (cross < 0) negativeCount++;
|
|
1385
|
-
else return false;
|
|
1386
|
-
}
|
|
1387
|
-
return positiveCount === 4 || negativeCount === 4;
|
|
1388
|
-
}
|
|
1389
|
-
/**
|
|
1390
|
-
*
|
|
1391
|
-
* @returns
|
|
1392
|
-
*/
|
|
1393
|
-
toBox() {
|
|
1394
|
-
let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
|
|
1395
|
-
this.points.forEach((p) => {
|
|
1396
|
-
maxX = Math.max(p.x, maxX);
|
|
1397
|
-
minX = Math.min(p.x, minX);
|
|
1398
|
-
maxY = Math.max(p.x, maxY);
|
|
1399
|
-
minY = Math.min(p.x, minY);
|
|
1400
|
-
});
|
|
1401
|
-
return new Box2(minX, maxX, minY, maxY);
|
|
1402
|
-
}
|
|
1403
|
-
/**
|
|
1404
|
-
*
|
|
1405
|
-
* @param line
|
|
1406
|
-
* @param width
|
|
1407
|
-
* @returns
|
|
1408
|
-
*/
|
|
1409
|
-
static fromByLineSegment(line, width = 0.1, horizontal = false, hScale = 0.5) {
|
|
1410
|
-
const p1 = line.points[0], p2 = line.points[1], normal = p2.normal(p1), pDirect = horizontal ? p2.direction(p1).mutiplyScalar(width * hScale) : Point.zero(), nDirect = horizontal ? p1.direction(p2).mutiplyScalar(width * hScale) : Point.zero();
|
|
1411
|
-
const offsetX = normal.x * width * 0.5;
|
|
1412
|
-
const offsetY = normal.y * width * 0.5;
|
|
1413
|
-
return new Rectangle([
|
|
1414
|
-
new Point(p1.x + offsetX, p1.y + offsetY).add(nDirect),
|
|
1415
|
-
new Point(p2.x + offsetX, p2.y + offsetY).add(pDirect),
|
|
1416
|
-
new Point(p2.x - offsetX, p2.y - offsetY).add(pDirect),
|
|
1417
|
-
new Point(p1.x - offsetX, p1.y - offsetY).add(nDirect)
|
|
1418
|
-
]);
|
|
1419
|
-
}
|
|
1420
|
-
}
|
|
1421
1602
|
class Quadtree {
|
|
1422
1603
|
bounds;
|
|
1423
1604
|
// 包围盒
|
|
@@ -1615,6 +1796,28 @@ class Quadtree {
|
|
|
1615
1796
|
}
|
|
1616
1797
|
return result;
|
|
1617
1798
|
}
|
|
1799
|
+
/**
|
|
1800
|
+
* 查询与线段相交的线段节点
|
|
1801
|
+
* @param lineSegment 线段
|
|
1802
|
+
* @returns 相交的节点数组
|
|
1803
|
+
*/
|
|
1804
|
+
queryLineSegment(lineSegment) {
|
|
1805
|
+
const result = [];
|
|
1806
|
+
if (!this.bounds.intersectLineSegment(lineSegment)) {
|
|
1807
|
+
return result;
|
|
1808
|
+
}
|
|
1809
|
+
for (const node of this.nodes) {
|
|
1810
|
+
if (lineSegment.intersectLineSegment(node.line)) {
|
|
1811
|
+
result.push(node);
|
|
1812
|
+
}
|
|
1813
|
+
}
|
|
1814
|
+
if (!this.isLeaf) {
|
|
1815
|
+
for (const child of this.children) {
|
|
1816
|
+
result.push(...child.queryLineSegment(lineSegment));
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1819
|
+
return result;
|
|
1820
|
+
}
|
|
1618
1821
|
/**
|
|
1619
1822
|
* 包围盒转换为数组
|
|
1620
1823
|
* @param array
|
|
@@ -1772,6 +1975,7 @@ class LineAnalysis extends Component {
|
|
|
1772
1975
|
this.Dxf = parent.findComponentByType(Dxf);
|
|
1773
1976
|
this.Variable = this.parent?.findComponentByType(Variable);
|
|
1774
1977
|
this.Dxf.addEventListener("setDta", this.lineAnalysis.bind(this));
|
|
1978
|
+
this.Dxf.addEventListener("createGroup", this.doorsAnalysis.bind(this));
|
|
1775
1979
|
}
|
|
1776
1980
|
/**
|
|
1777
1981
|
*
|
|
@@ -1839,8 +2043,9 @@ class LineAnalysis extends Component {
|
|
|
1839
2043
|
buildVirtualGrid() {
|
|
1840
2044
|
const dxf = this.Dxf;
|
|
1841
2045
|
const pointVirtualGrid = new PointVirtualGrid();
|
|
1842
|
-
dxf.
|
|
1843
|
-
|
|
2046
|
+
dxf.lineSegments.forEach((d, index2) => {
|
|
2047
|
+
if (d.userData?.isDoor) return;
|
|
2048
|
+
const [p1, p2] = [d.start, d.end];
|
|
1844
2049
|
pointVirtualGrid.insert(p1, { index: index2, type: "start" });
|
|
1845
2050
|
pointVirtualGrid.insert(p2, { index: index2, type: "end" });
|
|
1846
2051
|
});
|
|
@@ -1865,6 +2070,7 @@ class LineAnalysis extends Component {
|
|
|
1865
2070
|
this.lineSegmentList = lineSegmentList;
|
|
1866
2071
|
}
|
|
1867
2072
|
resultList = [];
|
|
2073
|
+
mergeWallLines = [];
|
|
1868
2074
|
/** 线段分析
|
|
1869
2075
|
* @description 判断两条线段距离是否较短且趋近平行,然后查找两条线段的重合部分的投影线,以此判断两根线是否需要合并
|
|
1870
2076
|
* @param data
|
|
@@ -1889,7 +2095,7 @@ class LineAnalysis extends Component {
|
|
|
1889
2095
|
});
|
|
1890
2096
|
this.appendLineSegmentList.length = 0;
|
|
1891
2097
|
resultList.forEach(this.createRectangle.bind(this));
|
|
1892
|
-
this.resultList =
|
|
2098
|
+
this.resultList = resultList;
|
|
1893
2099
|
}
|
|
1894
2100
|
/** 线段投影分析
|
|
1895
2101
|
* @param index
|
|
@@ -1921,10 +2127,259 @@ class LineAnalysis extends Component {
|
|
|
1921
2127
|
project2: p1
|
|
1922
2128
|
};
|
|
1923
2129
|
}
|
|
1924
|
-
if (!data || data.project.getLength() < 0.
|
|
2130
|
+
if (!data || data.project.getLength() < 0.2 || data.project2.getLength() < 0.2) return;
|
|
1925
2131
|
return data;
|
|
1926
2132
|
}
|
|
1927
2133
|
}
|
|
2134
|
+
doorSearchNearAngle = 110;
|
|
2135
|
+
doorSearchDistance = 2;
|
|
2136
|
+
doors = [];
|
|
2137
|
+
/**
|
|
2138
|
+
* 门的位置判断
|
|
2139
|
+
*/
|
|
2140
|
+
doorsAnalysis() {
|
|
2141
|
+
const dxf = this.Dxf;
|
|
2142
|
+
const doorPoints = [];
|
|
2143
|
+
const pointVirtualGrid = this.pointVirtualGrid;
|
|
2144
|
+
const quadtree = this.quadtree;
|
|
2145
|
+
const doorSearchNearAngle = this.doorSearchNearAngle;
|
|
2146
|
+
const doorSearchDistance = this.doorSearchDistance;
|
|
2147
|
+
const doors = [];
|
|
2148
|
+
const excludePoints = dxf.doors.flatMap((item) => {
|
|
2149
|
+
const index2 = item[4];
|
|
2150
|
+
const doorData = dxf.originalData[index2];
|
|
2151
|
+
if (doorData.drawDoorData) {
|
|
2152
|
+
const point = Point.from(doorData.drawDoorData.start);
|
|
2153
|
+
const direct = Point.from(doorData.drawDoorData.n);
|
|
2154
|
+
const resList = pointVirtualGrid.queryPoint(point).filter((res) => {
|
|
2155
|
+
if (res.userData?.index === index2) return false;
|
|
2156
|
+
const line = dxf.lineSegments[res.userData?.index];
|
|
2157
|
+
const direct2 = line.direction();
|
|
2158
|
+
if (line.start.equal(point)) direct2.multiplyScalar(-1);
|
|
2159
|
+
const angle = direct.angleBetween(direct2, "angle");
|
|
2160
|
+
return angle > 80 || angle < 10;
|
|
2161
|
+
});
|
|
2162
|
+
if (resList.length) {
|
|
2163
|
+
const index22 = resList[0].userData?.index;
|
|
2164
|
+
doorPoints.push({
|
|
2165
|
+
line: dxf.lineSegments[index22],
|
|
2166
|
+
point: Point.from(doorData.drawDoorData.start),
|
|
2167
|
+
index: index22,
|
|
2168
|
+
direct,
|
|
2169
|
+
sure: true
|
|
2170
|
+
});
|
|
2171
|
+
return [point];
|
|
2172
|
+
}
|
|
2173
|
+
}
|
|
2174
|
+
return [];
|
|
2175
|
+
});
|
|
2176
|
+
const excludeIndexMap = /* @__PURE__ */ new Map();
|
|
2177
|
+
this.resultList.flatMap((p) => {
|
|
2178
|
+
const line0 = this.lineSegmentList[p.sourceIndex], line1 = this.lineSegmentList[p.targetIndex], start0 = line1.projectPoint(line0.start), end0 = line1.projectPoint(line0.end), start1 = line0.projectPoint(line1.start), end1 = line0.projectPoint(line1.end), mode0 = start0 && end0 ? -1 : start0 ? 0 : end0 ? 1 : -1, mode1 = start1 && end1 ? -1 : start1 ? 0 : end1 ? 1 : -1;
|
|
2179
|
+
if (excludeIndexMap.has(p.sourceIndex)) {
|
|
2180
|
+
if (excludeIndexMap.get(p.sourceIndex) != mode0) excludeIndexMap.set(p.sourceIndex, -1);
|
|
2181
|
+
} else excludeIndexMap.set(p.sourceIndex, mode0);
|
|
2182
|
+
if (excludeIndexMap.has(p.targetIndex)) {
|
|
2183
|
+
if (excludeIndexMap.get(p.targetIndex) != mode1) excludeIndexMap.set(p.targetIndex, -1);
|
|
2184
|
+
} else excludeIndexMap.set(p.targetIndex, mode1);
|
|
2185
|
+
});
|
|
2186
|
+
dxf.lineSegments.forEach((line, i) => {
|
|
2187
|
+
if (line.userData?.isDoor) return;
|
|
2188
|
+
const excludeMode = excludeIndexMap.get(i);
|
|
2189
|
+
if (excludeMode === -1) return;
|
|
2190
|
+
line.points.forEach((p, j) => {
|
|
2191
|
+
if (excludeMode === j) return;
|
|
2192
|
+
if (excludePoints.find((p1) => p1.equal(p))) return;
|
|
2193
|
+
const res = this.pointVirtualGrid.queryPoint(p).filter((d) => d.userData?.index !== i);
|
|
2194
|
+
if (res.length === 0) {
|
|
2195
|
+
doorPoints.push({
|
|
2196
|
+
line,
|
|
2197
|
+
point: p,
|
|
2198
|
+
index: i
|
|
2199
|
+
});
|
|
2200
|
+
}
|
|
2201
|
+
});
|
|
2202
|
+
});
|
|
2203
|
+
function searchNearby({ point, line, index: index2 }, doorIndex, record2) {
|
|
2204
|
+
const direct = line.direction();
|
|
2205
|
+
if (line.start === point) direct.multiplyScalar(-1);
|
|
2206
|
+
const res = pointVirtualGrid.queryCircle(point, doorSearchDistance).filter(
|
|
2207
|
+
(r) => (
|
|
2208
|
+
// 不能是自己
|
|
2209
|
+
r.userData?.index !== index2 && // 存在于可疑点位内,且不能是已知门的点位
|
|
2210
|
+
!!doorPoints.find((p) => !p.sure && p.point === r.point)
|
|
2211
|
+
)
|
|
2212
|
+
).sort((a, b) => a.point.distance(point) - b.point.distance(point));
|
|
2213
|
+
const list = [];
|
|
2214
|
+
for (let i = 0; i < res.length; i++) {
|
|
2215
|
+
const doorIndex2 = doorPoints.findIndex((p) => p.point === res[i].point);
|
|
2216
|
+
if (record2.has(`${doorIndex}.${doorIndex2}`)) continue;
|
|
2217
|
+
record2.add(`${doorIndex}.${doorIndex2}`);
|
|
2218
|
+
record2.add(`${doorIndex2}.${doorIndex}`);
|
|
2219
|
+
const targetPoint = res[i].point, line2 = new LineSegment(point.clone(), targetPoint.clone()), angle = line2.direction().angleBetween(direct, "angle");
|
|
2220
|
+
if (angle < doorSearchNearAngle) {
|
|
2221
|
+
const direct2 = doorPoints[doorIndex2].line.direction();
|
|
2222
|
+
const line22 = dxf.lineSegments[res[i].userData?.index];
|
|
2223
|
+
if (line22.start.equal(res[i].point)) direct2.multiplyScalar(-1);
|
|
2224
|
+
const angle2 = line2.direction().multiplyScalar(-1).angleBetween(direct2, "angle");
|
|
2225
|
+
if (angle2 < doorSearchNearAngle) {
|
|
2226
|
+
if (!quadtree.queryLineSegment(line2).length) {
|
|
2227
|
+
list.push({
|
|
2228
|
+
findData: res[i],
|
|
2229
|
+
findDoorIndex: doorIndex2,
|
|
2230
|
+
findAngle: angle2,
|
|
2231
|
+
doorAngle: angle,
|
|
2232
|
+
doorLine: line2,
|
|
2233
|
+
doorIndex
|
|
2234
|
+
});
|
|
2235
|
+
}
|
|
2236
|
+
}
|
|
2237
|
+
}
|
|
2238
|
+
}
|
|
2239
|
+
return list;
|
|
2240
|
+
}
|
|
2241
|
+
function searchAlongDirection({ point, line }) {
|
|
2242
|
+
const direct = line.direction();
|
|
2243
|
+
if (line.start === point) direct.multiplyScalar(-1);
|
|
2244
|
+
const endPoint = point.clone().add(direct.clone().multiplyScalar(doorSearchDistance)), rline = new LineSegment(point.clone(), endPoint), result = quadtree.queryLineSegment(rline).map((l) => {
|
|
2245
|
+
const res = l.line.getIntersection(rline);
|
|
2246
|
+
return {
|
|
2247
|
+
point: res,
|
|
2248
|
+
line: l.line
|
|
2249
|
+
};
|
|
2250
|
+
}).filter((i) => i.point).sort((a, b) => point.distance(a.point) - point.distance(b.point));
|
|
2251
|
+
if (result.length) {
|
|
2252
|
+
const item = result[0];
|
|
2253
|
+
if (Math.abs(90 - item.line.direction().angleBetween(direct, "angle")) < 5) {
|
|
2254
|
+
return item;
|
|
2255
|
+
}
|
|
2256
|
+
}
|
|
2257
|
+
}
|
|
2258
|
+
function searchAlongNormalDirection({ point, line, index: index2 }) {
|
|
2259
|
+
const direct = line.direction();
|
|
2260
|
+
if (line.start === point) direct.multiplyScalar(-1);
|
|
2261
|
+
const normal = line.start.normal(line.end);
|
|
2262
|
+
const prePoint = line.start.clone();
|
|
2263
|
+
if (line.start === point) prePoint.copy(line.end);
|
|
2264
|
+
const result = pointVirtualGrid.queryPoint(prePoint).filter((r) => r.userData?.index !== index2);
|
|
2265
|
+
for (let i = 0; i < result.length; i++) {
|
|
2266
|
+
const element = result[i];
|
|
2267
|
+
const l = dxf.lineSegments[element.userData?.index ?? 0];
|
|
2268
|
+
const d1 = l.direction();
|
|
2269
|
+
if (l.start === element.point) direct.multiplyScalar(-1);
|
|
2270
|
+
const angle = d1.angleBetween(normal) / (Math.PI / 180);
|
|
2271
|
+
if (angle > 90) {
|
|
2272
|
+
normal.multiplyScalar(-1);
|
|
2273
|
+
break;
|
|
2274
|
+
}
|
|
2275
|
+
}
|
|
2276
|
+
const rline3 = new LineSegment(point.clone(), point.clone().add(normal.multiplyScalar(doorSearchDistance)));
|
|
2277
|
+
const r3 = quadtree.queryLineSegment(rline3).map((l) => {
|
|
2278
|
+
const res = l.line.getIntersection(rline3);
|
|
2279
|
+
return {
|
|
2280
|
+
point: res,
|
|
2281
|
+
line: l.line
|
|
2282
|
+
};
|
|
2283
|
+
}).filter((i) => i.point).sort((a, b) => point.distance(a.point) - point.distance(b.point));
|
|
2284
|
+
if (r3.length) {
|
|
2285
|
+
const item = r3[0];
|
|
2286
|
+
if (Math.abs(90 - item.line.direction().angleBetween(normal, "angle")) < 5) {
|
|
2287
|
+
return item;
|
|
2288
|
+
}
|
|
2289
|
+
}
|
|
2290
|
+
}
|
|
2291
|
+
function deepSearchNearHandle(index2, snFindRecord2, list, record2, other) {
|
|
2292
|
+
record2.add(`${index2}`);
|
|
2293
|
+
const newList = [];
|
|
2294
|
+
if (other) newList.push(other);
|
|
2295
|
+
for (let i = 0; i < list.length; i++) {
|
|
2296
|
+
const item = list[i];
|
|
2297
|
+
if (snFindRecord2.has(item.findDoorIndex)) {
|
|
2298
|
+
const list2 = snFindRecord2.get(item.findDoorIndex);
|
|
2299
|
+
if (deepSearchNearHandle(item.findDoorIndex, snFindRecord2, list2, record2, item)) newList.push(item);
|
|
2300
|
+
} else newList.push(item);
|
|
2301
|
+
}
|
|
2302
|
+
newList.sort((a, b) => a.doorLine.length() - b.doorLine.length());
|
|
2303
|
+
if (other && newList[0] === other) {
|
|
2304
|
+
list.splice(0);
|
|
2305
|
+
return true;
|
|
2306
|
+
}
|
|
2307
|
+
list.splice(1);
|
|
2308
|
+
return false;
|
|
2309
|
+
}
|
|
2310
|
+
const record = /* @__PURE__ */ new Set();
|
|
2311
|
+
const snFindRecord = /* @__PURE__ */ new Map();
|
|
2312
|
+
doorPoints.map((p, index2) => {
|
|
2313
|
+
if (dxf.doors.length >= 2 && !p.sure) return;
|
|
2314
|
+
const list = searchNearby(p, index2, record);
|
|
2315
|
+
if (list.length) snFindRecord.set(index2, list);
|
|
2316
|
+
});
|
|
2317
|
+
record.clear();
|
|
2318
|
+
const temMap = /* @__PURE__ */ new Map();
|
|
2319
|
+
snFindRecord.forEach((list, key) => {
|
|
2320
|
+
if (!record.has(`${key}`) && list.length) {
|
|
2321
|
+
deepSearchNearHandle(key, snFindRecord, list, record);
|
|
2322
|
+
}
|
|
2323
|
+
if (list.length) {
|
|
2324
|
+
const item = list[0];
|
|
2325
|
+
if (!temMap.has(item.doorIndex)) temMap.set(item.doorIndex, []);
|
|
2326
|
+
temMap.get(item.doorIndex)?.push(item);
|
|
2327
|
+
if (!temMap.has(item.findDoorIndex)) temMap.set(item.findDoorIndex, []);
|
|
2328
|
+
temMap.get(item.findDoorIndex)?.push(item);
|
|
2329
|
+
}
|
|
2330
|
+
});
|
|
2331
|
+
const deleteSet = /* @__PURE__ */ new Set();
|
|
2332
|
+
temMap.forEach((list) => {
|
|
2333
|
+
if (list.length > 1) {
|
|
2334
|
+
list.sort((a, b) => a.doorLine.length() - b.doorLine.length());
|
|
2335
|
+
for (let i = 1; i < list.length; i++) deleteSet.add(list[i]);
|
|
2336
|
+
}
|
|
2337
|
+
});
|
|
2338
|
+
const searchNearRasult = [], removeDoorPointsIndex = [];
|
|
2339
|
+
snFindRecord.forEach((list) => {
|
|
2340
|
+
if (list.length) {
|
|
2341
|
+
const item = list[0];
|
|
2342
|
+
if (!deleteSet.has(item)) {
|
|
2343
|
+
searchNearRasult.push(item);
|
|
2344
|
+
removeDoorPointsIndex.push(item.doorIndex, item.findDoorIndex);
|
|
2345
|
+
}
|
|
2346
|
+
}
|
|
2347
|
+
});
|
|
2348
|
+
const doors_ = doorPoints.map((item, index2) => {
|
|
2349
|
+
if (removeDoorPointsIndex.includes(index2)) return;
|
|
2350
|
+
if (dxf.doors.length >= 2 && !item.sure) return;
|
|
2351
|
+
const res2 = searchAlongDirection(item);
|
|
2352
|
+
if (res2) return {
|
|
2353
|
+
start: item.point,
|
|
2354
|
+
end: res2.point,
|
|
2355
|
+
index: item.index
|
|
2356
|
+
};
|
|
2357
|
+
const res3 = searchAlongNormalDirection(item);
|
|
2358
|
+
if (res3) return {
|
|
2359
|
+
start: item.point,
|
|
2360
|
+
end: res3.point,
|
|
2361
|
+
index: item.index
|
|
2362
|
+
};
|
|
2363
|
+
}).filter((i) => !!i && i.start.distance(i.end) < doorSearchDistance);
|
|
2364
|
+
doors.push(...doors_);
|
|
2365
|
+
searchNearRasult.forEach((item) => {
|
|
2366
|
+
doors.push({
|
|
2367
|
+
start: doorPoints[item.doorIndex].point,
|
|
2368
|
+
end: doorPoints[item.findDoorIndex].point,
|
|
2369
|
+
index: doorPoints[item.doorIndex].index
|
|
2370
|
+
});
|
|
2371
|
+
});
|
|
2372
|
+
dxf.doorLineSegment.length = 0;
|
|
2373
|
+
doors.forEach((p) => dxf.doorLineSegment.push(new LineSegment(p?.start, p?.end)));
|
|
2374
|
+
dxf.doorLineSegment = dxf.doorLineSegment.filter((i) => {
|
|
2375
|
+
const len = i.length();
|
|
2376
|
+
if (len < 0.4) return false;
|
|
2377
|
+
if (len > 1.2) {
|
|
2378
|
+
this.addData(i.start.clone(), i.end.clone());
|
|
2379
|
+
}
|
|
2380
|
+
return true;
|
|
2381
|
+
});
|
|
2382
|
+
}
|
|
1928
2383
|
}
|
|
1929
2384
|
class DxfSystem extends ComponentManager {
|
|
1930
2385
|
Dxf;
|
|
@@ -2012,11 +2467,10 @@ class WhiteModel extends Component {
|
|
|
2012
2467
|
depth: 2.8,
|
|
2013
2468
|
bevelSize: 0
|
|
2014
2469
|
});
|
|
2015
|
-
const mesh = new THREE.Mesh(geometry);
|
|
2016
|
-
mesh.material = this.material;
|
|
2470
|
+
const mesh = new THREE.Mesh(geometry, this.material);
|
|
2017
2471
|
this.whiteModelGroup.add(mesh);
|
|
2018
2472
|
this.whiteModelLineGroup.add(
|
|
2019
|
-
new THREE.LineSegments(new THREE.EdgesGeometry(geometry), new THREE.LineBasicMaterial({ color:
|
|
2473
|
+
new THREE.LineSegments(new THREE.EdgesGeometry(geometry), new THREE.LineBasicMaterial({ color: 0 }))
|
|
2020
2474
|
);
|
|
2021
2475
|
});
|
|
2022
2476
|
const walls = dxf.originalData.map(({ start, end, insetionArr }) => {
|
|
@@ -2049,41 +2503,87 @@ class WhiteModel extends Component {
|
|
|
2049
2503
|
whiteModelGroup: this.whiteModelGroup
|
|
2050
2504
|
});
|
|
2051
2505
|
}
|
|
2506
|
+
/**
|
|
2507
|
+
* 转为obj
|
|
2508
|
+
* @returns
|
|
2509
|
+
*/
|
|
2052
2510
|
toOBJ() {
|
|
2053
2511
|
return new Promise((resolve) => {
|
|
2054
|
-
|
|
2512
|
+
this.material.opacity = 1;
|
|
2513
|
+
this.material.needsUpdate = true;
|
|
2514
|
+
setTimeout(() => {
|
|
2515
|
+
resolve(exporter.parse(this.whiteModelGroup));
|
|
2516
|
+
this.material.opacity = 0.8;
|
|
2517
|
+
this.material.transparent = true;
|
|
2518
|
+
}, 20);
|
|
2055
2519
|
});
|
|
2056
2520
|
}
|
|
2057
|
-
|
|
2521
|
+
/**
|
|
2522
|
+
* 转为 glb
|
|
2523
|
+
* @param binary
|
|
2524
|
+
* @returns
|
|
2525
|
+
*/
|
|
2526
|
+
toGltf(binary = true) {
|
|
2058
2527
|
return new Promise((resolve) => {
|
|
2059
2528
|
this.material.opacity = 1;
|
|
2060
2529
|
this.material.needsUpdate = true;
|
|
2061
|
-
setTimeout(() => {
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2530
|
+
setTimeout(async () => {
|
|
2531
|
+
if (typeof window === "object") {
|
|
2532
|
+
glbExporter.parse(this.whiteModelGroup.children, (gltf) => {
|
|
2533
|
+
resolve(gltf);
|
|
2534
|
+
this.material.opacity = 0.8;
|
|
2535
|
+
this.material.transparent = true;
|
|
2536
|
+
}, () => {
|
|
2537
|
+
resolve(void 0);
|
|
2538
|
+
}, {
|
|
2539
|
+
binary
|
|
2540
|
+
});
|
|
2541
|
+
} else if (typeof global !== "function") {
|
|
2542
|
+
try {
|
|
2543
|
+
const obj2gltf = await include("obj2gltf", true);
|
|
2544
|
+
const fs = await include("fs", false);
|
|
2545
|
+
const obj = await this.toOBJ();
|
|
2546
|
+
fs.writeFileSync(this.uuid, obj);
|
|
2547
|
+
const result = await obj2gltf(this.uuid, {
|
|
2548
|
+
binary
|
|
2549
|
+
});
|
|
2550
|
+
fs.unlinkSync(this.uuid);
|
|
2551
|
+
if (binary) resolve(result);
|
|
2552
|
+
else resolve(JSON.stringify(result));
|
|
2553
|
+
} catch (error) {
|
|
2554
|
+
resolve(void 0);
|
|
2555
|
+
console.log(error);
|
|
2556
|
+
}
|
|
2557
|
+
}
|
|
2558
|
+
}, 20);
|
|
2072
2559
|
});
|
|
2073
2560
|
}
|
|
2561
|
+
/**
|
|
2562
|
+
* 转为 OBJBlob
|
|
2563
|
+
* @returns
|
|
2564
|
+
*/
|
|
2074
2565
|
async toOBJBlob() {
|
|
2075
2566
|
const buffer = await this.toOBJ();
|
|
2076
2567
|
if (buffer) {
|
|
2077
2568
|
return new Blob([buffer], { type: "application/octet-stream" });
|
|
2078
2569
|
}
|
|
2079
2570
|
}
|
|
2080
|
-
|
|
2081
|
-
|
|
2571
|
+
/**
|
|
2572
|
+
* 转为 GltfBlob
|
|
2573
|
+
* @returns
|
|
2574
|
+
*/
|
|
2575
|
+
async toGltfBlob(binary = true) {
|
|
2576
|
+
const buffer = await this.toGltf(binary);
|
|
2082
2577
|
if (buffer) {
|
|
2083
2578
|
return new Blob([buffer], { type: "application/octet-stream" });
|
|
2084
2579
|
}
|
|
2085
2580
|
}
|
|
2086
|
-
|
|
2581
|
+
/**
|
|
2582
|
+
* 下载 OBJ
|
|
2583
|
+
* @param filename
|
|
2584
|
+
* @returns
|
|
2585
|
+
*/
|
|
2586
|
+
async downloadOBJ(filename) {
|
|
2087
2587
|
if (typeof window !== "undefined") {
|
|
2088
2588
|
const blob = await this.toOBJBlob();
|
|
2089
2589
|
if (!blob) return;
|
|
@@ -2094,32 +2594,29 @@ class WhiteModel extends Component {
|
|
|
2094
2594
|
} else if (typeof global !== "undefined") {
|
|
2095
2595
|
const buffer = await this.toOBJ();
|
|
2096
2596
|
if (buffer) {
|
|
2097
|
-
const
|
|
2098
|
-
const { default: fs } = await import(
|
|
2099
|
-
/* @vite-ignore */
|
|
2100
|
-
packageName
|
|
2101
|
-
);
|
|
2597
|
+
const fs = await include("fs", false);
|
|
2102
2598
|
fs.writeFileSync(filename, buffer);
|
|
2103
2599
|
}
|
|
2104
2600
|
}
|
|
2105
2601
|
}
|
|
2106
|
-
|
|
2602
|
+
/**
|
|
2603
|
+
* 下载 Gltf
|
|
2604
|
+
* @param filename
|
|
2605
|
+
* @returns
|
|
2606
|
+
*/
|
|
2607
|
+
async downloadGltf(filename, binary = true) {
|
|
2107
2608
|
if (typeof window !== "undefined") {
|
|
2108
|
-
const blob = await this.
|
|
2609
|
+
const blob = await this.toGltfBlob(binary);
|
|
2109
2610
|
if (!blob) return;
|
|
2110
2611
|
const a = document.createElement("a");
|
|
2111
2612
|
a.href = URL.createObjectURL(blob);
|
|
2112
2613
|
a.download = filename;
|
|
2113
2614
|
a.click();
|
|
2114
2615
|
} else if (typeof global !== "undefined") {
|
|
2115
|
-
const buffer = await this.
|
|
2616
|
+
const buffer = await this.toGltf(binary);
|
|
2116
2617
|
if (buffer) {
|
|
2117
|
-
const
|
|
2118
|
-
|
|
2119
|
-
/* @vite-ignore */
|
|
2120
|
-
packageName
|
|
2121
|
-
);
|
|
2122
|
-
fs.writeFileSync(filename, buffer);
|
|
2618
|
+
const fs = await include("fs", false);
|
|
2619
|
+
fs.writeFileSync(filename, binary ? buffer : Buffer.from(buffer));
|
|
2123
2620
|
}
|
|
2124
2621
|
}
|
|
2125
2622
|
}
|
|
@@ -2236,8 +2733,10 @@ class DxfLineModel extends Component {
|
|
|
2236
2733
|
this.dxfLineModel.clear();
|
|
2237
2734
|
const dxfArray = dxf.to3DArray(1 / dxf.scale);
|
|
2238
2735
|
this.dxfLineModel.geometry = new THREE.BufferGeometry().setAttribute("position", new THREE.BufferAttribute(dxfArray, 3, true));
|
|
2239
|
-
const doorsArray = new Float32Array(
|
|
2240
|
-
|
|
2736
|
+
const doorsArray = new Float32Array(
|
|
2737
|
+
dxf.doorLineSegment.flatMap(({ start, end }) => [start.x, start.y, dxf.originalZAverage * 0.99, end.x, end.y, dxf.originalZAverage * 0.99])
|
|
2738
|
+
).map((n) => n / dxf.scale);
|
|
2739
|
+
const doorsColorArray = new Float32Array(dxf.doorLineSegment.flatMap(() => [1, 0, 0, 0, 1, 0]));
|
|
2241
2740
|
this.dxfDoorsLineModel.geometry = new THREE.BufferGeometry().setAttribute("position", new THREE.BufferAttribute(doorsArray, 3, true)).setAttribute("color", new THREE.BufferAttribute(doorsColorArray, 3));
|
|
2242
2741
|
}
|
|
2243
2742
|
}
|