build-dxf 0.0.12 → 0.0.14

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
@@ -27,7 +27,9 @@ detailsPoint.addEventListener("handleSuccess", () => {
27
27
  // 下载dxf文件
28
28
  dxfSystem.Dxf.download("01.dxf")
29
29
  // 下载白模,obj格式
30
- whiteModel.download("001.obj")
30
+ whiteModel.downloadOBJ("001.obj")
31
+ // 下载白模,gltf或glb格式, 第二个参数为true时是glb,默认为true
32
+ whiteModel.downloadGltf("001.obj", true)
31
33
 
32
34
  // desPoints 为射线点集合,根据需要使用
33
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.12",
3
+ "version": "0.0.14",
4
4
  "description": "线段构建双线墙壁的dxf版本",
5
5
  "main": "./src/index.js",
6
6
  "types": "./src/index.d.ts",
@@ -38,7 +38,8 @@
38
38
  "three-bvh-csg": ">=0.0.17",
39
39
  "three-csg-ts": ">=3.2.0",
40
40
  "@tweenjs/tween.js": ">=25.0.0",
41
- "vue": ">=3.0.0"
41
+ "vue": ">=3.0.0",
42
+ "obj2gltf": ">=3.1.6"
42
43
  },
43
44
  "author": "夏过初秋",
44
45
  "license": "ISC"
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(),
@@ -657,6 +757,18 @@ class LineSegment {
657
757
  );
658
758
  }
659
759
  }
