Jelajahi Sumber

feat: 单线段移动参考附近线段制作

bill 2 bulan lalu
induk
melakukan
28474fb224

+ 2 - 1
src/core/components/group/group.vue

@@ -74,7 +74,8 @@ const { shape, tData, data } = useComponentStatus<Rect, GroupData>({
     const updateChildren = debounce((data, mat) => {
       unUpdate = false;
       setShapeTransform(shape.value!.getNode(), mat);
-      matResponse({ data, mat, store }, prevMat);
+      const ctxs = data.ids.map(getShapeBelong).filter((item: any) => !!item);
+      matResponse({ data, mat, store }, prevMat, ctxs);
       prevMat = mat;
       getGroupShapes!().forEach((shape) => nextTick(() => shape.fire("bound-change")));
       if (autoUpdate.value) {

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

@@ -8,14 +8,9 @@
     :id="line.id"
     :disablePoint="!canEdit || mode.include(Mode.readonly)"
     :ndx="0"
-    @dragstart="dragstartHandler(points.map((item) => item.id))"
-    @update:line="
-      (p) => {
-        emit('updatePoint', { ...points[0], ...p[0] });
-        emit('updatePoint', { ...points[1], ...p[1] });
-      }
-    "
-    @dragend="dragendHandler"
+    @dragstart="dragstartLineHandler(points.map((item) => item.id))"
+    @update:line="dragLineHandler"
+    @dragend="dragendLineHandler"
     @add-point="addPoint"
   />
 
@@ -112,6 +107,10 @@ const emit = defineEmits<{
   (e: "updateLine", value: LineData["lines"][number]): 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;
 }>();
 
 const shape = ref<DC<Line>>();
@@ -231,4 +230,14 @@ const dragendHandler = () => {
   emit("update");
   snapInfos.forEach((item) => infos.remove(item));
 };
+
+const dragstartLineHandler = (eIds: string[]) => {
+  emit("dragLineStart", props.line);
+};
+const dragLineHandler = (ps: Pos[]) => {
+  emit("dragLine", props.line, ps);
+};
+const dragendLineHandler = () => {
+  emit("dragLineEnd", props.line);
+};
 </script>

+ 20 - 28
src/core/components/line/temp-line.vue

@@ -15,12 +15,11 @@
         @update-before="updateBeforeHandler"
         @update="updateHandler"
         @del-line="delLineHandler(item)"
+        @dragLineStart="dragstartLineHandler"
+        @dragLine="dragLineHandler"
+        @dragLineEnd="dragendLineHandler"
       />
     </v-group>
-    <!-- <v-rect
-      v-if="operMode.mulSelection"
-      :config="{ fill: 'red', ...lineBox, opacity: 0.1 }"
-    /> -->
   </v-group>
 </template>
 
@@ -28,14 +27,20 @@
 import { onlyId } from "@/utils/shared.ts";
 import { delPoint, LineData } from "./index.ts";
 import singleLine from "./single-line.vue";
-import { getInitCtx, NLineDataCtx, normalLineData, useInitData } from "./use-draw.ts";
+import {
+  genMoveLineHandler,
+  getInitCtx,
+  NLineDataCtx,
+  normalLineData,
+  useInitData,
+} from "./use-draw.ts";
 import { computed, ref } from "vue";
 import { useZIndex } from "@/core/hook/use-layer.ts";
 import { DC } from "@/deconstruction.js";
 import { Group } from "konva/lib/Group";
 import { useOperMode } from "@/core/hook/use-status.ts";
-import { useViewerInvertTransform } from "@/core/hook/use-viewer.ts";
 import { useMouseShapeStatus } from "@/core/hook/use-mouse-status.ts";
+import { Pos } from "@/utils/math.ts";
 
 const props = defineProps<{
   data: LineData;
@@ -54,28 +59,6 @@ const data = computed(() => {
   return initData.value;
 });
 
-const invMat = useViewerInvertTransform();
-const lineBox = computed(() => {
-  const rect = shape.value?.getNode().getClientRect();
-  if (!rect) {
-    return {};
-  }
-  const start = invMat.value.point({ x: rect.x, y: rect.y });
-  const end = invMat.value.point({ x: rect.x + rect.width, y: rect.y + rect.height });
-  let width = end.x - start.x;
-  let height = end.y - start.y;
-
-  if (width < 0) {
-    start.x = end.x;
-    width = -width;
-  }
-  if (height < 0) {
-    start.y = end.y;
-    height = -height;
-  }
-  return { ...start, width, height };
-});
-
 const dragPointIds = ref<string[]>();
 let track = false;
 let ctx: NLineDataCtx;
@@ -122,6 +105,15 @@ const updateHandler = () => {
   dragPointIds.value = undefined;
 };
 
+let moveHandler: ReturnType<typeof genMoveLineHandler>;
+const dragstartLineHandler = (line: LineData["lines"][0]) => {
+  moveHandler = genMoveLineHandler(props.data, line.id);
+};
+const dragLineHandler = (line: LineData["lines"][0], ps: Pos[]) => {
+  moveHandler(ps);
+};
+const dragendLineHandler = (line: LineData["lines"][0]) => {};
+
 const shape = ref<DC<Group>>();
 useZIndex(shape, data);
 useMouseShapeStatus(shape);

+ 194 - 31
src/core/components/line/use-draw.ts

@@ -13,10 +13,21 @@ import { useOperMode } from "@/core/hook/use-status";
 import { installGlobalVar, useCursor } from "@/core/hook/use-global-vars";
 import { useInteractiveDots } from "@/core/hook/use-interactive";
 import { computed, reactive, ref, watch } from "vue";
-import { copy, mergeFuns } from "@/utils/shared";
-import { eqPoint, Pos } from "@/utils/math";
-import { getSnapInfos, type LineData } from "./";
+import { copy, mergeFuns, onlyId, rangMod } from "@/utils/shared";
+import {
+  eqPoint,
+  getVectorLine,
+  linePointProjection,
+  lineVector,
+  Pos,
+  vector2IncludedAngle,
+  verticalVector,
+  zeroEq,
+} from "@/utils/math";
+import { defaultStyle, getSnapInfos, type LineData } from "./";
 import { useCustomSnapInfos } from "@/core/hook/use-snap";
+import { MathUtils, Vector2 } from "three";
+import { getBaseItem } from "../util";
 
 type PayData = Pos;
 
@@ -65,19 +76,18 @@ export const useDraw = () => {
       if (!drawItems[0]) return;
 
       if (isTempDraw) {
-        console.log(drawItems[0].points)
+        console.log(drawItems[0].points);
         drawItems[0].lines.pop();
         drawItems[0].points.pop();
         drawItems[0].polygon.pop();
       }
-      const lastP = drawItems[0].points[drawItems[0].points.length - 1]
+      const lastP = drawItems[0].points[drawItems[0].points.length - 1];
       if (lastP) {
-        console.log(lastP)
-        const ctx = getInitCtx()
-        ctx.add.points[lastP.id] = lastP
-        normalLineData(drawItems[0], ctx)
+        console.log(lastP);
+        const ctx = getInitCtx();
+        ctx.add.points[lastP.id] = lastP;
+        normalLineData(drawItems[0], ctx);
       }
-      
 
       snapInfos?.forEach(customSnapInfos.remove);
       drawSnapInfos?.forEach(customSnapInfos.remove);
@@ -289,9 +299,6 @@ export const useDraw = () => {
   return drawItems;
 };
 
-
-
-
 export type NLineDataCtx = {
   del: {
     points: Record<string, LineData["points"][0]>;
@@ -321,22 +328,30 @@ export const getInitCtx = (): NLineDataCtx => ({
   },
 });
 
-export const repPointRef = (data: LineData, delId: string, repId: string) => {
+export const repPointRef = (data: LineData, delId: string, repId: string, queUpdate = true) => {
   for (let i = 0; i < data.lines.length; i++) {
     const line = data.lines[i];
     if (line.a === delId) {
-      data.lines[i] = { ...line, a: repId };
+      if (queUpdate) {
+        data.lines[i] = { ...line, a: repId };
+      } else {
+        data.lines[i].a = repId
+      }
     }
     if (line.b === delId) {
-      data.lines[i] = { ...line, b: repId };
+      if (queUpdate) {
+        data.lines[i] = { ...line, b: repId };
+      } else {
+        data.lines[i].b = repId
+      }
     }
   }
-  return data
+  return data;
 };
 
 export const deduplicateLines = (data: LineData) => {
-  const seen = new Map<string, LineData['lines'][0]>();
-  let isChange = false
+  const seen = new Map<string, LineData["lines"][0]>();
+  let isChange = false;
   for (const line of data.lines) {
     if (line.a === line.b) continue;
     // 生成标准化键:确保 (a,b) 和 (b,a) 被视为相同,并且 a === b 时也去重
@@ -350,17 +365,17 @@ export const deduplicateLines = (data: LineData) => {
       // 如果存在重复键,覆盖旧值(保留尾部元素)
       seen.delete(existingKey);
       seen.set(key1, line); // 统一存储为 key1 格式
-      isChange = true
+      isChange = true;
     } else {
       // 新记录,直接存储
       seen.set(key1, line);
     }
   }
   if (isChange) {
-    data.lines = Array.from(seen.values())
+    data.lines = Array.from(seen.values());
   }
 
-  return data
+  return data;
 };
 
 export const normalLineData = (data: LineData, ctx: NLineDataCtx) => {
@@ -369,7 +384,6 @@ export const normalLineData = (data: LineData, ctx: NLineDataCtx) => {
     ...Object.values(ctx.update.points),
   ];
 
-
   // 合并相同点
   for (const p2 of changePoints) {
     const ndx = data.points.findIndex((item) => item.id === p2.id);
@@ -387,22 +401,171 @@ export const normalLineData = (data: LineData, ctx: NLineDataCtx) => {
 
   // 删除线a b 点一样的线段
   for (let i = 0; i < data.lines.length; i++) {
-    const line = data.lines[i]
+    const line = data.lines[i];
     if (line.a === line.b) {
-      data.lines.splice(i--, 1)
+      data.lines.splice(i--, 1);
     }
   }
 
-
   // 删除游离点
-  const pointIds = Object.values(ctx.del.lines).flatMap(item => [item.a, item.b])
-  pointIds.push(...Object.keys(ctx.add.points))
-  const linePointIds = data.lines.flatMap(item => [item.a, item.b])
+  const pointIds = Object.values(ctx.del.lines).flatMap((item) => [
+    item.a,
+    item.b,
+  ]);
+  pointIds.push(...Object.keys(ctx.add.points));
+  const linePointIds = data.lines.flatMap((item) => [item.a, item.b]);
   for (let id of pointIds) {
     if (!linePointIds.includes(id)) {
-      const ndx = data.points.findIndex(p => p.id === id)
-      ~ndx && data.points.splice(ndx, 1)
+      const ndx = data.points.findIndex((p) => p.id === id);
+      ~ndx && data.points.splice(ndx, 1);
     }
   }
   return deduplicateLines(data);
 };
+
+export const genMoveLineHandler = (data: LineData, lineId: string, ctx = getInitCtx()) => {
+  const line = data.lines.find((line) => line.id === lineId)!;
+  const pointIds = [line.a, line.b];
+  const points = pointIds.map((id) => data.points.find((p) => p.id === id)!);
+  const angleRange = [MathUtils.degToRad(10), MathUtils.degToRad(170)];
+  const getJoinLine = (pId: string) =>
+    data.lines
+      .filter(
+        (item) =>
+          (item.a === pId || item.b === pId) &&
+          !(pointIds.includes(item.a) && pointIds.includes(item.b))
+      )
+      .map((line) => {
+        const pointIds = pId === line.a ? [line.a, line.b] : [line.b, line.a];
+        return {
+          id: line.id,
+          points: pointIds.map((id) => data.points.find((p) => p.id === id)!),
+        };
+      });
+
+  const getRefInfo = (moveDire: Vector2, ndx: number) => {
+    const joinLines = getJoinLine(pointIds[ndx]);
+    const joinLineDires: Vector2[] = [];
+    const line = [points[ndx], points[Number(!ndx)]];
+    const lineDire = lineVector(line);
+
+    let invAngle = Number.MAX_VALUE;
+    let invSelectLineId: string;
+    let invSelectLineDire: Vector2 | null = null;
+
+    let alongAngle = -Number.MAX_VALUE;
+    let alongSelectLineId: string;
+    let alongSelectLineDire: Vector2 | null = null;
+
+    for (const line of joinLines) {
+      const joinDire = lineVector(line.points);
+      joinLineDires.push(joinDire);
+
+      const angle = vector2IncludedAngle(lineDire, joinDire);
+      if (angle > 0) {
+        if (angle < invAngle) {
+          invAngle = angle;
+          invSelectLineId = line.id;
+          invSelectLineDire = joinDire;
+        }
+      } else {
+        if (angle > alongAngle) {
+          alongAngle = angle;
+          alongSelectLineId = line.id;
+          alongSelectLineDire = joinDire;
+        }
+      }
+    }
+
+    if (!invSelectLineDire && !alongSelectLineDire) {
+      return;
+    }
+    let isAlong = !invSelectLineDire;
+    if (!isAlong && alongSelectLineDire) {
+      const invMoveAngle = Math.abs(
+        vector2IncludedAngle(moveDire, invSelectLineDire!)
+      );
+      const alongMoveAngle = Math.abs(
+        vector2IncludedAngle(moveDire, alongSelectLineDire!)
+      );
+      isAlong = alongMoveAngle! < invMoveAngle!;
+    }
+    let info = isAlong
+      ? {
+          lineDire,
+          selectLineDire: alongSelectLineDire!,
+          selectLineId: alongSelectLineId!,
+          angle: alongAngle!,
+        }
+      : {
+          lineDire,
+          selectLineDire: invSelectLineDire!,
+          selectLineId: invSelectLineId!,
+          angle: invAngle!,
+        };
+
+    
+    info.angle = rangMod(info.angle, Math.PI)
+    const needVertical = info.angle > angleRange[1] || info.angle < angleRange[0];
+    const needSplit = needVertical || joinLineDires.some(
+        (dire) =>
+          dire !== info.selectLineDire &&
+          !zeroEq(
+            rangMod(vector2IncludedAngle(dire, info.selectLineDire), Math.PI)
+          )
+      )
+    return { ...info, needSplit, needVertical }
+  };
+
+  let refInfos: ReturnType<typeof getRefInfo>[];
+  let snapLines: (null | Pos[])[]
+  let inited = false
+
+  const init = (moveDires: Vector2[]) => {
+    refInfos = [getRefInfo(moveDires[0], 0), getRefInfo(moveDires[0], 1)]
+    snapLines = []
+    for (let i = 0; i < refInfos.length; i++) {
+      const refInfo = refInfos[i]
+      if (!refInfo) {
+        continue
+      }
+      if (refInfo.needSplit) {
+        // 拆分点
+        const point = points[i]
+        const newPoint = { ...point, id: onlyId() }
+        data.points.push(newPoint)
+        repPointRef(data, point.id, newPoint.id, false)
+        const newLine = { ...getBaseItem(), ...defaultStyle, a: point.id, b: newPoint.id }
+        data.lines.push(newLine)
+        ctx.add.lines[newLine.id] = newLine
+        ctx.add.points[newPoint.id] = newPoint
+
+        if (i) {
+          line.b = point.id
+        } else {
+          line.a = point.id
+        }
+      }
+      const dire = refInfo.needVertical ? verticalVector(refInfo.selectLineDire) : refInfo.selectLineDire
+      snapLines[i] = getVectorLine(dire, copy(points[i]), 10)
+    }
+  }
+
+  const handler = (finalPoss: Pos[]) => {
+    const moveDires = finalPoss.map((pos, ndx) => lineVector([points[ndx], pos]))
+    if (!inited) {
+      inited = true
+      init(moveDires)
+    }
+    
+    for (let i = 0; i < snapLines.length; i++) {
+      const snapLine = snapLines[i]
+      const fpos = !snapLine ? finalPoss[i] : linePointProjection(snapLine, finalPoss[i])
+      console.log(snapLines[i], fpos, i)
+      Object.assign(points[i], fpos)
+      ctx.update.points[points[i].id] = points[i]
+    }
+  };
+
+  return handler
+};

+ 2 - 0
src/core/hook/use-expose.ts

@@ -36,6 +36,7 @@ import { components } from "../components/index.ts";
 import { useProportion } from "./use-proportion.ts";
 import { useGetDXF } from "./use-dxf.ts";
 import { getIconStyle } from "../components/icon/index.ts";
+import { useGetShapeBelong } from "./use-component.ts";
 
 // 自动粘贴服务
 export const useAutoPaste = () => {
@@ -115,6 +116,7 @@ export const useShortcutKey = () => {
   const status = useMouseShapesStatus();
   const getChildren = useGetFormalChildren();
   const operMode = useOperMode();
+  const getShapeBelong = useGetShapeBelong()
   const eSelection = useExcludeSelection()
   useListener(
     "keydown",