Procházet zdrojové kódy

feat: 优化line类型shape交接点

bill před 1 měsícem
rodič
revize
8cb3574b5f

+ 1 - 0
package.json

@@ -19,6 +19,7 @@
     "@types/node": "^22.9.0",
     "@types/svg-path-parser": "^1.1.6",
     "@types/three": "^0.169.0",
+    "clipper-lib": "^6.4.2",
     "dxf-writer": "^1.18.4",
     "element-plus": "^2.8.6",
     "html2canvas": "^1.4.1",

+ 8 - 0
pnpm-lock.yaml

@@ -26,6 +26,9 @@ importers:
       '@types/three':
         specifier: ^0.169.0
         version: 0.169.0
+      clipper-lib:
+        specifier: ^6.4.2
+        version: 6.4.2
       dxf-writer:
         specifier: ^1.18.4
         version: 1.18.4
@@ -783,6 +786,9 @@ packages:
     resolution: {integrity: sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==}
     engines: {node: '>= 10.0'}
 
+  clipper-lib@6.4.2:
+    resolution: {integrity: sha512-knglhjQX5ihNj/XCIs6zCHrTemdvHY3LPZP9XB2nq2/3igyYMFueFXtfp84baJvEE+f8pO1ZS4UVeEgmLnAprQ==}
+
   clone@2.1.2:
     resolution: {integrity: sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==}
     engines: {node: '>=0.8'}
@@ -2841,6 +2847,8 @@ snapshots:
     dependencies:
       source-map: 0.6.1
 
+  clipper-lib@6.4.2: {}
+
   clone@2.1.2: {}
 
   collection-visit@1.0.0:

+ 4 - 3
src/core/components/group/group.vue