760
+ async function include(path, exportDefault = true) {
761
+ if (typeof global !== "undefined" && typeof require !== "undefined") {
762
+ return require(path);
763
+ } else {
764
+ let pack = await import(
765
+ /* @vite-ignore */
766
+ path
767
+ );
768
+ if (exportDefault) pack = pack.default;
769
+ return pack;
770
+ }
771
+ }
660
772
  const units = {
661
773
  Unitless: 1,
662
774
  // 无单位,1米 = 1(无单位)
@@ -701,8 +813,25 @@ const units = {
701
813
  Parsecs: 3240779289666404e-32
702
814
  // 秒差距,1米 ≈ 0.00000000000000003240779289666404秒差距
703
815
  };
816
+ function pathToLines(path) {
817
+ const lineSegments = [];
818
+ for (let i = 0; i < path.length; i++) {
819
+ lineSegments.push(new LineSegment(
820
+ path[i],
821
+ path[(i + 1) % path.length]
822
+ ));
823
+ }
824
+ return lineSegments;
825
+ }
826
+ function linesToPath(lineSegments) {
827
+ return lineSegments.flatMap((line, index2) => {
828
+ if (index2 === lineSegments.length - 1) [...line.points, lineSegments[0].points[0]];
829
+ return [line.points[0]];
830
+ });
831
+ }
704
832
  class Dxf extends Component {
705
833
  static name = "Dxf";
834
+ shortLine = 0.04;
706
835
  width = 0.04;
707
836
  scale = 1;
708
837
  originalData = [];
@@ -740,6 +869,7 @@ class Dxf extends Component {
740
869
  super();
741
870
  this.width = width;
742
871
  this.scale = scale;
872
+ this.shortLine = width * 0.8;
743
873
  }
744
874
  /**
745
875
  * 设置
@@ -765,6 +895,7 @@ class Dxf extends Component {
765
895
  this.scale = scale;
766
896
  this.width = width;
767
897
  this.originalData = data;
898
+ this.lineSegments.length = 0;
768
899
  const zList = [];
769
900
  this.data = data.map(({ start, end, insetionArr, isDoor = false }, index2) => {
770
901
  zList.push(start.z ?? 0, end.z ?? 0);
@@ -875,55 +1006,158 @@ class Dxf extends Component {
875
1006
  /** etOpenRound 去除毛刺
876
1007
  * @description 检查连续的短线段数量,去除合并后产生的毛刺
877
1008
  */
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
- }
1009
+ squareRemoveBurr(path) {
1010
+ if (path.length < 3) return path;
1011
+ const filterLines = [path[0]];
1012
+ for (let i = 1; i < path.length; i++) {
1013
+ const prev = path[i - 1];
1014
+ const curr = path[i];
1015
+ const len = prev.distance(curr);
1016
+ if (len < this.width * 0.5) {
1017
+ let count = 0;
1018
+ for (let j = i + 1; j < path.length; j++) {
1019
+ const prev2 = path[j - 1];
1020
+ const curr2 = path[j];
1021
+ const len2 = prev2.distance(curr2);
1022
+ if (len2 < this.width * 0.8) count++;
1023
+ else break;
1024
+ }
1025
+ if (count === 0 && i + count === path.length - 1) ;
1026
+ else if (i == 1 && count === 1) ;
1027
+ else if (count === 3) {
1028
+ filterLines.push(path[i + 1]);
1029
+ i += count;
1030
+ } else if (count === 5) {
1031
+ filterLines.push(path[i + 2]);
1032
+ i += count;
906
1033
  } else {
907
1034
  filterLines.push(curr);
908
1035
  }
1036
+ } else {
1037
+ filterLines.push(curr);
909
1038
  }
910
- return filterLines;
911
- });
1039
+ }
1040
+ return filterLines;
1041
+ }
1042
+ epsilon = 1e-6;
1043
+ // 距离阈值,用于比较点
1044
+ /**
1045
+ * 移除共线点
1046
+ * @param path
1047
+ * @returns
1048
+ */
1049
+ removeCollinearPoints(path) {
1050
+ for (let i = 0; i < path.length; i++) {
1051
+ const p1 = path[i];
1052
+ const p2 = path[(i + 1) % path.length];
1053
+ if (p1.distance(p2) > this.shortLine) {
1054
+ path.push(...path.slice(0, i));
1055
+ path.splice(0, i);
1056
+ break;
1057
+ }
1058
+ }
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]];
1080
+ for (let i = 1; i < lines.length; i++) {
1081
+ const line = lines[i];
1082
+ const preLine = lines[(lines.length + i - 1) % lines.length];
1083
+ if (line.length() > this.width * 0.9) {
1084
+ filterLines.push(line);
1085
+ continue;
1086
+ }
1087
+ const line1 = lines[i + 1];
1088
+ if (line1 && line1.length() > this.width * 0.9) {
1089
+ filterLines.push(line);
1090
+ filterLines.push(line1);
1091
+ i = i + 1;
1092
+ continue;
1093
+ }
1094
+ const line2 = lines[i + 2];
1095
+ if (line2 && preLine.includedAngle(line2) < 1) {
1096
+ i = i + 2;
1097
+ filterLines.push(line2);
1098
+ } else filterLines.push(line);
1099
+ }
1100
+ return filterLines.length > 3 ? linesToPath(filterLines) : [];
1101
+ }
1102
+ /**
1103
+ * 移除短线段
1104
+ * @param path
1105
+ */
1106
+ removeShortLine(path, shortLine = this.shortLine) {
1107
+ const lines = pathToLines(path), filterLines = [], PI_1 = Math.PI / 180;
1108
+ for (let i = 0; i < lines.length; i++) {
1109
+ const line = lines[i], len = line.length();
1110
+ if (len > shortLine || filterLines.length === 0) {
1111
+ filterLines.push(line);
1112
+ continue;
1113
+ }
1114
+ let nextline = lines[++i];
1115
+ const preLine = filterLines[filterLines.length - 1], d1 = preLine.direction();
1116
+ while (i < lines.length) {
1117
+ const angle = d1.angleBetween(nextline.direction()) / PI_1;
1118
+ if (nextline.length() <= shortLine || angle < 4 || angle > 180 - 4) {
1119
+ nextline = lines[++i];
1120
+ } else break;
1121
+ }
1122
+ if (nextline) {
1123
+ const intersectPoint = preLine.getIntersection(nextline);
1124
+ if (intersectPoint) {
1125
+ const p0 = preLine.points[1].clone(), p1 = nextline.points[0].clone();
1126
+ preLine.points[1].copy(intersectPoint);
1127
+ nextline.points[0].copy(intersectPoint);
1128
+ if (preLine.length() < this.width) {
1129
+ preLine.points[1].copy(p0);
1130
+ nextline.points[0].copy(p0);
1131
+ } else if (nextline.length() < this.width) {
1132
+ preLine.points[1].copy(p1);
1133
+ nextline.points[0].copy(p1);
1134
+ }
1135
+ } else {
1136
+ preLine.points[1].copy(nextline.points[0]);
1137
+ }
1138
+ filterLines.push(nextline);
1139
+ }
1140
+ }
1141
+ return filterLines.length > 3 ? linesToPath(filterLines) : [];
912
1142
  }
