use-dxf.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447
  1. import {
  2. DxfWriter,
  3. point2d,
  4. LWPolylineFlags,
  5. point3d,
  6. TrueColor,
  7. pattern,
  8. HatchPredefinedPatterns,
  9. HatchBoundaryPaths,
  10. HatchPolylineBoundary,
  11. vertex,
  12. } from "@tarikjabiri/dxf";
  13. import { useStore } from "../store";
  14. import Zip from "jszip";
  15. import { LineData } from "../components/line";
  16. import { CircleData } from "../components/circle";
  17. import { Transform } from "konva/lib/Util";
  18. import {
  19. lineLen,
  20. lineVector,
  21. Pos,
  22. Size,
  23. verticalVectorLine,
  24. zeroEq,
  25. } from "@/utils/math";
  26. import { RectangleData } from "../components/rectangle";
  27. import { useStage } from "./use-global-vars";
  28. import { Group } from "konva/lib/Group";
  29. import { Text } from "konva/lib/shapes/Text";
  30. import { TextData } from "../components/text";
  31. import { ImageData } from "../components/image";
  32. import { asyncTimeout, onlyId } from "@/utils/shared";
  33. import { ArrowData, PointerPosition } from "../components/arrow";
  34. import { IconData } from "../components/icon";
  35. import {
  36. useSetViewport,
  37. useViewer,
  38. useViewerInvertTransform,
  39. } from "./use-viewer";
  40. import { nextTick } from "vue";
  41. import { SLineData } from "../components/sequent-line";
  42. import { IRect } from "konva/lib/types";
  43. import { LineIconData } from "../components/line-icon";
  44. import { Line } from "konva/lib/shapes/Line";
  45. export const useGetDXF = () => {
  46. const store = useStore();
  47. const stage = useStage();
  48. const invMat = useViewerInvertTransform();
  49. const { setViewport } = useSetViewport();
  50. const { viewer } = useViewer();
  51. return async () => {
  52. const writer = new DxfWriter();
  53. const $stage = stage.value!.getNode();
  54. const zip = new Zip();
  55. const genPromises: Promise<any>[] = [];
  56. const fontStyle = writer.tables.styleTable.records[0];
  57. fontStyle.fontFileName = "SimSun";
  58. type PL = {
  59. id?: string;
  60. points: Pos[];
  61. fill?: string;
  62. content?: string;
  63. strokeWidth?: number;
  64. stroke?: string;
  65. };
  66. const writerPolyline = (pl: PL) => {
  67. if (pl.fill) {
  68. const polyline = new HatchPolylineBoundary();
  69. const boundary = new HatchBoundaryPaths();
  70. pl.points.forEach((p) => polyline.add(vertex(p.x, -p.y)));
  71. boundary.addPolylineBoundary(polyline);
  72. writer.addHatch(
  73. boundary,
  74. pattern({ name: HatchPredefinedPatterns.SOLID }),
  75. { trueColor: TrueColor.fromHex(pl.fill).toString() }
  76. );
  77. }
  78. writer.addLWPolyline(
  79. pl.points.map((p) => ({ point: point2d(p.x, -p.y) })),
  80. {
  81. flags: LWPolylineFlags.Closed,
  82. constantWidth: pl.strokeWidth,
  83. trueColor: TrueColor.fromHex(pl.stroke || "#FFFFFF").toString(),
  84. }
  85. );
  86. if (!pl.content) return;
  87. const $text = $stage.findOne<Group>(`#${pl.id}`)?.findOne<Text>(".text");
  88. if ($text) {
  89. writeText($text);
  90. }
  91. };
  92. const writeText = ($text: Text, sp = true) => {
  93. const mat = $text.getTransform();
  94. const fontSize = $text.fontSize() * 0.8;
  95. let text = $text.text();
  96. text = text.replace(/(\n|\r|\t)/gi, "");
  97. const pad = $text.padding();
  98. const align = $text.align();
  99. const textArr: { content: string; charCount: number }[] = [];
  100. let lineNum = 1;
  101. const width = $text.width();
  102. const charWidth = fontSize * 0.9;
  103. const lineCharCount = sp
  104. ? Math.max(Math.floor(width / charWidth), 2)
  105. : Number.MAX_VALUE;
  106. let ndx = 0;
  107. let prevNdx = 0;
  108. let charCount = 0;
  109. while (ndx < text.length) {
  110. ndx++;
  111. const c = /[\u4e00-\u9fff]/.test(text.charAt(ndx)) ? 2.4 : 1;
  112. if (charCount === lineCharCount || ndx >= text.length) {
  113. charCount += c;
  114. textArr.push({
  115. content: text.substring(prevNdx, ndx + 1),
  116. charCount,
  117. });
  118. charCount = 0;
  119. prevNdx = ndx;
  120. } else if (charCount > lineCharCount) {
  121. textArr.push({
  122. content: text.substring(prevNdx, ndx),
  123. charCount,
  124. });
  125. charCount = 0;
  126. ndx--;
  127. prevNdx = ndx;
  128. } else {
  129. charCount += c;
  130. }
  131. }
  132. textArr.forEach((item) => {
  133. const lineWidth = charWidth * item.charCount;
  134. let p = { x: pad, y: pad + lineNum * fontSize * 1.2 };
  135. if (align === "center") {
  136. p.x = (width - lineWidth) / 2;
  137. } else if (align === "right") {
  138. p.x = width - lineWidth;
  139. }
  140. const start = mat.point(p);
  141. const text = writer.addText(
  142. point3d(start.x, -start.y),
  143. fontSize,
  144. item.content,
  145. {
  146. rotation: $text.rotation(),
  147. // horizontalAlignment:
  148. // align === "center"
  149. // ? TextHorizontalAlignment.Center
  150. // : align === "right"
  151. // ? TextHorizontalAlignment.Right
  152. // : TextHorizontalAlignment.Left,
  153. }
  154. );
  155. text.trueColor = TrueColor.fromHex($text.fill() as string).toString();
  156. text.height = fontSize;
  157. lineNum++;
  158. });
  159. };
  160. const writeImage = async (
  161. imgGroup: Group,
  162. scaleCallback?: (scale: Size, box: IRect) => () => void
  163. ) => {
  164. let curRect = imgGroup.getClientRect();
  165. const oldViewMat = viewer.viewMat;
  166. setViewport(curRect);
  167. await nextTick();
  168. const imgRect = imgGroup.getClientRect();
  169. const back =
  170. scaleCallback &&
  171. scaleCallback(
  172. {
  173. width: imgRect.width / curRect.width,
  174. height: imgRect.height / curRect.height,
  175. },
  176. imgRect
  177. );
  178. await nextTick();
  179. const img = (await imgGroup!
  180. .toImage({
  181. pixelRatio: 1,
  182. quality: 1,
  183. mimeType: "image/png",
  184. })
  185. .catch((e) => {
  186. console.error(e);
  187. throw e;
  188. })) as HTMLImageElement;
  189. back && back();
  190. await nextTick();
  191. const start = invMat.value.point({
  192. x: imgRect.x,
  193. y: imgRect.y + imgRect.height,
  194. });
  195. const end = invMat.value.point({
  196. x: imgRect.x + imgRect.width,
  197. y: imgRect.y,
  198. });
  199. const name = onlyId().replace(/\-/g, "");
  200. const path = name + ".png";
  201. const image = writer.addImage(
  202. path,
  203. name,
  204. point3d(start.x, -start.y),
  205. img.width,
  206. img.height,
  207. 1,
  208. 0
  209. );
  210. image.ratio = Math.abs(end.x - start.x) / img.width;
  211. genPromises.push(
  212. fetch(img.src)
  213. .then((res) => res.blob())
  214. .then((blob) => zip.file(path, blob, {}))
  215. .catch((e) => {
  216. console.error(e);
  217. })
  218. );
  219. viewer.setViewMat(oldViewMat);
  220. };
  221. const initViewOperate = async (operate: () => void) => {
  222. const oldViewMat = viewer.viewMat;
  223. viewer.setViewMat(new Transform());
  224. await asyncTimeout(50);
  225. operate();
  226. viewer.setViewMat(oldViewMat);
  227. await asyncTimeout(50);
  228. };
  229. for (const _item of store.sortItems) {
  230. if (_item.hide) continue;
  231. const type = store.getType(_item.id);
  232. let item;
  233. let mat;
  234. switch (type) {
  235. case "sequentLine":
  236. item = _item as SLineData;
  237. writer.addLWPolyline(
  238. item.points.map((p) => ({ point: point2d(p.x, -p.y) })),
  239. {
  240. flags: LWPolylineFlags.None,
  241. constantWidth: item.strokeWidth,
  242. trueColor: TrueColor.fromHex(item.stroke || "#FFFFFF").toString(),
  243. }
  244. );
  245. break;
  246. case "lineChunk":
  247. case "line":
  248. const litem = _item as LineData;
  249. await initViewOperate(() => {
  250. litem.lines.forEach((line) => {
  251. const $lines = stage
  252. .value!.getNode()
  253. .find<Line>(`.${line.id}-view`)!;
  254. for (let i = 0; i < $lines.length; i++) {
  255. const flatPoints = $lines[i].points();
  256. const points: Pos[] = [];
  257. for (let i = 0; i < flatPoints.length; i += 2) {
  258. points.push({ x: flatPoints[i], y: flatPoints[i + 1] });
  259. }
  260. const data: PL = {
  261. id: litem.id,
  262. points,
  263. // stroke: '#000000',
  264. // fill: line.stroke,
  265. stroke: line.stroke,
  266. strokeWidth: 1,
  267. };
  268. writerPolyline(data);
  269. }
  270. });
  271. });
  272. // litem.lines.forEach((line) => {
  273. // const a = litem.points.find((p) => p.id === line.a)!;
  274. // const b = litem.points.find((p) => p.id === line.b)!;
  275. // writer.addLWPolyline(
  276. // [
  277. // { point: point3d(a.x, -a.y, 0) },
  278. // { point: point3d(b.x, -b.y, 0) },
  279. // ],
  280. // {
  281. // flags: LWPolylineFlags.None,
  282. // constantWidth: line.strokeWidth,
  283. // trueColor: TrueColor.fromHex(
  284. // line.stroke || "#FFFFFF"
  285. // ).toString(),
  286. // }
  287. // );
  288. // });
  289. break;
  290. case "triangle":
  291. case "rectangle":
  292. case "polygon":
  293. item = _item as RectangleData;
  294. writerPolyline(item);
  295. break;
  296. case "circle":
  297. item = _item as CircleData;
  298. mat = new Transform(item.mat);
  299. const points = [];
  300. for (let angle = 0; angle <= 360; angle += 1) {
  301. const rad = angle * (Math.PI / 180);
  302. const x = item.radiusX * Math.cos(rad);
  303. const y = item.radiusY * Math.sin(rad);
  304. points.push(mat.point({ x, y }));
  305. }
  306. writerPolyline({ ...item, points });
  307. break;
  308. case "text":
  309. item = _item as TextData;
  310. writeText($stage.findOne<Text>(`#${item.id}`)!, !!item.width);
  311. break;
  312. case "arrow":
  313. item = _item as ArrowData;
  314. if (zeroEq(lineLen(item.points[0], item.points[1]))) {
  315. item = {
  316. ...item,
  317. points: [...item.points],
  318. };
  319. item.points[0] = {
  320. ...item.points[0],
  321. x: item.points[1].x - (item.pointerLength || 1),
  322. };
  323. }
  324. const isEnd = [PointerPosition.end, PointerPosition.all].includes(
  325. item.pointerPosition || PointerPosition.start
  326. );
  327. const isStart = [PointerPosition.start, PointerPosition.all].includes(
  328. item.pointerPosition || PointerPosition.start
  329. );
  330. for (let i = 0; i < item.points.length - 1; i++) {
  331. const line = [item.points[i], item.points[i + 1]];
  332. const nline = [...line];
  333. const vector = lineVector(line);
  334. if (isStart) {
  335. const start = vector
  336. .clone()
  337. .multiplyScalar(item.pointerLength! * 2)
  338. .add(line[0]);
  339. nline[0] = start;
  340. const l1 = verticalVectorLine(vector, start, item.pointerLength!);
  341. const l2 = verticalVectorLine(
  342. vector,
  343. start,
  344. -item.pointerLength!
  345. );
  346. writerPolyline({
  347. points: [line[0], l1[1], l2[1]],
  348. fill: item.fill,
  349. stroke: item.fill,
  350. });
  351. }
  352. if (isEnd) {
  353. const start = vector
  354. .clone()
  355. .multiplyScalar(-item.pointerLength! * 2)
  356. .add(line[1]);
  357. nline[1] = start;
  358. const l1 = verticalVectorLine(vector, start, item.pointerLength!);
  359. const l2 = verticalVectorLine(
  360. vector,
  361. start,
  362. -item.pointerLength!
  363. );
  364. writerPolyline({
  365. points: [line[1], l1[1], l2[1]],
  366. fill: item.fill,
  367. stroke: item.fill,
  368. });
  369. }
  370. writer.addLWPolyline(
  371. nline.map((p) => ({ point: point2d(p.x, -p.y) })),
  372. {
  373. flags: LWPolylineFlags.None,
  374. constantWidth: item.strokeWidth,
  375. trueColor: TrueColor.fromHex(item.fill || "#FFFFFF").toString(),
  376. }
  377. );
  378. }
  379. break;
  380. case "image":
  381. item = _item as ImageData;
  382. await writeImage($stage.findOne<Group>(`#${item.id}`)!);
  383. break;
  384. case "icon":
  385. const iconItem = _item as IconData;
  386. const pathGroup = $stage
  387. .findOne<Group>(`#${iconItem.id}`)!
  388. .findOne<Group>(".rep-position")!;
  389. await writeImage(pathGroup, () => {
  390. iconItem.strokeScaleEnabled = true;
  391. return () => {
  392. iconItem.strokeScaleEnabled = false;
  393. };
  394. });
  395. break;
  396. case "lineIcon":
  397. const lineIconItem = _item as LineIconData;
  398. const linePathGroup = $stage
  399. .findOne<Group>(`#${lineIconItem.id}`)!
  400. .findOne<Group>(".rep-position")!;
  401. await writeImage(linePathGroup, () => {
  402. lineIconItem.strokeScaleEnabled = true;
  403. return () => {
  404. lineIconItem.strokeScaleEnabled = false;
  405. };
  406. });
  407. break;
  408. }
  409. }
  410. let dxfString = writer.stringify();
  411. zip.file(onlyId() + ".dxf", dxfString);
  412. return Promise.all(genPromises).then(() =>
  413. zip.generateAsync({ type: "blob" })
  414. );
  415. };
  416. };