|
@@ -0,0 +1,375 @@
|
|
|
+import {
|
|
|
+ MessageAction,
|
|
|
+ penUpdatePoints,
|
|
|
+ useDrawRunning,
|
|
|
+ useInteractiveDrawShapeAPI,
|
|
|
+ usePointBeforeHandler,
|
|
|
+} from "@/core/hook/use-draw";
|
|
|
+import { components, ComponentSnapInfo, SnapPoint } from "..";
|
|
|
+import { useHistory, useHistoryAttach } from "@/core/hook/use-history";
|
|
|
+import { useStore } from "@/core/store";
|
|
|
+import { useViewerTransform } from "@/core/hook/use-viewer";
|
|
|
+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 { useCustomSnapInfos } from "@/core/hook/use-snap";
|
|
|
+
|
|
|
+type PayData = Pos;
|
|
|
+
|
|
|
+export let initData: LineData | undefined;
|
|
|
+export const useInitData = installGlobalVar(() => ref<LineData>());
|
|
|
+
|
|
|
+// 单例钢笔添加
|
|
|
+export const useDraw = () => {
|
|
|
+ const type = "line";
|
|
|
+ const { quitDrawShape } = useInteractiveDrawShapeAPI();
|
|
|
+ const isRuning = useDrawRunning(type);
|
|
|
+ const obj = components[type];
|
|
|
+ const beforeHandler = usePointBeforeHandler(true, true);
|
|
|
+ const history = useHistory();
|
|
|
+ const store = useStore();
|
|
|
+ const viewTransform = useViewerTransform();
|
|
|
+ const operMode = useOperMode();
|
|
|
+ const hInitData = useInitData();
|
|
|
+ const customSnapInfos = useCustomSnapInfos();
|
|
|
+
|
|
|
+ // 可能历史空间会撤销 重做更改到正在绘制的组件
|
|
|
+ const currentCursor = ref("/icons/m_add.png");
|
|
|
+ const cursor = useCursor();
|
|
|
+ let cursorPop: ReturnType<typeof cursor.push> | null = null;
|
|
|
+ let stopWatch: (() => void) | null = null;
|
|
|
+ let prev: SnapPoint;
|
|
|
+ let currentIsDel = false;
|
|
|
+ let isTempDraw = false;
|
|
|
+ let snapInfos: ComponentSnapInfo[] | null = null;
|
|
|
+ let drawSnapInfos: ComponentSnapInfo[] | null = null;
|
|
|
+
|
|
|
+ const ia = useInteractiveDots({
|
|
|
+ shapeType: type,
|
|
|
+ isRuning,
|
|
|
+ enter() {
|
|
|
+ cursorPop = cursor.push(currentCursor.value);
|
|
|
+ watch(currentCursor, () => {
|
|
|
+ cursorPop?.set(currentCursor.value);
|
|
|
+ });
|
|
|
+ },
|
|
|
+ quit: () => {
|
|
|
+ quitDrawShape();
|
|
|
+ beforeHandler.clear();
|
|
|
+ cursorPop && cursorPop();
|
|
|
+ stopWatch && stopWatch();
|
|
|
+ if (!drawItems[0]) return;
|
|
|
+
|
|
|
+ if (isTempDraw) {
|
|
|
+ drawItems[0].lines.pop();
|
|
|
+ drawItems[0].points.pop();
|
|
|
+ drawItems[0].polygon.pop();
|
|
|
+ }
|
|
|
+ snapInfos?.forEach(customSnapInfos.remove);
|
|
|
+ drawSnapInfos?.forEach(customSnapInfos.remove);
|
|
|
+ initData = hInitData.value = void 0;
|
|
|
+ },
|
|
|
+ beforeHandler: (p) => {
|
|
|
+ beforeHandler.clear();
|
|
|
+ const pa = beforeHandler.transform(p, prev && [prev, p]);
|
|
|
+ currentIsDel && beforeHandler.clear();
|
|
|
+ return pa;
|
|
|
+ },
|
|
|
+ });
|
|
|
+
|
|
|
+ const shapeId = computed(
|
|
|
+ () => ia.isRunning.value && store.getTypeItems(type)[0]?.id
|
|
|
+ );
|
|
|
+ const drawItems = reactive([]) as LineData[];
|
|
|
+ watch(
|
|
|
+ shapeId,
|
|
|
+ () => {
|
|
|
+ const data = shapeId.value
|
|
|
+ ? (store.getItemById(shapeId.value) as LineData)
|
|
|
+ : undefined;
|
|
|
+
|
|
|
+ if (data) {
|
|
|
+ initData = hInitData.value = {
|
|
|
+ ...data,
|
|
|
+ points: [...data.points],
|
|
|
+ lines: [...data.lines],
|
|
|
+ polygon: [...data.polygon],
|
|
|
+ };
|
|
|
+ drawItems[0] = {
|
|
|
+ ...data,
|
|
|
+ points: [],
|
|
|
+ lines: [],
|
|
|
+ polygon: [],
|
|
|
+ };
|
|
|
+ snapInfos = getSnapInfos(initData);
|
|
|
+ snapInfos.forEach(customSnapInfos.add);
|
|
|
+ } else {
|
|
|
+ drawItems.pop();
|
|
|
+ initData = hInitData.value = undefined;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ { immediate: true }
|
|
|
+ );
|
|
|
+
|
|
|
+ const messages = useHistoryAttach<Pos[]>(
|
|
|
+ `${type}-pen`,
|
|
|
+ isRuning,
|
|
|
+ () => [],
|
|
|
+ true
|
|
|
+ );
|
|
|
+
|
|
|
+ const getAddMessage = (cur: Pos) => {
|
|
|
+ let consumed = messages.value;
|
|
|
+ currentCursor.value = "/icons/m_add.png";
|
|
|
+ let pen: null | ReturnType<typeof penUpdatePoints> = null;
|
|
|
+ return {
|
|
|
+ pen,
|
|
|
+ consumed,
|
|
|
+ cur,
|
|
|
+ action: MessageAction.add,
|
|
|
+ } as any;
|
|
|
+ };
|
|
|
+
|
|
|
+ const setMessage = (cur: Pos) => {
|
|
|
+ const { pen, ...msg } = getAddMessage(cur);
|
|
|
+ if ((currentIsDel = pen?.oper === "del")) {
|
|
|
+ currentCursor.value = "/icons/m_reduce.png";
|
|
|
+ beforeHandler.clear();
|
|
|
+ }
|
|
|
+ return msg;
|
|
|
+ };
|
|
|
+
|
|
|
+ const pushMessages = (cur: Pos) => {
|
|
|
+ const { pen } = getAddMessage(cur);
|
|
|
+ if (pen) {
|
|
|
+ if (!pen.unchanged) {
|
|
|
+ messages.value = pen.points;
|
|
|
+ cur = pen.cur;
|
|
|
+ messages.value.push(cur);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ messages.value.push(cur);
|
|
|
+ }
|
|
|
+ return !pen?.unchanged;
|
|
|
+ };
|
|
|
+
|
|
|
+ const addItem = (cur: PayData) => {
|
|
|
+ if (!drawItems[0]) {
|
|
|
+ const data = obj.interactiveToData({
|
|
|
+ preset: ia.preset as any,
|
|
|
+ info: setMessage(cur),
|
|
|
+ viewTransform: viewTransform.value,
|
|
|
+ history,
|
|
|
+ store,
|
|
|
+ });
|
|
|
+ if (!data) {
|
|
|
+ drawItems.pop();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (initData?.id) {
|
|
|
+ data.id = initData?.id;
|
|
|
+ }
|
|
|
+ drawItems[0] = reactive(data);
|
|
|
+ }
|
|
|
+
|
|
|
+ let prevItemIds: string[] = [];
|
|
|
+ const storeAddItem = (cItem: LineData) => {
|
|
|
+ drawSnapInfos?.forEach(customSnapInfos.remove);
|
|
|
+ drawSnapInfos = getSnapInfos(cItem);
|
|
|
+ const ctx = getInitCtx();
|
|
|
+ cItem.points.forEach((p) => {
|
|
|
+ if (!prevItemIds.includes(p.id)) {
|
|
|
+ prevItemIds.push(p.id);
|
|
|
+ ctx.add.points[p.id] = p;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ cItem.lines.forEach((l) => {
|
|
|
+ if (!prevItemIds.includes(l.id)) {
|
|
|
+ prevItemIds.push(l.id);
|
|
|
+ ctx.add.lines[l.id] = l;
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ if (initData) {
|
|
|
+ cItem = {
|
|
|
+ ...cItem,
|
|
|
+ points: [...initData.points, ...cItem.points],
|
|
|
+ lines: [...initData.lines, ...cItem.lines],
|
|
|
+ polygon: [...initData.polygon, ...cItem.polygon],
|
|
|
+ };
|
|
|
+ } else {
|
|
|
+ cItem = {
|
|
|
+ ...cItem,
|
|
|
+ points: [...cItem.points],
|
|
|
+ lines: [...cItem.lines],
|
|
|
+ polygon: [...cItem.polygon],
|
|
|
+ };
|
|
|
+ }
|
|
|
+ cItem = normalLineData(cItem, ctx);
|
|
|
+ console.log(cItem);
|
|
|
+ drawSnapInfos.forEach(customSnapInfos.add);
|
|
|
+ if (drawItems[0] && store.getItemById(drawItems[0].id)) {
|
|
|
+ store.setItem(type, { id: cItem.id, value: cItem });
|
|
|
+ } else {
|
|
|
+ store.addItem(type, cItem);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ if (ia.singleDone.value) {
|
|
|
+ storeAddItem(drawItems[0]);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const update = () => {
|
|
|
+ const msg = setMessage(cur);
|
|
|
+ drawItems[0] = obj.interactiveFixData({
|
|
|
+ data: drawItems[0]!,
|
|
|
+ info: msg,
|
|
|
+ viewTransform: viewTransform.value,
|
|
|
+ history,
|
|
|
+ store,
|
|
|
+ });
|
|
|
+ isTempDraw = true;
|
|
|
+ };
|
|
|
+
|
|
|
+ stopWatch = mergeFuns(
|
|
|
+ watch(() => operMode.value.freeDraw, update),
|
|
|
+ watch(cur, update, { immediate: true, deep: true }),
|
|
|
+ watch(
|
|
|
+ messages,
|
|
|
+ () => {
|
|
|
+ if (!messages.value) return;
|
|
|
+ if (messages.value.length === 0) {
|
|
|
+ quitDrawShape();
|
|
|
+ } else {
|
|
|
+ update();
|
|
|
+ }
|
|
|
+ },
|
|
|
+ { deep: true }
|
|
|
+ ),
|
|
|
+ // 监听是否消费完毕
|
|
|
+ watch(ia.singleDone, () => {
|
|
|
+ prev = { ...cur, view: true };
|
|
|
+ const isChange = pushMessages(cur);
|
|
|
+ if (isChange) {
|
|
|
+ storeAddItem(copy(drawItems[0]));
|
|
|
+ }
|
|
|
+ beforeHandler.clear();
|
|
|
+ stopWatch && stopWatch();
|
|
|
+ stopWatch = null;
|
|
|
+ isTempDraw = false;
|
|
|
+ })
|
|
|
+ );
|
|
|
+ };
|
|
|
+
|
|
|
+ // 每次拽结束都加组件
|
|
|
+ watch(
|
|
|
+ () => ia.messages,
|
|
|
+ (datas: any) => {
|
|
|
+ datas.forEach(addItem);
|
|
|
+ ia.consume(datas);
|
|
|
+ },
|
|
|
+ { immediate: true }
|
|
|
+ );
|
|
|
+
|
|
|
+ return drawItems;
|
|
|
+};
|
|
|
+
|
|
|
+export type NLineDataCtx = {
|
|
|
+ del: {
|
|
|
+ points: Record<string, LineData["points"][0]>;
|
|
|
+ lines: Record<string, LineData["lines"][0]>;
|
|
|
+ };
|
|
|
+ add: {
|
|
|
+ points: Record<string, LineData["points"][0]>;
|
|
|
+ lines: Record<string, LineData["lines"][0]>;
|
|
|
+ };
|
|
|
+ update: {
|
|
|
+ points: Record<string, LineData["points"][0]>;
|
|
|
+ lines: Record<string, LineData["lines"][0]>;
|
|
|
+ };
|
|
|
+};
|
|
|
+export const getInitCtx = (): NLineDataCtx => ({
|
|
|
+ del: {
|
|
|
+ points: {},
|
|
|
+ lines: {},
|
|
|
+ },
|
|
|
+ add: {
|
|
|
+ points: {},
|
|
|
+ lines: {},
|
|
|
+ },
|
|
|
+ update: {
|
|
|
+ points: {},
|
|
|
+ lines: {},
|
|
|
+ },
|
|
|
+});
|
|
|
+
|
|
|
+export const repPointRef = (data: LineData, delId: string, repId: string) => {
|
|
|
+ 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 (line.b === delId) {
|
|
|
+ data.lines[i] = { ...line, b: repId };
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return data
|
|
|
+};
|
|
|
+
|
|
|
+export const deduplicateLines = (data: LineData) => {
|
|
|
+ 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 时也去重
|
|
|
+ const key1 = `${line.a},${line.b}`;
|
|
|
+ const key2 = `${line.b},${line.a}`;
|
|
|
+
|
|
|
+ // 检查是否已存在相同键
|
|
|
+ const existingKey = seen.has(key1) ? key1 : seen.has(key2) ? key2 : null;
|
|
|
+
|
|
|
+ if (existingKey) {
|
|
|
+ // 如果存在重复键,覆盖旧值(保留尾部元素)
|
|
|
+ seen.delete(existingKey);
|
|
|
+ seen.set(key1, line); // 统一存储为 key1 格式
|
|
|
+ isChange = true
|
|
|
+ } else {
|
|
|
+ // 新记录,直接存储
|
|
|
+ seen.set(key1, line);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (isChange) {
|
|
|
+ data.lines = Array.from(seen.values())
|
|
|
+ }
|
|
|
+
|
|
|
+ return data
|
|
|
+};
|
|
|
+
|
|
|
+export const normalLineData = (data: LineData, ctx: NLineDataCtx) => {
|
|
|
+ const changePoints = [
|
|
|
+ ...Object.values(ctx.add.points),
|
|
|
+ ...Object.values(ctx.update.points),
|
|
|
+ ];
|
|
|
+
|
|
|
+ for (const p2 of changePoints) {
|
|
|
+ const ndx = data.points.findIndex((item) => item.id === p2.id);
|
|
|
+ if (!~ndx) continue;
|
|
|
+
|
|
|
+ for (let i = 0; i < data.points.length; i++) {
|
|
|
+ const p1 = data.points[i];
|
|
|
+ if (p1.id !== p2.id && eqPoint(p1, p2)) {
|
|
|
+ repPointRef(data, p1.id, p2.id);
|
|
|
+ data.points.splice(i, 1);
|
|
|
+ i--;
|
|
|
+ console.log("p1 pre", p1, p2);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return deduplicateLines(data);
|
|
|
+};
|