913
1143
  /** 线偏移
914
1144
  * @description 使用 ClipperLib 对每个点组进行线偏移处理,生成具有指定宽度的墙体路径
915
1145
  */
916
1146
  lineOffset(endType = Dxf.EndType.etOpenSquare, joinType = Dxf.JoinType.jtMiter, scale = 1e4) {
917
- const solutions = new ClipperLib.Paths();
1147
+ let solutions = new ClipperLib.Paths();
918
1148
  const offset = new ClipperLib.ClipperOffset(20, 0.25);
919
1149
  this.pointsGroups.forEach((points) => {
920
1150
  const linePaths = this.lineTopology(points).map((linePath) => linePath.map((p) => p.clone().mutiplyScalar(scale)));
921
1151
  offset.AddPaths(linePaths, joinType, endType);
922
1152
  });
923
1153
  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();
1154
+ this.wallsGroup = solutions.map((ps) => {
1155
+ let path = ps.map((p) => Point.from(p).divisionScalar(scale));
1156
+ path = this.removeCollinearPoints(path);
1157
+ path = this.lineSegmentStraightening(path);
1158
+ if (endType == Dxf.EndType.etOpenSquare) path = this.squareRemoveBurr(path);
1159
+ return path;
1160
+ });
927
1161
  this.dispatchEvent({
928
1162
  type: "lineOffset",
929
1163
  wallsGroup: this.wallsGroup
@@ -982,11 +1216,7 @@ class Dxf extends Component {
982
1216
  a.download = filename + ".dxf";
983
1217
  a.click();
984
1218
  } else if (typeof global !== "undefined") {
985
- const packageName = "fs";
986
- const { default: fs } = await import(
987
- /* @vite-ignore */
988
- packageName
989
- );
1219
+ const fs = await include("fs", false);
990
1220
  fs.writeFileSync(filename, this.toDxfString(unit));
991
1221
  }
992
1222
  }
@@ -1550,31 +1780,6 @@ class LineAnalysis extends Component {
1550
1780
  this.Dxf = parent.findComponentByType(Dxf);
1551
1781
  this.Variable = this.parent?.findComponentByType(Variable);
1552
1782
  this.Dxf.addEventListener("setDta", this.lineAnalysis.bind(this));
1553
- this.Dxf.addEventListener("lineOffset", this.duplicatePointFiltering.bind(this));
1554
- }
1555
- /**
1556
- * 去除路径上重复的点
1557
- * @description 判断方向向量,一个连续的方向上,只应该出现两个点
1558
- */
1559
- duplicatePointFiltering() {
1560
- const dxf = this.Dxf;
1561
- dxf.wallsGroup = dxf.wallsGroup.map((points) => {
1562
- const filterPoints = [];
1563
- points.forEach((point, index2) => {
1564
- if (index2 < 1 || points.length - 1 === index2) return filterPoints.push(point);
1565
- const preP = points[index2 - 1], nextP = points[index2 + 1], direct1 = point.direction(preP), direct2 = nextP.direction(point), angle = direct1.angleBetween(direct2) / (Math.PI / 180);
1566
- if (angle > 0.02 && angle < 180 - 0.02) filterPoints.push(point);
1567
- });
1568
- points = [filterPoints[0]];
1569
- for (let i = 1; i < filterPoints.length; i++) {
1570
- const point = filterPoints[i];
1571
- const nextP = filterPoints[i - 1];
1572
- if (nextP.distance(point) < dxf.width * 0.5) ;
1573
- points.push(point);
1574
- }
1575
- return points;
1576
- });
1577
- dxf.wallsGroup = dxf.wallsGroup.filter((i) => i.length >= 4);
1578
1783
  }
1579
1784
  /**
1580
1785
  *
@@ -1619,6 +1824,9 @@ class LineAnalysis extends Component {
1619
1824
  createRectangle(result) {
1620
1825
  const dxf = this.Dxf;
1621
1826
  const project0 = result.project, project1 = result.project2;
1827
+ if (project0.includedAngle(project1) > 135) {
1828
+ project1.points = [project1.points[1], project1.points[0]];
1829
+ }
1622
1830
  this.addData(project0.points[0], project1.points[0]);
1623
1831
  this.addData(project0.points[1], project1.points[1]);
1624
1832
  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);
@@ -1701,10 +1909,7 @@ class LineAnalysis extends Component {
1701
1909
  const temLineSegment = lineSegmentList[index2], direct = sourceLineSegment.direction(), temDirect = temLineSegment.direction(), angle = direct.angleBetween(temDirect) / (Math.PI / 180);
1702
1910
  if (angle < this.errorAngle || angle > 180 - this.errorAngle) {
1703
1911
  let data;
1704
- const p1 = temLineSegment.projectLineSegment(sourceLineSegment), p2 = sourceLineSegment.projectLineSegment(temLineSegment), d1 = p1.direction(), d2 = p2.direction();
1705
- 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) {
1706
- p1.points = [p1.points[1], p1.points[0]];
1707
- }
1912
+ const p1 = temLineSegment.projectLineSegment(sourceLineSegment), p2 = sourceLineSegment.projectLineSegment(temLineSegment);
1708
1913
  if (p1.getLength() > p2.getLength()) {
1709
1914
  data = {
1710
1915
  target: temLineSegment,
@@ -1760,6 +1965,7 @@ class DxfSystem extends ComponentManager {
1760
1965
  }
1761
1966
  }
1762
1967
  const exporter = new OBJExporter();
1968
+ const glbExporter = new GLTFExporter();
1763
1969
  function lineSqueezing(p1, p2, width = 0.1) {
1764
1970
  const normal = p2.normal(p1);
1765
1971
  const pDirect = p2.direction(p1).mutiplyScalar(width * 0.5);
@@ -1785,8 +1991,11 @@ class WhiteModel extends Component {
1785
1991
  Variable = null;
1786
1992
  // dxf数据白模
1787
1993
  whiteModelGroup = new THREE.Group();
1994
+ // dxf数据白模边缘线
1995
+ whiteModelLineGroup = new THREE.Group();
1788
1996
  // 原始数据白模
1789
1997
  originalWhiteMode = new THREE.Group();
1998
+ material = new THREE.MeshBasicMaterial({ color: 16777215, transparent: true, opacity: 0.8, side: THREE.DoubleSide });
1790
1999
  onAddFromParent(parent) {
1791
2000
  this.Dxf = parent.findComponentByName("Dxf");
1792
2001
  this.Variable = parent.findComponentByName("Variable");
@@ -1800,6 +2009,8 @@ class WhiteModel extends Component {
1800
2009
  const dxf = this.Dxf;
1801
2010
  this.originalWhiteMode.clear();
1802
2011
  this.whiteModelGroup.clear();
2012
+ this.whiteModelLineGroup.clear();
2013
+ this.whiteModelGroup.add(this.whiteModelLineGroup);
1803
2014
  this.whiteModelGroup.position.z = dxf.originalZAverage;
1804
2015
  this.originalWhiteMode.position.z = dxf.originalZAverage;
1805
2016
  dxf.wallsGroup.forEach((points) => {
@@ -1809,11 +2020,10 @@ class WhiteModel extends Component {
1809
2020
  depth: 2.8,
1810
2021
  bevelSize: 0
1811
2022
  });
1812
- const mesh = new THREE.Mesh(geometry);
1813
- mesh.material = new THREE.MeshStandardMaterial({ color: 16777215, transparent: true, opacity: 0.8 });
2023
+ const mesh = new THREE.Mesh(geometry, this.material);
1814
2024
  this.whiteModelGroup.add(mesh);
1815
- this.whiteModelGroup.add(
1816
- new THREE.LineSegments(new THREE.EdgesGeometry(geometry), new THREE.LineBasicMaterial({ color: 6710886 }))
2025
+ this.whiteModelLineGroup.add(
2026
+ new THREE.LineSegments(new THREE.EdgesGeometry(geometry), new THREE.LineBasicMaterial({ color: 0 }))
1817
2027
  );
1818
2028
  });
1819
2029
  const walls = dxf.originalData.map(({ start, end, insetionArr }) => {
@@ -1846,20 +2056,89 @@ class WhiteModel extends Component {
1846
2056
  whiteModelGroup: this.whiteModelGroup
1847
2057
  });
1848
2058
  }
2059
+ /**
2060
+ * 转为obj
2061
+ * @returns
2062
+ */
1849
2063
  toOBJ() {
1850
2064
  return new Promise((resolve) => {
1851
- resolve(exporter.parse(this.whiteModelGroup));
2065
+ this.material.opacity = 1;
2066
+ this.material.needsUpdate = true;
2067
+ setTimeout(() => {
2068
+ resolve(exporter.parse(this.whiteModelGroup));
2069
+ this.material.opacity = 0.8;
2070
+ this.material.transparent = true;
2071
+ }, 20);
2072
+ });
2073
+ }
2074
+ /**
2075
+ * 转为 glb
2076
+ * @param binary
2077
+ * @returns
2078
+ */
2079
+ toGltf(binary = true) {
2080
+ return new Promise((resolve) => {
2081
+ this.material.opacity = 1;
2082
+ this.material.needsUpdate = true;
2083
+ setTimeout(async () => {
2084
+ if (typeof window === "object") {
2085
+ glbExporter.parse(this.whiteModelGroup.children, (gltf) => {
2086
+ resolve(gltf);
2087
+ this.material.opacity = 0.8;
2088
+ this.material.transparent = true;
2089
+ }, () => {
2090
+ resolve(void 0);
2091
+ }, {
2092
+ binary
2093
+ });
2094
+ } else if (typeof global !== "function") {
2095
+ try {
2096
+ const obj2gltf = await include("obj2gltf", true);
2097
+ const fs = await include("fs", false);
2098
+ const obj = await this.toOBJ();
2099
+ fs.writeFileSync(this.uuid, obj);
2100
+ const result = await obj2gltf(this.uuid, {
2101
+ binary
2102
+ });
2103
+ fs.unlinkSync(this.uuid);
2104
+ if (binary) resolve(result);
2105
+ else resolve(JSON.stringify(result));
2106
+ } catch (error) {
2107
+ resolve(void 0);
2108
+ console.log(error);
2109
+ }
2110
+ }
2111
+ }, 20);
1852
2112
  });
1853
2113
  }
1854
- async toBlob() {
2114
+ /**
2115
+ * 转为 OBJBlob
2116
+ * @returns
2117
+ */
2118
+ async toOBJBlob() {
1855
2119
  const buffer = await this.toOBJ();
1856
2120
  if (buffer) {
1857
2121
  return new Blob([buffer], { type: "application/octet-stream" });
1858
2122
  }
1859
2123
  }
1860
- async download(filename) {
2124
+ /**
2125
+ * 转为 GltfBlob
2126
+ * @returns
2127
+ */
2128
+ async toGltfBlob(binary = true) {
2129
+ const buffer = await this.toGltf(binary);
2130
+ if (buffer) {
2131
+ return new Blob([buffer], { type: "application/octet-stream" });
2132
+ }
2133
+ }
2134
+ /**
2135
+ * 下载 OBJ
2136
+ * @param filename
2137
+ * @returns
2138
+ */
2139
+ async downloadOBJ(filename) {
1861
2140
  if (typeof window !== "undefined") {
1862
- const blob = await this.toBlob();
2141
+ const blob = await this.toOBJBlob();
1863
2142
  if (!blob) return;
1864
2143
  const a = document.createElement("a");
1865
2144
  a.href = URL.createObjectURL(blob);
@@ -1868,15 +2147,32 @@ class WhiteModel extends Component {
1868
2147
  } else if (typeof global !== "undefined") {
1869
2148
  const buffer = await this.toOBJ();
1870
2149
  if (buffer) {
1871
- const packageName = "fs";
1872
- const { default: fs } = await import(
1873
- /* @vite-ignore */
1874
- packageName
1875
- );
2150
+ const fs = await include("fs", false);
1876
2151
  fs.writeFileSync(filename, buffer);
1877
2152
  }
1878
2153
  }
1879
2154
  }
2155
+ /**
2156
+ * 下载 Gltf
2157
+ * @param filename
2158
+ * @returns
2159
+ */
2160
+ async downloadGltf(filename, binary = true) {
2161
+ if (typeof window !== "undefined") {
2162
+ const blob = await this.toGltfBlob(binary);
2163
+ if (!blob) return;
2164
+ const a = document.createElement("a");
2165
+ a.href = URL.createObjectURL(blob);
2166
+ a.download = filename;
2167
+ a.click();
2168
+ } else if (typeof global !== "undefined") {
2169
+ const buffer = await this.toGltf(binary);
2170
+ if (buffer) {
2171
+ const fs = await include("fs", false);
2172
+ fs.writeFileSync(filename, binary ? buffer : Buffer.from(buffer));
2173
+ }
2174
+ }
2175
+ }
1880
2176
  }
1881
2177
  class DetailsPoint extends Component {
1882
2178
  static name = "DetailsPoint";
@@ -2016,6 +2312,7 @@ export {
2016
2312
  ModelDataPlugin as M,
2017
2313
  Point as P,
2018
2314
  Variable as V,
2315
+ WhiteModel as W,
2019
2316
  DetailsPoint as a,
2020
2317
  index as i,
2021
2318
  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).downloadGltf("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,42 @@ 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;
20
+ /**
21
+ * 转为obj
22
+ * @returns
23
+ */
18
24
  toOBJ(): Promise<string>;
19
- toBlob(): Promise<Blob | undefined>;
20
- download(filename: string): Promise<void>;
25
+ /**
26
+ * 转为 glb
27
+ * @param binary
28
+ * @returns
29
+ */
30
+ toGltf(binary?: boolean): Promise<string | ArrayBuffer | undefined>;
31
+ /**
32
+ * 转为 OBJBlob
33
+ * @returns
34
+ */
35
+ toOBJBlob(): Promise<Blob | undefined>;
36
+ /**
37
+ * 转为 GltfBlob
38
+ * @returns
39
+ */
40
+ toGltfBlob(binary?: boolean): Promise<Blob | undefined>;
41
+ /**
42
+ * 下载 OBJ
43
+ * @param filename
44
+ * @returns
45
+ */
46
+ downloadOBJ(filename: string): Promise<void>;
47
+ /**
48
+ * 下载 Gltf
49
+ * @param filename
50
+ * @returns
51
+ */
52
+ downloadGltf(filename: string, binary?: boolean): Promise<void>;
21
53
  }
@@ -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
  }
@@ -0,0 +1 @@
1
+ export declare function include(path: string, exportDefault?: boolean): Promise<any>;