import ezdxf from ezdxf.addons.drawing import Frontend, RenderContext, pymupdf from ezdxf import bbox # 修正导入方式 import fitz # PyMuPDF,用于二次裁剪 PDF 页面 from pathlib import Path import argparse # --- 配置区 (您需要修改的全部内容都在这里) --- # 移除了 DXF_FILE 和 OUTPUT_DIR 的硬编码 # 3. 定义标准纸张尺寸库 (宽, 高) in mm. # 这里我们为每种尺寸都定义了横向(landscape)和纵向(portrait) PAPER_SIZES_MM = { "A4": {"landscape": (297, 210), "portrait": (210, 297)}, "A3": {"landscape": (420, 297), "portrait": (297, 420)}, "A2": {"landscape": (594, 420), "portrait": (420, 594)}, "A1": {"landscape": (841, 594), "portrait": (594, 841)}, "A0": {"landscape": (1189, 841), "portrait": (841, 1189)}, } # 4. 【核心配置】指定哪些块需要打印,以及如何打印 # 格式: "您的块名": ("纸张代号", "方向"), # - "纸张代号" 必须是上面 PAPER_SIZES_MM 中定义的键 (例如 "A3") # - "方向" 必须是 "landscape" (横向) 或 "portrait" (纵向) PRINTABLE_BLOCKS = { # !! 注意:您需要为它们指定正确的纸张尺寸和方向 !! # 我在这里先做了假设,请根据实际情况修改 "A$C2EB80DB8": ("A1", "portrait"), # A1 竖向 "A$C1CC9093B": ("A1", "landscape"), # A1 横向 "A$C6D564680": ("A2", "portrait"), # A2 竖向 "新块": ("A3", "landscape"), # A3 横向 "新块1": ("A4", "portrait"), # A4 竖向 } # --- 代码正文 (通常无需修改) --- def export_frame_to_pdf(doc, search_space, block_ref, settings, output_filename): """ 根据精确的设置,将单个图框块导出为PDF。 """ layout_name = None # 初始化变量,增加代码健壮性 block_name = block_ref.dxf.name paper_size_key, orientation = settings try: page_dimensions = PAPER_SIZES_MM[paper_size_key][orientation] except KeyError: print(f" - 错误:为块 '{block_name}' 提供的设置 ('{paper_size_key}', '{orientation}') 无效。请检查配置。") return print(f" - 图框: '{block_name}', 设置: {paper_size_key} {orientation} ({page_dimensions[0]}x{page_dimensions[1]}mm)") try: entities = list(block_ref.virtual_entities()) if not entities: print(f" - 警告:块 '{block_name}' 是空的,跳过。") return # 修正:直接将实体列表传递给 extents() 函数, # 它会自动处理不同类型的实体,包括 LWPolyline。 bbox_result = bbox.extents(entities) if bbox_result.extmin is None or bbox_result.extmax is None: print(f" - 警告:块 '{block_name}' 的边界为空,跳过。") return # 页面尺寸(mm) page_width, page_height = page_dimensions print(f" - 正在生成 PDF...") ctx = RenderContext(doc) backend = pymupdf.PyMuPdfBackend() # 绘制整个布局,但跳过已知容易引发异常的实体类型 def _filter(e): t = e.dxftype() # 跳过容易引发坐标错误的实体类型 problematic_types = {"HATCH", "TRACE", "SOLID", "REGION", "BODY", "3DSOLID", "ACIS", "MESH"} if t in problematic_types: return False # 检查实体是否有有效的坐标 try: if hasattr(e, 'dxf'): # 检查基本坐标属性 for attr in ['start', 'end', 'center', 'insert']: if hasattr(e.dxf, attr): point = getattr(e.dxf, attr, None) if point is not None: # 检查坐标值是否有效 if any(coord is None or not isinstance(coord, (int, float)) for coord in [point.x, point.y]): return False except (AttributeError, TypeError): # 如果检查过程中出错,跳过该实体 return False return True try: # 只绘制该块中的实体,而不是整个布局 entities = list(block_ref.virtual_entities()) filtered_entities = [e for e in entities if _filter(e)] print(f" - 过滤后剩余实体数量: {len(filtered_entities)}/{len(entities)}") # 直接绘制过滤后的实体 for entity in filtered_entities: try: ctx.push_state() backend.set_background('#FFFFFF') # 设置白色背景 entity_type = entity.dxftype() # 只处理最安全的实体类型 if entity_type in {"LINE", "CIRCLE", "ARC", "TEXT", "MTEXT"}: from ezdxf.addons.drawing.frontend import Frontend temp_frontend = Frontend(ctx, backend) temp_frontend.draw_entity(entity, ctx.current_entity_properties()) except Exception as entity_error: print(f" - 跳过有问题的实体 {entity.dxftype()}: {entity_error}") continue finally: ctx.pop_state() except Exception as draw_error: print(f" - 警告:绘制过程中出现错误: {draw_error}") print(f" - 尝试只绘制基本线条和文字...") # 最后的备用方案:只绘制最基本的实体 try: entities = list(block_ref.virtual_entities()) for entity in entities: entity_type = entity.dxftype() if entity_type == "LINE": try: from ezdxf.addons.drawing.frontend import Frontend temp_frontend = Frontend(ctx, backend) temp_frontend.draw_entity(entity, ctx.current_entity_properties()) except: continue except Exception as final_error: print(f" - 错误:无法绘制任何实体,跳过此块: {final_error}") return from ezdxf.addons.drawing import layout as draw_layout page_def = draw_layout.Page(page_width, page_height, units=draw_layout.Units.mm) pdf_bytes = backend.get_pdf_bytes(page_def) # 用 PyMuPDF 对生成的 PDF 进行页面级裁剪,裁到块的范围 mm_to_pt = 72.0 / 25.4 minx_mm, miny_mm = bbox_result.extmin.x, bbox_result.extmin.y maxx_mm, maxy_mm = bbox_result.extmax.x, bbox_result.extmax.y x0 = max(0.0, minx_mm) * mm_to_pt x1 = max(0.0, maxx_mm) * mm_to_pt top = max(0.0, page_height - maxy_mm) * mm_to_pt bottom = max(0.0, page_height - miny_mm) * mm_to_pt pdf_doc = fitz.open(stream=pdf_bytes, filetype="pdf") page = pdf_doc[0] crop_rect = fitz.Rect(x0, top, x1, bottom) page.set_cropbox(crop_rect) pdf_doc.save(str(output_filename)) print(f" - 已保存到: {output_filename}") finally: pass def main(): parser = argparse.ArgumentParser( description="根据DXF文件中的块名,自动批量打印为带正确纸张尺寸的PDF。" ) parser.add_argument("input_dxf", type=Path, help="要处理的源DXF文件的路径。") parser.add_argument( "-o", "--output", type=Path, default=Path("plots_output"), help="指定PDF文件的输出目录 (默认为: plots_output)" ) parser.add_argument( "-l", "--layout", type=str, default=None, help="指定要在哪个图纸布局(Layout)中查找块。如果未提供,则在模型空间(Modelspace)中查找。" ) args = parser.parse_args() output_dir = args.output dxf_file = args.input_dxf output_dir.mkdir(exist_ok=True) try: doc = ezdxf.readfile(dxf_file) except IOError: print(f"错误:无法读取文件 '{dxf_file}'") return except ezdxf.DXFStructureError as e: print(f"DXF 文件结构错误: {e}") return # 根据参数决定在哪个空间查找块 if args.layout: try: search_space = doc.layouts.get(args.layout) print(f"--- 目标空间: 布局 '{args.layout}' ---") except KeyError: available = ", ".join(doc.layouts.names()) print(f"错误: 找不到名为 '{args.layout}' 的布局。可用的布局有: [{available}]") return else: search_space = doc.modelspace() print("--- 目标空间: 模型空间 ---") # 查找目标空间中所有的块参照 (INSERT entities) all_block_references = search_space.query('INSERT') # 根据 PRINTABLE_BLOCKS 字典的键来筛选需要处理的块 frame_references = [ ref for ref in all_block_references if ref.dxf.name in PRINTABLE_BLOCKS ] if not frame_references: print("未找到任何在配置字典 PRINTABLE_BLOCKS 中定义的可打印图框块。") return print(f"在 '{dxf_file}' 中共找到 {len(frame_references)} 个待处理的图框块。开始处理...") for i, block_ref in enumerate(frame_references): block_name = block_ref.dxf.name settings = PRINTABLE_BLOCKS[block_name] print(f"\n处理第 {i+1} 个图框: '{block_name}' (句柄: {block_ref.dxf.handle})") output_filename = output_dir / f"{block_name}_{i+1}.pdf" export_frame_to_pdf(doc, search_space, block_ref, settings, output_filename) print("\n所有指定的图框处理完毕!") if __name__ == "__main__": main()