build-dxf 0.0.8 → 0.0.10

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.
@@ -2,6 +2,7 @@ import * as THREE from "three";
2
2
  import { EventDispatcher as EventDispatcher$1 } from "three";
3
3
  import ClipperLib from "clipper-lib";
4
4
  import Drawing from "dxf-writer";
5
+ import { OBJExporter } from "three/examples/jsm/exporters/OBJExporter.js";
5
6
  function uuid() {
6
7
  return "xxxx-xxxx-4xxx-yxxx-xxxx".replace(/[xy]/g, function(c) {
7
8
  var r = Math.random() * 16 | 0, v = c == "x" ? r : r & 3 | 8;
@@ -31,6 +32,83 @@ class Component extends EventDispatcher {
31
32
  destroy() {
32
33
  }
33
34
  }
35
+ class ComponentManager extends EventDispatcher {
36
+ static EventType = {
37
+ ADD_COMPONENT: "addComponent"
38
+ };
39
+ components = [];
40
+ /**
41
+ * 添加组件
42
+ * @param component
43
+ */
44
+ addComponent(component) {
45
+ if (component) {
46
+ this.components.push(component);
47
+ this.dispatchEvent({
48
+ type: "addComponent",
49
+ component
50
+ });
51
+ component.dispatchEvent({
52
+ type: "addFromParent",
53
+ parent: this
54
+ });
55
+ }
56
+ return this;
57
+ }
58
+ /**
59
+ * 移除组件
60
+ * @param component
61
+ */
62
+ removeComponent(component) {
63
+ if (component instanceof Component) {
64
+ const index2 = this.components.indexOf(component);
65
+ if (index2 > -1) {
66
+ this.components.splice(index2, 1);
67
+ this.dispatchEvent({
68
+ type: "removeComponent",
69
+ component
70
+ });
71
+ component.dispatchEvent({
72
+ type: "removeFromParent",
73
+ parent: this
74
+ });
75
+ }
76
+ }
77
+ }
78
+ /**
79
+ * 查找符合条件的第一个组件
80
+ * @param callBack
81
+ */
82
+ findComponent(predicate) {
83
+ for (let i = 0; i < this.components.length; i++) {
84
+ const component = this.components[i];
85
+ if (predicate(component, i)) return component;
86
+ }
87
+ }
88
+ /**
89
+ * 查找所有符合条件的组件
90
+ * @param callBack
91
+ */
92
+ findComponents(predicate) {
93
+ return this.components.find(predicate);
94
+ }
95
+ /**
96
+ *
97
+ * @param type
98
+ */
99
+ findComponentByType(type) {
100
+ const component = this.findComponent((c) => c instanceof type);
101
+ return component ? component : null;
102
+ }
103
+ /**
104
+ *
105
+ * @param type
106
+ */
107
+ findComponentByName(name) {
108
+ const component = this.findComponent((c) => c.constructor.name === name);
109
+ return component ? component : null;
110
+ }
111
+ }
34
112
  class Point {
35
113
  x;
36
114
  y;
@@ -981,6 +1059,704 @@ class Variable extends Component {
981
1059
  if (key in this) return this[key];
982
1060
  }
983
1061
  }
1062
+ class Rectangle {
1063
+ points;
1064
+ get path() {
1065
+ return this.points.flatMap((p, i) => {
1066
+ const np = this.points[(i + 1) % this.points.length];
1067
+ return [p.x, p.y, 0, np.x, np.y, 0];
1068
+ });
1069
+ }
1070
+ constructor(points) {
1071
+ if (points.length !== 4) {
1072
+ throw new Error("Rectangle must be defined by exactly 4 points");
1073
+ }
1074
+ this.points = points;
1075
+ }
1076
+ /**
1077
+ * 判断线段是否与矩形相交
1078
+ * @param line 线段
1079
+ * @returns 是否与矩形相交
1080
+ */
1081
+ intersectLineSegment(line) {
1082
+ if (line.points.length !== 2) {
1083
+ throw new Error("LineSegment must have exactly 2 points");
1084
+ }
1085
+ const [p1, p2] = line.points;
1086
+ const doIntersect = (l1p1, l1p2, l2p1, l2p2) => {
1087
+ const orientation = (p, q, r) => {
1088
+ const val = (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y);
1089
+ if (val === 0) return 0;
1090
+ return val > 0 ? 1 : 2;
1091
+ };
1092
+ const onSegment = (p, q, r) => {
1093
+ 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);
1094
+ };
1095
+ const o1 = orientation(l1p1, l1p2, l2p1);
1096
+ const o2 = orientation(l1p1, l1p2, l2p2);
1097
+ const o3 = orientation(l2p1, l2p2, l1p1);
1098
+ const o4 = orientation(l2p1, l2p2, l1p2);
1099
+ if (o1 !== o2 && o3 !== o4) return true;
1100
+ if (o1 === 0 && onSegment(l1p1, l2p1, l1p2)) return true;
1101
+ if (o2 === 0 && onSegment(l1p1, l2p2, l1p2)) return true;
1102
+ if (o3 === 0 && onSegment(l2p1, l1p1, l2p2)) return true;
1103
+ if (o4 === 0 && onSegment(l2p1, l1p2, l2p2)) return true;
1104
+ return false;
1105
+ };
1106
+ for (let i = 0; i < 4; i++) {
1107
+ const rectP1 = this.points[i];
1108
+ const rectP2 = this.points[(i + 1) % 4];
1109
+ if (doIntersect(p1, p2, rectP1, rectP2)) {
1110
+ return true;
1111
+ }
1112
+ }
1113
+ if (this.containsLineSegment(line)) {
1114
+ return true;
1115
+ }
1116
+ return false;
1117
+ }
1118
+ /**
1119
+ * 判断线段是否完全位于矩形内部
1120
+ * @param line 线段
1121
+ * @returns 是否完全在矩形内部
1122
+ */
1123
+ containsLineSegment(line) {
1124
+ if (line.points.length !== 2) {
1125
+ throw new Error("LineSegment must have exactly 2 points");
1126
+ }
1127
+ const isPointInRectangle = (point) => {
1128
+ let sign = 0;
1129
+ for (let i = 0; i < 4; i++) {
1130
+ const p1 = this.points[i];
1131
+ const p2 = this.points[(i + 1) % 4];
1132
+ const edge = { x: p2.x - p1.x, y: p2.y - p1.y };
1133
+ const toPoint = { x: point.x - p1.x, y: point.y - p1.y };
1134
+ const cross = edge.x * toPoint.y - edge.y * toPoint.x;
1135
+ if (cross === 0) {
1136
+ const t = edge.x !== 0 ? (point.x - p1.x) / edge.x : (point.y - p1.y) / edge.y;
1137
+ if (t >= 0 && t <= 1) return true;
1138
+ } else {
1139
+ const currentSign = cross > 0 ? 1 : -1;
1140
+ if (sign === 0) sign = currentSign;
1141
+ if (sign !== currentSign) return false;
1142
+ }
1143
+ }
1144
+ return true;
1145
+ };
1146
+ return isPointInRectangle(line.points[0]) && isPointInRectangle(line.points[1]);
1147
+ }
1148
+ /**
1149
+ * 判断点是否完全位于矩形内部
1150
+ * @param point
1151
+ */
1152
+ containsPoint(point) {
1153
+ let positiveCount = 0;
1154
+ let negativeCount = 0;
1155
+ for (let i = 0; i < 4; i++) {
1156
+ const p1 = this.points[i];
1157
+ const p2 = this.points[(i + 1) % 4];
1158
+ const cross = (p2.x - p1.x) * (point.y - p1.y) - (p2.y - p1.y) * (point.x - p1.x);
1159
+ if (cross > 0) positiveCount++;
1160
+ else if (cross < 0) negativeCount++;
1161
+ else return false;
1162
+ }
1163
+ return positiveCount === 4 || negativeCount === 4;
1164
+ }
1165
+ /**
1166
+ *
1167
+ * @returns
1168
+ */
1169
+ toBox() {
1170
+ let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
1171
+ this.points.forEach((p) => {
1172
+ maxX = Math.max(p.x, maxX);
1173
+ minX = Math.min(p.x, minX);
1174
+ maxY = Math.max(p.x, maxY);
1175
+ minY = Math.min(p.x, minY);
1176
+ });
1177
+ return new Box2(minX, maxX, minY, maxY);
1178
+ }
1179
+ /**
1180
+ *
1181
+ * @param line
1182
+ * @param width
1183
+ * @returns
1184
+ */
1185
+ static fromByLineSegment(line, width = 0.1, horizontal = false, hScale = 0.5) {
1186
+ 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();
1187
+ const offsetX = normal.x * width * 0.5;
1188
+ const offsetY = normal.y * width * 0.5;
1189
+ return new Rectangle([
1190
+ new Point(p1.x + offsetX, p1.y + offsetY).add(nDirect),
1191
+ new Point(p2.x + offsetX, p2.y + offsetY).add(pDirect),
1192
+ new Point(p2.x - offsetX, p2.y - offsetY).add(pDirect),
1193
+ new Point(p1.x - offsetX, p1.y - offsetY).add(nDirect)
1194
+ ]);
1195
+ }
1196
+ }
1197
+ class Quadtree {
1198
+ bounds;
1199
+ // 包围盒
1200
+ capacity;
1201
+ // 节点容量
1202
+ maxDepth;
1203
+ // 最大深度
1204
+ depth;
1205
+ // 当前深度
1206
+ isLeaf = true;
1207
+ // 是否为叶子节点
1208
+ children = null;
1209
+ // 子节点数组
1210
+ nodes = [];
1211
+ // 存储的节点
1212
+ color = [Math.random(), Math.random(), Math.random()];
1213
+ // 颜色
1214
+ constructor(bounds, capacity = 8, maxDepth = 10, depth = 1) {
1215
+ this.bounds = bounds;
1216
+ this.capacity = capacity;
1217
+ this.depth = depth;
1218
+ this.maxDepth = maxDepth;
1219
+ }
1220
+ /**
1221
+ * 插入线段节点
1222
+ * @param node 线段节点
1223
+ */
1224
+ insert(node) {
1225
+ if (!this.isLeaf) {
1226
+ const quadrant = this.getQuadrant(node.line);
1227
+ if (quadrant !== -1) {
1228
+ this.children[quadrant].insert(node);
1229
+ return;
1230
+ }
1231
+ }
1232
+ this.nodes.push(node);
1233
+ if (this.isLeaf && this.nodes.length > this.capacity && this.depth < this.maxDepth) {
1234
+ this.subdivide();
1235
+ const nodes = this.nodes;
1236
+ this.nodes = [];
1237
+ for (const n of nodes) {
1238
+ const quadrant = this.getQuadrant(n.line);
1239
+ if (quadrant !== -1) {
1240
+ this.children[quadrant].insert(n);
1241
+ } else {
1242
+ this.nodes.push(n);
1243
+ }
1244
+ }
1245
+ }
1246
+ }
1247
+ /**
1248
+ * 获取线段所属的象限
1249
+ * @param line 线段
1250
+ * @returns 象限索引(0:西北,1:东北,2:西南,3:东南)或-1(跨多个象限)
1251
+ */
1252
+ getQuadrant(line) {
1253
+ const intersectsNW = this.children[0].bounds.intersectLineSegment(line);
1254
+ const intersectsNE = this.children[1].bounds.intersectLineSegment(line);
1255
+ const intersectsSW = this.children[2].bounds.intersectLineSegment(line);
1256
+ const intersectsSE = this.children[3].bounds.intersectLineSegment(line);
1257
+ let count = 0;
1258
+ let quadrant = -1;
1259
+ if (intersectsNW) {
1260
+ count++;
1261
+ quadrant = 0;
1262
+ }
1263
+ if (intersectsNE) {
1264
+ count++;
1265
+ quadrant = 1;
1266
+ }
1267
+ if (intersectsSW) {
1268
+ count++;
1269
+ quadrant = 2;
1270
+ }
1271
+ if (intersectsSE) {
1272
+ count++;
1273
+ quadrant = 3;
1274
+ }
1275
+ if (count === 1) return quadrant;
1276
+ return -1;
1277
+ }
1278
+ /**
1279
+ * 细分当前节点为四个子节点
1280
+ */
1281
+ subdivide() {
1282
+ if (!this.isLeaf) return;
1283
+ this.isLeaf = false;
1284
+ this.children = [];
1285
+ const midX = (this.bounds.minX + this.bounds.maxX) / 2;
1286
+ const midY = (this.bounds.minY + this.bounds.maxY) / 2;
1287
+ this.children[0] = new Quadtree(
1288
+ new Box2(this.bounds.minX, midX, this.bounds.minY, midY),
1289
+ this.capacity,
1290
+ this.maxDepth,
1291
+ this.depth + 1
1292
+ );
1293
+ this.children[1] = new Quadtree(
1294
+ new Box2(midX, this.bounds.maxX, this.bounds.minY, midY),
1295
+ this.capacity,
1296
+ this.maxDepth,
1297
+ this.depth + 1
1298
+ );
1299
+ this.children[2] = new Quadtree(
1300
+ new Box2(this.bounds.minX, midX, midY, this.bounds.maxY),
1301
+ this.capacity,
1302
+ this.maxDepth,
1303
+ this.depth + 1
1304
+ );
1305
+ this.children[3] = new Quadtree(
1306
+ new Box2(midX, this.bounds.maxX, midY, this.bounds.maxY),
1307
+ this.capacity,
1308
+ this.maxDepth,
1309
+ this.depth + 1
1310
+ );
1311
+ }
1312
+ /**
1313
+ * 查询与包围盒相交的线段节点
1314
+ * @param box2 包围盒
1315
+ * @returns 相交的节点数组
1316
+ */
1317
+ queryBox(box2) {
1318
+ const result = [];
1319
+ if (!this.bounds.intersectBox(box2)) {
1320
+ return result;
1321
+ }
1322
+ for (const node of this.nodes) {
1323
+ if (box2.intersectLineSegment(node.line)) {
1324
+ result.push(node);
1325
+ }
1326
+ }
1327
+ if (!this.isLeaf) {
1328
+ for (const child of this.children) {
1329
+ result.push(...child.queryBox(box2));
1330
+ }
1331
+ }
1332
+ return result;
1333
+ }
1334
+ /**
1335
+ * 查询与圆形区域相交的线段节点
1336
+ * @param pos 圆心
1337
+ * @param radius 半径
1338
+ * @returns 相交的节点数组
1339
+ */
1340
+ queryCircle(pos, radius) {
1341
+ const result = [];
1342
+ const circleBox = new Box2(
1343
+ pos.x - radius,
1344
+ pos.x + radius,
1345
+ pos.y - radius,
1346
+ pos.y + radius
1347
+ );
1348
+ if (!this.bounds.intersectBox(circleBox)) {
1349
+ return result;
1350
+ }
1351
+ for (const node of this.nodes) {
1352
+ const [p1, p2] = node.line.points;
1353
+ const dx = p2.x - p1.x;
1354
+ const dy = p2.y - p1.y;
1355
+ const l2 = dx * dx + dy * dy;
1356
+ let t = ((pos.x - p1.x) * dx + (pos.y - p1.y) * dy) / l2;
1357
+ t = Math.max(0, Math.min(1, t));
1358
+ const closestX = p1.x + t * dx;
1359
+ const closestY = p1.y + t * dy;
1360
+ const distance = pos.distance(new Point(closestX, closestY));
1361
+ if (distance <= radius) {
1362
+ result.push(node);
1363
+ }
1364
+ }
1365
+ if (!this.isLeaf) {
1366
+ for (const child of this.children) {
1367
+ result.push(...child.queryCircle(pos, radius));
1368
+ }
1369
+ }
1370
+ return result;
1371
+ }
1372
+ /**
1373
+ * 查询与矩形相交的线段节点
1374
+ * @param rectangle 矩形
1375
+ * @returns 相交的节点数组
1376
+ */
1377
+ queryRect(rectangle) {
1378
+ const result = [];
1379
+ if (!this.bounds.intersectRectangle(rectangle)) {
1380
+ return result;
1381
+ }
1382
+ for (const node of this.nodes) {
1383
+ if (rectangle.intersectLineSegment(node.line)) {
1384
+ result.push(node);
1385
+ }
1386
+ }
1387
+ if (!this.isLeaf) {
1388
+ for (const child of this.children) {
1389
+ result.push(...child.queryRect(rectangle));
1390
+ }
1391
+ }
1392
+ return result;
1393
+ }
1394
+ /**
1395
+ * 包围盒转换为数组
1396
+ * @param array
1397
+ * @param colors
1398
+ * @returns
1399
+ */
1400
+ boundsToArray(array = [], colors, recursion = true) {
1401
+ if (!this.isLeaf && recursion) {
1402
+ this.children?.forEach((child) => child.boundsToArray(array, colors));
1403
+ }
1404
+ array.push(...this.bounds.points.flatMap((p, i, array2) => {
1405
+ const np = array2[(i + 1) % array2.length];
1406
+ colors?.push(...this.color);
1407
+ colors?.push(...this.color);
1408
+ return [p.x, p.y, 0, np.x, np.y, 0];
1409
+ }));
1410
+ return array;
1411
+ }
1412
+ }
1413
+ class PointVirtualGrid {
1414
+ map = /* @__PURE__ */ new Map();
1415
+ gridSize;
1416
+ constructor(gridSize = 2) {
1417
+ this.gridSize = gridSize;
1418
+ }
1419
+ /**
1420
+ * 插入
1421
+ * @param point
1422
+ * @param userData
1423
+ */
1424
+ insert(point, userData) {
1425
+ if (!point || isNaN(point.x) || isNaN(point.y)) {
1426
+ throw new Error("无效的点坐标");
1427
+ }
1428
+ const id = this.getGridId(point);
1429
+ if (!this.map.has(id)) this.map.set(id, /* @__PURE__ */ new Set());
1430
+ const set = this.map.get(id);
1431
+ set?.add({ point, userData });
1432
+ }
1433
+ /**
1434
+ * 批量加入
1435
+ * @param points
1436
+ */
1437
+ insertBatch(points) {
1438
+ for (const { point, userData } of points) {
1439
+ this.insert(point, userData);
1440
+ }
1441
+ }
1442
+ /**
1443
+ * 获取通过坐标,获取唯一网格索引
1444
+ * @param point
1445
+ * @returns
1446
+ */
1447
+ getGridId(point) {
1448
+ const i = Math.ceil(point.x / this.gridSize), j = Math.ceil(point.y / this.gridSize);
1449
+ return `${i}.${j}`;
1450
+ }
1451
+ /**
1452
+ *
1453
+ * @param gridId
1454
+ * @returns
1455
+ */
1456
+ decodeGridId(gridId) {
1457
+ const [i, j] = gridId.split(".").map(Number);
1458
+ return new Point(i, j);
1459
+ }
1460
+ /**
1461
+ * 查询与矩形相交的点
1462
+ * @param rectangle 矩形
1463
+ * @returns 相交的节点数组
1464
+ */
1465
+ queryRect(rectangle) {
1466
+ const box2 = rectangle.toBox();
1467
+ const minI = Math.ceil(box2.minX / this.gridSize), maxI = Math.ceil(box2.maxX / this.gridSize), minJ = Math.ceil(box2.minY / this.gridSize), maxJ = Math.ceil(box2.maxY / this.gridSize);
1468
+ for (let i = minI; i <= maxI; i++) {
1469
+ for (let j = minJ; j <= maxJ; j++) {
1470
+ const id = `${i}.${j}`;
1471
+ if (!this.map.has(id)) continue;
1472
+ const set = this.map.get(id);
1473
+ set?.forEach((item) => {
1474
+ if (rectangle.containsPoint(item.point)) ;
1475
+ });
1476
+ }
1477
+ }
1478
+ }
1479
+ /**
1480
+ * 查询与圆形区域相交的点
1481
+ * @param pos 圆心
1482
+ * @param radius 半径
1483
+ * @returns 相交的节点数组
1484
+ */
1485
+ queryCircle(pos, radius) {
1486
+ const box2 = new Box2(pos.x - radius, pos.x + radius, pos.y - radius, pos.y + radius);
1487
+ const minI = Math.ceil(box2.minX / this.gridSize), maxI = Math.ceil(box2.maxX / this.gridSize), minJ = Math.ceil(box2.minY / this.gridSize), maxJ = Math.ceil(box2.maxY / this.gridSize), list = [];
1488
+ for (let i = minI; i <= maxI; i++) {
1489
+ for (let j = minJ; j <= maxJ; j++) {
1490
+ const id = `${i}.${j}`;
1491
+ if (!this.map.has(id)) continue;
1492
+ const set = this.map.get(id);
1493
+ set?.forEach((item) => {
1494
+ if (pos.distance(item.point) <= radius) list.push(item);
1495
+ });
1496
+ }
1497
+ }
1498
+ return list;
1499
+ }
1500
+ /**
1501
+ * 查询与包围盒相交的点
1502
+ * @param box2 包围盒
1503
+ * @returns 相交的节点数组
1504
+ */
1505
+ queryBox(box2) {
1506
+ const minI = Math.ceil(box2.minX / this.gridSize), maxI = Math.ceil(box2.maxX / this.gridSize), minJ = Math.ceil(box2.minY / this.gridSize), maxJ = Math.ceil(box2.maxY / this.gridSize), list = [];
1507
+ for (let i = minI; i <= maxI; i++) {
1508
+ for (let j = minJ; j <= maxJ; j++) {
1509
+ const id = `${i}.${j}`;
1510
+ if (!this.map.has(id)) continue;
1511
+ const set = this.map.get(id);
1512
+ set?.forEach((item) => {
1513
+ if (box2.containsPoint(item.point)) list.push(item);
1514
+ });
1515
+ }
1516
+ }
1517
+ return list;
1518
+ }
1519
+ /**
1520
+ * 查找相同点
1521
+ * @param point
1522
+ */
1523
+ queryPoint(point) {
1524
+ const id = this.getGridId(point), list = [];
1525
+ if (this.map.has(id)) {
1526
+ const set = this.map.get(id);
1527
+ set?.forEach((item) => {
1528
+ if (point.equal(item.point)) list.push(item);
1529
+ });
1530
+ }
1531
+ return list;
1532
+ }
1533
+ }
1534
+ class LineAnalysis extends Component {
1535
+ Dxf = null;
1536
+ Variable = null;
1537
+ lineSegmentList = [];
1538
+ container = new THREE.Group();
1539
+ // 误差角度
1540
+ errorAngle = 4;
1541
+ width = 0.4;
1542
+ /**
1543
+ *
1544
+ * @param parent
1545
+ */
1546
+ onAddFromParent(parent) {
1547
+ this.Dxf = parent.findComponentByName("Dxf");
1548
+ this.Variable = this.parent?.findComponentByName("Variable");
1549
+ this.Dxf.addEventListener("setDta", this.lineAnalysis.bind(this));
1550
+ this.Dxf.addEventListener("lineOffset", this.duplicatePointFiltering.bind(this));
1551
+ }
1552
+ /**
1553
+ * 去除路径上重复的点
1554
+ * @description 判断方向向量,一个连续的方向上,只应该出现两个点
1555
+ */
1556
+ duplicatePointFiltering() {
1557
+ const dxf = this.Dxf;
1558
+ dxf.wallsGroup = dxf.wallsGroup.map((points) => {
1559
+ const filterPoints = [];
1560
+ points.forEach((point, index2) => {
1561
+ if (index2 < 1 || points.length - 1 === index2) return filterPoints.push(point);
1562
+ const preP = points[index2 - 1], nextP = points[index2 + 1], direct1 = point.direction(preP), direct2 = nextP.direction(point), angle = direct1.angleBetween(direct2) / (Math.PI / 180);
1563
+ if (angle > 0.02 && angle < 180 - 0.02) filterPoints.push(point);
1564
+ });
1565
+ points = [filterPoints[0]];
1566
+ for (let i = 1; i < filterPoints.length; i++) {
1567
+ const point = filterPoints[i];
1568
+ const nextP = filterPoints[i - 1];
1569
+ if (nextP.distance(point) < dxf.width * 0.5) ;
1570
+ points.push(point);
1571
+ }
1572
+ return points;
1573
+ });
1574
+ dxf.wallsGroup = dxf.wallsGroup.filter((i) => i.length >= 4);
1575
+ }
1576
+ /**
1577
+ *
1578
+ * @param p1
1579
+ * @param p2
1580
+ * @param width
1581
+ * @returns
1582
+ */
1583
+ expandLineSegment(p1, p2, width = 0.1) {
1584
+ const normal = p2.normal(p1);
1585
+ const pDirect = p2.direction(p1).mutiplyScalar(width * 0.5);
1586
+ const nDirect = p1.direction(p2).mutiplyScalar(width * 0.5);
1587
+ const offsetX = normal.x * width * 0.5;
1588
+ const offsetY = normal.y * width * 0.5;
1589
+ return {
1590
+ points: [
1591
+ // 第一条线
1592
+ new Point(p1.x + offsetX, p1.y + offsetY).add(nDirect),
1593
+ new Point(p2.x + offsetX, p2.y + offsetY).add(pDirect),
1594
+ // 第二条线
1595
+ new Point(p1.x - offsetX, p1.y - offsetY).add(nDirect),
1596
+ new Point(p2.x - offsetX, p2.y - offsetY).add(pDirect)
1597
+ ],
1598
+ indices: [0, 1, 1, 3, 3, 2, 2, 0],
1599
+ rectIndices: [0, 1, 3, 2, 0]
1600
+ };
1601
+ }
1602
+ appendLineSegmentList = [];
1603
+ /**
1604
+ * 追加数据
1605
+ * @param p1
1606
+ * @param p2
1607
+ */
1608
+ addData(p1, p2) {
1609
+ const dxf = this.Dxf;
1610
+ dxf.data.push([p1.clone(), p2.clone(), [], false, dxf.data.length]);
1611
+ this.appendLineSegmentList.push(new LineSegment(p1.clone(), p2.clone()));
1612
+ }
1613
+ /** 结果分析创建矩形
1614
+ * @param result
1615
+ */
1616
+ createRectangle(result) {
1617
+ const dxf = this.Dxf;
1618
+ const project0 = result.project, project1 = result.project2;
1619
+ this.addData(project0.points[0], project1.points[0]);
1620
+ this.addData(project0.points[1], project1.points[1]);
1621
+ 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);
1622
+ direction.multiplyScalar(dxf.width * 0.5);
1623
+ const _leftP = leftP.clone().add(direction), _rightP = rightP.clone().add(direction.multiplyScalar(-1)), d1 = leftP.direction(rightP), d2 = _leftP.direction(_rightP);
1624
+ 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) return;
1625
+ leftP.set(_leftP.x, _leftP.y);
1626
+ rightP.set(_rightP.x, _rightP.y);
1627
+ for (let i = 1; i < count; i++) {
1628
+ const left = leftDirection.clone().multiplyScalar(leftFragment * i), right = rightDirection.clone().multiplyScalar(rightFragment * i), p1 = leftP.clone().add(left), p2 = rightP.clone().add(right);
1629
+ this.addData(p1, p2);
1630
+ }
1631
+ }
1632
+ pointVirtualGrid = new PointVirtualGrid();
1633
+ /**
1634
+ * 构建点的虚拟网格索引
1635
+ */
1636
+ buildVirtualGrid() {
1637
+ const dxf = this.Dxf;
1638
+ const pointVirtualGrid = new PointVirtualGrid();
1639
+ dxf.originalData.forEach((d, index2) => {
1640
+ const [p1, p2] = [Point.from(d.start), Point.from(d.end)];
1641
+ pointVirtualGrid.insert(p1, { index: index2, type: "start" });
1642
+ pointVirtualGrid.insert(p2, { index: index2, type: "end" });
1643
+ });
1644
+ this.pointVirtualGrid = pointVirtualGrid;
1645
+ }
1646
+ quadtree;
1647
+ /**
1648
+ * 构建线段四叉树,快速查找,
1649
+ */
1650
+ buildQuadtree() {
1651
+ const dxf = this.Dxf;
1652
+ const lineSegmentList = [];
1653
+ this.quadtree = new Quadtree(dxf.originalBox, 2);
1654
+ dxf.lineSegments.forEach((lineSegment) => {
1655
+ if (lineSegment.userData?.isDoor) return;
1656
+ this.quadtree?.insert({
1657
+ line: lineSegment,
1658
+ userData: lineSegmentList.length
1659
+ });
1660
+ lineSegmentList.push(lineSegment);
1661
+ });
1662
+ this.lineSegmentList = lineSegmentList;
1663
+ }
1664
+ resultList = [];
1665
+ /** 线段分析
1666
+ * @description 判断两条线段距离是否较短且趋近平行,然后查找两条线段的重合部分的投影线,以此判断两根线是否需要合并
1667
+ * @param data
1668
+ */
1669
+ lineAnalysis() {
1670
+ this.buildQuadtree();
1671
+ this.buildVirtualGrid();
1672
+ const quadtree = this.quadtree;
1673
+ const lineSegmentList = this.lineSegmentList;
1674
+ const visited = /* @__PURE__ */ new Set(), resultList = [];
1675
+ lineSegmentList.forEach((_0, i) => {
1676
+ const sourceLineSegment = lineSegmentList[i], rectangle = Rectangle.fromByLineSegment(sourceLineSegment, this.width * 2, false, -0.01), ids = quadtree.queryRect(rectangle).map((i2) => i2.userData).filter((index2) => index2 !== i);
1677
+ ids.forEach((id) => {
1678
+ try {
1679
+ if (visited.has(`${i}-${id}`) || visited.has(`${id}-${i}`)) return;
1680
+ const res = this.projectionAnalysis(id, i, sourceLineSegment, lineSegmentList);
1681
+ if (res) resultList.push(res);
1682
+ visited.add(`${i}-${id}`);
1683
+ } catch (error) {
1684
+ }
1685
+ });
1686
+ });
1687
+ this.appendLineSegmentList.length = 0;
1688
+ resultList.forEach(this.createRectangle.bind(this));
1689
+ this.resultList = [];
1690
+ }
1691
+ /** 线段投影分析
1692
+ * @param index
1693
+ * @param sourceLineSegment
1694
+ * @param lineSegmentList
1695
+ * @returns
1696
+ */
1697
+ projectionAnalysis(index2, sourceIndex, sourceLineSegment, lineSegmentList) {
1698
+ const temLineSegment = lineSegmentList[index2], direct = sourceLineSegment.direction(), temDirect = temLineSegment.direction(), angle = direct.angleBetween(temDirect) / (Math.PI / 180);
1699
+ if (angle < this.errorAngle || angle > 180 - this.errorAngle) {
1700
+ let data;
1701
+ const p1 = temLineSegment.projectLineSegment(sourceLineSegment), p2 = sourceLineSegment.projectLineSegment(temLineSegment), d1 = p1.direction(), d2 = p2.direction();
1702
+ 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) {
1703
+ p1.points = [p1.points[1], p1.points[0]];
1704
+ }
1705
+ if (p1.getLength() > p2.getLength()) {
1706
+ data = {
1707
+ target: temLineSegment,
1708
+ targetIndex: index2,
1709
+ source: sourceLineSegment,
1710
+ sourceIndex,
1711
+ project: p1,
1712
+ project2: p2
1713
+ };
1714
+ } else {
1715
+ data = {
1716
+ target: sourceLineSegment,
1717
+ targetIndex: sourceIndex,
1718
+ source: temLineSegment,
1719
+ sourceIndex: index2,
1720
+ project: p2,
1721
+ project2: p1
1722
+ };
1723
+ }
1724
+ if (!data || data.project.getLength() < 0.01) return;
1725
+ return data;
1726
+ }
1727
+ }
1728
+ }
1729
+ class DxfSystem extends ComponentManager {
1730
+ Dxf;
1731
+ Variable;
1732
+ wallWidth;
1733
+ environment;
1734
+ /** 构造函数
1735
+ * @param wallWidth 输出墙壁厚度,该墙壁厚度不受缩放影响
1736
+ * @param scale 原始数据缩放比例
1737
+ */
1738
+ constructor(wallWidth = 0.1, scale = 1) {
1739
+ super();
1740
+ this.environment = typeof window !== "undefined" ? "browser" : typeof global !== "undefined" ? "node" : "unknown";
1741
+ this.wallWidth = wallWidth;
1742
+ this.Dxf = new Dxf(this.wallWidth, scale);
1743
+ this.Variable = new Variable();
1744
+ this.addComponent(this.Variable);
1745
+ this.addComponent(this.Dxf);
1746
+ this.addComponent(new LineAnalysis());
1747
+ }
1748
+ usePlugin(plugin) {
1749
+ if (typeof plugin === "function") plugin.call(this, this);
1750
+ return this;
1751
+ }
1752
+ destroy() {
1753
+ this.components.forEach((com) => {
1754
+ this.removeComponent(com);
1755
+ com.destroy();
1756
+ });
1757
+ }
1758
+ }
1759
+ const exporter = new OBJExporter();
984
1760
  function lineSqueezing(p1, p2, width = 0.1) {
985
1761
  const normal = p2.normal(p1);
986
1762
  const pDirect = p2.direction(p1).mutiplyScalar(width * 0.5);
@@ -1008,8 +1784,8 @@ class WhiteModel extends Component {
1008
1784
  // 原始数据白模
1009
1785
  originalWhiteMode = new THREE.Group();
1010
1786
  onAddFromParent(parent) {
1011
- this.Dxf = parent.findComponentByType(Dxf);
1012
- this.Variable = parent.findComponentByType(Variable);
1787
+ this.Dxf = parent.findComponentByName("Dxf");
1788
+ this.Variable = parent.findComponentByName("Variable");
1013
1789
  this.originalWhiteMode.visible = false;
1014
1790
  this.Dxf?.addEventListener("lineOffset", () => {
1015
1791
  this.updateModel();
@@ -1030,7 +1806,7 @@ class WhiteModel extends Component {
1030
1806
  bevelSize: 0
1031
1807
  });
1032
1808
  const mesh = new THREE.Mesh(geometry);
1033
- mesh.material = new THREE.MeshPhongMaterial({ color: 16777215, transparent: true, opacity: 0.8 });
1809
+ mesh.material = new THREE.MeshStandardMaterial({ color: 16777215, transparent: true, opacity: 0.8 });
1034
1810
  this.whiteModelGroup.add(mesh);
1035
1811
  this.whiteModelGroup.add(
1036
1812
  new THREE.LineSegments(new THREE.EdgesGeometry(geometry), new THREE.LineBasicMaterial({ color: 6710886 }))
@@ -1066,6 +1842,37 @@ class WhiteModel extends Component {
1066
1842
  whiteModelGroup: this.whiteModelGroup
1067
1843
  });
1068
1844
  }
1845
+ toOBJ() {
1846
+ return new Promise((resolve) => {
1847
+ resolve(exporter.parse(this.whiteModelGroup));
1848
+ });
1849
+ }
1850
+ async toBlob() {
1851
+ const buffer = await this.toOBJ();
1852
+ if (buffer) {
1853
+ return new Blob([buffer], { type: "application/octet-stream" });
1854
+ }
1855
+ }
1856
+ async download(filename) {
1857
+ if (typeof window !== "undefined") {
1858
+ const blob = await this.toBlob();
1859
+ if (!blob) return;
1860
+ const a = document.createElement("a");
1861
+ a.href = URL.createObjectURL(blob);
1862
+ a.download = filename;
1863
+ a.click();
1864
+ } else if (typeof global !== "undefined") {
1865
+ const buffer = await this.toOBJ();
1866
+ if (buffer) {
1867
+ const packageName = "fs";
1868
+ const { default: fs } = await import(
1869
+ /* @vite-ignore */
1870
+ packageName
1871
+ );
1872
+ fs.writeFileSync(filename, buffer);
1873
+ }
1874
+ }
1875
+ }
1069
1876
  }
1070
1877
  class DetailsPoint extends Component {
1071
1878
  Dxf = null;
@@ -1075,8 +1882,8 @@ class DetailsPoint extends Component {
1075
1882
  raylines = [];
1076
1883
  data = [];
1077
1884
  onAddFromParent(parent) {
1078
- this.Dxf = parent.findComponentByType(Dxf);
1079
- this.Variable = parent.findComponentByType(Variable);
1885
+ this.Dxf = parent.findComponentByName("Dxf");
1886
+ this.Variable = parent.findComponentByName("Variable");
1080
1887
  this.Dxf?.addEventListener("setDta", () => {
1081
1888
  this.updateModel();
1082
1889
  });
@@ -1085,7 +1892,22 @@ class DetailsPoint extends Component {
1085
1892
  * 设置值
1086
1893
  * @param data
1087
1894
  */
1088
- set(data) {
1895
+ async set(data) {
1896
+ if (typeof data === "string") {
1897
+ if (typeof global !== "undefined") {
1898
+ const packageName = "fs";
1899
+ const { default: fs } = await import(
1900
+ /* @vite-ignore */
1901
+ packageName
1902
+ );
1903
+ const buffer = fs.readFileSync(data);
1904
+ const json = JSON.parse(buffer.toString("utf-8"));
1905
+ this.set(json);
1906
+ return;
1907
+ } else {
1908
+ throw new Error("非node环境不允许使用路径");
1909
+ }
1910
+ }
1089
1911
  this.data = data;
1090
1912
  this.updateModel();
1091
1913
  }
@@ -1111,7 +1933,7 @@ class DetailsPoint extends Component {
1111
1933
  if (this._timer) clearTimeout(this._timer);
1112
1934
  this._timer = setTimeout(() => {
1113
1935
  this._timer = null;
1114
- const whiteModel = this.parent?.findComponentByType(WhiteModel);
1936
+ const whiteModel = this.parent?.findComponentByName("WhiteModel");
1115
1937
  this.raylines.length = 0;
1116
1938
  this.desPoints.length = 0;
1117
1939
  this.data.forEach((item) => {
@@ -1151,14 +1973,14 @@ class DxfLineModel extends Component {
1151
1973
  dxfDoorsLineModel = new THREE.LineSegments();
1152
1974
  dxfModelGroup = new THREE.Group();
1153
1975
  onAddFromParent(parent) {
1154
- const dxf = parent.findComponentByType(Dxf);
1976
+ const dxf = parent.findComponentByName("Dxf");
1155
1977
  this.dxfModelGroup.add(this.dxfLineModel);
1156
1978
  this.dxfModelGroup.add(this.dxfDoorsLineModel);
1157
1979
  this.dxfDoorsLineModel.material = new THREE.LineBasicMaterial({ color: 16776960, vertexColors: true });
1158
1980
  dxf?.addEventListener("lineOffset", () => this.updateMode());
1159
1981
  }
1160
1982
  updateMode() {
1161
- const dxf = this.parent?.findComponentByType(Dxf);
1983
+ const dxf = this.parent?.findComponentByName("Dxf");
1162
1984
  this.dxfLineModel.clear();
1163
1985
  const dxfArray = dxf.to3DArray(1 / dxf.scale);
1164
1986
  this.dxfLineModel.geometry = new THREE.BufferGeometry().setAttribute("position", new THREE.BufferAttribute(dxfArray, 3, true));
@@ -1167,18 +1989,33 @@ class DxfLineModel extends Component {
1167
1989
  this.dxfDoorsLineModel.geometry = new THREE.BufferGeometry().setAttribute("position", new THREE.BufferAttribute(doorsArray, 3, true)).setAttribute("color", new THREE.BufferAttribute(doorsColorArray, 3));
1168
1990
  }
1169
1991
  }
1170
- const index = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
1992
+ const index$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
1171
1993
  __proto__: null,
1172
1994
  DetailsPoint,
1173
1995
  DxfLineModel,
1174
1996
  WhiteModel
1175
1997
  }, Symbol.toStringTag, { value: "Module" }));
1176
- function ModeDataPlugin(dxfSystem) {
1998
+ function ModelDataPlugin(dxfSystem) {
1177
1999
  dxfSystem.addComponent(new DxfLineModel());
1178
2000
  dxfSystem.addComponent(new WhiteModel());
1179
2001
  dxfSystem.addComponent(new DetailsPoint());
1180
2002
  }
2003
+ const index = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
2004
+ __proto__: null,
2005
+ ModelDataPlugin,
2006
+ components: index$1
2007
+ }, Symbol.toStringTag, { value: "Module" }));
2008
+ function loadRenderPlugin() {
2009
+ return import("./index2.js");
2010
+ }
1181
2011
  export {
1182
- ModeDataPlugin,
1183
- index as components
2012
+ Box2 as B,
2013
+ Component as C,
2014
+ DxfSystem as D,
2015
+ ModelDataPlugin as M,
2016
+ Point as P,
2017
+ Variable as V,
2018
+ DetailsPoint as a,
2019
+ index as i,
2020
+ loadRenderPlugin as l
1184
2021
  };