platform-draw.ts 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. import { genBound, onlyId } from "@/utils/shared";
  2. import { Draw } from "../components/container/use-draw";
  3. import { SceneResource, getResource } from "./platform-resource";
  4. import { getBaseItem } from "@/core/components/util";
  5. import { LineData, defaultStyle } from "@/core/components/line";
  6. import {
  7. getInitCtx,
  8. normalLineData,
  9. } from "@/core/components/line/attach-server";
  10. import { getIconStyle, IconData } from "@/core/components/icon";
  11. import { defaultStyle as textDefaultStyle } from "@/core/components/text";
  12. import { Transform } from "konva/lib/Util";
  13. import { ElMessage } from "element-plus";
  14. import { ImageData } from "@/core/components/image";
  15. import { Pos, Size } from "@/utils/math";
  16. import { watchEffect } from "vue";
  17. import { TextData } from "@/core/components/text";
  18. import { SelectSceneData } from "../dialog/ai";
  19. const getSizePlaceBoxImage = ({ width, height }: Size) => {
  20. const borderWidth = 10;
  21. const canvas = document.createElement("canvas");
  22. canvas.width = width;
  23. canvas.height = height;
  24. const ctx = canvas.getContext("2d")!;
  25. // 画背景白色
  26. ctx.fillStyle = "#fff";
  27. ctx.fillRect(0, 0, width, height);
  28. // 画边框
  29. ctx.lineWidth = borderWidth;
  30. ctx.strokeStyle = "#000"; // 黑色边框
  31. // 注意strokeRect位置和尺寸要配合线宽,这里我们让边框完整显示在canvas内
  32. ctx.strokeRect(
  33. borderWidth / 2,
  34. borderWidth / 2,
  35. width - borderWidth,
  36. height - borderWidth
  37. );
  38. return canvas.toDataURL(); // 返回图片的Data URL字符串
  39. };
  40. const getCoverShapes = (cover: SceneResource["cover"]) => {
  41. let geo: LineData = {
  42. ...getBaseItem(),
  43. lines: [],
  44. points: [],
  45. polygon: [],
  46. zIndex: -1,
  47. createTime: Date.now(),
  48. };
  49. if (!cover) {
  50. return { geo };
  51. }
  52. const geoCtx = getInitCtx();
  53. cover.geos.forEach((item) => {
  54. let a: string = onlyId(),
  55. b: string;
  56. let i = 0;
  57. for (i = 0; i < item.length - 1; i++) {
  58. b = onlyId();
  59. const lId = onlyId();
  60. const l = { id: onlyId(), a, b, ...defaultStyle };
  61. const p = { id: a, ...item[i] };
  62. geoCtx.add.points[a] = p;
  63. geoCtx.add.lines[lId] = l;
  64. geo.points.push(p);
  65. geo.lines.push(l);
  66. a = b;
  67. }
  68. const p = { id: b!, ...item[i] };
  69. geoCtx.add.points[b!] = p;
  70. geo.points.push(p);
  71. });
  72. geo = normalLineData(geo, geoCtx);
  73. if (!cover.thumb) return { geo };
  74. const width = cover.bound.x_max - cover.bound.x_min;
  75. const height = cover.bound.y_max - cover.bound.y_min;
  76. const mat = new Transform().translate(
  77. cover.bound.x_min + width / 2,
  78. cover.bound.y_min + height / 2
  79. );
  80. const thumb: ImageData = {
  81. ...getBaseItem(),
  82. createTime: geo.createTime,
  83. url: cover.thumb,
  84. mat: mat.m,
  85. width,
  86. height,
  87. cornerRadius: 0,
  88. zIndex: -2,
  89. };
  90. return { geo, thumb };
  91. };
  92. const getTaggingShapes = async (taggings: SceneResource["taggings"]) => {
  93. const icons: IconData[] = [];
  94. const images: ImageData[] = [];
  95. const texts: TextData[] = [];
  96. const now = Date.now();
  97. const reqs: Promise<any>[] = [];
  98. for (let ndx = 0; ndx < taggings.length; ndx++) {
  99. const item = taggings[ndx];
  100. const mat = new Transform(item.mat);
  101. if (!item.mat && item.position) {
  102. mat.translate(item.position.x, item.position.y);
  103. item.rotate && mat.rotate(item.rotate);
  104. }
  105. if (item.isText) {
  106. texts.push({
  107. ...getBaseItem(),
  108. ...textDefaultStyle,
  109. content: item.url,
  110. zIndex: 1,
  111. mat: mat.m,
  112. });
  113. continue;
  114. }
  115. const shape = {
  116. ...getBaseItem(),
  117. ...(item.size || { width: 100, height: 100 }),
  118. name: item.name,
  119. createTime: now + ndx,
  120. url: item.url,
  121. mat: mat.m,
  122. zIndex: 1,
  123. };
  124. if (!item.url.includes(".svg")) {
  125. images.push(shape);
  126. continue;
  127. }
  128. reqs.push(
  129. getIconStyle(item.url, shape.width, shape.height, item.fixed)
  130. .then((style) => {
  131. icons.push({ ...shape, ...style });
  132. })
  133. .catch(() => {})
  134. );
  135. }
  136. await Promise.all(reqs)
  137. return {
  138. texts,
  139. images,
  140. icons,
  141. };
  142. };
  143. const getDrawResourceOffset = (
  144. draw: Draw,
  145. bound: ReturnType<typeof genBound>,
  146. thumb?: ImageData
  147. ) =>
  148. new Promise<Pos>((resolve, reject) => {
  149. ElMessage.warning("请在画图面板中选择放置位置,按右键取消");
  150. const data = thumb ? thumb : { url: getSizePlaceBoxImage(bound.get()!) };
  151. const key = "place-plaform";
  152. draw.enterDrawShape("image", { ...data, key }, true);
  153. const stopWatch = watchEffect(() => {
  154. if (draw.drawing) return;
  155. stopWatch();
  156. const item = draw.store.items.find(
  157. (item) => item.key === key
  158. ) as ImageData;
  159. if (!item) {
  160. reject("用户已取消");
  161. } else {
  162. draw.store.delItem("image", item.id);
  163. resolve({
  164. x: item.mat[4],
  165. y: item.mat[5],
  166. });
  167. }
  168. });
  169. });
  170. const drawSceneResource = async (resource: SceneResource, draw: Draw) => {
  171. const { geo, thumb } = getCoverShapes(resource.cover);
  172. const geoKey = "scene-geo-resource";
  173. let offset = { x: 0, y: 0 };
  174. let oldGeo = draw.store.getTypeItems("line")[0];
  175. if (oldGeo?.itemName === geoKey) {
  176. const bound = genBound();
  177. geo.points.forEach((p) => bound.update(p));
  178. offset = await getDrawResourceOffset(draw, bound, thumb);
  179. } else if (oldGeo) {
  180. oldGeo.itemName = geoKey;
  181. } else {
  182. geo.itemName = geoKey;
  183. }
  184. const bound = genBound();
  185. geo.points.forEach((p) => {
  186. p.x += offset.x;
  187. p.y += offset.y;
  188. bound.update(p);
  189. });
  190. const { icons, images, texts } = await getTaggingShapes(resource.taggings);
  191. const tagShapes = [...icons, ...images, ...texts, thumb];
  192. tagShapes.forEach((shape) => {
  193. if (shape) {
  194. shape.mat[4] += offset.x;
  195. shape.mat[5] += offset.y;
  196. bound.update({ x: shape.mat[4], y: shape.mat[5] });
  197. }
  198. });
  199. if (oldGeo) {
  200. oldGeo.points = oldGeo.points.concat(geo.points);
  201. oldGeo.lines = oldGeo.lines.concat(geo.lines);
  202. oldGeo.polygon = oldGeo.polygon.concat(geo.polygon);
  203. draw.store.setItem("line", { id: geo.id, value: geo });
  204. } else {
  205. draw.store.addItem("line", geo);
  206. }
  207. console.log(icons, texts, images)
  208. draw.store.addItems("icon", icons);
  209. draw.store.addItems("text", texts);
  210. draw.store.addItems("image", images);
  211. if (thumb) {
  212. draw.store.addItem("image", thumb);
  213. }
  214. return bound.get()!;
  215. };
  216. export const drawPlatformResource = async (
  217. sceneData: SelectSceneData,
  218. draw: Draw
  219. ) => {
  220. // 默认为米,转为厘米
  221. const resource = await getResource({ ...sceneData, scale: 100 });
  222. let bound = null as ReturnType<ReturnType<typeof genBound>["get"]>;
  223. console.log(resource)
  224. await draw.history.onceTrack(async () => {
  225. draw.store.setConfig({ proportion: { scale: 10, unit: "mm" } });
  226. bound = await drawSceneResource(resource, draw);
  227. if (typeof resource.compass === "number") {
  228. draw.store.setConfig({
  229. compass: {
  230. rotation: resource.compass,
  231. url: draw.store.config.compass.url,
  232. },
  233. });
  234. }
  235. });
  236. if (!draw.viewer.size || !bound) return;
  237. const size = draw.viewer.size;
  238. if (bound.width < 10 || bound.height < 10) {
  239. draw.viewer.setViewMat([
  240. 1,
  241. 0,
  242. 0,
  243. 1,
  244. bound.center.x + size.width / 2,
  245. bound.center.y + size.height / 2,
  246. ]);
  247. } else {
  248. const viewWidth = Math.max(bound.width, size.width);
  249. const viewHeight = Math.max(bound.height, size.height);
  250. const padding = Math.max(
  251. Math.min((viewWidth - bound.width) / 2, (viewHeight - bound.height) / 2),
  252. 40
  253. );
  254. draw.viewer.setBound({ targetBound: bound, padding });
  255. }
  256. };