DxfDocWriter.java 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405
  1. package com.fdkankan.dxf.generate;
  2. import com.fdkankan.dxf.generate.model.*;
  3. import com.fdkankan.dxf.generate.model.base.BaseDxfEntity;
  4. import com.fdkankan.dxf.generate.model.base.DxfEntity;
  5. import com.fdkankan.dxf.generate.model.support.DxfExtent;
  6. import com.fdkankan.dxf.generate.util.DxfUtil;
  7. import com.fdkankan.dxf.generate.util.FreemarkerSupport;
  8. import com.fdkankan.dxf.generate.util.StreamUtil;
  9. import com.fdkankan.dxf.generate.util.StringUtil;
  10. import java.io.*;
  11. import java.nio.charset.Charset;
  12. import java.nio.charset.StandardCharsets;
  13. import java.util.*;
  14. /**
  15. * Dxf处理类,支持向矿图中添加自定义图形,目前支持的图形有圆形Circle,圆弧Arc,直线Line,多线段LwPolyLine,文字Text 目前仅仅支持对圆形Circle和其子类Arc的颜色填充。 可以通过调用 {@link #addEntity(DxfEntity)} 和 {@link #removeEntity(DxfEntity)} 进行自定义图元的添加和删除。
  16. * <p>
  17. * 并支持对新dxf文件的导出,可以选择调用函数 {@link #save(String, boolean)} 导出所有文件,这样的文件可以在AutoCad中打开 也可以通过函数 {@link #saveEntities(String, boolean)} 仅仅导出entity图元信息,这样的文件仅仅支持使用解析库进行解析
  18. * <p>
  19. * 另外和文件流一样,dxfDocument对象也需要在使用结束后关闭,通过调用 {@link #close()} 函数进行关闭,可以以想流式对象那样使用,如 <blockquote>
  20. *
  21. * <pre>
  22. * try(DxfDocument dxf = new DxfDocument(path)) {
  23. * body
  24. * }
  25. * </pre>
  26. *
  27. * </blockquote>
  28. *
  29. * @author YTZJJ
  30. */
  31. @SuppressWarnings("unused")
  32. public class DxfDocWriter implements Closeable {
  33. /**
  34. * 当选择保存为dxf文件的时候,要保留的Entities类型的数组,不在数组中的对象将会被忽略掉(不包含自定义加入的图元)
  35. */
  36. public static final String[] DEFAULT_ENTITY_NO_REDUCE_PART = {"LINE", "CIRCLE", "ARC", "TEXT", "MTEXT", "LWPOLYLINE"};
  37. private List<String> entityNoReducePart = Arrays.asList(DEFAULT_ENTITY_NO_REDUCE_PART);
  38. private List<DxfEntity> newDxfEntityList;
  39. private long maxMeta;
  40. private Charset charset;
  41. private DxfExtent extent;
  42. private BufferedReader br;
  43. private File dxfFile;
  44. /**
  45. * 加载dxf文件,默认的文件加载编码为UFT8
  46. *
  47. * @param dxfFilePath dxf文件位置
  48. */
  49. public DxfDocWriter(String dxfFilePath, DxfExtent extent) {
  50. this(dxfFilePath, extent, StandardCharsets.UTF_8);
  51. }
  52. /**
  53. * 以指定编码格式加载dxf文件
  54. *
  55. * @param dxfFilePath dxf文件位置
  56. * @param charset 文件编码
  57. * @see StandardCharsets
  58. */
  59. public DxfDocWriter(String dxfFilePath, DxfExtent extent, Charset charset) {
  60. this.charset = charset;
  61. this.extent = extent;
  62. newDxfEntityList = new ArrayList<>();
  63. if (dxfFilePath != null) {
  64. this.dxfFile = new File(dxfFilePath);
  65. try {
  66. this.br = StreamUtil.getFileReader(dxfFile.getAbsolutePath(), charset);
  67. } catch (FileNotFoundException e) {
  68. System.err.println("file " + dxfFilePath + "not exists" + e.getMessage());
  69. }
  70. maxMeta = DxfUtil.readMaxMeta(dxfFilePath);
  71. } else {
  72. maxMeta = Long.parseLong("1F6", 16);
  73. }
  74. }
  75. /**
  76. * 创建一个空的dxf文件
  77. */
  78. public DxfDocWriter() {
  79. this(null, null, StandardCharsets.UTF_8);
  80. }
  81. /**
  82. * 向矿图文件中添加一个图元,目前仅仅支持 {@link DxfCircle},{@link DxfArc},{@link DxfLine},{@link DxfLwPolyLine},{@link DxfText}
  83. * <p>
  84. * 也可以通过继承 {@link BaseDxfEntity} 或实现 {@link DxfEntity} 接口来扩展图元
  85. *
  86. * @param dxfEntity 图元
  87. */
  88. public void addEntity(DxfEntity dxfEntity) {
  89. if (dxfEntity instanceof DxfRay) {
  90. if (Vector3.ZERO.equals(((DxfRay) dxfEntity).getDirection())) {
  91. System.err.println(dxfEntity.getEntityName() + " direction cant be zero!!, will ignore this entity");
  92. return;
  93. }
  94. }
  95. if (dxfEntity instanceof DxfLwPolyLine) {
  96. if (((DxfLwPolyLine) dxfEntity).isEmpty()) {
  97. System.err.println("LwPolyLine not contains any point, will ignore this entity");
  98. return;
  99. }
  100. }
  101. dxfEntity.setMeta(++maxMeta);
  102. this.newDxfEntityList.add(dxfEntity);
  103. if (dxfEntity instanceof DxfCircle || dxfEntity instanceof DxfLwPolyLine) {
  104. if (((BaseDxfEntity) dxfEntity).isSolid()) {
  105. addEntity(DxfHatch.buildHatchBy((BaseDxfEntity) dxfEntity));
  106. }
  107. }
  108. }
  109. /**
  110. * 移除一个自定义的图元
  111. *
  112. * @param dxfEntity 图元
  113. */
  114. public void removeEntity(DxfEntity dxfEntity) {
  115. this.newDxfEntityList.remove(dxfEntity);
  116. for (DxfEntity entity : newDxfEntityList) {
  117. if (entity instanceof DxfHatch) {
  118. if (((DxfHatch) dxfEntity).getDxfSolid().getDxfEntity() == dxfEntity) {
  119. this.newDxfEntityList.remove(dxfEntity);
  120. }
  121. }
  122. }
  123. }
  124. /**
  125. * 设置要保存的矿图文件中要保留的图元类型列表,如果设置为 null, 那么原矿图中的所有图元都将会被保留 默认的要保存的图元列表表为 {@link #DEFAULT_ENTITY_NO_REDUCE_PART}
  126. *
  127. * @param entityNoReducePart 要保留的图元列表
  128. */
  129. public void setEntityNoReducePart(String... entityNoReducePart) {
  130. if (entityNoReducePart == null) {
  131. this.entityNoReducePart = null;
  132. } else {
  133. this.entityNoReducePart = Arrays.asList(entityNoReducePart);
  134. }
  135. }
  136. /**
  137. * 仅仅保存矿图中的图元信息(ENTITIES 部分)到文件中,其他部分(HEADER, CLASSES, TABLES...)都将被舍弃 通过 {@link #setEntityNoReducePart(String...)} 可以自定义保留那些类型的图元
  138. *
  139. * @param entitiesFilePath 要保存的文件位置
  140. * @param containNewEntity 是否包含自定义添加进去的图元信息
  141. */
  142. public void saveEntities(String entitiesFilePath, boolean containNewEntity) {
  143. save(entitiesFilePath, true, containNewEntity);
  144. }
  145. /**
  146. * 保存为新的dxf文件
  147. *
  148. * @param saveDxfFilePath 要保存的问题件位置
  149. * @param containNewEntity 是否报刊自定义添加进去的图元信息
  150. */
  151. public void save(String saveDxfFilePath, boolean containNewEntity) {
  152. save(saveDxfFilePath, false, containNewEntity);
  153. }
  154. public String readString(boolean containNewEntity) {
  155. StringBuilder result = new StringBuilder();
  156. read(false, containNewEntity, result::append);
  157. return result.toString();
  158. }
  159. private void read(boolean justSaveEntity, boolean containNewEntity, ReadDxfConsumer readStr) {
  160. FreemarkerSupport.getInstance();
  161. try {
  162. if (dxfFile != null) {
  163. br.mark((int) (dxfFile.length() + 1));
  164. } else {
  165. String value = FreemarkerSupport.getInstance().processPath("/dxf/empty.dxf.ftl", extent);
  166. br = StreamUtil.getReader(new ByteArrayInputStream(value.getBytes()));
  167. //
  168. // br = StreamUtil.getReader(StreamUtil.getResourceStream("empty.dxf"));
  169. }
  170. StringBuffer writeBuffer = new StringBuffer();
  171. while (true) {
  172. String[] pair = StreamUtil.readNextPair(br);
  173. if (pair == null) {
  174. break;
  175. }
  176. if ("0".equals(pair[0].trim()) && "SECTION".equals(pair[1].trim())) {
  177. // nextPartStart
  178. String[] nextPairTitleTag = StreamUtil.readNextPair(br);
  179. if (nextPairTitleTag == null) {
  180. continue;
  181. }
  182. String nextPartName = nextPairTitleTag[1].trim();
  183. if (justSaveEntity) {
  184. justSaveEntity(writeBuffer, pair, nextPairTitleTag, containNewEntity);
  185. } else {
  186. handleAll(writeBuffer, pair, nextPairTitleTag, containNewEntity);
  187. }
  188. readStr.accept(writeBuffer.toString());
  189. writeBuffer = new StringBuffer();
  190. System.out.println("handle part " + nextPartName + " end");
  191. } else if ("EOF".equals(pair[1].trim())) {
  192. StringUtil.appendLnCrLf(writeBuffer, pair);
  193. readStr.accept(writeBuffer.toString());
  194. System.out.println("completed!! dxf reduce end<<<<<<<<<<<<<");
  195. break;
  196. }
  197. }
  198. } catch (Exception e) {
  199. System.err.println("Reduce dxf fail!!!!!!" + e.getMessage());
  200. } finally {
  201. try {
  202. if (dxfFile != null) {
  203. br.reset();
  204. } else {
  205. br.close();
  206. }
  207. } catch (IOException e) {
  208. e.printStackTrace();
  209. }
  210. }
  211. }
  212. private void save(String saveDxfFilePath, boolean justSaveEntity, boolean containNewEntity) {
  213. try (BufferedWriter fileWriter = StreamUtil.getFileWriter(saveDxfFilePath, charset)) {
  214. read(justSaveEntity, containNewEntity, fileWriter::write);
  215. fileWriter.flush();
  216. } catch (IOException e) {
  217. System.err.println("save fail" + e.getLocalizedMessage());
  218. }
  219. }
  220. private void justSaveEntity(StringBuffer writeBuffer, String[] pair, String[] nextPairTitleTag, boolean containNewEntity) throws IOException {
  221. String nextPartName = nextPairTitleTag[1].trim();
  222. if ("ENTITIES".equals(nextPartName)) {
  223. StringUtil.appendLnCrLf(writeBuffer, pair);
  224. StringUtil.appendLnCrLf(writeBuffer, nextPairTitleTag);
  225. handEntities(writeBuffer, containNewEntity);
  226. } else {
  227. ignoreNextPart();
  228. }
  229. }
  230. private void handleAll(StringBuffer writeBuffer, String[] pair, String[] nextPairTitleTag, boolean containNewEntity) throws IOException {
  231. String nextPartName = nextPairTitleTag[1].trim();
  232. switch (nextPartName) {
  233. case "HEADER":
  234. StringUtil.appendLnCrLf(writeBuffer, pair);
  235. StringUtil.appendLnCrLf(writeBuffer, nextPairTitleTag);
  236. handHeader(writeBuffer);
  237. break;
  238. case "ENTITIES":
  239. StringUtil.appendLnCrLf(writeBuffer, pair);
  240. StringUtil.appendLnCrLf(writeBuffer, nextPairTitleTag);
  241. handEntities(writeBuffer, containNewEntity);
  242. break;
  243. case "CLASSES":
  244. case "TABLES":
  245. case "BLOCKS":
  246. case "OBJECTS":
  247. StringUtil.appendLnCrLf(writeBuffer, pair);
  248. StringUtil.appendLnCrLf(writeBuffer, nextPairTitleTag);
  249. justOutput(writeBuffer);
  250. break;
  251. default:
  252. ignoreNextPart();
  253. break;
  254. }
  255. }
  256. private void ignoreNextPart() throws IOException {
  257. while (true) {
  258. String[] piar = StreamUtil.readNextPair(br);
  259. if (piar == null) {
  260. break;
  261. }
  262. if ("ENDSEC".equals(piar[1].trim())) {
  263. // header end, endsec is the end tag
  264. break;
  265. }
  266. }
  267. }
  268. private void justOutput(StringBuffer writeBuffer) throws IOException {
  269. System.out.println("part keep raw info");
  270. while (true) {
  271. String[] piar = StreamUtil.readNextPair(br);
  272. if (piar == null) {
  273. break;
  274. }
  275. StringUtil.appendLnCrLf(writeBuffer, piar);
  276. if ("ENDSEC".equals(piar[1].trim())) {
  277. // header end, endsec is the end tag
  278. break;
  279. }
  280. }
  281. }
  282. private void handEntities(StringBuffer writeBuffer, boolean containNewEntity) throws IOException {
  283. Map<String, Integer> keepMapResult = new HashMap<>(4);
  284. Map<String, Integer> ignoreMapResult = new HashMap<>(4);
  285. String[] nextPair = StreamUtil.readNextPair(br);
  286. while (true) {
  287. if (nextPair == null) {
  288. break;
  289. }
  290. if ("ENDSEC".equals(nextPair[1].trim())) {
  291. // read entities end
  292. if (containNewEntity) {
  293. appendNewEntities(writeBuffer);
  294. }
  295. StringUtil.appendLnCrLf(writeBuffer, nextPair);
  296. break;
  297. } else {
  298. // read entities body
  299. String nextEntityName = nextPair[1].trim();
  300. boolean isReduced = entityNoReducePart != null && !entityNoReducePart.contains(nextEntityName);
  301. if (!isReduced) {
  302. StringUtil.appendLnCrLf(writeBuffer, nextPair);
  303. keepMapResult.put(nextEntityName, keepMapResult.getOrDefault(nextEntityName, 0) + 1);
  304. } else {
  305. ignoreMapResult.put(nextEntityName, ignoreMapResult.getOrDefault(nextEntityName, 0) + 1);
  306. }
  307. nextPair = readNextEntity(writeBuffer, isReduced);
  308. }
  309. }
  310. System.out.println("[Entities]keep=>" + keepMapResult + "ignore=>" + ignoreMapResult);
  311. }
  312. private void appendNewEntities(StringBuffer buffer) {
  313. for (DxfEntity dxfEntity : newDxfEntityList) {
  314. buffer.append(dxfEntity.getDxfStr());
  315. }
  316. }
  317. /**
  318. * add not reduce tag content to writeBuffer, and return next entity title tag
  319. *
  320. * @param writeBuffer content buffer
  321. * @return next entity title tag
  322. * @throws IOException if read file error throw this exception
  323. */
  324. private String[] readNextEntity(StringBuffer writeBuffer, boolean isReduced) throws IOException {
  325. // check tag is need to reduce
  326. while (true) {
  327. String[] pair = StreamUtil.readNextPair(br);
  328. if (pair == null) {
  329. return null;
  330. }
  331. // code equals zero mean meet next entity tag title, and mean this entity end
  332. if ("0".equals(pair[0].trim())) {
  333. return pair;
  334. }
  335. if (!isReduced) {
  336. StringUtil.appendLnCrLf(writeBuffer, pair);
  337. }
  338. }
  339. }
  340. private void handHeader(StringBuffer writeBuffer) throws IOException {
  341. while (true) {
  342. String[] pair = StreamUtil.readNextPair(br);
  343. if (pair == null) {
  344. break;
  345. }
  346. StringUtil.appendLnCrLf(writeBuffer, pair);
  347. if (!newDxfEntityList.isEmpty() && DxfUtil.isPairNameEquals(pair, "$HANDSEED")) {
  348. pair = StreamUtil.readNextPair(br);
  349. if (pair == null) {
  350. return;
  351. }
  352. if ("5".equals(pair[0].trim())) {
  353. pair[1] = DxfUtil.formatMeta(maxMeta + 2);
  354. }
  355. StringUtil.appendLnCrLf(writeBuffer, pair);
  356. }
  357. if ("ENDSEC".equals(pair[1].trim())) {
  358. // header end, endsec is the end tag
  359. break;
  360. }
  361. }
  362. }
  363. /**
  364. * close
  365. *
  366. * @throws IOException IOException
  367. */
  368. @Override
  369. public void close() throws IOException {
  370. StreamUtil.closeStream(br);
  371. System.out.println("Dxf document has been closed.");
  372. }
  373. interface ReadDxfConsumer {
  374. void accept(String string) throws IOException;
  375. }
  376. }