use-draw.ts 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397
  1. import {
  2. MessageAction,
  3. penUpdatePoints,
  4. useDrawRunning,
  5. useInteractiveDrawShapeAPI,
  6. usePointBeforeHandler,
  7. } from "@/core/hook/use-draw";
  8. import { components, ComponentSnapInfo, SnapPoint } from "..";
  9. import { useHistory, useHistoryAttach } from "@/core/hook/use-history";
  10. import { useStore } from "@/core/store";
  11. import { useViewerTransform } from "@/core/hook/use-viewer";
  12. import { useOperMode } from "@/core/hook/use-status";
  13. import { installGlobalVar, useCursor } from "@/core/hook/use-global-vars";
  14. import { useInteractiveDots } from "@/core/hook/use-interactive";
  15. import { computed, reactive, ref, watch } from "vue";
  16. import { copy, mergeFuns } from "@/utils/shared";
  17. import { eqPoint, Pos } from "@/utils/math";
  18. import { getSnapInfos, type LineData } from "./";
  19. import { useCustomSnapInfos } from "@/core/hook/use-snap";
  20. type PayData = Pos;
  21. export let initData: LineData | undefined;
  22. export const useInitData = installGlobalVar(() => ref<LineData>());
  23. // 单例钢笔添加
  24. export const useDraw = () => {
  25. const type = "line";
  26. const { quitDrawShape } = useInteractiveDrawShapeAPI();
  27. const isRuning = useDrawRunning(type);
  28. const obj = components[type];
  29. const beforeHandler = usePointBeforeHandler(true, true);
  30. const history = useHistory();
  31. const store = useStore();
  32. const viewTransform = useViewerTransform();
  33. const operMode = useOperMode();
  34. const hInitData = useInitData();
  35. const customSnapInfos = useCustomSnapInfos();
  36. // 可能历史空间会撤销 重做更改到正在绘制的组件
  37. const currentCursor = ref("./icons/m_add.png");
  38. const cursor = useCursor();
  39. let cursorPop: ReturnType<typeof cursor.push> | null = null;
  40. let stopWatch: (() => void) | null = null;
  41. let prev: SnapPoint;
  42. let currentIsDel = false;
  43. let isTempDraw = false;
  44. let snapInfos: ComponentSnapInfo[] | null = null;
  45. let drawSnapInfos: ComponentSnapInfo[] | null = null;
  46. const ia = useInteractiveDots({
  47. shapeType: type,
  48. isRuning,
  49. enter() {
  50. cursorPop = cursor.push(currentCursor.value);
  51. watch(currentCursor, () => {
  52. cursorPop?.set(currentCursor.value);
  53. });
  54. },
  55. quit: () => {
  56. quitDrawShape();
  57. beforeHandler.clear();
  58. cursorPop && cursorPop();
  59. stopWatch && stopWatch();
  60. if (!drawItems[0]) return;
  61. if (isTempDraw) {
  62. console.log(drawItems[0].points)
  63. drawItems[0].lines.pop();
  64. drawItems[0].points.pop();
  65. drawItems[0].polygon.pop();
  66. }
  67. const lastP = drawItems[0].points[drawItems[0].points.length - 1]
  68. if (lastP) {
  69. console.log(lastP)
  70. const ctx = getInitCtx()
  71. ctx.add.points[lastP.id] = lastP
  72. normalLineData(drawItems[0], ctx)
  73. }
  74. snapInfos?.forEach(customSnapInfos.remove);
  75. drawSnapInfos?.forEach(customSnapInfos.remove);
  76. initData = hInitData.value = void 0;
  77. },
  78. beforeHandler: (p) => {
  79. beforeHandler.clear();
  80. const pa = beforeHandler.transform(p, prev && [prev, p]);
  81. currentIsDel && beforeHandler.clear();
  82. return pa;
  83. },
  84. });
  85. const shapeId = computed(
  86. () => ia.isRunning.value && store.getTypeItems(type)[0]?.id
  87. );
  88. const drawItems = reactive([]) as LineData[];
  89. watch(
  90. shapeId,
  91. () => {
  92. const data = shapeId.value
  93. ? (store.getItemById(shapeId.value) as LineData)
  94. : undefined;
  95. if (data) {
  96. initData = hInitData.value = {
  97. ...data,
  98. points: [...data.points],
  99. lines: [...data.lines],
  100. polygon: [...data.polygon],
  101. };
  102. drawItems[0] = {
  103. ...data,
  104. createTime: initData.createTime + 1,
  105. points: [],
  106. lines: [],
  107. polygon: [],
  108. };
  109. snapInfos = getSnapInfos(initData);
  110. snapInfos.forEach(customSnapInfos.add);
  111. } else {
  112. drawItems.pop();
  113. initData = hInitData.value = undefined;
  114. }
  115. },
  116. { immediate: true }
  117. );
  118. const messages = useHistoryAttach<Pos[]>(
  119. `${type}-pen`,
  120. isRuning,
  121. () => [],
  122. true
  123. );
  124. const getAddMessage = (cur: Pos) => {
  125. let consumed = messages.value;
  126. currentCursor.value = "./icons/m_add.png";
  127. let pen: null | ReturnType<typeof penUpdatePoints> = null;
  128. return {
  129. pen,
  130. consumed,
  131. cur,
  132. action: MessageAction.add,
  133. } as any;
  134. };
  135. const setMessage = (cur: Pos) => {
  136. const { pen, ...msg } = getAddMessage(cur);
  137. if ((currentIsDel = pen?.oper === "del")) {
  138. currentCursor.value = "./icons/m_reduce.png";
  139. beforeHandler.clear();
  140. }
  141. return msg;
  142. };
  143. const pushMessages = (cur: Pos) => {
  144. const { pen } = getAddMessage(cur);
  145. if (pen) {
  146. if (!pen.unchanged) {
  147. messages.value = pen.points;
  148. cur = pen.cur;
  149. messages.value.push(cur);
  150. }
  151. } else {
  152. messages.value.push(cur);
  153. }
  154. return !pen?.unchanged;
  155. };
  156. const addItem = (cur: PayData) => {
  157. if (!drawItems[0]) {
  158. const data = obj.interactiveToData({
  159. preset: ia.preset as any,
  160. info: setMessage(cur),
  161. viewTransform: viewTransform.value,
  162. history,
  163. store,
  164. });
  165. if (!data) {
  166. drawItems.pop();
  167. return;
  168. }
  169. if (initData?.id) {
  170. data.id = initData?.id;
  171. }
  172. drawItems[0] = reactive(data);
  173. }
  174. let prevItemIds: string[] = [];
  175. const storeAddItem = (cItem: LineData) => {
  176. drawSnapInfos?.forEach(customSnapInfos.remove);
  177. drawSnapInfos = getSnapInfos(cItem);
  178. const ctx = getInitCtx();
  179. cItem.points.forEach((p) => {
  180. if (!prevItemIds.includes(p.id)) {
  181. prevItemIds.push(p.id);
  182. ctx.add.points[p.id] = p;
  183. }
  184. });
  185. cItem.lines.forEach((l) => {
  186. if (!prevItemIds.includes(l.id)) {
  187. prevItemIds.push(l.id);
  188. ctx.add.lines[l.id] = l;
  189. }
  190. });
  191. if (initData) {
  192. cItem = {
  193. ...cItem,
  194. points: [...initData.points, ...cItem.points],
  195. lines: [...initData.lines, ...cItem.lines],
  196. polygon: [...initData.polygon, ...cItem.polygon],
  197. };
  198. } else {
  199. cItem = {
  200. ...cItem,
  201. points: [...cItem.points],
  202. lines: [...cItem.lines],
  203. polygon: [...cItem.polygon],
  204. };
  205. }
  206. cItem = normalLineData(cItem, ctx);
  207. drawSnapInfos.forEach(customSnapInfos.add);
  208. if (drawItems[0] && store.getItemById(drawItems[0].id)) {
  209. store.setItem(type, { id: cItem.id, value: cItem });
  210. } else {
  211. store.addItem(type, cItem);
  212. }
  213. };
  214. if (ia.singleDone.value) {
  215. storeAddItem(drawItems[0]);
  216. return;
  217. }
  218. const update = () => {
  219. const msg = setMessage(cur);
  220. drawItems[0] = obj.interactiveFixData({
  221. data: drawItems[0]!,
  222. info: msg,
  223. viewTransform: viewTransform.value,
  224. history,
  225. store,
  226. });
  227. isTempDraw = true;
  228. };
  229. stopWatch = mergeFuns(
  230. watch(() => operMode.value.freeDraw, update),
  231. watch(cur, update, { immediate: true, deep: true }),
  232. watch(
  233. messages,
  234. () => {
  235. if (!messages.value) return;
  236. if (messages.value.length === 0) {
  237. quitDrawShape();
  238. } else {
  239. update();
  240. }
  241. },
  242. { deep: true }
  243. ),
  244. // 监听是否消费完毕
  245. watch(ia.singleDone, () => {
  246. prev = { ...cur, view: true };
  247. const isChange = pushMessages(cur);
  248. if (isChange) {
  249. storeAddItem(copy(drawItems[0]));
  250. }
  251. beforeHandler.clear();
  252. stopWatch && stopWatch();
  253. stopWatch = null;
  254. isTempDraw = false;
  255. })
  256. );
  257. };
  258. // 每次拽结束都加组件
  259. watch(
  260. () => ia.messages,
  261. (datas: any) => {
  262. datas.forEach(addItem);
  263. ia.consume(datas);
  264. },
  265. { immediate: true }
  266. );
  267. return drawItems;
  268. };
  269. export type NLineDataCtx = {
  270. del: {
  271. points: Record<string, LineData["points"][0]>;
  272. lines: Record<string, LineData["lines"][0]>;
  273. };
  274. add: {
  275. points: Record<string, LineData["points"][0]>;
  276. lines: Record<string, LineData["lines"][0]>;
  277. };
  278. update: {
  279. points: Record<string, LineData["points"][0]>;
  280. lines: Record<string, LineData["lines"][0]>;
  281. };
  282. };
  283. export const getInitCtx = (): NLineDataCtx => ({
  284. del: {
  285. points: {},
  286. lines: {},
  287. },
  288. add: {
  289. points: {},
  290. lines: {},
  291. },
  292. update: {
  293. points: {},
  294. lines: {},
  295. },
  296. });
  297. export const repPointRef = (data: LineData, delId: string, repId: string) => {
  298. for (let i = 0; i < data.lines.length; i++) {
  299. const line = data.lines[i];
  300. if (line.a === delId) {
  301. data.lines[i] = { ...line, a: repId };
  302. }
  303. if (line.b === delId) {
  304. data.lines[i] = { ...line, b: repId };
  305. }
  306. }
  307. return data
  308. };
  309. export const deduplicateLines = (data: LineData) => {
  310. const seen = new Map<string, LineData['lines'][0]>();
  311. let isChange = false
  312. for (const line of data.lines) {
  313. if (line.a === line.b) continue;
  314. // 生成标准化键:确保 (a,b) 和 (b,a) 被视为相同,并且 a === b 时也去重
  315. const key1 = `${line.a},${line.b}`;
  316. const key2 = `${line.b},${line.a}`;
  317. // 检查是否已存在相同键
  318. const existingKey = seen.has(key1) ? key1 : seen.has(key2) ? key2 : null;
  319. if (existingKey) {
  320. // 如果存在重复键,覆盖旧值(保留尾部元素)
  321. seen.delete(existingKey);
  322. seen.set(key1, line); // 统一存储为 key1 格式
  323. isChange = true
  324. } else {
  325. // 新记录,直接存储
  326. seen.set(key1, line);
  327. }
  328. }
  329. if (isChange) {
  330. data.lines = Array.from(seen.values())
  331. }
  332. return data
  333. };
  334. export const normalLineData = (data: LineData, ctx: NLineDataCtx) => {
  335. const changePoints = [
  336. ...Object.values(ctx.add.points),
  337. ...Object.values(ctx.update.points),
  338. ];
  339. for (const p2 of changePoints) {
  340. const ndx = data.points.findIndex((item) => item.id === p2.id);
  341. if (!~ndx) continue;
  342. for (let i = 0; i < data.points.length; i++) {
  343. const p1 = data.points[i];
  344. if (p1.id !== p2.id && eqPoint(p1, p2)) {
  345. repPointRef(data, p1.id, p2.id);
  346. data.points.splice(i, 1);
  347. i--;
  348. }
  349. }
  350. }
  351. const pointIds = Object.values(ctx.del.lines).flatMap(item => [item.a, item.b])
  352. pointIds.push(...Object.keys(ctx.add.points))
  353. const linePointIds = data.lines.flatMap(item => [item.a, item.b])
  354. for (let id of pointIds) {
  355. if (!linePointIds.includes(id)) {
  356. const ndx = data.points.findIndex(p => p.id === id)
  357. ~ndx && data.points.splice(ndx, 1)
  358. }
  359. }
  360. return deduplicateLines(data);
  361. };