use-dxf.ts 12 KB

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