build-dxf 0.1.105 → 0.1.107

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "build-dxf",
3
- "version": "0.1.105",
3
+ "version": "0.1.107",
4
4
  "description": "线段构建双线墙壁的dxf版本",
5
5
  "main": "./src/index.js",
6
6
  "types": "./src/index.d.ts",
package/src/build.js CHANGED
@@ -316,12 +316,12 @@ class Rectangle {
316
316
  for (let i = 0; i < 4; i++) {
317
317
  const p1 = this.points[i];
318
318
  const p2 = this.points[(i + 1) % 4];
319
- axes.push(p1.normal(p2));
319
+ axes.push(p1.leftNormal(p2));
320
320
  }
321
321
  for (let i = 0; i < 4; i++) {
322
322
  const p1 = rectangle.points[i];
323
323
  const p2 = rectangle.points[(i + 1) % 4];
324
- axes.push(p1.normal(p2));
324
+ axes.push(p1.leftNormal(p2));
325
325
  }
326
326
  function projectRectangle(rect, axis) {
327
327
  const projections = rect.points.map((point2) => point2.dot(axis));
@@ -377,7 +377,7 @@ class Rectangle {
377
377
  * @returns
378
378
  */
379
379
  static fromByLineSegment(line, width = 0.1, horizontal = false, hScale = 0.5) {
380
- const p1 = line.points[0], p2 = line.points[1], normal = p2.normal(p1), pDirect = horizontal ? p2.directionFrom(p1).mutiplyScalar(width * hScale) : Point.zero(), nDirect = horizontal ? p1.directionFrom(p2).mutiplyScalar(width * hScale) : Point.zero();
380
+ const p1 = line.points[0], p2 = line.points[1], normal = p2.leftNormal(p1), pDirect = horizontal ? p2.directionFrom(p1).multiplyScalar(width * hScale) : Point.zero(), nDirect = horizontal ? p1.directionFrom(p2).multiplyScalar(width * hScale) : Point.zero();
381
381
  const offsetX = normal.x * width * 0.5;
382
382
  const offsetY = normal.y * width * 0.5;
383
383
  return new Rectangle([
@@ -451,9 +451,17 @@ class LineSegment {
451
451
  }
452
452
  setLength(len) {
453
453
  const direct = this.direction();
454
- const start = this.#center.clone().add(direct.clone().multiplyScalar(len * 0.5));
455
- const end = this.#center.clone().add(direct.clone().multiplyScalar(-len * 0.5));
456
- this.set(start, end);
454
+ const half = len * 0.5;
455
+ const dx = direct.x * half;
456
+ const dy = direct.y * half;
457
+ this.start.set(
458
+ this.#center.x - dx,
459
+ this.#center.y - dy
460
+ );
461
+ this.end.set(
462
+ this.#center.x + dx,
463
+ this.#center.y + dy
464
+ );
457
465
  return this;
458
466
  }
459
467
  /** 获取另一个点
@@ -603,10 +611,10 @@ class LineSegment {
603
611
  return this.#direction.clone();
604
612
  }
605
613
  /**
606
- * 获取发向量
614
+ * 获取发向量-左法线
607
615
  */
608
616
  normal() {
609
- return this.points[1].normal(this.points[0]);
617
+ return this.points[1].leftNormal(this.points[0]);
610
618
  }
611
619
  /**
612
620
  * @param line
@@ -623,9 +631,9 @@ class LineSegment {
623
631
  */
624
632
  toRectangle(width = 0.1, cap = "square") {
625
633
  const p1 = this.start, p2 = this.end;
626
- const normal = p2.normal(p1);
627
- const pDirect = cap === "butt" ? Point.zero() : p2.directionFrom(p1).mutiplyScalar(width * 0.5);
628
- const nDirect = cap === "butt" ? Point.zero() : p1.directionFrom(p2).mutiplyScalar(width * 0.5);
634
+ const normal = p2.leftNormal(p1);
635
+ const pDirect = cap === "butt" ? Point.zero() : p2.directionFrom(p1).multiplyScalar(width * 0.5);
636
+ const nDirect = cap === "butt" ? Point.zero() : p1.directionFrom(p2).multiplyScalar(width * 0.5);
629
637
  const offsetX = normal.x * width * 0.5;
630
638
  const offsetY = normal.y * width * 0.5;
631
639
  const point2 = [
@@ -995,7 +1003,17 @@ class LineSegment {
995
1003
  return point2.currentData[LINE_SYMBOL] ?? null;
996
1004
  }
997
1005
  }
998
- const PRECISION = 1e-6;
1006
+ const POW10 = [
1007
+ 1,
1008
+ 10,
1009
+ 100,
1010
+ 1e3,
1011
+ 1e4,
1012
+ 1e5,
1013
+ 1e6,
1014
+ 1e7,
1015
+ 1e8
1016
+ ];
999
1017
  class Point {
1000
1018
  x;
1001
1019
  y;
@@ -1051,22 +1069,34 @@ class Point {
1051
1069
  this.y = arr[1];
1052
1070
  return this;
1053
1071
  }
1054
- /**
1055
- * multiplyScalar
1072
+ /** 乘法
1056
1073
  * @param scalar
1074
+ * @returns
1057
1075
  */
1058
- mutiplyScalar(scalar) {
1059
- this.x *= scalar;
1060
- this.y *= scalar;
1076
+ multiply(point2) {
1077
+ this.x *= point2.x;
1078
+ this.y *= point2.y;
1061
1079
  return this;
1062
1080
  }
1081
+ /** 乘以一个标量
1082
+ * @param scalar
1083
+ * @returns
1084
+ */
1063
1085
  multiplyScalar(scalar) {
1064
1086
  this.x *= scalar;
1065
1087
  this.y *= scalar;
1066
1088
  return this;
1067
1089
  }
1068
- /**
1069
- *
1090
+ /** 除法
1091
+ * @param scalar
1092
+ * @returns
1093
+ */
1094
+ division(point2) {
1095
+ this.x /= point2.x;
1096
+ this.y /= point2.y;
1097
+ return this;
1098
+ }
1099
+ /** 除一个标量
1070
1100
  * @param scalar
1071
1101
  * @returns
1072
1102
  */
@@ -1081,20 +1111,20 @@ class Point {
1081
1111
  * @param point
1082
1112
  * @returns
1083
1113
  */
1084
- division(point2) {
1114
+ subtract(point2) {
1085
1115
  this.x -= point2.x;
1086
1116
  this.y -= point2.y;
1087
1117
  return this;
1088
1118
  }
1089
1119
  /**
1090
- * 减法
1120
+ * 减一个标量
1091
1121
  * @description 将当前点的坐标减去指定点的坐标
1092
1122
  * @param point
1093
1123
  * @returns
1094
1124
  */
1095
- subtract(point2) {
1096
- this.x -= point2.x;
1097
- this.y -= point2.y;
1125
+ subtractScalar(value) {
1126
+ this.x -= value;
1127
+ this.y -= value;
1098
1128
  return this;
1099
1129
  }
1100
1130
  /**
@@ -1108,8 +1138,7 @@ class Point {
1108
1138
  this.y += point2.y;
1109
1139
  return this;
1110
1140
  }
1111
- /**
1112
- * 加法
1141
+ /** 加一个标量
1113
1142
  * @description 将当前点的坐标加上指定点的坐标
1114
1143
  * @param point
1115
1144
  * @returns
@@ -1118,6 +1147,35 @@ class Point {
1118
1147
  this.x += value;
1119
1148
  this.y += value;
1120
1149
  }
1150
+ /**
1151
+ * 加法(不可变)
1152
+ * @description 返回当前点与指定点相加后的新点,不修改当前点
1153
+ * @param point
1154
+ * @param out 输出对象,用于复用对象减少 GC
1155
+ * @returns 相加后的点
1156
+ */
1157
+ added(point2, out = new Point()) {
1158
+ return out.set(
1159
+ this.x + point2.x,
1160
+ this.y + point2.y
1161
+ );
1162
+ }
1163
+ /**
1164
+ * 减法(不可变)
1165
+ * @description 返回当前点减去指定点后的新点,不修改当前点
1166
+ * @param point
1167
+ * @param out 输出对象,用于复用对象减少 GC
1168
+ * @returns 相减后的点
1169
+ */
1170
+ subtracted(point2, out = new Point()) {
1171
+ return out.set(
1172
+ this.x - point2.x,
1173
+ this.y - point2.y
1174
+ );
1175
+ }
1176
+ /** 转为整型
1177
+ * @returns
1178
+ */
1121
1179
  parseInt() {
1122
1180
  this.x = ~~this.x;
1123
1181
  this.y = ~~this.y;
@@ -1139,21 +1197,40 @@ class Point {
1139
1197
  this.y = rotatedY + point2.y;
1140
1198
  return this;
1141
1199
  }
1200
+ /**
1201
+ * 旋转(不可变)
1202
+ * @description 返回当前点绕指定中心旋转后的新点,不修改当前点
1203
+ * @param center 旋转中心
1204
+ * @param angle 旋转角度(弧度)
1205
+ * @param out 输出对象,用于复用对象减少 GC
1206
+ * @returns 旋转后的点
1207
+ */
1208
+ rotated(center, angle, out = new Point()) {
1209
+ const dx = this.x - center.x;
1210
+ const dy = this.y - center.y;
1211
+ const cos = Math.cos(angle);
1212
+ const sin = Math.sin(angle);
1213
+ return out.set(
1214
+ dx * cos - dy * sin + center.x,
1215
+ dx * sin + dy * cos + center.y
1216
+ );
1217
+ }
1142
1218
  /**
1143
1219
  * 保留小数位数
1144
1220
  * @param count
1145
1221
  */
1146
- fixed(count) {
1147
- this.x = Number(this.x.toFixed(count));
1148
- this.y = Number(this.y.toFixed(count));
1222
+ precision(count) {
1223
+ const factor = POW10[count] ?? 10 ** count;
1224
+ this.x = Math.round(this.x * factor) / factor;
1225
+ this.y = Math.round(this.y * factor) / factor;
1149
1226
  return this;
1150
1227
  }
1151
- /** 精度处理
1228
+ /** 保留四位小数
1152
1229
  * @returns
1153
1230
  */
1154
- precision() {
1155
- this.x = Math.round(this.x / PRECISION) * PRECISION;
1156
- this.y = Math.round(this.y / PRECISION) * PRECISION;
1231
+ precision4() {
1232
+ this.x = Math.round(this.x * 1e4) / 1e4;
1233
+ this.y = Math.round(this.y * 1e4) / 1e4;
1157
1234
  return this;
1158
1235
  }
1159
1236
  /**
@@ -1169,12 +1246,28 @@ class Point {
1169
1246
  return this;
1170
1247
  }
1171
1248
  /**
1172
- * 获取单位法向量
1249
+ * 归一化(不可变)
1250
+ * @description 返回当前向量归一化后的新点,不修改当前点
1251
+ * @param out 输出对象,用于复用对象减少 GC
1252
+ * @returns 单位向量
1253
+ */
1254
+ normalized(out = new Point()) {
1255
+ const length = Math.hypot(this.x, this.y);
1256
+ if (length === 0) {
1257
+ return out.set(0, 0);
1258
+ }
1259
+ return out.set(
1260
+ this.x / length,
1261
+ this.y / length
1262
+ );
1263
+ }
1264
+ /**
1265
+ * 获取单位左法向量
1173
1266
  * @description 计算当前点到指定点的方向向量,并返回逆时针旋转90度后的单位法向量
1174
1267
  * @param point
1175
1268
  * @returns
1176
1269
  */
1177
- normal(point2) {
1270
+ leftNormal(point2) {
1178
1271
  const dx = this.x - point2.x;
1179
1272
  const dy = this.y - point2.y;
1180
1273
  const length = Math.sqrt(dx * dx + dy * dy);
@@ -1329,12 +1422,15 @@ class Point {
1329
1422
  this.y = p2.y ?? 0;
1330
1423
  return this;
1331
1424
  }
1332
- /**
1333
- *
1425
+ /** 转为数组
1334
1426
  */
1335
1427
  toArray() {
1336
1428
  return [this.x, this.y];
1337
1429
  }
1430
+ /** 转为json
1431
+ * @param z
1432
+ * @returns
1433
+ */
1338
1434
  toJson(z = 0) {
1339
1435
  return {
1340
1436
  x: this.x,
@@ -2952,10 +3048,18 @@ class PointUtils {
2952
3048
  /**保留小数位数
2953
3049
  * @param points
2954
3050
  */
2955
- static round(points, scale2 = 1e4) {
3051
+ static precision(points, count) {
3052
+ for (const p2 of points) {
3053
+ p2.precision(count);
3054
+ }
3055
+ return points;
3056
+ }
3057
+ /**保留四位小数位数
3058
+ * @param points
3059
+ */
3060
+ static precision4(points) {
2956
3061
  for (const p2 of points) {
2957
- p2.x = Math.round(p2.x * scale2) / scale2;
2958
- p2.y = Math.round(p2.y * scale2) / scale2;
3062
+ p2.precision4();
2959
3063
  }
2960
3064
  return points;
2961
3065
  }
@@ -3034,9 +3138,30 @@ class UnionFindSet {
3034
3138
  }
3035
3139
  }
3036
3140
  class LineSegmentUtils {
3141
+ // 克隆
3037
3142
  clone(lines) {
3038
3143
  return lines.map((line) => line.clone());
3039
3144
  }
3145
+ /**保留小数位数
3146
+ * @param points
3147
+ */
3148
+ static precision(lines, count) {
3149
+ for (const l of lines) {
3150
+ l.start.precision(count);
3151
+ l.end.precision(count);
3152
+ }
3153
+ return lines;
3154
+ }
3155
+ /**保留四位小数位数
3156
+ * @param points
3157
+ */
3158
+ static precision4(lines) {
3159
+ for (const l of lines) {
3160
+ l.start.precision4();
3161
+ l.end.precision4();
3162
+ }
3163
+ return lines;
3164
+ }
3040
3165
  /** 获取最长线段索引
3041
3166
  * @param lines
3042
3167
  */
@@ -5362,13 +5487,49 @@ class Polygon extends Array {
5362
5487
  this.length = 0;
5363
5488
  this.push(...points);
5364
5489
  }
5365
- getCenter() {
5366
- let countX = 0, countY = 0;
5367
- this.forEach((p2) => {
5368
- countX += p2.x;
5369
- countY += p2.y;
5370
- });
5371
- return new Point(countX / this.length, countY / this.length);
5490
+ /**
5491
+ * 获取多边形几何中心(面积质心)
5492
+ * @description 使用多边形面积质心公式计算
5493
+ * @description 适用于闭合简单多边形(顺时针或逆时针均可)
5494
+ * @description 如果面积为 0,则退化为顶点平均中心
5495
+ * @param out 输出对象,用于减少 GC
5496
+ * @returns
5497
+ */
5498
+ getCenter(out = new Point()) {
5499
+ const len = this.length;
5500
+ if (len === 0) {
5501
+ return out.set(0, 0);
5502
+ }
5503
+ let area2 = 0;
5504
+ let centerX = 0;
5505
+ let centerY = 0;
5506
+ for (let i = 0; i < len; i++) {
5507
+ const p1 = this[i];
5508
+ const p2 = this[(i + 1) % len];
5509
+ const cross = p1.x * p2.y - p2.x * p1.y;
5510
+ area2 += cross;
5511
+ centerX += (p1.x + p2.x) * cross;
5512
+ centerY += (p1.y + p2.y) * cross;
5513
+ }
5514
+ area2 *= 0.5;
5515
+ if (Math.abs(area2) < 1e-9) {
5516
+ let sumX = 0;
5517
+ let sumY = 0;
5518
+ for (let i = 0; i < len; i++) {
5519
+ sumX += this[i].x;
5520
+ sumY += this[i].y;
5521
+ }
5522
+ const inv = 1 / len;
5523
+ return out.set(
5524
+ sumX * inv,
5525
+ sumY * inv
5526
+ );
5527
+ }
5528
+ const factor = 1 / (6 * area2);
5529
+ return out.set(
5530
+ centerX * factor,
5531
+ centerY * factor
5532
+ );
5372
5533
  }
5373
5534
  /** 计算二维包围盒
5374
5535
  * @returns
@@ -6428,6 +6589,7 @@ function segmentationPath(lines, clipLine) {
6428
6589
  function clippingDoubleWall(lines) {
6429
6590
  lines = lines.map((line) => line.clone());
6430
6591
  lines = mergeChains(lines);
6592
+ if (lines.length === 4) return [lines];
6431
6593
  const queue = [lines], groups = [], maxCount2 = 2e4;
6432
6594
  let pendingLines, count = 0;
6433
6595
  while ((pendingLines = queue.pop()) !== void 0) {
@@ -6457,6 +6619,7 @@ function clippingDoubleWall(lines) {
6457
6619
  }
6458
6620
  const miniCircles = new MiniCircles();
6459
6621
  function clippingInsertObjectDoubleWall(lines) {
6622
+ if (lines.length === 4) return [lines];
6460
6623
  const hasInsertObject = lines.some((line) => WallInsertObject.isInsertObject(line));
6461
6624
  if (!hasInsertObject) return clippingDoubleWall(lines);
6462
6625
  const quadtree = createQuadtree(lines), poly = Polygon.fromByLines2(lines), far = 10;
@@ -9396,6 +9559,12 @@ class BuildGroup {
9396
9559
  return buildDoubleWallGroup;
9397
9560
  }
9398
9561
  }
9562
+ function precision4(value) {
9563
+ return Math.round(value * 1e4) * 1e-4;
9564
+ }
9565
+ function precision4Nullable(value) {
9566
+ return value == null ? void 0 : precision4(value);
9567
+ }
9399
9568
  const holeTypeMap = {
9400
9569
  door: "DOOR",
9401
9570
  RAILING: "RAILING",
@@ -9433,11 +9602,21 @@ class ThreeVJiaPipeline extends Pipeline {
9433
9602
  const { door, window: windowLines, passageEntrance, untreatedDoubleWallGroup } = WallInsertObjectDrawData.handleHoleDrawData(groups, []);
9434
9603
  const walls = untreatedDoubleWallGroup.flatMap((lines2) => {
9435
9604
  if (lines2.length < 4) return lines2;
9436
- lines2 = lines2.sort((a2, b4) => a2.length() - b4.length());
9437
- const line1 = lines2[0], line2 = lines2[1];
9438
- const newLine = new LineSegment(line1.center.clone(), line2.center.clone());
9605
+ let newLine = null;
9606
+ let wallWidth = 0;
9607
+ const poly = Polygon.fromByLinePath(lines2).getMinimumBoundingRectangle();
9608
+ if (poly) {
9609
+ const len1 = poly[0].distance(poly[1]), len2 = poly[1].distance(poly[2]), direct = len1 > len2 ? poly[0].directionFrom(poly[1]) : poly[1].directionFrom(poly[2]), center = poly.getCenter(), len = Math.max(len1, len2);
9610
+ newLine = center.expandAsLine(direct, len).translate(-len * 0.5, direct);
9611
+ wallWidth = Math.min(len1, len2);
9612
+ } else {
9613
+ lines2 = [...lines2].sort((a2, b4) => a2.length() - b4.length());
9614
+ const line1 = lines2[0], line2 = lines2[1];
9615
+ newLine = new LineSegment(line1.center.clone(), line2.center.clone());
9616
+ wallWidth = line1.length();
9617
+ }
9439
9618
  mergeLineUserData(newLine, lines2);
9440
- newLine.userData.wallWidth = line1.length();
9619
+ newLine.userData.wallWidth = wallWidth;
9441
9620
  return [newLine];
9442
9621
  });
9443
9622
  this.manager.add({ type: "wallHole", lines: windowLines });
@@ -9534,12 +9713,12 @@ class ThreeVJiaPipeline extends Pipeline {
9534
9713
  const hole = {
9535
9714
  id: this.index++,
9536
9715
  type,
9537
- start: line.start.toJson2D(),
9538
- end: line.end.toJson2D(),
9539
- depth: line.userData.depth,
9540
- height: line.userData?.height ?? DEFAULT_DOOR_HEIGHT,
9541
- sillHeight: line.userData?.groundClearance ?? DOOR_GROUND_CLEARANCE_HEIGHT,
9542
- groundClearance: line.userData?.groundClearance ?? DOOR_GROUND_CLEARANCE_HEIGHT
9716
+ start: line.start.precision4().toJson2D(),
9717
+ end: line.end.precision4().toJson2D(),
9718
+ depth: precision4Nullable(line.userData.depth),
9719
+ height: precision4Nullable(line.userData?.height ?? DEFAULT_DOOR_HEIGHT),
9720
+ sillHeight: precision4Nullable(line.userData?.groundClearance ?? DOOR_GROUND_CLEARANCE_HEIGHT),
9721
+ groundClearance: precision4Nullable(line.userData?.groundClearance ?? DOOR_GROUND_CLEARANCE_HEIGHT)
9543
9722
  // ?非三维家需要数据
9544
9723
  };
9545
9724
  if (type === "DOOR") Object.assign(hole, { openSide: "RIGHT" });
@@ -9575,16 +9754,16 @@ class ThreeVJiaPipeline extends Pipeline {
9575
9754
  };
9576
9755
  this.manager.forEach((group) => this.run(group.type, { lines: group.lines, json: option }, cache));
9577
9756
  cache.wallLines.forEach((line) => {
9757
+ line.setLength(line.len + 4e-3);
9578
9758
  option.walls.push({
9579
9759
  ID: this.index++,
9580
- start: line.start.toJson2D(),
9581
- end: line.end.toJson2D(),
9582
- thickness: Math.max(line.userData.wallWidth ? line.userData.wallWidth : 0.12, 0.12),
9760
+ start: line.start.precision4().toJson2D(),
9761
+ end: line.end.precision4().toJson2D(),
9762
+ thickness: precision4(Math.max(line.userData.wallWidth ? line.userData.wallWidth : 0.12, 0.12)),
9583
9763
  type: "LINE",
9584
9764
  isDoor: false,
9585
9765
  loadBearingWall: false,
9586
- // height: line.userData.height ?? DEFAULT_WALL_HEIGHT
9587
- height: line.currentData[WALL_HEIGHT_KEY] ?? this.maxHeight
9766
+ height: precision4(line.currentData[WALL_HEIGHT_KEY] ?? this.maxHeight)
9588
9767
  });
9589
9768
  });
9590
9769
  return option;
@@ -9616,6 +9795,7 @@ class ThreeVJiaPipeline extends Pipeline {
9616
9795
  const rooms = json.rooms, roomPloys = rooms.map((room) => new Polygon(room.polygon.map((p2) => Point.from(p2))));
9617
9796
  if (roomPloys.length === 0) return json;
9618
9797
  publicInfo.itemInfo.forEach((item, i) => {
9798
+ if (item.category !== "switch" && item.category !== "socket") return;
9619
9799
  const itemPoly = new Polygon(item.contour.map((p2) => Point.from(p2).rotate(json.center, json.angle))), center = itemPoly.getCenter(), index2 = roomPloys.findIndex((poly) => poly.pointWithin(center));
9620
9800
  if (index2 < 0) return;
9621
9801
  json.placeHolders.push({
@@ -9661,7 +9841,7 @@ class ThreeVJiaPipeline extends Pipeline {
9661
9841
  });
9662
9842
  });
9663
9843
  if (heights.length === 0) heights.push(DEFAULT_WALL_HEIGHT);
9664
- const maxHeight = Math.max(...heights);
9844
+ const maxHeight = precision4(Math.max(...heights));
9665
9845
  if (updateGroup) lineSegments = BuildGroup.doubleWall(lineSegments);
9666
9846
  const threeVJiaJson = new ThreeVJiaPipeline(lineSegments, maxHeight);
9667
9847
  const json = threeVJiaJson.transform();
@@ -9968,8 +10148,8 @@ class CAD {
9968
10148
  resetOrigin() {
9969
10149
  const lines = this.groups.flatMap((group) => group.lines), box = Box2.fromByLineSegment(...lines), center = box.center;
9970
10150
  this.groups.forEach((group) => group.lines.forEach((line) => {
9971
- line.start.division(center);
9972
- line.end.division(center);
10151
+ line.start.subtract(center);
10152
+ line.end.subtract(center);
9973
10153
  }));
9974
10154
  this.needUpdate = true;
9975
10155
  }
@@ -9981,8 +10161,8 @@ class CAD {
9981
10161
  const lines = this.groups.flatMap((group) => group.lines), box = Box2.fromByLineSegment(...lines), center = box.center;
9982
10162
  this.groups.forEach((group) => group.lines.forEach((line) => {
9983
10163
  line.rotate(angle, center);
9984
- line.start.division(center);
9985
- line.end.division(center);
10164
+ line.start.subtract(center);
10165
+ line.end.subtract(center);
9986
10166
  }));
9987
10167
  this.needUpdate = true;
9988
10168
  }
@@ -10534,13 +10714,19 @@ class DoubleWallHelper {
10534
10714
  const newLine1 = new LineSegment(project0.start.clone(), project1.start.clone());
10535
10715
  const newLine2 = new LineSegment(project0.end.clone(), project1.end.clone());
10536
10716
  newLine1.userData.height = line0.userData.height;
10717
+ newLine1.userData.rooftopPz = line0.userData.rooftopPz;
10537
10718
  newLine2.userData.height = line1.userData.height;
10719
+ newLine2.userData.rooftopPz = line1.userData.rooftopPz;
10538
10720
  appendLines.push(newLine1, newLine2);
10539
10721
  addClipingMap(line0, project0);
10540
10722
  addClipingMap(line1, project1);
10541
10723
  });
10542
10724
  clipingMap.forEach((list, line) => {
10543
10725
  const newLines = LineSegmentUtils.clippingByPoints(line, list);
10726
+ newLines.forEach((newLine) => {
10727
+ newLine.userData.height = line.userData.height;
10728
+ newLine.userData.rooftopPz = line.userData.rooftopPz;
10729
+ });
10544
10730
  lines.push(...newLines);
10545
10731
  });
10546
10732
  lines = lines.filter((line) => !removeLines.has(line));
@@ -10551,9 +10737,11 @@ class DoubleWallHelper {
10551
10737
  const queryLines = quadtree.queryRect(line.toRectangle(1e-3, "butt")).filter((item) => item.line.isParallelTo(line)).map((item) => item.line);
10552
10738
  if (queryLines.length) {
10553
10739
  const newLines = LineSegmentUtils.clippingByLines(line, queryLines);
10554
- if (newLines) return newLines.forEach((line2) => {
10555
- quadtree.insert(line2);
10556
- lines.push(line2);
10740
+ if (newLines) return newLines.forEach((newLine) => {
10741
+ newLine.userData.height = line.userData.height;
10742
+ newLine.userData.rooftopPz = line.userData.rooftopPz;
10743
+ quadtree.insert(newLine);
10744
+ lines.push(newLine);
10557
10745
  });
10558
10746
  }
10559
10747
  quadtree.insert(line);
@@ -10581,9 +10769,6 @@ class DoubleWallHelper {
10581
10769
  lineSegments = this.complementSide(lineSegments, otherLines);
10582
10770
  WIO.recomputed(lineSegments);
10583
10771
  lineSegments = BuildGroup.doubleWall(lineSegments, true);
10584
- PointUtils.round(
10585
- PointUtils.quantize(LineSegmentUtils.flatPoints(lineSegments), 1e-4)
10586
- );
10587
10772
  return [...lineSegments, ...otherLines];
10588
10773
  }
10589
10774
  }
@@ -10889,7 +11074,7 @@ class AlignToParallelSegments {
10889
11074
  * @param esp 垂直投影值分组距离
10890
11075
  * @param gap 平行轴投影区间间隙
10891
11076
  */
10892
- static group(lines, parallelAxis, verticalAxis, esp = 0.05, gap = 0.01) {
11077
+ static group(lines, parallelAxis, verticalAxis, esp = 0.05, gap = 0.01, onGroup) {
10893
11078
  esp = esp / verticalAxis.len;
10894
11079
  gap = gap / parallelAxis.len;
10895
11080
  const vls = lines.map((line) => [
@@ -10897,16 +11082,29 @@ class AlignToParallelSegments {
10897
11082
  line,
10898
11083
  [parallelAxis.projectPointValue(line.start), parallelAxis.projectPointValue(line.end)].sort((a2, b4) => a2 - b4)
10899
11084
  ]).sort((a2, b4) => a2[0] - b4[0]), unionFindSet = new UnionFindSet(vls.length);
10900
- for (let i = 0; i < vls.length; i++) {
10901
- const [ipv, _iline, [istart, iend]] = vls[i];
10902
- for (let j = i + 1; j < vls.length; j++) {
11085
+ for (let i2 = 0; i2 < vls.length; i2++) {
11086
+ const [ipv, _iline, [istart, iend]] = vls[i2];
11087
+ for (let j = i2 + 1; j < vls.length; j++) {
10903
11088
  const [jpv, _jline, [jstart, jend]] = vls[j];
10904
11089
  if (jpv - ipv > esp) break;
10905
11090
  const overlap = Math.min(iend, jend) - Math.max(istart, jstart);
10906
- if (overlap > -gap) unionFindSet.union(i, j);
10907
- }
10908
- }
10909
- const groups = unionFindSet.getAllSets().map((v2) => v2.map((v22) => vls[v22][1]));
11091
+ if (overlap > -gap) unionFindSet.union(i2, j);
11092
+ }
11093
+ }
11094
+ const map = unionFindSet.getAllSets();
11095
+ const groups = new Array(map.size);
11096
+ let i = 0;
11097
+ map.forEach((list) => {
11098
+ const group = new Array(list.length);
11099
+ const pvList = new Array(list.length);
11100
+ groups[i++] = group;
11101
+ for (let i2 = 0; i2 < list.length; i2++) {
11102
+ const [pv, line] = vls[list[i2]];
11103
+ group[i2] = line;
11104
+ pvList[i2] = pv;
11105
+ }
11106
+ onGroup?.(group, pvList, verticalAxis);
11107
+ });
10910
11108
  return groups;
10911
11109
  }
10912
11110
  /** 拟合并对齐线段
@@ -10946,13 +11144,13 @@ class AlignToParallelSegments {
10946
11144
  * @returns
10947
11145
  */
10948
11146
  static align(lines, opt = {}) {
10949
- const { esp = 0.06, gap = 0.05 } = opt;
11147
+ const { esp = 0.08, gap = 0.05 } = opt;
10950
11148
  const axisLine = lines[LineSegmentUtils.maxLengthLineIndex(lines)].clone().setLength(100), axisLineV = axisLine.clone().rotate(Math.PI * 0.5, axisLine.center);
10951
11149
  let [pllLines, verticalLines] = LineSegmentUtils.groupByParallelToAxis(lines, axisLine);
10952
- const groups = this.group(pllLines, axisLine, axisLineV, esp, gap);
11150
+ const groups = this.group(pllLines, axisLine, axisLineV, esp, gap, opt.onGroup);
10953
11151
  pllLines = this.fittingAlignment(groups, verticalLines, opt);
10954
11152
  verticalLines = verticalLines.filter((line) => line.length() > 1e-6);
10955
- const groups2 = this.group(verticalLines, axisLineV, axisLine, esp, gap);
11153
+ const groups2 = this.group(verticalLines, axisLineV, axisLine, esp, gap, opt.onGroup);
10956
11154
  verticalLines = this.fittingAlignment(groups2, pllLines, opt);
10957
11155
  pllLines = pllLines.filter((line) => line.length() > 1e-6);
10958
11156
  return [
@@ -11088,7 +11286,17 @@ function correction(lines, targettLine, option) {
11088
11286
  lines = AxisAlignCorr.start(lines, targettLine);
11089
11287
  lines = lineSegmentClipping(lines, 1e-9);
11090
11288
  lines = AlignToParallelSegments.align(lines, {
11091
- onMergeLine: mergeLineUserData
11289
+ onMergeLine: mergeLineUserData,
11290
+ onGroup(group, projValues, axis) {
11291
+ if (group.length <= 1) return;
11292
+ const max = Math.max(...projValues);
11293
+ const min = Math.min(...projValues);
11294
+ const dist = precision4((max - min) * axis.len);
11295
+ if (dist < DEFAULT_WALL_WIDTH) return;
11296
+ group.forEach((line) => {
11297
+ line.userData.wallWidth = dist;
11298
+ });
11299
+ }
11092
11300
  // esp: 0.4
11093
11301
  });
11094
11302
  lines = adsorption(lines, option);
@@ -11314,7 +11522,7 @@ class DoorFind {
11314
11522
  }).filter((item) => {
11315
11523
  return item.point.distance(dock.point) / length > 0.5;
11316
11524
  }).filter((item) => {
11317
- const normal = item.point.normal(dock.point), center = new Point((item.point.x + dock.point.x) / 2, (item.point.y + dock.point.y) / 2), length2 = item.point.distance(dock.point);
11525
+ const normal = item.point.leftNormal(dock.point), center = new Point((item.point.x + dock.point.x) / 2, (item.point.y + dock.point.y) / 2), length2 = item.point.distance(dock.point);
11318
11526
  const normalLine = new LineSegment(
11319
11527
  center,
11320
11528
  center.clone().add(normal.clone().multiplyScalar(length2))
@@ -11601,7 +11809,7 @@ class HeightQuery {
11601
11809
  if (contoursData.length) return contoursData;
11602
11810
  points.filter((p2) => {
11603
11811
  point.set(p2[0], p2[1]);
11604
- new Array(...new Set(quadtree?.queryCircle(point, 0.4).map((item) => item.line.currentData.contourIndex))).forEach((index2) => {
11812
+ new Array(...new Set(quadtree?.queryCircle(point, 0.6).map((item) => item.line.currentData.contourIndex))).forEach((index2) => {
11605
11813
  if (index2 === maxContourIndex) return;
11606
11814
  contoursData.push({ i: index2, averagePz: contours[index2].averagePz });
11607
11815
  });
@@ -11625,9 +11833,17 @@ function wallHeightHandle(lineSegments, option) {
11625
11833
  Object.defineProperty(option.publicInfo.rootTopContourInfo, "quadtree", { value: quadtree });
11626
11834
  }
11627
11835
  }
11836
+ const psh = PointSpatialHash.fromByLines(lineSegments);
11628
11837
  lineSegments.forEach((line) => {
11629
11838
  if (!("height" in line.userData)) {
11630
- if (!line.userData.rooftopPz && option?.publicInfo?.rootTopContourInfo) line.userData.rooftopPz = HeightQuery.query(line, option.publicInfo.rootTopContourInfo);
11839
+ if (!line.userData.rooftopPz && option?.publicInfo?.rootTopContourInfo) {
11840
+ const list = psh.queryPoints(line.points, true).map((item) => item.userData?.userData.rooftopPz ?? 0);
11841
+ if (list.length) {
11842
+ line.userData.rooftopPz = Math.max(...list);
11843
+ } else {
11844
+ line.userData.rooftopPz = HeightQuery.query(line, option.publicInfo.rootTopContourInfo);
11845
+ }
11846
+ }
11631
11847
  if ("rooftopPz" in line.userData) line.userData.height = Math.abs(line.userData.rooftopPz - (option.originalZ ?? 0));
11632
11848
  }
11633
11849
  });
package/src/index3.js CHANGED
@@ -16415,7 +16415,7 @@ class VerticalCorrContinue extends CommandFlowComponent {
16415
16415
  }
16416
16416
  }
16417
16417
  function buildDashedHelperLine$1(start, end, offset = 0.5) {
16418
- const offsetDistance = end.normal(start).multiplyScalar(offset);
16418
+ const offsetDistance = end.leftNormal(start).multiplyScalar(offset);
16419
16419
  const startOffPoint = start.clone().add(offsetDistance);
16420
16420
  const endOffPoint = end.clone().add(offsetDistance);
16421
16421
  return [
@@ -16494,7 +16494,7 @@ class RayDistance extends CommandFlowComponent {
16494
16494
  }
16495
16495
  }
16496
16496
  function buildDashedHelperLine(start, end, offset = 0.5) {
16497
- const offsetDistance = end.normal(start).multiplyScalar(offset);
16497
+ const offsetDistance = end.leftNormal(start).multiplyScalar(offset);
16498
16498
  const startOffPoint = start.clone().add(offsetDistance);
16499
16499
  const endOffPoint = end.clone().add(offsetDistance);
16500
16500
  return [
@@ -1,8 +1,10 @@
1
1
  import { LineSegment } from './LineSegment';
2
+ type OnGroupFun = (group: LineSegment[], projValues: number[], axis: LineSegment) => void;
2
3
  export interface AlignDescriptor {
3
4
  esp?: number;
4
5
  gap?: number;
5
6
  onMergeLine?: (target: LineSegment, source: LineSegment) => void;
7
+ onGroup?: OnGroupFun;
6
8
  }
7
9
  /** 通过平行轴和垂直轴分组对齐线段,搭配垂直纠正使用
8
10
  */
@@ -14,7 +16,7 @@ export declare class AlignToParallelSegments {
14
16
  * @param esp 垂直投影值分组距离
15
17
  * @param gap 平行轴投影区间间隙
16
18
  */
17
- static group(lines: LineSegment[], parallelAxis: LineSegment, verticalAxis: LineSegment, esp?: number, gap?: number): LineSegment<Record<string, any>>[][];
19
+ static group(lines: LineSegment[], parallelAxis: LineSegment, verticalAxis: LineSegment, esp?: number, gap?: number, onGroup?: OnGroupFun): LineSegment<Record<string, any>>[][];
18
20
  /** 拟合并对齐线段
19
21
  * @param groups
20
22
  * @param verticalLines
@@ -26,3 +28,4 @@ export declare class AlignToParallelSegments {
26
28
  */
27
29
  static align(lines: LineSegment[], opt?: AlignDescriptor): LineSegment<Record<string, any>>[];
28
30
  }
31
+ export {};
@@ -101,7 +101,7 @@ export declare class LineSegment<T = Record<string, any>> {
101
101
  */
102
102
  direction(): Point<Record<string, any>>;
103
103
  /**
104
- * 获取发向量
104
+ * 获取发向量-左法线
105
105
  */
106
106
  normal(): Point<Record<string, any>>;
107
107
  /**
@@ -2,6 +2,14 @@ import { LineSegment } from './LineSegment';
2
2
  import { Point } from './Point';
3
3
  export declare class LineSegmentUtils {
4
4
  clone(lines: LineSegment[]): LineSegment<Record<string, any>>[];
5
+ /**保留小数位数
6
+ * @param points
7
+ */
8
+ static precision(lines: LineSegment[], count: number): LineSegment<Record<string, any>>[];
9
+ /**保留四位小数位数
10
+ * @param points
11
+ */
12
+ static precision4(lines: LineSegment[]): LineSegment<Record<string, any>>[];
5
13
  /** 获取最长线段索引
6
14
  * @param lines
7
15
  */
@@ -28,14 +28,22 @@ export declare class Point<T = Record<string, any>> {
28
28
  * @param arr
29
29
  */
30
30
  setByArray(arr: number[]): this;
31
- /**
32
- * multiplyScalar
31
+ /** 乘法
33
32
  * @param scalar
33
+ * @returns
34
+ */
35
+ multiply(point: Point): this;
36
+ /** 乘以一个标量
37
+ * @param scalar
38
+ * @returns
34
39
  */
35
- mutiplyScalar(scalar: number): this;
36
40
  multiplyScalar(scalar: number): this;
37
- /**
38
- *
41
+ /** 除法
42
+ * @param scalar
43
+ * @returns
44
+ */
45
+ division(point: Point): this;
46
+ /** 除一个标量
39
47
  * @param scalar
40
48
  * @returns
41
49
  */
@@ -46,14 +54,14 @@ export declare class Point<T = Record<string, any>> {
46
54
  * @param point
47
55
  * @returns
48
56
  */
49
- division(point: Point): this;
57
+ subtract(point: Point): this;
50
58
  /**
51
- * 减法
59
+ * 减一个标量
52
60
  * @description 将当前点的坐标减去指定点的坐标
53
61
  * @param point
54
62
  * @returns
55
63
  */
56
- subtract(point: Point): this;
64
+ subtractScalar(value: number): this;
57
65
  /**
58
66
  * 加法
59
67
  * @description 将当前点的坐标加上指定点的坐标
@@ -61,13 +69,31 @@ export declare class Point<T = Record<string, any>> {
61
69
  * @returns
62
70
  */
63
71
  add(point: Point): this;
64
- /**
65
- * 加法
72
+ /** 加一个标量
66
73
  * @description 将当前点的坐标加上指定点的坐标
67
74
  * @param point
68
75
  * @returns
69
76
  */
70
77
  addScalar(value: number): void;
78
+ /**
79
+ * 加法(不可变)
80
+ * @description 返回当前点与指定点相加后的新点,不修改当前点
81
+ * @param point
82
+ * @param out 输出对象,用于复用对象减少 GC
83
+ * @returns 相加后的点
84
+ */
85
+ added(point: Point, out?: Point<Record<string, any>>): Point<Record<string, any>>;
86
+ /**
87
+ * 减法(不可变)
88
+ * @description 返回当前点减去指定点后的新点,不修改当前点
89
+ * @param point
90
+ * @param out 输出对象,用于复用对象减少 GC
91
+ * @returns 相减后的点
92
+ */
93
+ subtracted(point: Point, out?: Point<Record<string, any>>): Point<Record<string, any>>;
94
+ /** 转为整型
95
+ * @returns
96
+ */
71
97
  parseInt(): this;
72
98
  /**
73
99
  * 绕point旋转angle
@@ -78,15 +104,27 @@ export declare class Point<T = Record<string, any>> {
78
104
  x: number;
79
105
  y: number;
80
106
  }, angle: number): this;
107
+ /**
108
+ * 旋转(不可变)
109
+ * @description 返回当前点绕指定中心旋转后的新点,不修改当前点
110
+ * @param center 旋转中心
111
+ * @param angle 旋转角度(弧度)
112
+ * @param out 输出对象,用于复用对象减少 GC
113
+ * @returns 旋转后的点
114
+ */
115
+ rotated(center: {
116
+ x: number;
117
+ y: number;
118
+ }, angle: number, out?: Point<Record<string, any>>): Point<Record<string, any>>;
81
119
  /**
82
120
  * 保留小数位数
83
121
  * @param count
84
122
  */
85
- fixed(count: number): this;
86
- /** 精度处理
123
+ precision(count: number): this;
124
+ /** 保留四位小数
87
125
  * @returns
88
126
  */
89
- precision(): this;
127
+ precision4(): this;
90
128
  /**
91
129
  * 归一化
92
130
  * @description 将当前点的坐标归一化为单位向量
@@ -94,12 +132,19 @@ export declare class Point<T = Record<string, any>> {
94
132
  */
95
133
  normalize(): this;
96
134
  /**
97
- * 获取单位法向量
135
+ * 归一化(不可变)
136
+ * @description 返回当前向量归一化后的新点,不修改当前点
137
+ * @param out 输出对象,用于复用对象减少 GC
138
+ * @returns 单位向量
139
+ */
140
+ normalized(out?: Point<Record<string, any>>): Point<Record<string, any>>;
141
+ /**
142
+ * 获取单位左法向量
98
143
  * @description 计算当前点到指定点的方向向量,并返回逆时针旋转90度后的单位法向量
99
144
  * @param point
100
145
  * @returns
101
146
  */
102
- normal(point: Point): Point<Record<string, any>>;
147
+ leftNormal(point: Point): Point<Record<string, any>>;
103
148
  /**
104
149
  * 获取由传入的点到该点的单位方向向量
105
150
  * @description 计算当前点到指定点的方向向量,并返回单位方向
@@ -176,10 +221,13 @@ export declare class Point<T = Record<string, any>> {
176
221
  x: number;
177
222
  y: number;
178
223
  }): this;
179
- /**
180
- *
224
+ /** 转为数组
181
225
  */
182
226
  toArray(): [number, number];
227
+ /** 转为json
228
+ * @param z
229
+ * @returns
230
+ */
183
231
  toJson(z?: number): {
184
232
  x: number;
185
233
  y: number;
@@ -8,7 +8,11 @@ export declare class PointUtils {
8
8
  /**保留小数位数
9
9
  * @param points
10
10
  */
11
- static round(points: Point[], scale?: number): Point<Record<string, any>>[];
11
+ static precision(points: Point[], count: number): Point<Record<string, any>>[];
12
+ /**保留四位小数位数
13
+ * @param points
14
+ */
15
+ static precision4(points: Point[]): Point<Record<string, any>>[];
12
16
  /** 点吸附
13
17
  * @param points
14
18
  * @param tolerance
@@ -13,7 +13,15 @@ export declare class Polygon<T = any> extends Array<Point<T>> {
13
13
  [Symbol.iterator](): ArrayIterator<Point<T>>;
14
14
  constructor(points?: Point<T>[]);
15
15
  set(points?: Point<T>[]): void;
16
- getCenter(): Point<Record<string, any>>;
16
+ /**
17
+ * 获取多边形几何中心(面积质心)
18
+ * @description 使用多边形面积质心公式计算
19
+ * @description 适用于闭合简单多边形(顺时针或逆时针均可)
20
+ * @description 如果面积为 0,则退化为顶点平均中心
21
+ * @param out 输出对象,用于减少 GC
22
+ * @returns
23
+ */
24
+ getCenter(out?: Point<Record<string, any>>): Point<Record<string, any>>;
17
25
  /** 计算二维包围盒
18
26
  * @returns
19
27
  */
@@ -0,0 +1,3 @@
1
+ export declare function precision(value: number, count: number): number;
2
+ export declare function precision4(value: number): number;
3
+ export declare function precision4Nullable(value?: number | null): number | undefined;