build-dxf 0.0.14 → 0.0.16

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 CHANGED
@@ -29,7 +29,7 @@ detailsPoint.addEventListener("handleSuccess", () => {
29
29
  // 下载白模,obj格式
30
30
  whiteModel.downloadOBJ("001.obj")
31
31
  // 下载白模,gltf或glb格式, 第二个参数为true时是glb,默认为true
32
- whiteModel.downloadGltf("001.obj", true)
32
+ whiteModel.downloadGltf("001.glb", true)
33
33
 
34
34
  // desPoints 为射线点集合,根据需要使用
35
35
  console.log("handleSuccess", detailsPoint.desPoints)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "build-dxf",
3
- "version": "0.0.14",
3
+ "version": "0.0.16",
4
4
  "description": "线段构建双线墙壁的dxf版本",
5
5
  "main": "./src/index.js",
6
6
  "types": "./src/index.d.ts",
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
@@ -817,8 +1072,8 @@ function pathToLines(path) {
817
1072
  const lineSegments = [];
818
1073
  for (let i = 0; i < path.length; i++) {
819
1074
  lineSegments.push(new LineSegment(
820
- path[i],
821
- path[(i + 1) % path.length]
1075
+ path[i].clone(),
1076
+ path[(i + 1) % path.length].clone()
822
1077
  ));
823
1078
  }
824
1079
  return lineSegments;
@@ -841,6 +1096,7 @@ class Dxf extends Component {
841
1096
  pointsGroups = [];
842
1097
  wallsGroup = [];
843
1098
  doors = [];
1099
+ doorLineSegment = [];
844
1100
  lineSegments = [];
845
1101
  originalZAverage = 0;
846
1102
  static EndType = {
@@ -869,7 +1125,7 @@ class Dxf extends Component {
869
1125
  super();
870
1126
  this.width = width;
871
1127
  this.scale = scale;
872
- this.shortLine = width * 0.8;
1128
+ this.shortLine = width * 0.4;
873
1129
  }
874
1130
  /**
875
1131
  * 设置
@@ -1003,6 +1259,28 @@ class Dxf extends Component {
1003
1259
  }
1004
1260
  return linePaths;
1005
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
+ }
1006
1284
  /** etOpenRound 去除毛刺
1007
1285
  * @description 检查连续的短线段数量,去除合并后产生的毛刺
1008
1286
  */
@@ -1039,44 +1317,22 @@ class Dxf extends Component {
1039
1317
  }
1040
1318
  return filterLines;
1041
1319
  }
1042
- epsilon = 1e-6;
1043
- // 距离阈值,用于比较点
1044
1320
  /**
1045
- * 移除共线点
1046
- * @param path
1047
- * @returns
1321
+ * 线段矫直, 线段中心突刺
1322
+ * @description 突变长度小于墙体宽度,该线段可能为突起线段,
1323
+ * @description 判断后续第2线段与上一条线段是否方向相同,相同就为突刺
1048
1324
  */
1049
- removeCollinearPoints(path) {
1325
+ lineSegmentStraightening(path) {
1050
1326
  for (let i = 0; i < path.length; i++) {
1051
1327
  const p1 = path[i];
1052
1328
  const p2 = path[(i + 1) % path.length];
1053
1329
  if (p1.distance(p2) > this.shortLine) {
1054
- path.push(...path.slice(0, i));
1055
- path.splice(0, i);
1330
+ path.push(...path.slice(0, i + 1));
1331
+ path.splice(0, i + 1);
1056
1332
  break;
1057
1333
  }
1058
1334
  }
1059
- if (path.length < 3) return path;
1060
- const cleanedPath = [];
1061
- const n = path.length;
1062
- for (let i = 0; i < n; i++) {
1063
- const p1 = path[i];
1064
- const p2 = path[(i + 1) % n];
1065
- const p3 = path[(i + 2) % n];
1066
- const cross = (p2.X - p1.X) * (p3.Y - p2.Y) - (p2.Y - p1.Y) * (p3.X - p2.X);
1067
- if (Math.abs(cross) > this.epsilon) {
1068
- cleanedPath.push(p1);
1069
- }
1070
- }
1071
- return cleanedPath;
1072
- }
1073
- /**
1074
- * 线段矫直, 线段中心突刺
1075
- * @description 突变长度小于墙体宽度,该线段可能为突起线段,
1076
- * @description 判断后续第2线段与上一条线段是否方向相同,相同就为突刺
1077
- */
1078
- lineSegmentStraightening(path) {
1079
- const lines = pathToLines(path), filterLines = [lines[0]];
1335
+ const lines = this.mergeSameDirectionLine(pathToLines(path)), filterLines = [lines[0]];
1080
1336
  for (let i = 1; i < lines.length; i++) {
1081
1337
  const line = lines[i];
1082
1338
  const preLine = lines[(lines.length + i - 1) % lines.length];
@@ -1092,15 +1348,16 @@ class Dxf extends Component {
1092
1348
  continue;
1093
1349
  }
1094
1350
  const line2 = lines[i + 2];
1095
- if (line2 && preLine.includedAngle(line2) < 1) {
1351
+ if (line2 && preLine.includedAngle(line2) < 2) {
1096
1352
  i = i + 2;
1097
1353
  filterLines.push(line2);
1098
1354
  } else filterLines.push(line);
1099
1355
  }
1100
- return filterLines.length > 3 ? linesToPath(filterLines) : [];
1356
+ return filterLines.length > 3 ? linesToPath(this.mergeSameDirectionLine(filterLines)) : [];
1101
1357
  }
1102
1358
  /**
1103
1359
  * 移除短线段
1360
+ * @todo 根据线段两端线段长度,选取参照物_|▔▔
1104
1361
  * @param path
1105
1362
  */
1106
1363
  removeShortLine(path, shortLine = this.shortLine) {
@@ -1153,9 +1410,9 @@ class Dxf extends Component {
1153
1410
  offset.Execute(solutions, this.width / 2 * scale);
1154
1411
  this.wallsGroup = solutions.map((ps) => {
1155
1412
  let path = ps.map((p) => Point.from(p).divisionScalar(scale));
1156
- path = this.removeCollinearPoints(path);
1157
1413
  path = this.lineSegmentStraightening(path);
1158
1414
  if (endType == Dxf.EndType.etOpenSquare) path = this.squareRemoveBurr(path);
1415
+ path = this.removeShortLine(path);
1159
1416
  return path;
1160
1417
  });
1161
1418
  this.dispatchEvent({
@@ -1179,6 +1436,32 @@ class Dxf extends Component {
1179
1436
  });
1180
1437
  return new Float32Array(array);
1181
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
+ }
1182
1465
  /**
1183
1466
  * 将点云结构转换为string
1184
1467
  */
@@ -1186,12 +1469,37 @@ class Dxf extends Component {
1186
1469
  const d = new Drawing();
1187
1470
  d.setUnits("Millimeters");
1188
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
+ }
1189
1475
  this.wallsGroup.forEach((points) => {
1190
1476
  for (let i = 0; i < points.length; i++) {
1191
1477
  const point1 = points[i];
1192
1478
  const nextIndex = i === points.length - 1 ? 0 : i + 1;
1193
1479
  const point2 = points[nextIndex];
1194
- d.drawLine(point1.X * s, point1.Y * s, point2.X * s, point2.Y * s);
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));
1195
1503
  }
1196
1504
  });
1197
1505
  return d.toDxfString();
@@ -1291,141 +1599,6 @@ class Variable extends Component {
1291
1599
  if (key in this) return this[key];
1292
1600
  }
1293
1601
  }
1294
- class Rectangle {
1295
- points;
1296
- get path() {
1297
- return this.points.flatMap((p, i) => {
1298
- const np = this.points[(i + 1) % this.points.length];
1299
- return [p.x, p.y, 0, np.x, np.y, 0];
1300
- });
1301
- }
1302
- constructor(points) {
1303
- if (points.length !== 4) {
1304
- throw new Error("Rectangle must be defined by exactly 4 points");
1305
- }
1306
- this.points = points;
1307
- }
1308
- /**
1309
- * 判断线段是否与矩形相交
1310
- * @param line 线段
1311
- * @returns 是否与矩形相交
1312
- */
1313
- intersectLineSegment(line) {
1314
- if (line.points.length !== 2) {
1315
- throw new Error("LineSegment must have exactly 2 points");
1316
- }
1317
- const [p1, p2] = line.points;
1318
- const doIntersect = (l1p1, l1p2, l2p1, l2p2) => {
1319
- const orientation = (p, q, r) => {
1320
- const val = (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y);
1321
- if (val === 0) return 0;
1322
- return val > 0 ? 1 : 2;
1323
- };
1324
- const onSegment = (p, q, r) => {
1325
- 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);
1326
- };
1327
- const o1 = orientation(l1p1, l1p2, l2p1);
1328
- const o2 = orientation(l1p1, l1p2, l2p2);
1329
- const o3 = orientation(l2p1, l2p2, l1p1);
1330
- const o4 = orientation(l2p1, l2p2, l1p2);
1331
- if (o1 !== o2 && o3 !== o4) return true;
1332
- if (o1 === 0 && onSegment(l1p1, l2p1, l1p2)) return true;
1333
- if (o2 === 0 && onSegment(l1p1, l2p2, l1p2)) return true;
1334
- if (o3 === 0 && onSegment(l2p1, l1p1, l2p2)) return true;
1335
- if (o4 === 0 && onSegment(l2p1, l1p2, l2p2)) return true;
1336
- return false;
1337
- };
1338
- for (let i = 0; i < 4; i++) {
1339
- const rectP1 = this.points[i];
1340
- const rectP2 = this.points[(i + 1) % 4];
1341
- if (doIntersect(p1, p2, rectP1, rectP2)) {
1342
- return true;
1343
- }
1344
- }
1345
- if (this.containsLineSegment(line)) {
1346
- return true;
1347
- }
1348
- return false;
1349
- }
1350
- /**
1351
- * 判断线段是否完全位于矩形内部
1352
- * @param line 线段
1353
- * @returns 是否完全在矩形内部
1354
- */
1355
- containsLineSegment(line) {
1356
- if (line.points.length !== 2) {
1357
- throw new Error("LineSegment must have exactly 2 points");
1358
- }
1359
- const isPointInRectangle = (point) => {
1360
- let sign = 0;
1361
- for (let i = 0; i < 4; i++) {
1362
- const p1 = this.points[i];
1363
- const p2 = this.points[(i + 1) % 4];
1364
- const edge = { x: p2.x - p1.x, y: p2.y - p1.y };
1365
- const toPoint = { x: point.x - p1.x, y: point.y - p1.y };
1366
- const cross = edge.x * toPoint.y - edge.y * toPoint.x;
1367
- if (cross === 0) {
1368
- const t = edge.x !== 0 ? (point.x - p1.x) / edge.x : (point.y - p1.y) / edge.y;
1369
- if (t >= 0 && t <= 1) return true;
1370
- } else {
1371
- const currentSign = cross > 0 ? 1 : -1;
1372
- if (sign === 0) sign = currentSign;
1373
- if (sign !== currentSign) return false;
1374
- }
1375
- }
1376
- return true;
1377
- };
1378
- return isPointInRectangle(line.points[0]) && isPointInRectangle(line.points[1]);
1379
- }
1380
- /**
1381
- * 判断点是否完全位于矩形内部
1382
- * @param point
1383
- */
1384
- containsPoint(point) {
1385
- let positiveCount = 0;
1386
- let negativeCount = 0;
1387
- for (let i = 0; i < 4; i++) {
1388
- const p1 = this.points[i];
1389
- const p2 = this.points[(i + 1) % 4];
1390
- const cross = (p2.x - p1.x) * (point.y - p1.y) - (p2.y - p1.y) * (point.x - p1.x);
1391
- if (cross > 0) positiveCount++;
1392
- else if (cross < 0) negativeCount++;
1393
- else return false;
1394
- }
1395
- return positiveCount === 4 || negativeCount === 4;
1396
- }
1397
- /**
1398
- *
1399
- * @returns
1400
- */
1401
- toBox() {
1402
- let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
1403
- this.points.forEach((p) => {
1404
- maxX = Math.max(p.x, maxX);
1405
- minX = Math.min(p.x, minX);
1406
- maxY = Math.max(p.x, maxY);
1407
- minY = Math.min(p.x, minY);
1408
- });
1409
- return new Box2(minX, maxX, minY, maxY);
1410
- }
1411
- /**
1412
- *
1413
- * @param line
1414
- * @param width
1415
- * @returns
1416
- */
1417
- static fromByLineSegment(line, width = 0.1, horizontal = false, hScale = 0.5) {
1418
- 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();
1419
- const offsetX = normal.x * width * 0.5;
1420
- const offsetY = normal.y * width * 0.5;
1421
- return new Rectangle([
1422
- new Point(p1.x + offsetX, p1.y + offsetY).add(nDirect),
1423
- new Point(p2.x + offsetX, p2.y + offsetY).add(pDirect),
1424
- new Point(p2.x - offsetX, p2.y - offsetY).add(pDirect),
1425
- new Point(p1.x - offsetX, p1.y - offsetY).add(nDirect)
1426
- ]);
1427
- }
1428
- }
1429
1602
  class Quadtree {
1430
1603
  bounds;
1431
1604
  // 包围盒
@@ -1623,6 +1796,28 @@ class Quadtree {
1623
1796
  }
1624
1797
  return result;
1625
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
+ }
1626
1821
  /**
1627
1822
  * 包围盒转换为数组
1628
1823
  * @param array
@@ -1780,6 +1975,7 @@ class LineAnalysis extends Component {
1780
1975
  this.Dxf = parent.findComponentByType(Dxf);
1781
1976
  this.Variable = this.parent?.findComponentByType(Variable);
1782
1977
  this.Dxf.addEventListener("setDta", this.lineAnalysis.bind(this));
1978
+ this.Dxf.addEventListener("createGroup", this.doorsAnalysis.bind(this));
1783
1979
  }
1784
1980
  /**
1785
1981
  *
@@ -1847,8 +2043,9 @@ class LineAnalysis extends Component {
1847
2043
  buildVirtualGrid() {
1848
2044
  const dxf = this.Dxf;
1849
2045
  const pointVirtualGrid = new PointVirtualGrid();
1850
- dxf.originalData.forEach((d, index2) => {
1851
- const [p1, p2] = [Point.from(d.start), Point.from(d.end)];
2046
+ dxf.lineSegments.forEach((d, index2) => {
2047
+ if (d.userData?.isDoor) return;
2048
+ const [p1, p2] = [d.start, d.end];
1852
2049
  pointVirtualGrid.insert(p1, { index: index2, type: "start" });
1853
2050
  pointVirtualGrid.insert(p2, { index: index2, type: "end" });
1854
2051
  });
@@ -1873,6 +2070,7 @@ class LineAnalysis extends Component {
1873
2070
  this.lineSegmentList = lineSegmentList;
1874
2071
  }
1875
2072
  resultList = [];
2073
+ mergeWallLines = [];
1876
2074
  /** 线段分析
1877
2075
  * @description 判断两条线段距离是否较短且趋近平行,然后查找两条线段的重合部分的投影线,以此判断两根线是否需要合并
1878
2076
  * @param data
@@ -1897,7 +2095,7 @@ class LineAnalysis extends Component {
1897
2095
  });
1898
2096
  this.appendLineSegmentList.length = 0;
1899
2097
  resultList.forEach(this.createRectangle.bind(this));
1900
- this.resultList = [];
2098
+ this.resultList = resultList;
1901
2099
  }
1902
2100
  /** 线段投影分析
1903
2101
  * @param index
@@ -1929,10 +2127,309 @@ class LineAnalysis extends Component {
1929
2127
  project2: p1
1930
2128
  };
1931
2129
  }
1932
- if (!data || data.project.getLength() < 0.01) return;
2130
+ if (!data || data.project.getLength() < 0.2 || data.project2.getLength() < 0.2) return;
1933
2131
  return data;
1934
2132
  }
1935
2133
  }
2134
+ doorSearchNearAngle = 110;
2135
+ doorSearchDistance = 2;
2136
+ doors = [];
2137
+ /**
2138
+ * 门的位置判断
2139
+ */
2140
+ doorsAnalysis() {
2141
+ this.parent?.findComponentByName("Renderer");
2142
+ const dxf = this.Dxf, doorPoints = [], pointVirtualGrid = this.pointVirtualGrid, quadtree = this.quadtree, doorSearchNearAngle = this.doorSearchNearAngle, doorSearchDistance = this.doorSearchDistance, doors = [];
2143
+ const excludePoints = dxf.doors.flatMap((item) => {
2144
+ const index2 = item[4];
2145
+ const doorData = dxf.originalData[index2];
2146
+ if (doorData.drawDoorData) {
2147
+ const point = Point.from(doorData.drawDoorData.start);
2148
+ const direct = Point.from(doorData.drawDoorData.n);
2149
+ const resList = pointVirtualGrid.queryPoint(point).filter((res) => {
2150
+ if (res.userData?.index === index2) return false;
2151
+ const line = dxf.lineSegments[res.userData?.index];
2152
+ const direct2 = line.direction();
2153
+ if (line.start.equal(point)) direct2.multiplyScalar(-1);
2154
+ const angle = direct.angleBetween(direct2, "angle");
2155
+ return angle > 80 || angle < 10;
2156
+ });
2157
+ if (resList.length) {
2158
+ const index22 = resList[0].userData?.index;
2159
+ doorPoints.push({
2160
+ line: dxf.lineSegments[index22],
2161
+ point: Point.from(doorData.drawDoorData.start),
2162
+ index: index22,
2163
+ direct,
2164
+ sure: true
2165
+ });
2166
+ return [point];
2167
+ }
2168
+ }
2169
+ return [];
2170
+ });
2171
+ const excludeIndexMap = /* @__PURE__ */ new Map();
2172
+ this.resultList.flatMap((p) => {
2173
+ 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;
2174
+ if (excludeIndexMap.has(p.sourceIndex)) {
2175
+ if (excludeIndexMap.get(p.sourceIndex) != mode0) excludeIndexMap.set(p.sourceIndex, -1);
2176
+ } else excludeIndexMap.set(p.sourceIndex, mode0);
2177
+ if (excludeIndexMap.has(p.targetIndex)) {
2178
+ if (excludeIndexMap.get(p.targetIndex) != mode1) excludeIndexMap.set(p.targetIndex, -1);
2179
+ } else excludeIndexMap.set(p.targetIndex, mode1);
2180
+ });
2181
+ dxf.lineSegments.forEach((line, i) => {
2182
+ if (line.userData?.isDoor) return;
2183
+ const excludeMode = excludeIndexMap.get(i);
2184
+ if (excludeMode === -1) return;
2185
+ line.points.forEach((p, j) => {
2186
+ if (excludeMode === j) return;
2187
+ if (excludePoints.find((p1) => p1.equal(p))) return;
2188
+ const res = this.pointVirtualGrid.queryPoint(p).filter((d) => d.userData?.index !== i);
2189
+ if (res.length === 0) {
2190
+ doorPoints.push({
2191
+ line,
2192
+ point: p,
2193
+ index: i
2194
+ });
2195
+ }
2196
+ });
2197
+ });
2198
+ function searchNearby({ point, line, index: index2 }, doorIndex, record2) {
2199
+ const direct = line.direction();
2200
+ if (line.start === point) direct.multiplyScalar(-1);
2201
+ const res = pointVirtualGrid.queryCircle(point, doorSearchDistance).filter(
2202
+ (r) => (
2203
+ // 不能是自己
2204
+ r.userData?.index !== index2 && // 存在于可疑点位内,且不能是已知门的点位
2205
+ !!doorPoints.find((p) => !p.sure && p.point === r.point)
2206
+ )
2207
+ ).sort((a, b) => a.point.distance(point) - b.point.distance(point));
2208
+ const list = [];
2209
+ for (let i = 0; i < res.length; i++) {
2210
+ const doorIndex2 = doorPoints.findIndex((p) => p.point === res[i].point);
2211
+ if (record2.has(`${doorIndex}.${doorIndex2}`)) continue;
2212
+ record2.add(`${doorIndex}.${doorIndex2}`);
2213
+ record2.add(`${doorIndex2}.${doorIndex}`);
2214
+ const targetPoint = res[i].point, line2 = new LineSegment(point.clone(), targetPoint.clone()), angle = line2.direction().angleBetween(direct, "angle");
2215
+ if (angle < doorSearchNearAngle) {
2216
+ const direct2 = doorPoints[doorIndex2].line.direction();
2217
+ const line22 = dxf.lineSegments[res[i].userData?.index];
2218
+ if (line22.start.equal(res[i].point)) direct2.multiplyScalar(-1);
2219
+ const angle2 = line2.direction().multiplyScalar(-1).angleBetween(direct2, "angle");
2220
+ if (angle2 < doorSearchNearAngle) {
2221
+ if (!quadtree.queryLineSegment(line2).length) {
2222
+ list.push({
2223
+ findData: res[i],
2224
+ findDoorIndex: doorIndex2,
2225
+ findAngle: angle2,
2226
+ doorAngle: angle,
2227
+ doorLine: line2,
2228
+ doorIndex
2229
+ });
2230
+ }
2231
+ }
2232
+ }
2233
+ }
2234
+ return list;
2235
+ }
2236
+ function searchAlongDirection({ point, line }) {
2237
+ const direct = line.direction();
2238
+ if (line.start === point) direct.multiplyScalar(-1);
2239
+ const endPoint = point.clone().add(direct.clone().multiplyScalar(doorSearchDistance)), rline = new LineSegment(point.clone(), endPoint), result = quadtree.queryLineSegment(rline).map((l) => {
2240
+ const res = l.line.getIntersection(rline);
2241
+ return {
2242
+ point: res,
2243
+ line: l.line
2244
+ };
2245
+ }).filter((i) => i.point).sort((a, b) => point.distance(a.point) - point.distance(b.point));
2246
+ if (result.length) {
2247
+ const item = result[0];
2248
+ if (Math.abs(90 - item.line.direction().angleBetween(direct, "angle")) < 5) {
2249
+ return item;
2250
+ }
2251
+ }
2252
+ }
2253
+ function searchAlongNormalDirection({ point, line, index: index2 }) {
2254
+ const direct = line.direction();
2255
+ if (line.start === point) direct.multiplyScalar(-1);
2256
+ const normal = line.start.normal(line.end);
2257
+ const prePoint = line.start.clone();
2258
+ if (line.start === point) prePoint.copy(line.end);
2259
+ const result = pointVirtualGrid.queryPoint(prePoint).filter((r) => r.userData?.index !== index2);
2260
+ for (let i = 0; i < result.length; i++) {
2261
+ const element = result[i];
2262
+ const l = dxf.lineSegments[element.userData?.index ?? 0];
2263
+ const d1 = l.direction();
2264
+ if (l.start === element.point) direct.multiplyScalar(-1);
2265
+ const angle = d1.angleBetween(normal) / (Math.PI / 180);
2266
+ if (angle > 90) {
2267
+ normal.multiplyScalar(-1);
2268
+ break;
2269
+ }
2270
+ }
2271
+ const rline3 = new LineSegment(point.clone(), point.clone().add(normal.multiplyScalar(doorSearchDistance)));
2272
+ const r3 = quadtree.queryLineSegment(rline3).map((l) => {
2273
+ const res = l.line.getIntersection(rline3);
2274
+ return {
2275
+ point: res,
2276
+ line: l.line
2277
+ };
2278
+ }).filter((i) => i.point).sort((a, b) => point.distance(a.point) - point.distance(b.point));
2279
+ if (r3.length) {
2280
+ const item = r3[0];
2281
+ if (Math.abs(90 - item.line.direction().angleBetween(normal, "angle")) < 5) {
2282
+ return item;
2283
+ }
2284
+ }
2285
+ }
2286
+ function deepSearchNearHandle(index2, snFindRecord2, list, record2, other) {
2287
+ record2.add(`${index2}`);
2288
+ const newList = [];
2289
+ if (other) newList.push(other);
2290
+ for (let i = 0; i < list.length; i++) {
2291
+ const item = list[i];
2292
+ if (snFindRecord2.has(item.findDoorIndex)) {
2293
+ const list2 = snFindRecord2.get(item.findDoorIndex);
2294
+ if (deepSearchNearHandle(item.findDoorIndex, snFindRecord2, list2, record2, item)) newList.push(item);
2295
+ } else newList.push(item);
2296
+ }
2297
+ newList.sort((a, b) => a.doorLine.length() - b.doorLine.length());
2298
+ if (other && newList[0] === other) {
2299
+ list.splice(0);
2300
+ return true;
2301
+ }
2302
+ list.splice(1);
2303
+ return false;
2304
+ }
2305
+ const record = /* @__PURE__ */ new Set();
2306
+ const snFindRecord = /* @__PURE__ */ new Map();
2307
+ doorPoints.map((p, index2) => {
2308
+ if (dxf.doors.length >= 2 && !p.sure) return;
2309
+ const list = searchNearby(p, index2, record);
2310
+ if (list.length) snFindRecord.set(index2, list);
2311
+ });
2312
+ record.clear();
2313
+ const temMap = /* @__PURE__ */ new Map();
2314
+ snFindRecord.forEach((list, key) => {
2315
+ if (!record.has(`${key}`) && list.length) {
2316
+ deepSearchNearHandle(key, snFindRecord, list, record);
2317
+ }
2318
+ if (list.length) {
2319
+ const item = list[0];
2320
+ if (!temMap.has(item.doorIndex)) temMap.set(item.doorIndex, []);
2321
+ temMap.get(item.doorIndex)?.push(item);
2322
+ if (!temMap.has(item.findDoorIndex)) temMap.set(item.findDoorIndex, []);
2323
+ temMap.get(item.findDoorIndex)?.push(item);
2324
+ }
2325
+ });
2326
+ const deleteSet = /* @__PURE__ */ new Set();
2327
+ temMap.forEach((list) => {
2328
+ if (list.length > 1) {
2329
+ list.sort((a, b) => a.doorLine.length() - b.doorLine.length());
2330
+ for (let i = 1; i < list.length; i++) deleteSet.add(list[i]);
2331
+ }
2332
+ });
2333
+ const searchNearRasult = [], removeDoorPointsIndex = [];
2334
+ snFindRecord.forEach((list) => {
2335
+ if (list.length) {
2336
+ const item = list[0];
2337
+ if (!deleteSet.has(item)) {
2338
+ searchNearRasult.push(item);
2339
+ removeDoorPointsIndex.push(item.doorIndex, item.findDoorIndex);
2340
+ }
2341
+ }
2342
+ });
2343
+ const doors_ = doorPoints.map((item, index2) => {
2344
+ if (removeDoorPointsIndex.includes(index2)) return;
2345
+ if (dxf.doors.length >= 2 && !item.sure) return;
2346
+ const res2 = searchAlongDirection(item);
2347
+ if (res2) return {
2348
+ start: item.point,
2349
+ end: res2.point,
2350
+ index: item.index
2351
+ };
2352
+ const res3 = searchAlongNormalDirection(item);
2353
+ if (res3) return {
2354
+ start: item.point,
2355
+ end: res3.point,
2356
+ index: item.index
2357
+ };
2358
+ }).filter((i) => !!i && i.start.distance(i.end) < doorSearchDistance);
2359
+ doors.push(...doors_);
2360
+ searchNearRasult.forEach((item, i) => {
2361
+ const start = doorPoints[item.doorIndex].point.clone();
2362
+ const end = doorPoints[item.findDoorIndex].point.clone();
2363
+ const startLine = this.findLongLineSegment(doorPoints[item.doorIndex].line);
2364
+ const endLine = this.findLongLineSegment(doorPoints[item.findDoorIndex].line);
2365
+ const p = startLine.projectPoint(end);
2366
+ if (p) {
2367
+ start.copy(p);
2368
+ const l = new LineSegment(start, end);
2369
+ const angle = endLine.includedAngle(l);
2370
+ if (angle < 10 || angle > 170 || Math.abs(90 - angle) < 10) {
2371
+ doors.push({
2372
+ start,
2373
+ end,
2374
+ index: doorPoints[item.doorIndex].index
2375
+ });
2376
+ }
2377
+ } else {
2378
+ const p2 = endLine.projectPoint(start);
2379
+ if (p2) end.copy(p2);
2380
+ const l = new LineSegment(start, end);
2381
+ const angle = startLine.includedAngle(l);
2382
+ if (angle < 10 || angle > 170 || Math.abs(90 - angle) < 10) {
2383
+ doors.push({
2384
+ start,
2385
+ end,
2386
+ index: doorPoints[item.doorIndex].index
2387
+ });
2388
+ }
2389
+ }
2390
+ });
2391
+ dxf.doorLineSegment.length = 0;
2392
+ doors.forEach((p) => {
2393
+ const line = new LineSegment(p?.start, p?.end);
2394
+ const len = line.length();
2395
+ if (len < 0.4) return;
2396
+ const center = line.center, normal = line.normal();
2397
+ const rLine = new LineSegment(
2398
+ center.clone(),
2399
+ center.clone().add(normal.clone().multiplyScalar(1))
2400
+ );
2401
+ rLine.directionMove(normal, -0.5);
2402
+ const res = this.quadtree?.queryLineSegment(rLine);
2403
+ if (res?.length) return;
2404
+ dxf.doorLineSegment.push(line);
2405
+ });
2406
+ }
2407
+ findLongLineSegment(line) {
2408
+ const resLine = line.clone();
2409
+ const res1 = this.pointVirtualGrid.queryPoint(line.start);
2410
+ const res2 = this.pointVirtualGrid.queryPoint(line.end);
2411
+ for (let i = 0; i < res1.length; i++) {
2412
+ const { userData } = res1[i];
2413
+ const line2 = this.lineSegmentList[userData?.index];
2414
+ if (line2 === line) continue;
2415
+ if (line2 && line2.directionEqual(line)) {
2416
+ if (line2.start.equal(line.start)) resLine.start.copy(line2.end);
2417
+ else resLine.start.copy(line2.start);
2418
+ break;
2419
+ }
2420
+ }
2421
+ for (let i = 0; i < res2.length; i++) {
2422
+ const { userData } = res2[i];
2423
+ const line2 = this.lineSegmentList[userData?.index];
2424
+ if (line2 === line) continue;
2425
+ if (line2 && line2.directionEqual(line)) {
2426
+ if (line2.end.equal(line.end)) resLine.end.copy(line2.start);
2427
+ else resLine.end.copy(line2.end);
2428
+ break;
2429
+ }
2430
+ }
2431
+ return resLine;
2432
+ }
1936
2433
  }
1937
2434
  class DxfSystem extends ComponentManager {
1938
2435
  Dxf;
@@ -2286,8 +2783,10 @@ class DxfLineModel extends Component {
2286
2783
  this.dxfLineModel.clear();
2287
2784
  const dxfArray = dxf.to3DArray(1 / dxf.scale);
2288
2785
  this.dxfLineModel.geometry = new THREE.BufferGeometry().setAttribute("position", new THREE.BufferAttribute(dxfArray, 3, true));
2289
- const doorsArray = new Float32Array(dxf.doors.flatMap(([p1, p2]) => [p1.x, p1.y, dxf.originalZAverage, p2.x, p2.y, dxf.originalZAverage])).map((n) => n / dxf.scale);
2290
- const doorsColorArray = new Float32Array(dxf.doors.flatMap(() => [1, 0, 0, 0, 1, 0]));
2786
+ const doorsArray = new Float32Array(
2787
+ dxf.doorLineSegment.flatMap(({ start, end }) => [start.x, start.y, dxf.originalZAverage * 0.99, end.x, end.y, dxf.originalZAverage * 0.99])
2788
+ ).map((n) => n / dxf.scale);
2789
+ const doorsColorArray = new Float32Array(dxf.doorLineSegment.flatMap(() => [1, 0, 0, 0, 1, 0]));
2291
2790
  this.dxfDoorsLineModel.geometry = new THREE.BufferGeometry().setAttribute("position", new THREE.BufferAttribute(doorsArray, 3, true)).setAttribute("color", new THREE.BufferAttribute(doorsColorArray, 3));
2292
2791
  }
2293
2792
  }
package/src/index2.js CHANGED
@@ -7127,7 +7127,7 @@ class ModelDataRender extends Component {
7127
7127
  group.clear();
7128
7128
  dxf.wallsGroup.forEach((path, j) => {
7129
7129
  path.forEach((p, i) => {
7130
- renderer.createText(`${i}`, p, { color: "#ff00ff", fontSize: "10px" }, group).position.z = dxf.originalZAverage * 0.99;
7130
+ renderer.createText(`${j}-${i}`, p, { color: "#ff00ff", fontSize: "10px" }, group).position.z = dxf.originalZAverage * 0.99;
7131
7131
  });
7132
7132
  });
7133
7133
  }
@@ -23,6 +23,18 @@ export interface OriginalDataItem {
23
23
  };
24
24
  }[];
25
25
  isDoor?: boolean;
26
+ drawDoorData?: {
27
+ start: {
28
+ x: number;
29
+ y: number;
30
+ z: number;
31
+ };
32
+ n: {
33
+ x: number;
34
+ y: number;
35
+ z: number;
36
+ };
37
+ };
26
38
  }
27
39
  /**
28
40
  * [开始点, 结束点, 相交点, 是否是门, 索引]
@@ -61,6 +73,7 @@ export declare class Dxf extends Component<{
61
73
  pointsGroups: Point[][][];
62
74
  wallsGroup: Point[][];
63
75
  doors: DataItem[];
76
+ doorLineSegment: LineSegment[];
64
77
  lineSegments: LineSegment<{
65
78
  isDoor: boolean;
66
79
  }>[];
@@ -108,18 +121,16 @@ export declare class Dxf extends Component<{
108
121
  * @description 处理线路拓扑,使线路有序链接,形成长路径
109
122
  * @param lines
110
123
  */
111
- private lineTopology;
124
+ lineTopology(lines: Point[][]): Point[][];
125
+ /** 合并方向相同的线段
126
+ * @param lines
127
+ * @param errAngle
128
+ */
129
+ private mergeSameDirectionLine;
112
130
  /** etOpenRound 去除毛刺
113
131
  * @description 检查连续的短线段数量,去除合并后产生的毛刺
114
132
  */
115
133
  private squareRemoveBurr;
116
- epsilon: number;
117
- /**
118
- * 移除共线点
119
- * @param path
120
- * @returns
121
- */
122
- removeCollinearPoints(path: Point[]): Point[];
123
134
  /**
124
135
  * 线段矫直, 线段中心突刺
125
136
  * @description 突变长度小于墙体宽度,该线段可能为突起线段,
@@ -128,6 +139,7 @@ export declare class Dxf extends Component<{
128
139
  lineSegmentStraightening(path: Point[]): Point[];
129
140
  /**
130
141
  * 移除短线段
142
+ * @todo 根据线段两端线段长度,选取参照物_|▔▔
131
143
  * @param path
132
144
  */
133
145
  removeShortLine(path: Point[], shortLine?: number): Point[];
@@ -139,6 +151,13 @@ export declare class Dxf extends Component<{
139
151
  * 将点云结构转换为Float32Array
140
152
  */
141
153
  to3DArray(scale: number): Float32Array<ArrayBuffer>;
154
+ /** 获取角度范围
155
+ * @param center
156
+ * @param p1
157
+ * @param p2
158
+ * @returns
159
+ */
160
+ private getArcAngleRange;
142
161
  /**
143
162
  * 将点云结构转换为string
144
163
  */
@@ -64,6 +64,7 @@ export declare class LineAnalysis extends Component {
64
64
  */
65
65
  buildQuadtree(): void;
66
66
  resultList: ProjectionAnalysisResult[];
67
+ mergeWallLines: LineSegment[][];
67
68
  /** 线段分析
68
69
  * @description 判断两条线段距离是否较短且趋近平行,然后查找两条线段的重合部分的投影线,以此判断两根线是否需要合并
69
70
  * @param data
@@ -76,5 +77,13 @@ export declare class LineAnalysis extends Component {
76
77
  * @returns
77
78
  */
78
79
  private projectionAnalysis;
80
+ doorSearchNearAngle: number;
81
+ doorSearchDistance: number;
82
+ doors: LineSegment[];
83
+ /**
84
+ * 门的位置判断
85
+ */
86
+ doorsAnalysis(): void;
87
+ findLongLineSegment(line: LineSegment): LineSegment<Record<string, any>>;
79
88
  }
80
89
  export {};
@@ -1,4 +1,5 @@
1
1
  import { Point } from './Point';
2
+ import { Rectangle } from './Rectangle';
2
3
  /**
3
4
  * 非轴对称线段
4
5
  */
@@ -9,6 +10,33 @@ export declare class LineSegment<T = Record<string, any>> {
9
10
  get start(): Point;
10
11
  get end(): Point;
11
12
  constructor(p1?: Point, p2?: Point);
13
+ /** 膨胀
14
+ * @description 向线段的两个端点分别膨胀 width
15
+ * @param width
16
+ */
17
+ expansion(width: number, direction?: "start" | "end" | "all"): this;
18
+ /** 向前
19
+ * @description 向前移动 width
20
+ * @param width
21
+ */
22
+ forward(width: number): this;
23
+ /** 向前
24
+ * @description 向前移动 width
25
+ * @param width
26
+ */
27
+ backward(width: number): this;
28
+ /**
29
+ * 向指定方向平移
30
+ * @param direct
31
+ * @param size
32
+ */
33
+ directionMove(direct: Point, size: number): this;
34
+ /** 膨胀为矩形
35
+ *
36
+ * @param width
37
+ * @returns {Rectangle}
38
+ */
39
+ expandToRectangle(width?: number): Rectangle;
12
40
  /**
13
41
  * 计算线段的长度
14
42
  * @returns 线段的长度
@@ -19,6 +47,10 @@ export declare class LineSegment<T = Record<string, any>> {
19
47
  * @returns
20
48
  */
21
49
  direction(): Point;
50
+ /**
51
+ * 获取发向量
52
+ */
53
+ normal(): Point;
22
54
  /**
23
55
  * 线段长度
24
56
  * @returns
@@ -30,6 +62,12 @@ export declare class LineSegment<T = Record<string, any>> {
30
62
  * @returns 投影并裁剪后的线段
31
63
  */
32
64
  projectLineSegment(line: LineSegment): LineSegment;
65
+ /**
66
+ * 计算一条线段在另一条直线上的投影,并裁剪超出目标线段的部分
67
+ * @param line 要投影的线段
68
+ * @returns 投影并裁剪后的线段
69
+ */
70
+ projectPoint(p1: Point): Point | null;
33
71
  /**
34
72
  * 判断线段是与线段相交
35
73
  * @param line
@@ -89,13 +89,20 @@ export declare class Point {
89
89
  * @returns
90
90
  */
91
91
  dot(point: Point): number;
92
+ /** 求两个点叉积
93
+ * @description 如果叉积大于 0,a 到 b 为逆时针方向。
94
+ * @description 如果叉积小于 0,a 到 b 为顺时针方向。
95
+ * @param point
96
+ * @returns
97
+ */
98
+ cross(point: Point): number;
92
99
  /** 计算两个向量夹角
93
100
  * @description 公式:a · b = |a| × |b| × cosθ
94
101
  * @description 结果为0(0度),两个向量方向一致,结果为3.1415926(180度, PI),两个向量方向相反
95
102
  * @param point
96
103
  * @returns
97
104
  */
98
- angleBetween(point: Point): number;
105
+ angleBetween(point: Point, type?: 'radian' | 'cos' | 'angle', angle?: "180" | "360"): number;
99
106
  /** 获取向量长度
100
107
  */
101
108
  length(): number;
@@ -50,6 +50,12 @@ export declare class Quadtree<T = any> {
50
50
  * @returns 相交的节点数组
51
51
  */
52
52
  queryRect(rectangle: Rectangle): QuadtreeNode<T>[];
53
+ /**
54
+ * 查询与线段相交的线段节点
55
+ * @param lineSegment 线段
56
+ * @returns 相交的节点数组
57
+ */
58
+ queryLineSegment(lineSegment: LineSegment): QuadtreeNode<T>[];
53
59
  /**
54
60
  * 包围盒转换为数组
55
61
  * @param array
@@ -7,6 +7,7 @@ import { Point } from './Point';
7
7
  export declare class Rectangle {
8
8
  points: Point[];
9
9
  get path(): number[];
10
+ path2D(callback?: (point1: Point, point2: Point) => void): number[];
10
11
  constructor(points: Point[]);
11
12
  /**
12
13
  * 判断线段是否与矩形相交