@@ -89,9 +89,10 @@ const propertyName = computed(() => {
 const changePropertyHandler = () => {
   history.onceTrack(() => {
     for (const item of setPropertyDatas.value) {
-      const type = store.getType(item.id);
-      if (type) {
-        store.setItem(type, { value: item, id: item.id });
+      const belong = getShapeBelong(item.id);
+      // getShapeBelong(item.id)
+      if (belong?.type) {
+        store.setItem(belong.type, { value: belong.item, id: belong.item.id });
       }
     }
   });

+ 4 - 6
src/core/components/line/attach-server.ts

@@ -608,7 +608,7 @@ export const useLineDescribes = (line: Ref<LineDataLine>) => {
 };
 
 export const useDrawLinePoint = (
-  data: LineData,
+  data: Ref<LineData>,
   line: LineDataLine,
   callback: (data: {
     prev: LineDataLine;
@@ -649,11 +649,9 @@ export const useDrawLinePoint = (
   const drawStore = useDrawIngData();
   const cursor = useCursor();
   const enterDraw = () => {
-    const points = [
-      data.points.find((p) => p.id === line.a)!,
-      data.points.find((p) => p.id === line.b)!,
-    ];
-    const cdata: LineData = { ...data, points, lines: [] };
+    const points = getLinePoints(data.value, line)
+    console.log(points, data.value, line)
+    const cdata: LineData = { ...data.value, points, lines: [] };
     const point = reactive({ ...lineCenter(points), id: onlyId() });
     const cIcons = icons.value.map((icon) => ({ ...icon, id: onlyId() }));
     const iconInfos = icons.value.map((icon) => {

+ 210 - 100
src/core/components/line/attach-view.ts

@@ -1,153 +1,263 @@
 import {
   eqPoint,
-  getDiffPolygons,
+  getLEJJoinNdxs,
   getLEJLineAngle,
   getLineEdgeJoinInfo,
   getLineEdges,
-  getVectorLine,
-  isPolygonPointInner,
   LEJInfo,
   LEJLine,
-  line2IncludedAngle,
-  lineCenter,
-  lineInner,
-  lineIntersection,
-  lineLen,
   lineVector,
-  lineVerticalVector,
   Pos,
-  vector2IncludedAngle,
   verticalVector,
 } from "@/utils/math";
 import { LineData, LineDataLine } from ".";
 import { getJoinLine, getLinePoints } from "./attach-server";
 import { MathUtils } from "three";
-import { diffArrayChange, rangMod } from "@/utils/shared";
+import { diffArrayChange, round } from "@/utils/shared";
 import { globalWatch, installGlobalVar } from "@/core/hook/use-global-vars";
 import { useStore } from "@/core/store";
-import { computed, reactive, Ref, toRaw, watchEffect } from "vue";
+import { computed, nextTick, reactive, Ref, watch, watchEffect } from "vue";
 import { Transform } from "konva/lib/Util";
 import { sortFn } from "@/core/store/store";
-import { getLineIconEndpoints, getSnapLine } from "../line-icon";
+import { getLineIconEndpoints } from "../line-icon";
 import { useDrawIngData } from "@/core/hook/use-draw";
+import { polygonDifference, polygonDifferenceOnly } from "@/utils/math-clip";
 
-export const useGetExtendPolygon = installGlobalVar(() => {
+export const useGetExtendPolygon = (lineData: Ref<LineData | undefined>) => {
   const minAngle = MathUtils.degToRad(0.1);
   const palAngle = MathUtils.degToRad(20);
-  const linePolygons: Record<string, Pos[]> = reactive({});
 
-  const lineJoinInfos = reactive({}) as Record<
-    string,
-    Record<string, LEJInfo | undefined>
-  >;
+  type JInfo = { lej: LEJInfo | undefined; diffPolygons?: Pos[][] };
+  const joinInfos = reactive({}) as Record<string, Record<string, JInfo>>;
 
   const getInfoKey = (line: LEJLine) =>
-    line.points.reduce((t, p) => p.x + p.y + t, "") + line.width;
+    line.points.reduce((t, p) => round(p.x, 3) + round(p.y, 3) + t, "") + line.width;
 
-  const updateLine2JoinInfo = (
+  const setLEJInfo = (
     data: LineData,
-    line1: LineDataLine,
-    line2: LineDataLine
+    originLine: LineDataLine,
+    targetLine: LineDataLine
   ) => {
+    const origin = {
+      points: getLinePoints(data, originLine),
+      width: originLine.strokeWidth,
+    };
+    const target = {
+      points: getLinePoints(data, targetLine),
+      width: targetLine.strokeWidth,
+    };
+    const { originNdx } = getLEJJoinNdxs(origin.points, target.points);
+    const lej = getLineEdgeJoinInfo(origin, target, minAngle, palAngle);
+    const originKey = getInfoKey(origin);
 
+    if (!(originKey in joinInfos)) {
+      joinInfos[originKey] = {};
+    }
+    if (!(originNdx in joinInfos[originKey])) {
+      joinInfos[originKey][originNdx] = { lej };
+    }
+    return joinInfos[originKey][originNdx];
   };
 
-  const getLineInfo = (data: LineData, line: LineDataLine) => {
+  const getLEJPolygon = (data: LineData, originLine: LineDataLine) => {
     const origin = {
-      points: getLinePoints(data, line),
-      width: line.strokeWidth,
+      points: getLinePoints(data, originLine),
+      width: originLine.strokeWidth,
     };
+    if (!origin.points[0] || !origin.points[1]) return [];
     const key = getInfoKey(origin);
-    if (!(key in lineJoinInfos)) {
-      lineJoinInfos[key] = {};
-    }
+    let originEdges: Pos[] = getLineEdges(origin.points, origin.width);
+    const initOriginEdges = [...originEdges];
 
-    const cache = lineJoinInfos[key];
-    const exist0 = "0" in cache;
-    const exist1 = "1" in cache;
-    if (exist0 && exist1) {
-      return [cache[0], cache[1]];
+    const jInfos: (JInfo | undefined)[] = [
+      joinInfos[key]?.[0],
+      joinInfos[key]?.[1],
+    ];
+
+    if (!(key in joinInfos)) {
+      return originEdges;
+    }
+    for (const info of jInfos) {
+      if (!info?.lej) continue;
+      for (const rep of info.lej) {
+        const ndx = originEdges.indexOf(initOriginEdges[rep.rep]);
+        originEdges.splice(ndx, 1, ...rep.points);
+      }
     }
 
-    const calcNdxs: number[] = [];
-    exist0 || calcNdxs.push(0);
-    exist1 || calcNdxs.push(1);
+    for (const info of jInfos) {
+      if (!info?.diffPolygons) continue;
+      originEdges = polygonDifferenceOnly(originEdges, info.diffPolygons);
+    }
 
-    const update2Line = (join: LineDataLine, originNdx: number) => {
-      const target = {
-        points: getLinePoints(data, join),
-        width: join.strokeWidth,
-      };
-      const joinInfo = getLineEdgeJoinInfo(origin, target, minAngle, palAngle);
-      cache[originNdx] = joinInfo?.origin;
+    return originEdges;
+  };
 
-      const targetKey = getInfoKey(target);
-      if (!(targetKey in lineJoinInfos)) {
-        lineJoinInfos[targetKey] = {};
-      }
-      const p = origin.points[originNdx];
-      lineJoinInfos[targetKey][target.points.indexOf(p)] = joinInfo?.target;
+  const setManyJoinInfo = (data: LineData, lines: LineDataLine[]) => {
+    type Select = ReturnType<typeof getLEJLineAngle> & {
+      origin: LineDataLine;
+      target: LineDataLine;
     };
-
-    const updateManyLine = (joins: LineDataLine[], originNdx: number) => {
-      let maxAngle = minAngle;
-      let select: ReturnType<typeof getLEJLineAngle> & {
-        origin: LineDataLine;
-        target: LineDataLine;
-      };
-      for (let i = 0; i < joins.length; i++) {
-        const line1 = getLinePoints(data, joins[i]);
-        for (let j = i + 1; j < joins.length; j++) {
-          const line2 = getLinePoints(data, joins[j]);
-          if (line2IncludedAngle(line1, line2)) {
-            const ejlAngle = getLEJLineAngle(line1, line2);
-            if (ejlAngle.angle > maxAngle) {
-              maxAngle = ejlAngle.angle;
-              select = {
-                ...ejlAngle,
-                origin: joins[i],
-                target: joins[j],
-              };
-            }
+    const selectLEJLines = (lines: LineDataLine[]) => {
+      let select: Select;
+      let maxAngle = -999;
+      for (let i = 0; i < lines.length; i++) {
+        const line1 = getLinePoints(data, lines[i]);
+        for (let j = i + 1; j < lines.length; j++) {
+          const line2 = getLinePoints(data, lines[j]);
+          const ejlAngle = getLEJLineAngle(line1, line2);
+          if (ejlAngle.norAngle > maxAngle) {
+            maxAngle = ejlAngle.norAngle;
+            select = {
+              ...ejlAngle,
+              origin: lines[i],
+              target: lines[j],
+            };
           }
         }
       }
+      return select!;
     };
 
-    for (const ndx of calcNdxs) {
-      const p = origin.points[ndx];
-      const joins = getJoinLine(data, line, p.id);
-
-      if (joins.length === 0) {
-        cache[ndx] = undefined;
-      } else if (joins.length !== 1) {
-        updateManyLine(joins, ndx);
+    let diffPolygons: Pos[][] = [];
+    const pointIds = lines.flatMap(l => [l.a, l.b])
+    const lineCount = lines.length
+    while (lines.length) {
+      if (lines.length > 1) {
+        const select = selectLEJLines(lines)!;
+        const origin = setLEJInfo(data, select.origin, select.target);
+        const target = setLEJInfo(data, select.target, select.origin);
+
+        lines = lines.filter(
+          (line) => line !== select.origin && line !== select.target
+        );
+        origin.diffPolygons = diffPolygons
+        target.diffPolygons = diffPolygons
+
+        diffPolygons = [
+          ...diffPolygons,
+          getLEJPolygon(data, select.origin),
+          getLEJPolygon(data, select.target),
+        ];
       } else {
-        update2Line(joins[0], ndx);
+        const key = getInfoKey({
+          points: getLinePoints(data, lines[0]),
+          width: lines[0].strokeWidth,
+        });
+        if (!(key in joinInfos)) {
+          joinInfos[key] = {};
+        }
+        const ndx = [lines[0].a, lines[0].b].findIndex(
+          (id) => pointIds.filter(pid => id === pid).length === lineCount
+        );
+        joinInfos[key][ndx] = { lej: undefined, diffPolygons };
+        lines = [];
       }
     }
-    return [cache[0], cache[1]];
   };
 
-  return (data: LineData, line: LineDataLine, useJoin = true) => {
-    const originEdges: Pos[] = getLineEdges(
-      getLinePoints(data, line),
-      line.strokeWidth
+  const genLEJLine = (lineData: LineData, line: LineDataLine) => ({
+    points: getLinePoints(lineData, line),
+    width: line.strokeWidth,
+  });
+
+  const init = (data: LineData) => {
+    const watchLine = (line: LineDataLine) => {
+      const joina = computed(() => getJoinLine(data, line, line.a));
+      const joinb = computed(() => getJoinLine(data, line, line.b));
+      const self = computed(() => genLEJLine(data, line));
+      const getWatchKey = () => {
+        const lines = [
+          ...joina.value.map((l) => genLEJLine(data, l)),
+          ...joinb.value.map((l) => genLEJLine(data, l)),
+          self.value,
+        ];
+        if (lines.some((l) => !l.points[0] || !l.points[1])) {
+          return null;
+        } else {
+          return lines.map(getInfoKey).join(",");
+        }
+      };
+
+      return watch(
+        getWatchKey,
+        (wkey, _2, onCleanup) => {
+          if (!wkey) return;
+          const key = getInfoKey(self.value);
+          const calcNdxs: number[] = [];
+
+          if (!(key in joinInfos)) {
+            joinInfos[key] = {};
+            calcNdxs.push(0, 1);
+          } else {
+            "0" in joinInfos[key] || calcNdxs.push(0);
+            "1" in joinInfos[key] || calcNdxs.push(1);
+          }
+
+          for (const ndx of calcNdxs) {
+            const joins = ndx === 0 ? joina.value : joinb.value;
+            if (joins.length === 0) {
+              joinInfos[key][ndx] = { lej: undefined };
+            } else if (joins.length !== 1) {
+              setManyJoinInfo(data, [line, ...joins]);
+            } else {
+              setLEJInfo(data, line, joins[0]);
+              setLEJInfo(data, joins[0], line);
+            }
+          }
+
+          onCleanup(() => {
+            delete joinInfos[key];
+          });
+        },
+        { immediate: true, flush: 'post' }
+      );
+    };
+
+    let isStop = false;
+    const stopMap = new WeakMap<LineDataLine, () => void>();
+    const stopWatch = watch(
+      () => [...data.lines],
+      async (newLines, oldLines = []) => {
+        const { added, deleted } = diffArrayChange(newLines, oldLines);
+        deleted.forEach((line) => {
+          const fn = stopMap.get(line);
+          fn && fn();
+        });
+        await nextTick();
+        if (!isStop) {
+          added.forEach((line) => {
+            stopMap.set(line, watchLine(line));
+          });
+        }
+        deleted;
+      },
+      { immediate: true }
     );
-    const initOriginEdges = [...originEdges];
-    if (!useJoin) {
-      return originEdges;
-    }
-    getLineInfo(data, line).forEach((info) => {
-      if (!info) return;
-      for (const rep of info) {
-        const ndx = originEdges.indexOf(initOriginEdges[rep.rep]);
-        originEdges.splice(ndx, 1, ...rep.points);
-      }
-    });
-    return originEdges;
+
+    return () => {
+      isStop = true;
+      stopWatch();
+      data.lines.forEach((line) => {
+        const fn = stopMap.get(line);
+        fn && fn();
+      });
+    };
   };
-});
+
+  watch(
+    lineData,
+    (data, _, onCleanup) => {
+      data && onCleanup(init(data));
+    },
+    { immediate: true }
+  );
+
+  return (line: LineDataLine) => {
+    const polygon = lineData.value ? getLEJPolygon(lineData.value, line) : [];
+    return polygon
+  };
+};
 
 // 计算与icon相差的多边形
 export const useGetDiffIconPolygons = installGlobalVar(() => {
@@ -197,7 +307,7 @@ export const useGetDiffIconPolygons = installGlobalVar(() => {
     var: (polygon: Pos[]) => {
       const targets = Object.values(iconPolygons);
       if (!targets.length) return [polygon];
-      return getDiffPolygons(polygon, targets);
+      return polygonDifference(polygon, targets);
     },
     onDestroy: stopWatch,
   };
@@ -316,7 +426,7 @@ export const useGetDiffLineIconPolygons = (
   return {
     diff: (polygon: Pos[]) => {
       const result = interPolygons.value.length
-        ? getDiffPolygons(polygon, interPolygons.value)
+        ? polygonDifference(polygon, interPolygons.value)
         : [polygon];
 
       return result;

+ 22 - 17
src/core/components/line/single-line.vue

@@ -32,7 +32,8 @@
       listening: false,
     }"
   />
-
+  <!-- v-if="status.active" -->
+  <!-- <v-text :config="{ ...pointsCenter(points), text: line.id }" /> -->
   <template
     v-if="
       status.active ||
@@ -81,6 +82,7 @@
       :data="drawProps.data"
       :line="drawProps.prev"
       :drawMode="drawProps.point"
+      :get-extend-polygon="drawGetExtendPolygon"
     />
     <singlePoint
       :data="drawProps.data"
@@ -91,6 +93,7 @@
       :data="drawProps.data"
       :line="drawProps.next"
       :drawMode="drawProps.point"
+      :get-extend-polygon="drawGetExtendPolygon"
     />
     <singlePoint
       :data="drawProps.data"
@@ -104,9 +107,9 @@
 import EditLine from "../share/edit-line.vue";
 import singlePoint from "./single-point.vue";
 import { computed, ref } from "vue";
-import { getMouseStyle, LineData, shapeName } from "./index.ts";
+import { getMouseStyle, LineData, LineDataLine, shapeName } from "./index.ts";
 import { flatPositions, onlyId } from "@/utils/shared.ts";
-import { Pos } from "@/utils/math.ts";
+import { pointsCenter, Pos } from "@/utils/math.ts";
 import { Line } from "konva/lib/shapes/Line";
 import { DC } from "@/deconstruction.js";
 import SizeLine from "../share/size-line.vue";
@@ -119,6 +122,7 @@ import {
 import { themeColor } from "@/constant";
 import { useGetDiffLineIconPolygons, useGetExtendPolygon } from "./attach-view.ts";
 import {
+  getLinePoints,
   useDrawLinePoint,
   useLineDataSnapInfos,
   useLineDescribes,
@@ -127,35 +131,33 @@ import { useStore } from "@/core/store/index.ts";
 import { useHistory } from "@/core/hook/use-history.ts";
 
 const props = defineProps<{
-  line: LineData["lines"][number];
+  line: LineDataLine;
   addMode?: boolean;
   canEdit?: boolean;
   data: LineData;
   dragPointIds?: string[];
   drawMode?: LineData["points"][number];
+  getExtendPolygon: (line: LineDataLine) => Pos[];
 }>();
 
 const emit = defineEmits<{
   (e: "updatePoint", value: LineData["points"][number]): void;
   (e: "addPoint", value: LineData["points"][number]): void;
-  (e: "addLine", value: LineData["lines"][number]): void;
+  (e: "addLine", value: LineDataLine): void;
   (e: "delLine"): void;
-  (e: "updateLine", value: LineData["lines"][number]): void;
+  (e: "updateLine", value: LineDataLine): void;
   (e: "updateBefore", value: string[]): void;
   (e: "update"): void;
 
-  (e: "dragLineStart", value: LineData["lines"][number]): void;
-  (e: "dragLine", line: LineData["lines"][number], move: Pos[]): void;
-  (e: "dragLineEnd", value: LineData["lines"][number]): void;
+  (e: "dragLineStart", value: LineDataLine): void;
+  (e: "dragLine", line: LineDataLine, move: Pos[]): void;
+  (e: "dragLineEnd", value: LineDataLine): void;
 }>();
 
-const getExtendPolygon = useGetExtendPolygon();
-const polygon = computed(() => getExtendPolygon(props.data, props.line, true));
-const points = computed(() => [
-  props.data.points.find((p) => p.id === props.line.a)!,
-  props.data.points.find((p) => p.id === props.line.b)!,
-]);
-const gd = useGetDiffLineIconPolygons(props.line, points);
+const polygon = computed(() => props.getExtendPolygon(line.value));
+const line = computed(() => props.line);
+const points = computed(() => getLinePoints(props.data, props.line));
+const gd = useGetDiffLineIconPolygons(line.value, points);
 const polygons = computed(() => gd.diff(polygon.value));
 
 const shape = ref<DC<Line>>();
@@ -171,7 +173,7 @@ const delHandler = () => {
 const store = useStore();
 const history = useHistory();
 const { drawProps, enter: enterDrawLinePoint } = useDrawLinePoint(
-  props.data,
+  computed(() => props.data),
   props.line,
   (data) => {
     emit("updateBefore", [props.line.a, props.line.b]);
@@ -186,6 +188,9 @@ const { drawProps, enter: enterDrawLinePoint } = useDrawLinePoint(
     emit("update");
   }
 );
+
+const drawGetExtendPolygon = useGetExtendPolygon(computed(() => drawProps.value?.data));
+
 const menus = [
   { label: "加点", handler: enterDrawLinePoint },
   { label: "删除", handler: delHandler },

+ 3 - 1
src/core/components/line/temp-line.vue

@@ -19,6 +19,7 @@
         @dragLineStart="dragstartLineHandler"
         @dragLine="dragLineHandler"
         @dragLineEnd="dragendLineHandler"
+        :getExtendPolygon="getExtendPolygon"
       />
     </v-group>
     <v-group>
@@ -40,7 +41,6 @@
 </template>
 
 <script lang="ts" setup>
-import { onlyId } from "@/utils/shared.ts";
 import { delPoint, LineData } from "./index.ts";
 import singleLine from "./single-line.vue";
 import singlePoint from "./single-point.vue";
@@ -60,6 +60,7 @@ import { useMouseShapeStatus } from "@/core/hook/use-mouse-status.ts";
 import { Pos } from "@/utils/math.ts";
 import { useSnapConfig, useSnapResultInfo } from "@/core/hook/use-snap.ts";
 import { Line } from "konva/lib/shapes/Line";
+import { useGetExtendPolygon } from "./attach-view.ts";
 
 const props = defineProps<{
   data: LineData;
@@ -78,6 +79,7 @@ const data = computed(() => {
   if (!initData.value || props.addMode) return props.data;
   return initData.value;
 });
+const getExtendPolygon = useGetExtendPolygon(computed(() => data.value));
 
 const dragPointIds = ref<string[]>();
 let track = false;

+ 25 - 42
src/core/hook/use-history.ts

@@ -2,7 +2,7 @@ import mitt from "mitt";
 import { SingleHistory } from "../history";
 import { installGlobalVar } from "./use-global-vars";
 import { Ref, ref, watch } from "vue";
-import { copy } from "@/utils/shared";
+import { copy, trackFlag } from "@/utils/shared";
 
 type HistoryItem = { attachs: string; data: string };
 export class DrawHistory {
@@ -19,8 +19,15 @@ export class DrawHistory {
     return this.list.find(item => item.id === this.currentId)?.data
   }
 
-  private preventFlag = 0;
-  private onceFlag = 0;
+  private once = trackFlag((flag) => {
+    if (flag === 0 && this.onceHistory) {
+      this.push(this.onceHistory);
+      this.onceHistory = null;
+    }
+  })
+  private prevent = trackFlag()
+  private enforce = trackFlag()
+
   private onceHistory: string | null = null;
   private rendererRaw?: (data: string) => void;
   initData: string | null = null;
@@ -47,6 +54,18 @@ export class DrawHistory {
     };
   }
 
+  onceTrack<T>(fn: () => T): T {
+    return this.once.track(fn)
+  }
+
+  preventTrack(fn: () => any) {
+    return this.prevent.track(fn)
+  }
+
+  enforceTrack(fn: () => any) {
+    return this.enforce.track(fn)
+  }
+
   setRenderer(renderer: (data: string) => void) {
     this.rendererRaw = renderer
   }
@@ -78,20 +97,6 @@ export class DrawHistory {
     this.push(data);
   }
 
-  private preventTrackCallback() {
-    this.preventFlag--
-  }
-
-  preventTrack(fn: () => any) {
-    this.preventFlag++
-    const result = fn();
-    if (result instanceof Promise) {
-      result.then(() => this.preventTrackCallback())
-    } else {
-      this.preventTrackCallback()
-    }
-  }
-
   private saveKeyPrev = '__history__'
   private saveKeyId: string | null = null
   get saveKey() {
@@ -141,10 +146,10 @@ export class DrawHistory {
   }
 
   push(data: string) {
-    if (this.preventFlag) return;
-    if (this.onceFlag) {
+    if (this.prevent.flag) return;
+    if (this.once.flag) {
       this.onceHistory = data;
-    } else if (data !== this.current?.data) {
+    } else if (data !== this.current?.data || this.enforce.flag) {
       console.log('push history')
       this.bus.emit("push", data);
       this.history.push({ attachs: JSON.stringify(this.pushAttachs), data });
@@ -167,28 +172,6 @@ export class DrawHistory {
     return data;
   }
 
-  private onceTrackCallback() {
-    this.onceFlag--
-    if (this.onceHistory) {
-      this.push(this.onceHistory);
-      this.onceHistory = null;
-    }
-  }
-
-  onceTrack<T>(fn: () => T): T {
-    this.onceFlag++
-    const result = fn();
-    if (result instanceof Promise) {
-      return result.then((data) => {
-        this.onceTrackCallback()
-        return data
-      }) as T
-    } else {
-      this.onceTrackCallback()
-      return result
-    }
-  }
-
   clearCurrent(): void {
     this.renderer({ data: this.clearData, attachs: "" });
     this.push(this.clearData);

+ 1 - 1
src/deconstruction.d.ts

@@ -13,4 +13,4 @@ type FilterNever<T> = {
 
 type FilterKeysWithPrefix<T, P extends string> = {  
 	[K in keyof T as K extends `${P}${string}` ? never : K]: T[K]  
-};  
+};  

+ 21 - 3
src/example/fuse/enter.ts

@@ -95,11 +95,11 @@ const login = (isBack = true) => {
     });
 
     if (!isBack) {
-      let url: URL
+      let url: URL;
       try {
         url = new URL(link);
       } catch {
-        url = new URL(link, location.origin)
+        url = new URL(link, location.origin);
       }
       url.searchParams.delete("redirect");
       const query = urlGetQuery(url.toString());
@@ -168,6 +168,15 @@ const saveOverviewData = genLoading(
     data: {
       store: StoreData;
       viewport: number[] | null;
+      caseTabulation: {
+        id: string,
+        store: StoreData;
+        viewport: number[] | null;
+        isAutoGen: boolean;
+        cover: TabCover | null;
+        paperKey?: string;
+        overviewId: string;
+      };
     }
   ) => {
     const item = await post(`fusion/caseOverview/addOrUpdate`, {
@@ -176,6 +185,15 @@ const saveOverviewData = genLoading(
       id,
       store: JSON.stringify(data.store),
       viewport: JSON.stringify(data.viewport),
+      caseTabulation: {
+        ...data.caseTabulation,
+        store: JSON.stringify(data.caseTabulation.store),
+        viewport: JSON.stringify(data.caseTabulation.viewport),
+        cover: JSON.stringify(data.caseTabulation.cover),
+        isAutoGen: Number(data.caseTabulation.isAutoGen),
+        paperKey: data.caseTabulation.paperKey,
+        overviewId: data.caseTabulation.overviewId,
+      }
     });
     return item.id;
   }
@@ -213,7 +231,7 @@ const getTabulationData = genLoading(async (id: string) => {
     viewport: JSON.parse(data.viewport),
     cover: JSON.parse(data.cover),
     isAutoGen: Number(data.isAutoGen),
-    paperKey: data.paperKey || 'a4',
+    paperKey: data.paperKey || "a4",
   };
 });
 

+ 18 - 15
src/example/fuse/views/overview/header.vue

@@ -242,13 +242,8 @@ const saveHandler = repeatedlyOnly(async () => {
     window.platform.uploadResourse(new File([kkBlob], `kankan-cover.png`)),
   ]);
 
-  overviewId.value = await window.platform.saveOverviewData(overviewId.value, {
-    ...overviewData.value,
-    listCover: listUrl,
-    kankanCover: kankanUrl,
-    store: storeData,
-    viewport: draw!.viewer.transform.m,
-  });
+  tabulationId.value = await window.platform.getTabulationId(overviewId.value);
+  await refreshTabulationData();
 
   const cover = {
     url: tabUrl,
@@ -257,8 +252,6 @@ const saveHandler = repeatedlyOnly(async () => {
     proportion: { ...draw.store.config.proportion, scale },
   };
 
-  tabulationId.value = await window.platform.getTabulationId(overviewId.value);
-  await refreshTabulationData();
   const tabStore = await repTabulationStore(
     tabulationData.value.paperKey,
     storeData.config.compass.rotation,
@@ -267,13 +260,23 @@ const saveHandler = repeatedlyOnly(async () => {
   );
 
   tabStore.config.compass = storeData.config.compass;
-  tabulationId.value = await window.platform.saveTabulationData(tabulationId.value, {
-    ...tabulationData.value,
-    title: overviewData.value.title,
-    cover,
-    store: tabStore,
-    overviewId: overviewId.value,
+
+  overviewId.value = await window.platform.saveOverviewData(overviewId.value, {
+    ...overviewData.value,
+    listCover: listUrl,
+    kankanCover: kankanUrl,
+    store: storeData,
+    viewport: draw!.viewer.transform.m,
+    caseTabulation: {
+      ...tabulationData.value,
+      id: tabulationId.value,
+      title: overviewData.value.title,
+      cover,
+      store: tabStore,
+      overviewId: overviewId.value,
+    },
   });
+  tabulationId.value = await window.platform.getTabulationId(overviewId.value);
   console.log("保存完毕");
 });
 

+ 1 - 0
src/example/platform/platform-draw.ts

@@ -127,6 +127,7 @@ const getTaggingShapes = async (taggings: SceneResource["taggings"]) => {
         content: item.url,
         zIndex: 1,
         mat: mat.m,
+        createTime: now + taggings.length + ndx,
       });
       continue;
     }

+ 20 - 9
src/example/platform/resource-swkk.ts

@@ -8,12 +8,10 @@ import {
   TaggingInfo,
   WallTaggingInfo,
 } from "./platform-resource";
-import { lineLen, lineVector, Pos, zeroEq } from "@/utils/math";
+import { lineLen, Pos, zeroEq } from "@/utils/math";
 import { aiIconMap, getIconItem, styleIconMap } from "../constant";
 import { MathUtils, Object3D, Quaternion, Vector3 } from "three";
 import { extractConnectedSegments } from "@/utils/polygon";
-import { getSvgContent, parseSvgContent } from "@/utils/resource";
-import { getLineIconMat } from "@/core/components/line-icon";
 
 const fetchResource = genCache(
   (scene: Scene) => scene.m,
@@ -200,13 +198,24 @@ const getBillYaw = (bill: any) => {
   return -yaw;
 };
 
-export const getBillTaggingInfos = async (scene: Scene, scale: number) => {
+export const getBillTaggingInfos = async (
+  scene: Scene,
+  scale: number,
+  subgroup: number
+) => {
   const { billboards } = await fetchResource(scene);
   const infos: TaggingInfo[] = [];
   const reqs: Promise<any>[] = [];
 
   for (const bill of billboards) {
-    if (!validNum(bill.pos[0]) || !validNum(bill.pos[2])) continue;
+    if (
+      !validNum(bill.pos[0]) ||
+      !validNum(bill.pos[2]) ||
+      ("subgroup" in bill && bill.subgroup !== subgroup)
+    )
+      continue;
+
+      console.log(bill.subgroup , subgroup)
     const styleMap = (styleIconMap as any)[bill.icon];
     const getIcon =
       bill.icon.indexOf("style-") === 0
@@ -349,7 +358,9 @@ export const getWallAITaggingInfos = async (
         infos.push({ start, end, inv });
       }
     }
-    const sortInfos = [...infos].sort((a, b) => (a.start.len + a.end.len) - (b.start.len + b.end.len));
+    const sortInfos = [...infos].sort(
+      (a, b) => a.start.len + a.end.len - (b.start.len + b.end.len)
+    );
     const target = sortInfos[0];
     if (target.start.len + target.end.len < 10) {
       return { points: [target.start.p, target.end.p], inv: target.inv };
@@ -386,7 +397,7 @@ export const getWallAITaggingInfos = async (
       if (!wall) {
         continue;
       }
-      const points = wall.inv ? wall.points.reverse() : wall.points
+      const points = wall.inv ? wall.points.reverse() : wall.points;
       infos.push({
         name: name,
         url: `./icons/${icon ? icon : "circle"}.svg`,
@@ -394,7 +405,7 @@ export const getWallAITaggingInfos = async (
         endLen: lineLen(points[0], line[1]),
         openSide: item.openSide,
         type: itemIcon?.parse?.type || "full",
-        pointIds: points.map(item => item.key),
+        pointIds: points.map((item) => item.key),
       });
     }
   }
@@ -442,7 +453,7 @@ export const getResource = async ({
   }
   if (syncs.includes("signage")) {
     reqs.push(
-      getBillTaggingInfos(scene, scale).then((ts) => taggings.push(...ts))
+      getBillTaggingInfos(scene, scale, key).then((ts) => taggings.push(...ts))
     );
   }
 

+ 72 - 0
src/utils/math-clip.ts

@@ -0,0 +1,72 @@
+import { Pos } from "./math";
+import ClipperLib from "clipper-lib";
+
+// Clipper 缩放比例,clipper要求整数坐标
+const SCALE = 100000;
+
+// 转换为 Clipper 路径
+function toClipperPath(polygon: Pos[]) {
+  return polygon.map((p) => ({
+    X: Math.round(p.x * SCALE),
+    Y: Math.round(p.y * SCALE),
+  }));
+}
+
+// 转换回普通坐标
+function fromClipperPath(path: any[]): Pos[] {
+  return path.map((p) => ({
+    x: p.X / SCALE,
+    y: p.Y / SCALE,
+  }));
+}
+
+// 多边形差集,clipper版本
+export function polygonDifference(subject: Pos[], clips: Pos[][]): Pos[][] {
+  const c = new ClipperLib.Clipper();
+
+  c.AddPath(toClipperPath(subject), ClipperLib.PolyType.ptSubject, true);
+  clips.forEach((clip) =>
+    c.AddPath(toClipperPath(clip), ClipperLib.PolyType.ptClip, true)
+  );
+
+  const solution = new ClipperLib.Paths();
+
+  c.Execute(
+    ClipperLib.ClipType.ctDifference,
+    solution,
+    ClipperLib.PolyFillType.pftNonZero,
+    ClipperLib.PolyFillType.pftNonZero
+  );
+
+  // 返回的是多条多边形路径
+  return solution.map(fromClipperPath);
+}
+
+export function polygonArea(poly: Pos[]): number {
+  let area = 0;
+  const n = poly.length;
+  for (let i = 0; i < n; i++) {
+    const p1 = poly[i];
+    const p2 = poly[(i + 1) % n];
+    area += p1.x * p2.y - p2.x * p1.y;
+  }
+  return Math.abs(area / 2);
+}
+
+// 多边形差集,clipper版本
+export function polygonDifferenceOnly(subject: Pos[], clips: Pos[][]): Pos[] {
+  const polygons = polygonDifference(subject, clips);
+  // 多多边形时选最大面积的作为主边界
+  let maxPoly = polygons[0];
+  if (!maxPoly) return subject;
+
+  let maxArea = polygonArea(maxPoly);
+  for (let k = 1; k < polygons.length; k++) {
+    const area = polygonArea(polygons[k]);
+    if (area > maxArea) {
+      maxArea = area;
+      maxPoly = polygons[k];
+    }
+  }
+  return maxPoly;
+}

+ 14 - 70
src/utils/math.ts

@@ -1,8 +1,7 @@
 import { Vector2, ShapeUtils, Box2 } from "three";
 import { Transform } from "konva/lib/Util";
-import { flatPositions, rangMod, round } from "./shared.ts";
+import { rangMod, round } from "./shared.ts";
 import { IRect } from "konva/lib/types";
-import { diff as diffPolygons, MultiPolygon } from "martinez-polygon-clipping";
 
 export type Pos = { x: number; y: number };
 export type Size = { width: number; height: number };
@@ -639,28 +638,6 @@ export const isRectContained = (rect1: IRect, rect2: IRect) => {
   );
 };
 
-export const getDiffPolygons = (
-  originPolygon: Pos[],
-  targetPolygons: Pos[][]
-) => {
-  const geo1 = originPolygon.map(({ x, y }) => [x, y]);
-  geo1.push(geo1[0]);
-  const geo2s = targetPolygons.map((targetPolygon) => {
-    const geo2 = targetPolygon.map(({ x, y }) => [x, y]);
-    geo2.push(geo2[0]);
-    return geo2;
-  });
-  try {
-    const subGeos = diffPolygons([geo1], geo2s);
-    return subGeos.map((mulPolygon) => {
-      const polygon = mulPolygon as number[][][];
-      return polygon[0].map((position) => ({ x: position[0], y: position[1] }));
-    }) as Pos[][];
-  } catch {
-    return [originPolygon];
-  }
-};
-
 export const getLineEdges = (points: Pos[], strokeWidth: number) => {
   const v = lineVector(points);
   const vv = verticalVector(v);
@@ -673,7 +650,8 @@ export const getLineEdges = (points: Pos[], strokeWidth: number) => {
 
 export type LEJLine = { points: Pos[]; width: number };
 export type LEJInfo = { rep: number; points: Pos[] }[];
-export const getLEJLineAngle = (originLine: Pos[], targetLine: Pos[]) => {
+
+export const getLEJJoinNdxs = (originLine: Pos[], targetLine: Pos[]) => {
   let originNdx = -1,
     targetNdx = -1;
   for (let i = 0; i < originLine.length; i++) {
@@ -683,7 +661,10 @@ export const getLEJLineAngle = (originLine: Pos[], targetLine: Pos[]) => {
       break;
     }
   }
-
+  return { originNdx, targetNdx };
+};
+export const getLEJLineAngle = (originLine: Pos[], targetLine: Pos[]) => {
+  const { originNdx, targetNdx } = getLEJJoinNdxs(originLine, targetLine);
   const targetInvFlag = originNdx === targetNdx;
   const targetPoints = targetInvFlag ? [...targetLine].reverse() : targetLine;
   const originVector = lineVector(originLine);
@@ -697,9 +678,11 @@ export const getLEJLineAngle = (originLine: Pos[], targetLine: Pos[]) => {
   }
   return {
     angle,
+    norAngle: rangMod(Math.abs(angle), Math.PI),
     originNdx,
     targetNdx,
     targetPoints,
+    targetInvFlag
   };
 };
 /**
@@ -716,15 +699,12 @@ export const getLineEdgeJoinInfo = (
   minAngle: number,
   palAngle: number
 ) => {
-  const {
-    angle,
-    originNdx,
-    targetNdx,
-    targetPoints
-  } = getLEJLineAngle(origin.points, target.points)
+  const { originNdx, targetPoints, norAngle } = getLEJLineAngle(
+    origin.points,
+    target.points
+  );
 
   // 最小可平滑处理角度
-  const norAngle = rangMod(Math.abs(angle), Math.PI);
   if (norAngle < minAngle || norAngle > Math.PI - minAngle) {
     return;
   }
@@ -786,8 +766,6 @@ export const getLineEdgeJoinInfo = (
     return;
   }
 
-  let targetInnerPoints: Pos[] = [innerJoin];
-  let targetOuterPoints: Pos[] = [outerJoin];
   let originInnerPoints: Pos[] = [innerJoin];
   let originOuterPoints: Pos[] = [outerJoin];
 
@@ -796,12 +774,9 @@ export const getLineEdgeJoinInfo = (
   if (norAngle < palAngle) {
     const pal = getVectorLine(lineVerticalVector([join, outerJoin]), join);
     originOuterPoints = [lineIntersection(pal, originOuter)!, join];
-    targetOuterPoints = [lineIntersection(pal, targetOuter)!, join];
   }
 
   const originRepInfos: LEJInfo = [];
-  const targetRepInfos: LEJInfo = [];
-
   if (originNdx === 0) {
     originRepInfos.push(
       {
@@ -830,36 +805,5 @@ export const getLineEdgeJoinInfo = (
     );
   }
 
-  if (targetNdx === 0) {
-    targetRepInfos.push(
-      {
-        rep: 0,
-        points: targetEdgesInv
-          ? targetOuterPoints.reverse()
-          : targetInnerPoints,
-      },
-      {
-        rep: 3,
-        points: targetEdgesInv ? targetInnerPoints : targetOuterPoints,
-      }
-    );
-  } else {
-    targetRepInfos.push(
-      {
-        rep: 1,
-        points: targetEdgesInv ? targetOuterPoints : targetInnerPoints,
-      },
-      {
-        rep: 2,
-        points: targetEdgesInv
-          ? targetInnerPoints
-          : targetOuterPoints.reverse(),
-      }
-    );
-  }
-
-  return {
-    target: targetRepInfos,
-    origin: originRepInfos,
-  };
+  return originRepInfos
 };

+ 44 - 15
src/utils/shared.ts

@@ -429,27 +429,28 @@ export const genCache = <T, R>(
   calcResult: (args: T) => R,
   delay: number | null = null
 ) => {
-  const cache: Record<string, R> = {}
+  const cache: Record<string, R> = {};
 
   return (args: T): R => {
-    const key = getKey(args)
+    const key = getKey(args);
     if (key in cache) {
-      return cache[key]
+      return cache[key];
     }
 
-    const result = cache[key] = calcResult(args)
+    const result = (cache[key] = calcResult(args));
     if (delay !== null) {
       setTimeout(() => {
-        delete cache[key]
-      }, delay)
+        delete cache[key];
+      }, delay);
     }
 
     return result;
-  }
+  };
 };
 
-
-export const hoverManage = <T, K>(hoverHandler: (data: T) => (data: K) => void) => {
+export const hoverManage = <T, K>(
+  hoverHandler: (data: T) => (data: K) => void
+) => {
   let enter = false;
   let timeout: any;
   let leave: undefined | ((data: K) => void);
@@ -461,17 +462,45 @@ export const hoverManage = <T, K>(hoverHandler: (data: T) => (data: K) => void)
     }
   };
   const immediatelyLeave = (data: K) => {
-      enter = false;
-      leave && leave(data);
-  }
+    enter = false;
+    leave && leave(data);
+  };
   const leaveHandler = (data: K) => {
     timeout = setTimeout(() => {
-      immediatelyLeave(data)
+      immediatelyLeave(data);
     }, 160);
   };
   return {
     enterHandler,
     leaveHandler,
-    immediatelyLeave
+    immediatelyLeave,
+  };
+};
+
+export const trackFlag = (trackCallback?: (flag: number) => void) => {
+  let flag = 0;
+  const callback = () => {
+    flag--
+    trackCallback && trackCallback(flag)
   }
-}
+
+  return {
+    get flag() {
+      return flag
+    },
+    track<T>(fn: () => T): T {
+      flag++;
+      const result = fn();
+      if (result instanceof Promise) {
+        return result.then((data) => {
+          console.log('callback')
+          callback()
+          return data
+        }) as T;
+      } else {
+        callback();
+        return result
+      }
+    },
+  };
+};

+ 3 - 0
src/vite-env.d.ts

@@ -13,3 +13,6 @@ interface ImportMeta {
   const content: any;
   export default content;
 }
+
+
+declare module 'clipper-lib'