build-dxf 0.0.11 → 0.0.13

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
@@ -7,10 +7,7 @@ npm i build-dxf
7
7
 
8
8
  # node 使用示例
9
9
  ```typescript
10
-
11
- import { DxfSystem } from "build-dxf"
12
- import { ModelDataPlugin, components } from "build-dxf/ModelDataPlugin"
13
-
10
+ import { DxfSystem, ModelDataPlugin, components } from "build-dxf"
14
11
  /**
15
12
  * json 文件格式为{ start: {x: number, y: number}, end: {x: number, y: number } , insetionArr: {index: number}[] }[]
16
13
  */
@@ -27,14 +24,35 @@ const detailsPoint = dxfSystem.findComponentByType(components.DetailsPoint)
27
24
 
28
25
  // 监听详情点数据处理完成
29
26
  detailsPoint.addEventListener("handleSuccess", () => {
30
- console.log("handleSuccess", detailsPoint.desPoints)
31
- })
32
-
33
- dxfSystem.Dxf.set(path).then(async ()=> {
34
- dxfSystem.Dxf.lineOffset()
27
+ // 下载dxf文件
35
28
  dxfSystem.Dxf.download("01.dxf")
29
+ // 下载白模,obj格式
36
30
  whiteModel.download("001.obj")
31
+
32
+ // desPoints 为射线点集合,根据需要使用
33
+ console.log("handleSuccess", detailsPoint.desPoints)
37
34
  })
38
35
 
36
+ dxfSystem.Dxf.set(path).then(()=> dxfSystem.Dxf.lineOffset())
39
37
  detailsPoint.set("./json/dp8.json")
38
+
39
+ ```
40
+
41
+
42
+ # 浏览器 使用示例
43
+ ```html
44
+ <template>
45
+ <div class="w-[100vw] h-[100vh]">
46
+ <Dxf3DView :lines="(data as any)" :details-point="dp8" />
47
+ </div>
48
+ </template>
49
+
50
+ <style scoped></style>
51
+
52
+ <script setup lang="ts">
53
+ import { Dxf3DView } from "build-dxf/RenderPlugin"
54
+ import "build-dxf/index.css"
55
+ import data from "../data/d4.json"
56
+ import dp8 from "../data/dp8.json"
57
+ </script>
40
58
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "build-dxf",
3
- "version": "0.0.11",
3
+ "version": "0.0.13",
4
4
  "description": "线段构建双线墙壁的dxf版本",
5
5
  "main": "./src/index.js",
6
6
  "types": "./src/index.d.ts",
@@ -10,12 +10,12 @@
10
10
  "exports": {
11
11
  ".": {
12
12
  "import": {
13
- "types": "./src/index.d.ts",
13
+ "types": "./src/build.d.ts",
14
14
  "default": "./src/index.js",
15
15
  "style": "./src/index.css"
16
16
  },
17
17
  "require": {
18
- "types": "./src/index.d.ts",
18
+ "types": "./src/build.d.ts",
19
19
  "default": "./src/index.js"
20
20
  }
21
21
  },
package/src/build.js CHANGED
@@ -3,6 +3,7 @@ import { EventDispatcher as EventDispatcher$1 } from "three";
3
3
  import ClipperLib from "clipper-lib";
4
4
  import Drawing from "dxf-writer";
5
5
  import { OBJExporter } from "three/examples/jsm/exporters/OBJExporter.js";
6
+ import { GLTFExporter } from "three/examples/jsm/exporters/GLTFExporter.js";
6
7
  function uuid() {
7
8
  return "xxxx-xxxx-4xxx-yxxx-xxxx".replace(/[xy]/g, function(c) {
8
9
  var r = Math.random() * 16 | 0, v = c == "x" ? r : r & 3 | 8;
@@ -274,6 +275,7 @@ class Point {
274
275
  }
275
276
  /** 计算两个向量夹角
276
277
  * @description 公式:a · b = |a| × |b| × cosθ
278
+ * @description 结果为0(0度),两个向量方向一致,结果为3.1415926(180度, PI),两个向量方向相反
277
279
  * @param point
278
280
  * @returns
279
281
  */
@@ -311,6 +313,14 @@ class Point {
311
313
  clone() {
312
314
  return new Point(this.x, this.y);
313
315
  }
316
+ /**
317
+ * 克隆
318
+ * @returns
319
+ */
320
+ copy(p) {
321
+ this.x = p.x ?? 0;
322
+ this.y = p.y ?? 0;
323
+ }
314
324
  static from(arr) {
315
325
  if (Array.isArray(arr)) {
316
326
  return new Point(arr[0], arr[1]);
@@ -600,6 +610,13 @@ class LineSegment {
600
610
  direction() {
601
611
  return this.points[1].direction(this.points[0]);
602
612
  }
613
+ /**
614
+ * 线段长度
615
+ * @returns
616
+ */
617
+ length() {
618
+ return this.points[1].distance(this.points[0]);
619
+ }
603
620
  /**
604
621
  * 计算一条线段在另一条直线上的投影,并裁剪超出目标线段的部分
605
622
  * @param line 要投影的线段
@@ -650,6 +667,89 @@ class LineSegment {
650
667
  }
651
668
  return new LineSegment(projP1, projP2);
652
669
  }
670
+ /**
671
+ * 判断线段是与线段相交
672
+ * @param line
673
+ */
674
+ intersectLineSegment(line) {
675
+ const p1 = this.start;
676
+ const p2 = this.end;
677
+ const p3 = line.start;
678
+ const p4 = line.end;
679
+ function crossProduct(p12, p22, p32) {
680
+ return (p22.x - p12.x) * (p32.y - p12.y) - (p22.y - p12.y) * (p32.x - p12.x);
681
+ }
682
+ const d1 = crossProduct(p1, p2, p3);
683
+ const d2 = crossProduct(p1, p2, p4);
684
+ const d3 = crossProduct(p3, p4, p1);
685
+ const d4 = crossProduct(p3, p4, p2);
686
+ return d1 * d2 < 0 && d3 * d4 < 0;
687
+ }
688
+ /**
689
+ * 获取交点
690
+ * @param line
691
+ * @returns
692
+ */
693
+ getIntersection(line) {
694
+ const p1 = this.start;
695
+ const p2 = this.end;
696
+ const p3 = line.start;
697
+ const p4 = line.end;
698
+ const denom = (p1.x - p2.x) * (p3.y - p4.y) - (p1.y - p2.y) * (p3.x - p4.x);
699
+ if (Math.abs(denom) < 1e-10) {
700
+ return null;
701
+ }
702
+ const t = ((p1.x - p3.x) * (p3.y - p4.y) - (p1.y - p3.y) * (p3.x - p4.x)) / denom;
703
+ const x = p1.x + t * (p2.x - p1.x);
704
+ const y = p1.y + t * (p2.y - p1.y);
705
+ return new Point(x, y);
706
+ }
707
+ /**
708
+ * 获取两条线段夹角
709
+ * @param line
710
+ */
711
+ includedAngle(line) {
712
+ const d1 = this.direction(), d2 = line.direction();
713
+ return d1.angleBetween(d2) / (Math.PI / 180);
714
+ }
715
+ /**
716
+ * 两条线段方向是否一致
717
+ * @param line
718
+ */
719
+ directionEqual(line, errAngle = 0.1) {
720
+ return this.includedAngle(line) < errAngle;
721
+ }
722
+ /**
723
+ * 两条线段方向相反否一致
724
+ * @param line
725
+ */
726
+ directionOpposite(line, errAngle = 0.1) {
727
+ return 180 - this.includedAngle(line) < errAngle;
728
+ }
729
+ /**
730
+ * 判断两条线是否平行
731
+ * @param line
732
+ */
733
+ isParallel(line, errAngle = 4) {
734
+ const angle = this.includedAngle(line);
735
+ return angle < errAngle || angle > 180 - errAngle;
736
+ }
737
+ /**
738
+ * 判断两条直线是否重合
739
+ * @param line
740
+ * @returns
741
+ */
742
+ areLinesCoincident(line) {
743
+ const p1 = this.start, p2 = this.end, p3 = line.start, p4 = line.end;
744
+ const m1 = (p2.y - p1.y) / (p2.x - p1.x);
745
+ const b1 = p1.y - m1 * p1.x;
746
+ const m2 = (p4.y - p3.y) / (p4.x - p3.x);
747
+ const b2 = p3.y - m2 * p3.x;
748
+ if (!isFinite(m1) && !isFinite(m2)) {
749
+ return p1.x === p3.x && p2.x === p3.x;
750
+ }
751
+ return Math.abs(m1 - m2) < 1e-3 && Math.abs(b1 - b2) < 1e-3;
752
+ }
653
753
  clone() {
654
754
  return new LineSegment(
655
755
  this.points[0].clone(),
@@ -701,8 +801,25 @@ const units = {
701
801
  Parsecs: 3240779289666404e-32
702
802
  // 秒差距,1米 ≈ 0.00000000000000003240779289666404秒差距
703
803
  };
804
+ function pathToLines(path) {
805
+ const lineSegments = [];
806
+ for (let i = 0; i < path.length; i++) {
807
+ lineSegments.push(new LineSegment(
808
+ path[i],
809
+ path[(i + 1) % path.length]
810
+ ));
811
+ }
812
+ return lineSegments;
813
+ }
814
+ function linesToPath(lineSegments) {
815
+ return lineSegments.flatMap((line, index2) => {
816
+ if (index2 === lineSegments.length - 1) [...line.points, lineSegments[0].points[0]];
817
+ return [line.points[0]];
818
+ });
819
+ }
704
820
  class Dxf extends Component {
705
821
  static name = "Dxf";
822
+ shortLine = 0.04;
706
823
  width = 0.04;
707
824
  scale = 1;
708
825
  originalData = [];
@@ -740,6 +857,7 @@ class Dxf extends Component {
740
857
  super();
741
858
  this.width = width;
742
859
  this.scale = scale;
860
+ this.shortLine = width * 0.8;
743
861
  }
744
862
  /**
745
863
  * 设置
@@ -765,6 +883,7 @@ class Dxf extends Component {
765
883
  this.scale = scale;
766
884
  this.width = width;
767
885
  this.originalData = data;
886
+ this.lineSegments.length = 0;
768
887
  const zList = [];
769
888
  this.data = data.map(({ start, end, insetionArr, isDoor = false }, index2) => {
770
889
  zList.push(start.z ?? 0, end.z ?? 0);
@@ -875,55 +994,158 @@ class Dxf extends Component {
875
994
  /** etOpenRound 去除毛刺
876
995
  * @description 检查连续的短线段数量,去除合并后产生的毛刺
877
996
  */
878
- squareRemoveBurr() {
879
- this.wallsGroup = this.wallsGroup.map((lines) => {
880
- if (lines.length < 3) return lines;
881
- const filterLines = [lines[0]];
882
- for (let i = 1; i < lines.length; i++) {
883
- const prev = lines[i - 1];
884
- const curr = lines[i];
885
- const len = prev.distance(curr);
886
- if (len < this.width * 0.5) {
887
- let count = 0;
888
- for (let j = i + 1; j < lines.length; j++) {
889
- const prev2 = lines[j - 1];
890
- const curr2 = lines[j];
891
- const len2 = prev2.distance(curr2);
892
- if (len2 < this.width * 0.8) count++;
893
- else break;
894
- }
895
- if (count === 0 && i + count === lines.length - 1) ;
896
- else if (i == 1 && count === 1) ;
897
- else if (count === 3) {
898
- filterLines.push(lines[i + 1]);
899
- i += count;
900
- } else if (count === 5) {
901
- filterLines.push(lines[i + 2]);
902
- i += count;
903
- } else {
904
- filterLines.push(curr);
905
- }
997
+ squareRemoveBurr(path) {
998
+ if (path.length < 3) return path;
999
+ const filterLines = [path[0]];
1000
+ for (let i = 1; i < path.length; i++) {
1001
+ const prev = path[i - 1];
1002
+ const curr = path[i];
1003
+ const len = prev.distance(curr);
1004
+ if (len < this.width * 0.5) {
1005
+ let count = 0;
1006
+ for (let j = i + 1; j < path.length; j++) {
1007
+ const prev2 = path[j - 1];
1008
+ const curr2 = path[j];
1009
+ const len2 = prev2.distance(curr2);
1010
+ if (len2 < this.width * 0.8) count++;
1011
+ else break;
1012
+ }
1013
+ if (count === 0 && i + count === path.length - 1) ;
1014
+ else if (i == 1 && count === 1) ;
1015
+ else if (count === 3) {
1016
+ filterLines.push(path[i + 1]);
1017
+ i += count;
1018
+ } else if (count === 5) {
1019
+ filterLines.push(path[i + 2]);
1020
+ i += count;
906
1021
  } else {
907
1022
  filterLines.push(curr);
908
1023
  }
1024
+ } else {
1025
+ filterLines.push(curr);
909
1026
  }
910
- return filterLines;
911
- });
1027
+ }
1028
+ return filterLines;
1029
+ }
1030
+ epsilon = 1e-6;
1031
+ // 距离阈值,用于比较点
1032
+ /**
1033
+ * 移除共线点
1034
+ * @param path
1035
+ * @returns
1036
+ */
1037
+ removeCollinearPoints(path) {
1038
+ for (let i = 0; i < path.length; i++) {
1039
+ const p1 = path[i];
1040
+ const p2 = path[(i + 1) % path.length];
1041
+ if (p1.distance(p2) > this.shortLine) {
1042
+ path.push(...path.slice(0, i));
1043
+ path.splice(0, i);
1044
+ break;
1045
+ }
1046
+ }
1047
+ if (path.length < 3) return path;
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]];
1068
+ for (let i = 1; i < lines.length; i++) {
1069
+ const line = lines[i];
1070
+ const preLine = lines[(lines.length + i - 1) % lines.length];
1071
+ if (line.length() > this.width * 0.9) {
1072
+ filterLines.push(line);
1073
+ continue;
1074
+ }
1075
+ const line1 = lines[i + 1];
1076
+ if (line1 && line1.length() > this.width * 0.9) {
1077
+ filterLines.push(line);
1078
+ filterLines.push(line1);
1079
+ i = i + 1;
1080
+ continue;
1081
+ }
1082
+ const line2 = lines[i + 2];
1083
+ if (line2 && preLine.includedAngle(line2) < 1) {
1084
+ i = i + 2;
1085
+ filterLines.push(line2);
1086
+ } else filterLines.push(line);
1087
+ }
1088
+ return filterLines.length > 3 ? linesToPath(filterLines) : [];
1089
+ }
1090
+ /**
1091
+ * 移除短线段
1092
+ * @param path
1093
+ */
1094
+ removeShortLine(path, shortLine = this.shortLine) {
1095
+ const lines = pathToLines(path), filterLines = [], PI_1 = Math.PI / 180;
1096
+ for (let i = 0; i < lines.length; i++) {
1097
+ const line = lines[i], len = line.length();
1098
+ if (len > shortLine || filterLines.length === 0) {
1099
+ filterLines.push(line);
1100
+ continue;
1101
+ }
1102
+ let nextline = lines[++i];
1103
+ const preLine = filterLines[filterLines.length - 1], d1 = preLine.direction();
1104
+ while (i < lines.length) {
1105
+ const angle = d1.angleBetween(nextline.direction()) / PI_1;
1106
+ if (nextline.length() <= shortLine || angle < 4 || angle > 180 - 4) {
1107
+ nextline = lines[++i];
1108
+ } else break;
1109
+ }
1110
+ if (nextline) {
1111
+ const intersectPoint = preLine.getIntersection(nextline);
1112
+ if (intersectPoint) {
1113
+ const p0 = preLine.points[1].clone(), p1 = nextline.points[0].clone();
1114
+ preLine.points[1].copy(intersectPoint);
1115
+ nextline.points[0].copy(intersectPoint);
1116
+ if (preLine.length() < this.width) {
1117
+ preLine.points[1].copy(p0);
1118
+ nextline.points[0].copy(p0);
1119
+ } else if (nextline.length() < this.width) {
1120
+ preLine.points[1].copy(p1);
1121
+ nextline.points[0].copy(p1);
1122
+ }
1123
+ } else {
1124
+ preLine.points[1].copy(nextline.points[0]);
1125
+ }
1126
+ filterLines.push(nextline);
1127
+ }
1128
+ }
1129
+ return filterLines.length > 3 ? linesToPath(filterLines) : [];
912
1130
  }
913
1131
  /** 线偏移
914
1132
  * @description 使用 ClipperLib 对每个点组进行线偏移处理,生成具有指定宽度的墙体路径
915
1133
  */
916
1134
  lineOffset(endType = Dxf.EndType.etOpenSquare, joinType = Dxf.JoinType.jtMiter, scale = 1e4) {
917
- const solutions = new ClipperLib.Paths();
1135
+ let solutions = new ClipperLib.Paths();
918
1136
  const offset = new ClipperLib.ClipperOffset(20, 0.25);
919
1137
  this.pointsGroups.forEach((points) => {
920
1138
  const linePaths = this.lineTopology(points).map((linePath) => linePath.map((p) => p.clone().mutiplyScalar(scale)));
921
1139
  offset.AddPaths(linePaths, joinType, endType);
922
1140
  });
923
1141
  offset.Execute(solutions, this.width / 2 * scale);
924
- offset.Execute(solutions, this.width / 2 * scale);
925
- this.wallsGroup = solutions.map((ps) => ps.map((p) => Point.from(p).divisionScalar(scale)));
926
- if (endType == Dxf.EndType.etOpenSquare) this.squareRemoveBurr();
1142
+ this.wallsGroup = solutions.map((ps) => {
1143
+ let path = ps.map((p) => Point.from(p).divisionScalar(scale));
1144
+ path = this.removeCollinearPoints(path);
1145
+ path = this.lineSegmentStraightening(path);
1146
+ if (endType == Dxf.EndType.etOpenSquare) path = this.squareRemoveBurr(path);
1147
+ return path;
1148
+ });
927
1149
  this.dispatchEvent({
928
1150
  type: "lineOffset",
929
1151
  wallsGroup: this.wallsGroup
@@ -1549,33 +1771,7 @@ class LineAnalysis extends Component {
1549
1771
  onAddFromParent(parent) {
1550
1772
  this.Dxf = parent.findComponentByType(Dxf);
1551
1773
  this.Variable = this.parent?.findComponentByType(Variable);
1552
- console.log(this.Dxf);
1553
1774
  this.Dxf.addEventListener("setDta", this.lineAnalysis.bind(this));
1554
- this.Dxf.addEventListener("lineOffset", this.duplicatePointFiltering.bind(this));
1555
- }
1556
- /**
1557
- * 去除路径上重复的点
1558
- * @description 判断方向向量,一个连续的方向上,只应该出现两个点
1559
- */
1560
- duplicatePointFiltering() {
1561
- const dxf = this.Dxf;
1562
- dxf.wallsGroup = dxf.wallsGroup.map((points) => {
1563
- const filterPoints = [];
1564
- points.forEach((point, index2) => {
1565
- if (index2 < 1 || points.length - 1 === index2) return filterPoints.push(point);
1566
- const preP = points[index2 - 1], nextP = points[index2 + 1], direct1 = point.direction(preP), direct2 = nextP.direction(point), angle = direct1.angleBetween(direct2) / (Math.PI / 180);
1567
- if (angle > 0.02 && angle < 180 - 0.02) filterPoints.push(point);
1568
- });
1569
- points = [filterPoints[0]];
1570
- for (let i = 1; i < filterPoints.length; i++) {
1571
- const point = filterPoints[i];
1572
- const nextP = filterPoints[i - 1];
1573
- if (nextP.distance(point) < dxf.width * 0.5) ;
1574
- points.push(point);
1575
- }
1576
- return points;
1577
- });
1578
- dxf.wallsGroup = dxf.wallsGroup.filter((i) => i.length >= 4);
1579
1775
  }
1580
1776
  /**
1581
1777
  *
@@ -1620,6 +1816,9 @@ class LineAnalysis extends Component {
1620
1816
  createRectangle(result) {
1621
1817
  const dxf = this.Dxf;
1622
1818
  const project0 = result.project, project1 = result.project2;
1819
+ if (project0.includedAngle(project1) > 135) {
1820
+ project1.points = [project1.points[1], project1.points[0]];
1821
+ }
1623
1822
  this.addData(project0.points[0], project1.points[0]);
1624
1823
  this.addData(project0.points[1], project1.points[1]);
1625
1824
  const leftHeight = project0.points[0].distance(project1.points[0]), rightHeight = project0.points[1].distance(project1.points[1]), count = Math.ceil(Math.max(leftHeight, rightHeight) / dxf.width), leftFragment = leftHeight / count, rightFragment = rightHeight / count, leftDirection = project1.points[0].direction(project0.points[0]), rightDirection = project1.points[1].direction(project0.points[1]), leftP = project0.points[0].clone(), rightP = project0.points[1].clone(), direction = rightP.direction(leftP);
@@ -1702,10 +1901,7 @@ class LineAnalysis extends Component {
1702
1901
  const temLineSegment = lineSegmentList[index2], direct = sourceLineSegment.direction(), temDirect = temLineSegment.direction(), angle = direct.angleBetween(temDirect) / (Math.PI / 180);
1703
1902
  if (angle < this.errorAngle || angle > 180 - this.errorAngle) {
1704
1903
  let data;
1705
- const p1 = temLineSegment.projectLineSegment(sourceLineSegment), p2 = sourceLineSegment.projectLineSegment(temLineSegment), d1 = p1.direction(), d2 = p2.direction();
1706
- if (d1.x > 0 && d2.x < 0 || d1.x < 0 && d2.x > 0 || d1.y > 0 && d2.y < 0 || d1.y < 0 && d2.y > 0) {
1707
- p1.points = [p1.points[1], p1.points[0]];
1708
- }
1904
+ const p1 = temLineSegment.projectLineSegment(sourceLineSegment), p2 = sourceLineSegment.projectLineSegment(temLineSegment);
1709
1905
  if (p1.getLength() > p2.getLength()) {
1710
1906
  data = {
1711
1907
  target: temLineSegment,
@@ -1761,6 +1957,7 @@ class DxfSystem extends ComponentManager {
1761
1957
  }
1762
1958
  }
1763
1959
  const exporter = new OBJExporter();
1960
+ const glbExporter = new GLTFExporter();
1764
1961
  function lineSqueezing(p1, p2, width = 0.1) {
1765
1962
  const normal = p2.normal(p1);
1766
1963
  const pDirect = p2.direction(p1).mutiplyScalar(width * 0.5);
@@ -1786,8 +1983,11 @@ class WhiteModel extends Component {
1786
1983
  Variable = null;
1787
1984
  // dxf数据白模
1788
1985
  whiteModelGroup = new THREE.Group();
1986
+ // dxf数据白模边缘线
1987
+ whiteModelLineGroup = new THREE.Group();
1789
1988
  // 原始数据白模
1790
1989
  originalWhiteMode = new THREE.Group();
1990
+ material = new THREE.MeshBasicMaterial({ color: 16777215, transparent: true, opacity: 0.8, side: THREE.DoubleSide });
1791
1991
  onAddFromParent(parent) {
1792
1992
  this.Dxf = parent.findComponentByName("Dxf");
1793
1993
  this.Variable = parent.findComponentByName("Variable");
@@ -1801,6 +2001,8 @@ class WhiteModel extends Component {
1801
2001
  const dxf = this.Dxf;
1802
2002
  this.originalWhiteMode.clear();
1803
2003
  this.whiteModelGroup.clear();
2004
+ this.whiteModelLineGroup.clear();
2005
+ this.whiteModelGroup.add(this.whiteModelLineGroup);
1804
2006
  this.whiteModelGroup.position.z = dxf.originalZAverage;
1805
2007
  this.originalWhiteMode.position.z = dxf.originalZAverage;
1806
2008
  dxf.wallsGroup.forEach((points) => {
@@ -1811,9 +2013,9 @@ class WhiteModel extends Component {
1811
2013
  bevelSize: 0
1812
2014
  });
1813
2015
  const mesh = new THREE.Mesh(geometry);
1814
- mesh.material = new THREE.MeshStandardMaterial({ color: 16777215, transparent: true, opacity: 0.8 });
2016
+ mesh.material = this.material;
1815
2017
  this.whiteModelGroup.add(mesh);
1816
- this.whiteModelGroup.add(
2018
+ this.whiteModelLineGroup.add(
1817
2019
  new THREE.LineSegments(new THREE.EdgesGeometry(geometry), new THREE.LineBasicMaterial({ color: 6710886 }))
1818
2020
  );
1819
2021
  });
@@ -1852,15 +2054,58 @@ class WhiteModel extends Component {
1852
2054
  resolve(exporter.parse(this.whiteModelGroup));
1853
2055
  });
1854
2056
  }
1855
- async toBlob() {
2057
+ toGlb() {
2058
+ return new Promise((resolve) => {
2059
+ this.material.opacity = 1;
2060
+ this.material.needsUpdate = true;
2061
+ setTimeout(() => {
2062
+ glbExporter.parse(this.whiteModelGroup.children, (gltf) => {
2063
+ resolve(gltf);
2064
+ this.material.opacity = 0.8;
2065
+ this.material.transparent = true;
2066
+ }, () => {
2067
+ resolve(void 0);
2068
+ }, {
2069
+ binary: true
2070
+ });
2071
+ }, 100);
2072
+ });
2073
+ }
2074
+ async toOBJBlob() {
1856
2075
  const buffer = await this.toOBJ();
1857
2076
  if (buffer) {
1858
2077
  return new Blob([buffer], { type: "application/octet-stream" });
1859
2078
  }
1860
2079
  }
2080
+ async toGlbBlob() {
2081
+ const buffer = await this.toGlb();
2082
+ if (buffer) {
2083
+ return new Blob([buffer], { type: "application/octet-stream" });
2084
+ }
2085
+ }
1861
2086
  async download(filename) {
1862
2087
  if (typeof window !== "undefined") {
1863
- const blob = await this.toBlob();
2088
+ const blob = await this.toOBJBlob();
2089
+ if (!blob) return;
2090
+ const a = document.createElement("a");
2091
+ a.href = URL.createObjectURL(blob);
2092
+ a.download = filename;
2093
+ a.click();
2094
+ } else if (typeof global !== "undefined") {
2095
+ const buffer = await this.toOBJ();
2096
+ if (buffer) {
2097
+ const packageName = "fs";
2098
+ const { default: fs } = await import(
2099
+ /* @vite-ignore */
2100
+ packageName
2101
+ );
2102
+ fs.writeFileSync(filename, buffer);
2103
+ }
2104
+ }
2105
+ }
2106
+ async downloadGlb(filename) {
2107
+ if (typeof window !== "undefined") {
2108
+ const blob = await this.toGlbBlob();
1864
2109
  if (!blob) return;
1865
2110
  const a = document.createElement("a");
1866
2111
  a.href = URL.createObjectURL(blob);
@@ -2017,6 +2262,7 @@ export {
2017
2262
  ModelDataPlugin as M,
2018
2263
  Point as P,
2019
2264
  Variable as V,
2265
+ WhiteModel as W,
2020
2266
  DetailsPoint as a,
2021
2267
  index as i,
2022
2268
  loadRenderPlugin as l
package/src/index2.js CHANGED
@@ -1,6 +1,6 @@
1
- import { C as Component, P as Point, B as Box2, V as Variable, D as DxfSystem, M as ModelDataPlugin, a as DetailsPoint } from "./build.js";
1
+ import { C as Component, P as Point, B as Box2, V as Variable, D as DxfSystem, M as ModelDataPlugin, a as DetailsPoint, W as WhiteModel } from "./build.js";
2
2
  import * as THREE from "three";
3
- import { BufferAttribute, Vector3, Plane, Line3, Vector2, Triangle, Sphere, Matrix4, Box3, BackSide, DoubleSide, REVISION, FrontSide, Mesh, BatchedMesh, Ray } from "three";
3
+ import { BufferAttribute, Vector3, Plane, Line3, Vector2, Triangle, Sphere, Matrix4, Box3, BackSide, DoubleSide, REVISION, FrontSide, Mesh, BatchedMesh, Ray, Group } from "three";
4
4
  import "clipper-lib";
5
5
  import "dxf-writer";
6
6
  import { CSS3DObject, CSS3DSprite, CSS3DRenderer } from "three/addons/renderers/CSS3DRenderer.js";
@@ -3490,6 +3490,7 @@ class Renderer extends Component {
3490
3490
  if (pos instanceof Point) box.position.set(pos.x, pos.y, 0);
3491
3491
  else if (pos instanceof THREE.Vector3) box.position.copy(pos);
3492
3492
  parent.add(box);
3493
+ return box;
3493
3494
  }
3494
3495
  /**
3495
3496
  * 创建文本
@@ -3500,11 +3501,12 @@ class Renderer extends Component {
3500
3501
  createText(text, pos, style, parent = this.container) {
3501
3502
  const div = document.createElement("div");
3502
3503
  div.innerHTML = text;
3503
- Object.assign(div.style, { fontSize: "12px", color: "#ffffff", ...style });
3504
+ Object.assign(div.style, { fontSize: "10px", color: "#ffffff", ...style });
3504
3505
  const css2DObject = new Renderer.CSS2DObject(div);
3505
3506
  if (pos instanceof Point) css2DObject.position.set(pos.x, pos.y, 0);
3506
3507
  else if (pos instanceof THREE.Vector3) css2DObject.position.copy(pos);
3507
3508
  parent.add(css2DObject);
3509
+ return css2DObject;
3508
3510
  }
3509
3511
  /**
3510
3512
  * 创建几何缓冲区
@@ -7109,12 +7111,27 @@ class ModelDataRender extends Component {
7109
7111
  const renderer = parent.findComponentByName("Renderer");
7110
7112
  const variable = parent.findComponentByName("Variable");
7111
7113
  const dxfLineModel = parent.findComponentByName("DxfLineModel");
7114
+ const dxf = parent.findComponentByName("Dxf");
7112
7115
  renderer.container.add(dxfLineModel.dxfModelGroup);
7113
7116
  variable.addEventListener("dxfVisible", (e) => {
7114
7117
  dxfLineModel.dxfModelGroup.visible = e.value;
7115
7118
  });
7119
+ let group = new Group();
7120
+ dxfLineModel.dxfModelGroup.add(group);
7116
7121
  variable?.addEventListener("currentKeyUp", (e) => {
7117
7122
  if (e.value === "w") variable.set("dxfVisible", !variable.dxfVisible);
7123
+ if (e.value === "b") {
7124
+ if (group.visible) group.visible = false;
7125
+ else {
7126
+ group.visible = true;
7127
+ group.clear();
7128
+ dxf.wallsGroup.forEach((path, j) => {
7129
+ path.forEach((p, i) => {
7130
+ renderer.createText(`${i}`, p, { color: "#ff00ff", fontSize: "10px" }, group).position.z = dxf.originalZAverage * 0.99;
7131
+ });
7132
+ });
7133
+ }
7134
+ }
7118
7135
  });
7119
7136
  }
7120
7137
  whiteModel(parent) {
@@ -7237,7 +7254,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
7237
7254
  setup(__props) {
7238
7255
  THREE.Object3D.DEFAULT_UP = new THREE.Vector3(0, 0, 1);
7239
7256
  const props = __props;
7240
- const elRef = ref(), originalLineVisible = ref(true), dxfVisible = ref(true), whiteModelVisible = ref(true), isLook = ref(false), dxfSystem = new DxfSystem().usePlugin(ModelDataPlugin).usePlugin(RenderPlugin), desPoint = dxfSystem.findComponentByType(DetailsPoint), domContainer = dxfSystem.findComponentByType(DomContainer);
7257
+ const elRef = ref(), originalLineVisible = ref(true), dxfVisible = ref(true), whiteModelVisible = ref(true), isLook = ref(false), dxfSystem = new DxfSystem().usePlugin(ModelDataPlugin).usePlugin(RenderPlugin), desPoint = dxfSystem.findComponentByType(DetailsPoint), domContainer = dxfSystem.findComponentByType(DomContainer), whiteModel = dxfSystem.findComponentByType(WhiteModel);
7241
7258
  watch(() => props.lines, () => props.lines && setLines(props.lines));
7242
7259
  watch(() => props.detailsPoint, () => props.detailsPoint && desPoint?.set(props.detailsPoint));
7243
7260
  function setLines(lines) {
@@ -7281,59 +7298,70 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
7281
7298
  type: "primary",
7282
7299
  onClick: _cache[0] || (_cache[0] = ($event) => unref(dxfSystem).Dxf.download("test.dxf"))
7283
7300
  }, {
7284
- default: withCtx(() => _cache[5] || (_cache[5] = [
7301
+ default: withCtx(() => _cache[6] || (_cache[6] = [
7285
7302
  createTextVNode(" 下载 DXF ", -1)
7286
7303
  ])),
7287
7304
  _: 1,
7288
- __: [5]
7305
+ __: [6]
7306
+ }),
7307
+ createVNode(unref(ElButton), {
7308
+ size: "small",
7309
+ type: "primary",
7310
+ onClick: _cache[1] || (_cache[1] = ($event) => unref(whiteModel).downloadGlb("test.glb"))
7311
+ }, {
7312
+ default: withCtx(() => _cache[7] || (_cache[7] = [
7313
+ createTextVNode(" 下载 白模 ", -1)
7314
+ ])),
7315
+ _: 1,
7316
+ __: [7]
7289
7317
  }),
7290
7318
  createVNode(unref(ElButton), {
7291
7319
  size: "small",
7292
7320
  type: "success",
7293
7321
  onClick: selectLocalFile
7294
7322
  }, {
7295
- default: withCtx(() => _cache[6] || (_cache[6] = [
7323
+ default: withCtx(() => _cache[8] || (_cache[8] = [
7296
7324
  createTextVNode(" 选择线路文件 ", -1)
7297
7325
  ])),
7298
7326
  _: 1,
7299
- __: [6]
7327
+ __: [8]
7300
7328
  }),
7301
7329
  createVNode(unref(ElButton), {
7302
7330
  size: "small",
7303
7331
  type: "success",
7304
7332
  onClick: selectDetailsPointFile
7305
7333
  }, {
7306
- default: withCtx(() => _cache[7] || (_cache[7] = [
7334
+ default: withCtx(() => _cache[9] || (_cache[9] = [
7307
7335
  createTextVNode(" 选择详情点文件 ", -1)
7308
7336
  ])),
7309
7337
  _: 1,
7310
- __: [7]
7338
+ __: [9]
7311
7339
  })
7312
7340
  ]),
7313
7341
  isLook.value ? (openBlock(), createElementBlock("div", {
7314
7342
  key: 0,
7315
- onClick: _cache[1] || (_cache[1] = ($event) => unref(dxfSystem).Variable.set("isLook", false)),
7343
+ onClick: _cache[2] || (_cache[2] = ($event) => unref(dxfSystem).Variable.set("isLook", false)),
7316
7344
  class: "text-[#fff] h-fit text-[14px] rounded-[10px] bg-black p-[5px_20px] select-none cursor-pointer"
7317
7345
  }, " 点击这或按 ESC 取消 ")) : createCommentVNode("", true),
7318
7346
  createElementVNode("div", null, [
7319
7347
  createElementVNode("div", _hoisted_2, [
7320
7348
  createVNode(unref(ElCheckbox), {
7321
7349
  modelValue: originalLineVisible.value,
7322
- "onUpdate:modelValue": _cache[2] || (_cache[2] = ($event) => originalLineVisible.value = $event),
7350
+ "onUpdate:modelValue": _cache[3] || (_cache[3] = ($event) => originalLineVisible.value = $event),
7323
7351
  label: "线(Q)"
7324
7352
  }, null, 8, ["modelValue"]),
7325
7353
  createVNode(unref(ElCheckbox), {
7326
7354
  modelValue: dxfVisible.value,
7327
- "onUpdate:modelValue": _cache[3] || (_cache[3] = ($event) => dxfVisible.value = $event),
7355
+ "onUpdate:modelValue": _cache[4] || (_cache[4] = ($event) => dxfVisible.value = $event),
7328
7356
  label: "dxf(W)"
7329
7357
  }, null, 8, ["modelValue"]),
7330
7358
  createVNode(unref(ElCheckbox), {
7331
7359
  modelValue: whiteModelVisible.value,
7332
- "onUpdate:modelValue": _cache[4] || (_cache[4] = ($event) => whiteModelVisible.value = $event),
7360
+ "onUpdate:modelValue": _cache[5] || (_cache[5] = ($event) => whiteModelVisible.value = $event),
7333
7361
  label: "墙体(E)"
7334
7362
  }, null, 8, ["modelValue"])
7335
7363
  ]),
7336
- _cache[8] || (_cache[8] = createStaticVNode('<div class="mt-[5px] text-[#c9c9c9] text-[10px]"><p class="text-right">详情点射线辅助线快捷键:R</p><p class="text-right">墙壁合并追加线快捷键:A</p><p class="text-right">线段序号快捷键:T</p><p class="text-right">线段端点标示快捷键:P</p><p class="text-right">线段长度(单位米)快捷键:L</p></div>', 1))
7364
+ _cache[10] || (_cache[10] = createStaticVNode('<div class="mt-[5px] text-[#c9c9c9] text-[10px]"><p class="text-right">详情点射线辅助线快捷键:R</p><p class="text-right">墙壁合并追加线快捷键:A</p><p class="text-right">线段序号快捷键:T</p><p class="text-right">线段端点标示快捷键:P</p><p class="text-right">线段长度(单位米)快捷键:L</p></div>', 1))
7337
7365
  ])
7338
7366
  ])
7339
7367
  ], 512);
@@ -51,6 +51,7 @@ export declare class Dxf extends Component<{
51
51
  };
52
52
  }> {
53
53
  static name: string;
54
+ shortLine: number;
54
55
  width: number;
55
56
  scale: number;
56
57
  originalData: OriginalDataItem[];
@@ -112,6 +113,24 @@ export declare class Dxf extends Component<{
112
113
  * @description 检查连续的短线段数量,去除合并后产生的毛刺
113
114
  */
114
115
  private squareRemoveBurr;
116
+ epsilon: number;
117
+ /**
118
+ * 移除共线点
119
+ * @param path
120
+ * @returns
121
+ */
122
+ removeCollinearPoints(path: Point[]): Point[];
123
+ /**
124
+ * 线段矫直, 线段中心突刺
125
+ * @description 突变长度小于墙体宽度,该线段可能为突起线段,
126
+ * @description 判断后续第2线段与上一条线段是否方向相同,相同就为突刺
127
+ */
128
+ lineSegmentStraightening(path: Point[]): Point[];
129
+ /**
130
+ * 移除短线段
131
+ * @param path
132
+ */
133
+ removeShortLine(path: Point[], shortLine?: number): Point[];
115
134
  /** 线偏移
116
135
  * @description 使用 ClipperLib 对每个点组进行线偏移处理,生成具有指定宽度的墙体路径
117
136
  */
@@ -27,11 +27,6 @@ export declare class LineAnalysis extends Component {
27
27
  * @param parent
28
28
  */
29
29
  onAddFromParent(parent: ComponentManager): void;
30
- /**
31
- * 去除路径上重复的点
32
- * @description 判断方向向量,一个连续的方向上,只应该出现两个点
33
- */
34
- duplicatePointFiltering(): void;
35
30
  /**
36
31
  *
37
32
  * @param p1
@@ -12,10 +12,15 @@ export declare class WhiteModel extends Component<{
12
12
  Dxf: Dxf | null;
13
13
  Variable: Variable | null;
14
14
  whiteModelGroup: THREE.Group<THREE.Object3DEventMap>;
15
+ whiteModelLineGroup: THREE.Group<THREE.Object3DEventMap>;
15
16
  originalWhiteMode: THREE.Group<THREE.Object3DEventMap>;
17
+ material: THREE.MeshBasicMaterial;
16
18
  onAddFromParent(parent: ComponentManager): void;
17
19
  updateModel(): void;
18
20
  toOBJ(): Promise<string>;
19
- toBlob(): Promise<Blob | undefined>;
21
+ toGlb(): Promise<ArrayBuffer | undefined>;
22
+ toOBJBlob(): Promise<Blob | undefined>;
23
+ toGlbBlob(): Promise<Blob | undefined>;
20
24
  download(filename: string): Promise<void>;
25
+ downloadGlb(filename: string): Promise<void>;
21
26
  }
@@ -64,14 +64,14 @@ export declare class Renderer extends Component {
64
64
  * 创建点
65
65
  * @param pos
66
66
  */
67
- createPointMesh(pos?: Point | THREE.Vector3, size?: number, parameters?: THREE.MeshBasicMaterialParameters, parent?: THREE.Object3D): void;
67
+ createPointMesh(pos?: Point | THREE.Vector3, size?: number, parameters?: THREE.MeshBasicMaterialParameters, parent?: THREE.Object3D): THREE.Mesh<THREE.SphereGeometry, THREE.MeshBasicMaterial, THREE.Object3DEventMap>;
68
68
  /**
69
69
  * 创建文本
70
70
  * @param text
71
71
  * @param pos
72
72
  * @param style
73
73
  */
74
- createText(text: any, pos?: Point | THREE.Vector3, style?: any, parent?: THREE.Object3D): void;
74
+ createText(text: any, pos?: Point | THREE.Vector3, style?: any, parent?: THREE.Object3D): CSS2DObject;
75
75
  /**
76
76
  * 创建几何缓冲区
77
77
  * @param map
@@ -19,11 +19,53 @@ export declare class LineSegment<T = Record<string, any>> {
19
19
  * @returns
20
20
  */
21
21
  direction(): Point;
22
+ /**
23
+ * 线段长度
24
+ * @returns
25
+ */
26
+ length(): number;
22
27
  /**
23
28
  * 计算一条线段在另一条直线上的投影,并裁剪超出目标线段的部分
24
29
  * @param line 要投影的线段
25
30
  * @returns 投影并裁剪后的线段
26
31
  */
27
32
  projectLineSegment(line: LineSegment): LineSegment;
33
+ /**
34
+ * 判断线段是与线段相交
35
+ * @param line
36
+ */
37
+ intersectLineSegment(line: LineSegment): boolean;
38
+ /**
39
+ * 获取交点
40
+ * @param line
41
+ * @returns
42
+ */
43
+ getIntersection(line: LineSegment): Point | null;
44
+ /**
45
+ * 获取两条线段夹角
46
+ * @param line
47
+ */
48
+ includedAngle(line: LineSegment): number;
49
+ /**
50
+ * 两条线段方向是否一致
51
+ * @param line
52
+ */
53
+ directionEqual(line: LineSegment, errAngle?: number): boolean;
54
+ /**
55
+ * 两条线段方向相反否一致
56
+ * @param line
57
+ */
58
+ directionOpposite(line: LineSegment, errAngle?: number): boolean;
59
+ /**
60
+ * 判断两条线是否平行
61
+ * @param line
62
+ */
63
+ isParallel(line: LineSegment, errAngle?: number): boolean;
64
+ /**
65
+ * 判断两条直线是否重合
66
+ * @param line
67
+ * @returns
68
+ */
69
+ areLinesCoincident(line: LineSegment): boolean;
28
70
  clone(): LineSegment<Record<string, any>>;
29
71
  }
@@ -91,6 +91,7 @@ export declare class Point {
91
91
  dot(point: Point): number;
92
92
  /** 计算两个向量夹角
93
93
  * @description 公式:a · b = |a| × |b| × cosθ
94
+ * @description 结果为0(0度),两个向量方向一致,结果为3.1415926(180度, PI),两个向量方向相反
94
95
  * @param point
95
96
  * @returns
96
97
  */
@@ -108,6 +109,14 @@ export declare class Point {
108
109
  * @returns
109
110
  */
110
111
  clone(): Point;
112
+ /**
113
+ * 克隆
114
+ * @returns
115
+ */
116
+ copy(p: {
117
+ x: number;
118
+ y: number;
119
+ }): void;
111
120
  static from(arr: any): Point;
112
121
  static zero(): Point;
113
122
  }