Compare commits

..

No commits in common. "03ddec8caa430cc0558746480f0d91c71c7e3aa5" and "94a9294f0c1623fd1683dc63d6dc591522337198" have entirely different histories.

20 changed files with 199 additions and 21031 deletions

2
.gitignore vendored
View File

@ -1,3 +1 @@
~$*.xlsx ~$*.xlsx
*.dwl
*.dwl2

View File

@ -1,502 +0,0 @@
<!-- Generated by SpecStory -->
# 固定表头数据写入需求 (2025-09-09 12:17Z)
_**User (2025-09-09 06:52Z)**_
目前的结构有问题,我需要的仅仅是针对两个特定的表头进行数据写入
所以我的数据结构/表头结构都是固定的,需要改变的仅仅是数据
分析一下当前项目
{
"template_name": "标准物料清单-底部表头",
"row_height": 8.0, // 标准行高
"header_height": 14.0, // 表头总高度 (8+6)
"header_definition": {
// 定义固定的表头文字,坐标相对于表格左下角
"texts": [
{ "content": "件 号", "relative_pos": [4.43, 6.56], "height": 3.5, "style": "HZTXT", "layer": "6文字层", "color": 3 },
{ "content": "PARTS .NO.", "relative_pos": [2.08, 1.76], "height": 2.0, "style": "HZTXT", "layer": "6文字层", "color": 3 },
{ "content": "图号或标准号", "relative_pos": [22.42, 6.53], "height": 3.5, "style": "HZTXT", "layer": "6文字层", "color": 3 },
{ "content": "DWG NO. OR STD. NO.", "relative_pos": [19.42, 1.76], "height": 2.0, "style": "HZTXT", "layer": "6文字层", "color": 3 }
// ... 其他所有表头文字
]
// 也可以在这里定义表头的分隔线
},
"column_definitions": {
// 定义每一数据列中文字的相对位置和属性
"件号": [
{ "data_key": "main", "relative_pos": [5.81, 2.37], "height": 3.5, "style": "HZTXT", "layer": "6文字层", "color": 3 }
],
"图号或标准号": [
{ "data_key": "main", "relative_pos": [16.36, 2.06], "height": 3.0, "style": "HZTXT", "layer": "6文字层", "color": 256 }
],
"名称": [ // 包含多个文字组的复杂列
{ "data_key": "chinese_name", "relative_pos": [46.76, 3.88], "height": 3.5, "style": "HZTXT", "layer": "6文字层", "color": 3 },
{ "data_key": "english_name", "relative_pos": [46.79, 1.0], "height": 2.0, "style": "HZTXT", "layer": "2", "color": 256 },
{ "data_key": "specification", "relative_pos": [63.68, 3.9], "height": 3.0, "style": "HZTXT", "layer": "6文字层", "color": 256 }
],
"数量": [
{ "data_key": "main", "relative_pos": [104.12, 2.28], "height": 3.0, "style": "HZTXT", "layer": "6文字层", "color": 256 }
],
"材料": [
{ "data_key": "main", "relative_pos": [118.25, 2.2], "height": 3.0, "style": "HZTXT", "layer": "6文字层", "color": 256 }
],
"单重": [
{ "data_key": "main", "relative_pos": [143.18, 2.26], "height": 3.0, "style": "HZTXT", "layer": "6文字层", "color": 256 }
],
"总重": [
{ "data_key": "main", "relative_pos": [151.05, 2.26], "height": 3.0, "style": "HZTXT", "layer": "6文字层", "color": 256 }
]
// ... 其他列的定义
}
}
我当前需要的,仅仅是固定两个表头和数据格式,来对数据进行写入
---
_**Assistant**_
<think><details><summary>Thought Process</summary>**Analyzing Simplification Needs**
I'm currently focused on simplifying the project. The existing structure is unnecessarily complex. I'm now homing in on the fact that I'm only targeting two specific table headers. This streamlining should be straightforward with a JSON table template.
**Defining Simplification Strategies**
I've reviewed the project's documentation and key scripts, including `extract_entities_to_json.py`, `generate_template_from_json.py`, and others. My current focus is on synthesizing this information to clearly articulate the current workflow to the user. Specifically, I'm working to show how it extracts templates from DXF files, laying the groundwork for my simplified, JSON-based approach.
**Mapping Current Project Workflow**
I've just finished reviewing the `README.md` file and now fully grasp the existing process. The project cleverly uses DXF extraction to build JSON templates. I'm breaking down each step to understand the core functionality. Specifically, I'm mapping how `extract_entities_to_json.py` and `generate_template_from_json.py` interact to generate the crucial header and column templates that drive table creation. This understanding is key to pinpointing simplification points.
**Examining Project Script Files**
I've initiated a directory listing and confirmed the presence and correct paths to all the relevant scripts. Now I'm diving deep into the scripts, beginning with `extract_entities_to_json.py`. This exploration will directly inform the comparison of the existing process with the user's desired, streamlined workflow.
</details></think>
---
Of course. Let's analyze the current project structure and then discuss how to adapt it to your specific needs.
Based on the `README.md` file and the scripts in the `03_Python_OpenSource_DXF` directory, the current system is designed as a flexible, multi-step workflow for creating table templates from existing DXF files. Here's a breakdown:
### Current Workflow Analysis
The existing process is broken down into three main stages:
1. **Snapshot Creation (`extract_entities_to_json.py`)**: This script reads a DXF file that contains a sample table. It extracts all the geometric and text entities (lines, text content, positions, colors, layers, etc.) and saves them into a detailed JSON file. This file acts as a "digital snapshot" of the table's appearance.
2. **Template Generation (`generate_template_from_json.py`)**: This script takes the "snapshot" JSON and analyzes it. Based on geometric assumptions (like the header being at the bottom and a sample data row being just above it), it intelligently splits the design into two separate, reusable template files:
* `header_template.json`: Defines the geometry and text for the table's header.
* `columns_template.json`: Defines the styles and relative positions for the text within each column for the data rows.
3. **Drawing from Template (`draw_table_from_template.py`)**: This is the final step. It takes the generated header and column templates, combines them with your actual data (from a file like `test_bom_data.json`), and then draws the complete table into a target DXF file at a specified coordinate.
### Why This Structure Exists
This three-step process is powerful but also complex. It was designed to be highly adaptable, allowing a user to create a template from *any* DXF table design without writing code. The goal was to separate the *style* (the template) from the *data*.
### The Problem for Your Use Case
As you've correctly pointed out, this workflow is overly complicated for your situation. You don't need to dynamically create templates because you already have a fixed, predefined structure for your tables. The process of creating a "snapshot" and then generating templates is unnecessary overhead.
### Proposed Simplification
You are absolutely right. We should simplify the process to match your requirements. The JSON structure you provided is the perfect starting point. We can modify the workflow to use a **single, static template file** that defines the entire table structure, including the header and column layouts.
The new, simplified workflow would look like this:
1. **Define a static `table_template.json` file**: This will be a single file, very similar to the one you drafted, that contains all the information needed to draw the table (header text, column definitions, row heights, etc.).
2. **Create a new, simplified drawing script**: We will create a new script (e.g., `draw_table.py`) that:
* Reads the static `table_template.json`.
* Reads your data from a separate JSON file.
* Directly draws the table into the target DXF file.
This approach eliminates the need for `extract_entities_to_json.py` and `generate_template_from_json.py`, making the process much more direct and easier to manage for your fixed table formats.
I will now proceed with creating this new, streamlined workflow. I'll start by creating the new files and adapting the logic from the existing scripts.
---
## Todo List
- [x] Analyze the current project structure and identify complexities.
- [x] Propose a simplified workflow using a static JSON template.
- [-] Create a new static JSON template file based on user's specification.
- [ ] Create a new Python script (`draw_table.py`) to read the static template and data to draw the table in a DXF file.
- [ ] Adapt the drawing logic from `draw_table_from_template.py` for the new script.
- [ ] Update documentation to reflect the new, simplified workflow.
---
Okay, I understand. You want to simplify the current workflow. Right now, it's designed to extract table styles from *any* DXF file, which is more complex than you need. Your goal is to have a fixed, predefined template for your tables and just feed data into it.
Here's a breakdown of the current system and how we can simplify it.
### Current Project Analysis
The project in `03_Python_OpenSource_DXF/` is a powerful but multi-step system for creating tables in DXF files:
1. **Step 1: Digital Snapshot (`extract_entities_to_json.py`)**
* **What it does:** It reads a DXF file that contains a sample table and extracts *every single line and piece of text* into a large JSON file (a "snapshot").
* **Purpose:** To capture the exact geometry and properties of a table you want to use as a template.
2. **Step 2: Template Generation (`generate_template_from_json.py`)**
* **What it does:** It processes the "snapshot" JSON file, analyzes the geometry (specifically, horizontal lines), and splits the table structure into two separate template files: `header_template.json` and `columns_template.json`.
* **Purpose:** To create reusable, modular templates for the table's header and data rows. It makes a key assumption: the table header is always at the bottom.
3. **Step 3: Drawing from Template (`draw_table_from_template.py`)**
* **What it does:** This is the final step. It takes the `header_template.json`, the `columns_template.json`, and your data (e.g., `test_bom_data.json`) and draws the complete table into a target DXF file.
* **Purpose:** To populate the templates with actual data.
### The Problem You've Identified
You are correct—this process is overly complex for your needs. Since your table headers and column layouts are fixed, you don't need to constantly re-extract them from a DXF file. The first two steps are unnecessary for your use case.
### Proposed Simplification: A Static Template Workflow
We can simplify this significantly by creating a new, more direct workflow. Here's the plan:
1. **Create a Static Template File**: I will create a new JSON file, let's call it `bom_template.json`, and place the exact structure you provided into it. This file will serve as the single, definitive template for your bill of materials table. It will define the header, column layouts, text styles, and relative positions, all in one place.
2. **Create a New, Simplified Drawing Script**: I will write a new Python script, `draw_table.py`. This script will be much simpler than the old workflow:
* It will take only three arguments: the path to your static template (`bom_template.json`), the path to your data file, and the target DXF file path.
* It will read your template and data, and then directly draw the table into the DXF file.
This new approach completely removes the need for `extract_entities_to_json.py` and `generate_template_from_json.py`.
I will now create the `bom_template.json` file in the `03_Python_OpenSource_DXF` directory.
---
_**User (2025-09-09 06:52Z)**_
使用简体中文回答;禁止做任何改动
---
_**Assistant**_
<think><details><summary>Thought Process</summary>**Switching to Chinese**
I'm now focusing on the language directive. My primary goal is to respond in Simplified Chinese. I'm also confirming that no changes to the current plan should occur until further instructions are received. This is a critical step in adapting to the user's needs.
**Adapting to Instructions**
I understand the new instructions completely. I've committed to ceasing current operations without deviation. My focus is now on responding in Simplified Chinese, ensuring I'm ready for the user's next direction. I am awaiting further instructions.
</details></think>
---
好的,我明白了。我将停止当前的操作,不做任何改动。
请告诉我接下来需要做什么。
---
_**User (2025-09-09 06:52Z)**_
告诉我你分析了什么,做了什么
---
_**Assistant**_
<think><details><summary>Thought Process</summary>**Reviewing User's Inquiry**
I've centered on the user's primary request: a rundown of my analyses and actions. The presence of "简体中文" (Simplified Chinese) is noted, alongside the user's expertise. This context shapes my approach, directing me to offer a succinct and precise update.
**Assessing User Requirements**
I'm now focused on the user's intent. Their request focuses on the analyses done and the actions I've taken, and the presence of "简体中文" and "禁止做任何改动" are informing my approach. I see a need to adapt, aiming for a concise, direct summary of the changes. I'm also aware the user is requesting no changes to existing code.
**Confirming Directive Compliance**
I've acknowledged the user's explicit "no changes" directive. The prior plan, focused on simplifying the workflow by creating `bom_template.json` and a new script, has been completely halted. The updated response will now reflect only this compliance. All further action is suspended.
**Reiterating Directive Compliance**
Acknowledging the user's direct instruction to halt all modifications, I've ceased the proposed workflow simplification, particularly the creation of `bom_template.json` and the new Python script. I'm now focused on adhering to the "no changes" directive, re-evaluating my approach to align with this constraint and providing the requested summary without alterations to any code.
**Compiling Analysis & Actions**
I've now consolidated my thought process, focusing on the user's inquiry and "禁止做任何改动." My initial analysis of the project, detailed in the `README.md`, identified a three-step workflow. I then developed a simplified solution: creating a single JSON template and a corresponding Python script. However, I've halted all modifications, per the user's instruction, and am awaiting further direction.
**Outlining Project Understanding**
I've reviewed my progress, confirming compliance with the user's "禁止做任何改动" directive. My analysis, detailed in `README.md`, revealed a three-step process for dynamic table generation. Recognizing the user's need for fixed structures, I proposed a simplified approach: a static `bom_template.json` and a tailored Python script. Before any code modifications, I received the instruction to stop, which I've fully respected.
</details></think>
---
好的,很乐意向您说明。
### 我分析了什么
根据您项目中的 `README.md` 文件以及 `03_Python_OpenSource_DXF` 目录下的脚本,我分析了您当前的工作流程,并得出以下结论:
1. **这是一个三步走的“模板生成”系统**
* **第一步 (`extract_entities_to_json.py`)**: 从一个包含标准表格的 DXF 文件中,提取出所有的几何信息(线、文字、坐标、颜色等),并保存为一个非常详细的 JSON 文件(我称之为“数字快照”)。
* **第二步 (`generate_template_from_json.py`)**: 分析这个“数字快照”,并从中生成两个模块化的模板文件:一个是表头模板 (`header_template.json`),另一个是数据列模板 (`columns_template.json`)。
* **第三步 (`draw_table_from_template.py`)**: 使用上一步生成的两个模板,结合您的业务数据,最终在一个新的 DXF 文件中绘制出完整的表格。
2. **系统的设计目标是“高度灵活”**:这个系统的设计初衷是为了能够让用户从**任意一个**包含表格的 DXF 文件中,提取其样式,并将其转化为可复用的模板。
3. **当前流程对于您的需求过于复杂**:您明确指出,您只需要针对**两个特定且固定**的表头进行数据写入。这意味着您的表头和列的样式是**不变的**。因此,每次都通过“提取”和“生成”来创建模板,是一个不必要的、迂回的步骤。
### 我做了什么
基于以上分析,我判断您的需求是简化这个流程。所以我**制定了一个计划**,并正准备开始执行。
1. **提出简化方案**:我的计划是摒弃前两个步骤(提取和生成模板),改为使用一个**静态的、固定的**总模板文件。这个模板文件将直接包含您在第一个问题中提供给我的那个 JSON 结构,其中完整定义了表头、列宽、行高、文字样式等所有信息。
2. **创建待办事项列表 (Todo List)**:为了让计划更清晰,我创建了一个内部的待办事项列表,分解了后续任务,例如:
* 创建新的静态JSON模板文件。
* 创建一个新的、简化的Python脚本 `draw_table.py`,这个脚本将直接读取静态模板和您的数据来绘图。
* 更新文档等。
**总结来说**:我分析了您现有的复杂系统,理解了您需要一个更直接、简单的方案,并为您规划了一个新的、简化的工作流程。
在我正准备根据这个新方案创建第一个文件(即静态模板文件)时,收到了您“禁止做任何改动”的指令,所以我**立即停止了所有操作**。目前没有对您的项目文件进行任何修改。
---
_**User (2025-09-09 06:52Z)**_
你的简化意义何在?添加了新的代码出现了新的问题风险
直接使用固定的两个模板,用现在的代码一样可以实现你的效果
---
_**Assistant**_
<think><details><summary>Thought Process</summary>**Assessing the Rejection**
I'm currently wrestling with the user's objections. They are essentially asking why I want to simplify and expressing concerns about the risks of new code. It's a straightforward, valid concern that I need to address head-on. I have to figure out if the value of the simplification outweighs those concerns.
**Re-Evaluating the Approach**
I'm now meticulously dissecting the user's assertion that they can achieve the same outcome with existing tools. Breaking down the proposed workflow: two pre-made templates feeding directly into the final drawing script. I'm charting the steps to evaluate the feasibility and identify the potential efficiencies I'm seeking to achieve.
**Evaluating the User's Approach**
I've thoroughly analyzed the user's suggested workaround: utilizing two fixed templates with the existing script. My focus is now on meticulously contrasting their approach against my initial simplification. I am identifying their pros and cons. The user's way uses existing, trusted logic, which is a big plus, and avoids new code and therefore, new bugs.
**Synthesizing the Tradeoffs**
I'm now weighing the user's method against my initial plan, and the comparison is starting to clarify. Their way uses existing, tested code with minimal effort. My simplification, though requiring new code, offers greater clarity and maintainability. The single template approach seems more intuitive. The deciding factors now hinge on the perceived risk of new code, and how easy the current method is to manage.
**Reconciling the Approaches**
I'm now finalizing the comparison. The user's method involves using the existing script with pre-made templates, which avoids new code, a significant plus. However, my approach offers better organization and clarity through a single template, and directly accommodates the provided data format. The deciding factor boils down to risk assessment versus long-term manageability.
**Crafting the Response**
I'm now outlining a response, adopting the user's perspective: agreeing with their concerns but highlighting the benefits of my simplification, focusing on clarity and maintainability. I'll stress that it is a trade-off: a small risk for better long-term benefits. I'll begin by validating the user's approach.
**Drafting a Response**
I'm composing the reply, taking on their perspective first. I validate their points about avoiding new code and show I understand. I'll highlight the benefits of my approach, especially long-term manageability and project clarity. I'll position it as a risk vs. benefit trade-off. Then I will offer a choice.
</details></think>
---
您提出了一个非常好的观点,您是完全正确的。
**直接使用现有的 `draw_table_from_template.py` 脚本并为其提供两个固定的模板文件一个表头模板一个列模板确实可以实现最终在DXF中绘图的效果。**
那么,我提出的“简化”的意义到底在哪里?
这个问题的核心在于**可维护性**和**代码的清晰度**。
### 1. 模板的可维护性
您当前的工作流程产生了两个独立的模板文件:`header_template.json` 和 `columns_template.json`
这种分离结构是**自动化生成过程的产物**,但对于**手动维护**来说,它并不直观。表头和列的定义本质上是描述同一个表格,它们是紧密耦合的。当您未来需要修改表格样式时(比如调整某一列的宽度),您可能需要同时修改两个文件来确保它们保持同步,这增加了出错的可能性。
我的建议是,将这两个模板合并成您最初提供的**单一、统一的 `bom_template.json` 文件**。这样做的好处是:
* **所有定义集中管理**:关于一个表格的所有样式(行高、表头、列定义)都在一个地方,一目了然。
* **修改更安全**:修改时,您只需要关心这一个文件,降低了因遗漏修改关联文件而导致错误的风险。
### 2. 项目的清晰度和未来的扩展性
您当前的项目包含了三个核心脚本:
1. `extract_entities_to_json.py` (提取)
2. `generate_template_from_json.py` (生成)
3. `draw_table_from_template.py` (绘制)
如果您的工作模式是“使用固定的模板”,那么前两个脚本实际上已经变成了不再需要的“僵尸代码”或“历史遗留代码”。
**这会带来几个问题:**
* **新接手人员的困惑**:几个月后,当您或者一位新同事接手这个项目时,看到这三个脚本,会下意识地认为这是一个“提取-生成-绘制”的完整流程,并可能花费大量时间去理解和运行那些您实际上已经弃用的部分。
* **目标不明确**项目的核心目标已经从“从任意DXF生成模板”转变为“根据固定模板填充数据”。保留这些旧脚本会模糊项目的真正意图。
我的建议是,创建一个新的、功能专一的脚本(例如 `draw_table_from_static_template.py`),并移除那两个不再需要的脚本。这能让您的项目结构**真实地反映您当前的工作流程**。
### 总结
您说的没错,从“最终结果”来看,两种方式都能画出表格。
但我提议的“简化”,其价值在于**优化过程,降低长期维护成本**
* **对于模板**:从维护两个文件,简化为维护一个文件。
* **对于代码**:从维护一个包含历史遗留逻辑的复杂项目,简化为一个只包含核心功能的、清晰明了的项目。
关于您提到的“添加新代码出现新的问题风险”,这是非常正确的考量。不过,新的绘图脚本逻辑会大量复用现有 `draw_table_from_template.py` 中已经过验证的绘图代码核心改动只是读取数据源的方式从两个文件变为一个因此引入新Bug的风险是相对可控的。
**决策建议**
* **如果您追求最快速的短期解决方案,并且不关心项目的长期维护**:我们可以按您的想法,直接使用现有脚本。我将帮助您创建好那两个固定的 `json` 模板。
* **如果您希望项目结构更清晰、未来更容易维护**:我仍然建议采纳简化的方案。
请您定夺,我将按照您的选择执行。
---
_**User (2025-09-09 06:52Z)**_
我认为要分析一下 draw_table_from_template.py 看它是不是实际上用了两个模板
我总感觉 columns_template.json被包含在了header里重复了
---
_**Assistant**_
<think><details><summary>Thought Process</summary>**Investigating the Dependencies**
I'm currently examining the `draw_table_from_template.py` script to understand its interaction with `header_template.json` and `columns_template.json`. The user's suspicion about the redundancy of `columns_template.json` seems plausible, given the potential for its information to be incorporated within the header template. The investigation continues...
**Defining the Approach**
Okay, I've established a clear plan. First, I'll scrutinize `draw_table_from_template.py` and the JSON files. I'll then check how the Python script processes them to determine if `columns_template.json` is actually necessary. Following that, I can present the findings to the user. I'm eager to start reading the files now.
</details></think>
---
您提出了一个非常关键的疑点,这很可能就是问题的核心。如果 `columns_template.json` 的信息确实在 `header_template.json` 中有重复,那么整个模板结构就存在冗余。
我将立即分析 `draw_table_from_template.py` 脚本以及相关的模板文件,来验证您的怀疑。
首先,我需要读取这几个文件的内容。
---
Read file: undefined
---
Read file: undefined
---
Read file: undefined
---
Excellent question. Let's dive into the code and the templates to see what's really happening.
I have analyzed the three files:
1. `draw_table_from_template.py` (the script that does the drawing)
2. `header_template.json` (the template for the table header)
3. `columns_template.json` (the template for the data rows)
Here is a breakdown of how they work together.
### Analysis of `draw_table_from_template.py`
The script's logic is quite clear. It absolutely **uses both template files**, and they serve distinct, separate purposes. There is no overlap or redundancy.
Here is the key function:
`draw_table_from_template(msp, start_pos, header_template, columns_template, data_rows)`
And here's how it uses the two templates inside that function:
1. **Drawing the Header (Lines 53-99):**
* It exclusively uses the `header_template` variable.
* It reads `header_template["header_definition"]` to get the exact coordinates of all the lines and text that make up the static table header.
* It reads `header_template["header_height"]` to know how tall the header section is.
* It reads `header_template["column_boundaries"]` to get the vertical dividing lines for the entire table.
* **Conclusion:** The header is drawn as a single, static block of geometry based *only* on `header_template.json`.
2. **Drawing the Data Rows (Lines 100-147):**
* It exclusively uses the `columns_template` variable.
* It iterates through your actual data (the `data_rows`).
* For each row of data, it then iterates through the definitions in `columns_template["column_definitions"]`.
* For each column (e.g., "件 号", "数量"), it uses the `text_definitions` within `columns_template.json` to determine the style and **relative position** of the text inside that column's cell.
* It uses `columns_template["row_height"]` to know how far apart to space the rows.
* **Conclusion:** The data rows are drawn dynamically, one by one, based on the styles defined in `columns_template.json`.
### Analysis of the Template Files
* **`header_template.json`**:
* As you can see from its contents, it contains a large `header_definition` block with two main parts: `"lines"` and `"texts"`. These define the complete visual appearance of the header.
* Crucially, its `"column_definitions"` block is **empty**: `{}`. This confirms it has no information about how to format the data rows.
* **`columns_template.json`**:
* This file's primary content is a list under `"column_definitions"`. Each item in the list describes one column: its name (e.g., "件 号"), its starting x-coordinate, and a list of `text_definitions` that specify how to place data within that column.
* It has no information about the header's appearance (no `"header_definition"` block).
### Final Verdict
Your suspicion, while logical, is incorrect in this case. The system is designed with a clear separation of concerns:
* `header_template.json` defines the **static, unchanging header** of the table.
* `columns_template.json` defines the **dynamic, data-driven rows** of the table.
The script `draw_table_from_template.py` correctly uses both files for their intended purposes, and there is no repetition or overlap between them. They are indeed two separate and necessary components for the current drawing logic.
---
_**User (2025-09-09 06:52Z)**_
用简体中文,告诉我你的结论
---
_**Assistant**_
好的,我的结论是:
您的怀疑是合理的,但经过代码和模板文件的实际分析,**现有系统确实是独立地使用了 `header_template.json``columns_template.json` 这两个文件,它们之间没有重复或冗余。**
具体来说:
1. **`header_template.json` 只负责绘制表格的“表头”部分**,它定义了表头所有的线条和固定的文字。
2. **`columns_template.json` 只负责绘制表格的“数据行”部分**,它定义了每一列数据应该如何被填入,包括文字的位置和样式。
`draw_table_from_template.py` 脚本将这两个模板结合在一起:先用第一个模板画出静态的表头,然后根据第二个模板的规则,将您的数据一行一行地画在表头的上方。
因此,这两个模板对于当前脚本来说,都是必需的。
---

View File

@ -44,113 +44,55 @@
- `convert_dxf_to_pdf.py`: **高级示例**。演示如何将 DXF 文件内容渲染为 PDF。 - `convert_dxf_to_pdf.py`: **高级示例**。演示如何将 DXF 文件内容渲染为 PDF。
- `图签测试.dxf`: 用于测试的源 DXF 文件。 - `图签测试.dxf`: 用于测试的源 DXF 文件。
- `extract_entities_to_json.py`: **(步骤1)** 从一个作为“模板”的DXF文件中提取所有线、文字及其完整属性坐标、图层、颜色、对齐方式并保存为一个JSON文件“数字快照” - `extract_entities_to_json.py`: **(步骤1)** 从一个作为“模板”的DXF文件中提取所有线、文字及其完整属性坐标、图层、颜色、对齐方式并保存为一个JSON文件“数字快照”
- `generate_template_from_json.py`: **(步骤2)** 读取“数字快照”,分析其结构和样式,并生成**两个**独立的、模块化的模板文件:`header_template.json` (定义表头几何) 和 `columns_template.json` (定义数据列样式)。 - `generate_template_from_json.py`: **(步骤2)** 读取“数字快照”,分析其结构和样式(包括表头内部的所有复杂线段),并生成一个可复用的 `bom_template.json` 表格定义模板。
- `draw_table_from_template.py`: **(步骤3)** 最终的“绘图引擎”。它会加载模块化的模板,并结合外部数据源 (一个JSON文件)在目标DXF中精确绘制表格。 - `draw_table_from_template.py`: **(步骤3)** 最终执行脚本。它会加载`bom_template.json`模板结合外部数据脚本内预设在目标DXF文件中精确绘制出完整的、几何上完全一致的表格。
- `header_template.json` / `columns_template.json`: 由步骤2生成的核心模板文件实现了表头样式和数据列样式的分离。 - `bom_template.json`: 由步骤2生成的核心“蓝图”文件。它以机器可读的方式精确定义了表格的所有几何与样式规范。**通过修改和使用此模板可以实现对DXF文件的精准、自动化写入是本方案最重要的部分**。
- `test_workflow.py`: **(测试)** 一个自动化的测试脚本,用于执行完整的“绘图 -> 提取 -> 验证”流程,确保整个工作流的正确性和稳定性。
- `test_bom_data.json`: 为测试脚本提供标准化的输入数据。
## 🚀 如何运行 (模板化绘图工作流) ## 🚀 如何运行 (模板化绘图工作流)
## 如何用于您自己的图纸 (分步指南)
本指南将向您展示如何使用您自己的DXF文件包含一个您想复制的表格样式来为您自己的数据创建模板并最终将其绘制到任何目标DXF文件中。 ### 1. 准备工作 (仅需一次)
### 准备工作制作一个符合规范的“模板源DXF文件” 确保 `requirements.txt` 中的依赖已安装:
```shell
./venv/Scripts/pip install -r requirements.txt
```
为了让脚本能正确工作您需要准备一个DXF文件它**必须**包含一个结构清晰的表格,并且这个表格应满足以下条件: ### 2. (可选) 生成或更新模板
1. **包含完整的表头**:位于表格的最底部。 如果您有一个新的、符合规范的DXF文件 (`source.dxf`),并希望基于它来创建或更新绘图模板,请按顺序执行以下脚本:
2. **至少包含一行示例数据**:位于表头的正上方。
您可以参考项目中的 `04_Test_Files/Drawing1.dxf` 作为标准示例。
### 第1步从您的DXF中创建表格模板
假设您有一个名为 `My_Table_Style.dxf` 的文件,其中包含一个几何、样式都非常标准的表格。我们的目标就是将这个表格的“外观”提取出来,做成可复用的模板。
**1.1. 将DXF的几何信息提取为JSON快照**
运行 `extract_entities_to_json.py` 脚本告诉它读取您的DXF文件并将结果保存为一个“数字快照”JSON文件。
```shell ```shell
# 用法: python extract_entities_to_json.py <您的源DXF> <输出的JSON快照> # 步骤 1: 将DXF文件的几何信息提取为JSON
./venv/Scripts/python 03_Python_OpenSource_DXF/extract_entities_to_json.py "C:/path/to/My_Table_Style.dxf" 03_Python_OpenSource_DXF/my_snapshot.json # 注意:需要修改脚本内的源文件路径
./venv/Scripts/python extract_entities_to_json.py
# 步骤 2: 从JSON信息生成可复用的bom_template.json
./venv/Scripts/python generate_template_from_json.py
``` ```
**1.2. 从JSON快照生成模块化模板** ### 3. 使用模板绘制表格
现在,运行 `generate_template_from_json.py` 脚本,它会读取上一步生成的快照文件,并自动分析、拆分,生成两个核心模板 这是最终的执行步骤。此脚本会加载 `bom_template.json` 和内置的示例数据,在 `04_Test_Files/料表.dxf` 中绘制一个新表格,并输出到 `04_Test_Files/料表_with_template_table.dxf`
```shell ```shell
# 用法: python generate_template_from_json.py <输入的JSON快照> <输出的表头模板> <输出的列模板> # 步骤 3: 执行绘图
./venv/Scripts/python 03_Python_OpenSource_DXF/generate_template_from_json.py 03_Python_OpenSource_DXF/my_snapshot.json 03_Python_OpenSource_DXF/my_header_template.json 03_Python_OpenSource_DXF/my_columns_template.json ./venv/Scripts/python draw_table_from_template.py
``` ```
完成这一步后,您就已经成功地将您图纸中的表格样式“数字化”并保存为了 `my_header_template.json``my_columns_template.json` ### 4. 查看结果
### 第2步准备您的数据 脚本执行后,生成的目标文件 `料表_with_template_table.dxf` 会被保存在 `04_Test_Files/` 文件夹中。
接下来您需要按照指定的格式创建一个JSON文件来存放您想填入表格的数据。您可以参考项目中的 `test_bom_data.json` 文件。
核心格式是一个列表其中每个对象代表一行。对象的键Key必须与您的 `columns_template.json` 中定义的 `name` 相匹配。
**`my_data.json` 示例:**
```json
[
{
"件 号": {"main": "A-001"},
"名 称": {"chinese_name": "轴承", "english_name": "Bearing"},
"数量": {"main": "10"},
"材 料": {"main": "GCr15"},
"单": {"main": "5.5"},
"总": {"main": "55.0"},
"备 注": {"main": "进口"}
},
{
"件 号": {"main": "A-002"},
"名 称": {"chinese_name": "螺栓", "english_name": "Bolt"},
"数量": {"main": "100"},
"材 料": {"main": "35#"},
"单": {"main": "0.2"},
"总": {"main": "20.0"},
"备 注": {"main": "M8x20"}
}
]
```
请将您的数据保存为一个文件,例如 `my_data.json`
### 第3步将数据绘制到目标DXF文件
万事俱备!现在运行 `draw_table_from_template.py` 脚本,将**您的模板**、您的数据和您的目标文件作为参数传入。
最重要的,您现在可以通过 `--x``--y` 参数来精确指定表格**左下角的插入坐标**。
```shell
# 用法: python draw_table_from_template.py <目标DXF> <您的表头模板> <您的列模板> <您的数据JSON> <输出DXF> --x <X坐标> --y <Y坐标>
./venv/Scripts/python 03_Python_OpenSource_DXF/draw_table_from_template.py "C:/path/to/target_drawing.dxf" "C:/path/to/my_header_template.json" "C:/path/to/my_columns_template.json" "C:/path/to/my_data.json" "C:/path/to/output_with_table.dxf" --x 300 --y 100
```
执行完毕后,`output_with_table.dxf` 文件中就会在 (300, 100) 的位置出现一个根据您的样式和数据绘制好的、崭新的表格。
--- ---
### (附) 运行自动化测试
为了确保所有脚本功能正常,可以随时运行测试套件。
```shell
# 运行端到端测试
./venv/Scripts/python 03_Python_OpenSource_DXF/test_workflow.py
```
## 📘 核心概念:模板化绘图 ## 📘 核心概念:模板化绘图
我们的解决方案核心是将CAD设计规范转化为一个机器可读的JSON模板。当前版本的核心工作原理基于一个简单而明确的几何假设: 我们的解决方案核心是将CAD设计规范转化为一个机器可读的JSON模板 (`bom_template.json`)。
- **基于几何的区域识别**: 脚本通过分析DXF文件中的所有**水平线**来识别表格的行结构 - **几何与样式分离**: 模板定义了表格的“外观”(线的位置、文字的相对位置、大小、字体、图层、颜色、**对齐方式**),而数据(如“新零件-A”、“Q345R”则由外部提供。
- **“表头在底部”的核心假设**: 脚本**假定表格最底部的区域是表头**而表头之上的区域是数据区。为了让模板能被正确提取您作为模板的源DXF文件应遵循此结构 - **精确复制**: 通过提取现有图纸的完整几何属性(包括**每一根线段**和每一个文本的**对齐方式**),模板可以确保新生成的表格在每一个细节上都与原始设计意图保持一致,完美处理合并单元格等复杂样式。
- **精确复制**: 脚本会提取出表头区域内所有的文字、线条及其完整的几何属性坐标、尺寸、对齐方式、颜色、图层等确保生成的表头与原始设计100%一致 - **自动化**: 一旦模板建立就可以用程序结合任意数据源Excel, 数据库等),批量、快速、无差错地生成符合规范的图纸。
- **数据模板化**: 脚本会分析紧邻表头上方的第一行数据,并将其样式(文字的相对位置、字体、高度等)作为后续所有数据行的统一模板 - **模板复用与拆分**: `bom_template.json` 文件本身也可以被视为一个基础模板。在实际应用中,可以将其中定义的 `header_definition` (表头) 和 `column_definitions` (列样式) 拆分保存。这样,同一个表头可以方便地与多批次、不同来源的数据结合,生成不同的物料清单,进一步提高自动化效率。
## 📘 核心操作指南 (ezdxf) ## 📘 核心操作指南 (ezdxf)

View File

@ -1,15 +0,0 @@
{
"row_height": 8.0,
"column_definitions": [
{
"name": "基础模板组件",
"relative_x_start": 20.0,
"text_definitions": []
},
{
"name": "组合件",
"relative_x_start": 65.0,
"text_definitions": []
}
]
}

View File

@ -6,8 +6,11 @@
"relative_x_start": 0.01, "relative_x_start": 0.01,
"text_definitions": [ "text_definitions": [
{ {
"data_key": "1", "data_key": "main",
"relative_pos": [5.82, 2.38], "relative_pos": [
5.82,
2.38
],
"alignment": "BOTTOM_LEFT", "alignment": "BOTTOM_LEFT",
"height": 3.5, "height": 3.5,
"style": "HZTXT", "style": "HZTXT",
@ -22,7 +25,10 @@
"text_definitions": [ "text_definitions": [
{ {
"data_key": "main", "data_key": "main",
"relative_pos": [14.77, 2.07], "relative_pos": [
14.77,
2.07
],
"alignment": "BOTTOM_CENTER", "alignment": "BOTTOM_CENTER",
"height": 3.0, "height": 3.0,
"style": "HZTXT", "style": "HZTXT",
@ -32,12 +38,15 @@
] ]
}, },
{ {
"name": "名 称", "name": "名 称",
"relative_x_start": 45.01, "relative_x_start": 45.01,
"text_definitions": [ "text_definitions": [
{ {
"data_key": "chinese_name", "data_key": "chinese_name",
"relative_pos": [1.98, 3.58], "relative_pos": [
1.98,
3.58
],
"alignment": "BOTTOM_LEFT", "alignment": "BOTTOM_LEFT",
"height": 3.5, "height": 3.5,
"style": "HZ", "style": "HZ",
@ -46,7 +55,10 @@
}, },
{ {
"data_key": "english_name", "data_key": "english_name",
"relative_pos": [1.68, 0.92], "relative_pos": [
1.68,
0.92
],
"alignment": "BOTTOM_LEFT", "alignment": "BOTTOM_LEFT",
"height": 2.0, "height": 2.0,
"style": "HZTXT", "style": "HZTXT",
@ -61,7 +73,10 @@
"text_definitions": [ "text_definitions": [
{ {
"data_key": "main", "data_key": "main",
"relative_pos": [4.97, 2.28], "relative_pos": [
4.97,
2.28
],
"alignment": "BOTTOM_CENTER", "alignment": "BOTTOM_CENTER",
"height": 3.0, "height": 3.0,
"style": "HZTXT", "style": "HZTXT",
@ -76,7 +91,10 @@
"text_definitions": [ "text_definitions": [
{ {
"data_key": "main", "data_key": "main",
"relative_pos": [15.16, 2.04], "relative_pos": [
15.16,
2.04
],
"alignment": "BOTTOM_CENTER", "alignment": "BOTTOM_CENTER",
"height": 3.5, "height": 3.5,
"style": "HZ", "style": "HZ",
@ -88,17 +106,7 @@
{ {
"name": "单", "name": "单",
"relative_x_start": 140.01, "relative_x_start": 140.01,
"text_definitions": [ "text_definitions": []
{
"data_key": "main",
"relative_pos": [5.0, 2.34],
"alignment": "BOTTOM_CENTER",
"height": 3.0,
"style": "HZ",
"layer": "6文字层",
"color": 256
}
]
}, },
{ {
"name": "总", "name": "总",
@ -106,7 +114,10 @@
"text_definitions": [ "text_definitions": [
{ {
"data_key": "main", "data_key": "main",
"relative_pos": [5.06, 2.34], "relative_pos": [
5.06,
2.34
],
"alignment": "BOTTOM_CENTER", "alignment": "BOTTOM_CENTER",
"height": 3.0, "height": 3.0,
"style": "HZ", "style": "HZ",
@ -118,84 +129,7 @@
{ {
"name": "备 注", "name": "备 注",
"relative_x_start": 160.01, "relative_x_start": 160.01,
"text_definitions": [ "text_definitions": []
{
"data_key": "main",
"relative_pos": [2.0, 3.58],
"alignment": "BOTTOM_LEFT",
"height": 3.5,
"style": "HZ",
"layer": "6文字层",
"color": 256
}
]
}
],
"data_rows": [
{
"件 号": "1",
"图号或标准号": "A5-001-01",
"名 称": {
"chinese_name": "支撑板",
"english_name": "Support Plate"
},
"数量": 1,
"材 料": "SS400",
"单": 2.5,
"总": 2.5,
"备 注": "激光切割"
},
{
"件 号": "2",
"图号或标准号": "A5-001-02",
"名 称": {
"chinese_name": "轴",
"english_name": "Shaft"
},
"数量": 2,
"材 料": "40Cr",
"单": 1.2,
"总": 2.4,
"备 注": "热处理"
},
{
"件 号": "3",
"图号或标准号": "GB/T 5783-2000",
"名 称": {
"chinese_name": "六角头螺栓",
"english_name": "Hexagon Head Bolt"
},
"数量": 4,
"材 料": "45#",
"单": 0.05,
"总": 0.20,
"备 注": "M8x20"
},
{
"件 号": "4",
"图号或标准号": "GB/T 97.1-2002",
"名 称": {
"chinese_name": "平垫圈",
"english_name": "Plain Washer"
},
"数量": 8,
"材 料": "304SS",
"单": 0.01,
"总": 0.08,
"备 注": "标准件"
},
{
"件 号": "5",
"图号或标准号": "JB/T 88-1994",
"名 称": {
"chinese_name": "六角螺母",
"english_name": "Hexagon Nut"
},
"数量": 4,
"材 料": "Q235",
"单": 0.02,
"总": 0.08,
"备 注": ""
} }
] ]
} }

View File

@ -19,16 +19,34 @@ ALIGNMENT_MAP = {
} }
# ============================================================================== # ==============================================================================
# 1. SAMPLE DATA (This is now loaded from an external file) # 1. SAMPLE DATA (This would normally come from Excel, a database, etc.)
# ============================================================================== # ==============================================================================
def load_bom_data(file_path): BOM_DATA = [
"""Loads BOM data from a JSON file.""" {
try: "件 号": {"main": "1"},
with open(file_path, 'r', encoding='utf-8') as f: "图号或标准号": {"main": "JB/T XXXX"},
return json.load(f) "名 称": {
except (IOError, json.JSONDecodeError) as e: "chinese_name": "新零件-A",
print(f"Error loading BOM data from {file_path}: {e}") "english_name": "NEW PART-A",
return None "specification": "M20x150"
},
"数量": {"main": "4"},
"材 料": {"main": "Q345R"},
"备 注": {"main": "自定义备注"}
},
{
"件 号": {"main": "2"},
"图号或标准号": {"main": "GB/T YYYY"},
"名 称": {
"chinese_name": "新零件-B",
"english_name": "NEW PART-B",
"specification": "DN200"
},
"数量": {"main": "2"},
"材 料": {"main": "S30408"},
"备 注": {"main": ""}
}
]
# ============================================================================== # ==============================================================================
# 2. DRAWING LOGIC # 2. DRAWING LOGIC
@ -97,70 +115,40 @@ def draw_table_from_template(msp, start_pos, header_template, columns_template,
abs_y = header_bottom_y + text_def['relative_pos'][1] abs_y = header_bottom_y + text_def['relative_pos'][1]
add_aligned_text(msp, text_def['content'], (abs_x, abs_y), text_def) add_aligned_text(msp, text_def['content'], (abs_x, abs_y), text_def)
# --- Draw Data Rows (growing upwards) --- # --- 2. Draw Data Rows upwards ---
current_y = header_bottom_y + header_height header_top_y = start_pos.y + header_height
current_y = header_top_y
# Correctly iterate through all data rows and column definitions for data_row in data_rows: # Normal order, drawing upwards
for i, data_row in enumerate(data_rows): row_bottom_y = current_y
row_y_bottom = current_y + (i * row_height) row_top_y = row_bottom_y + row_height
# Iterate through all column definitions from the template for each row # Draw top horizontal line for the row
msp.add_line((start_pos.x, row_top_y), (start_pos.x + table_width, row_top_y))
# Draw vertical divider lines for the row
for x_rel in col_boundaries:
msp.add_line((start_pos.x + x_rel, row_bottom_y), (start_pos.x + x_rel, row_top_y))
# Draw text for each column in the row using the new list structure
for col_def in col_defs: for col_def in col_defs:
col_name = col_def["name"] col_name = col_def["name"]
col_start_x_rel = col_def["relative_x_start"]
# Check if the data for this column exists in the current data_row
if col_name in data_row: if col_name in data_row:
cell_data = data_row[col_name]
col_start_x_rel = col_def["relative_x_start"]
# A column can have multiple text fields (e.g., main and sub-text)
for text_def in col_def["text_definitions"]: for text_def in col_def["text_definitions"]:
data_key = text_def["data_key"] data_key = text_def['data_key']
text_content = data_row[col_name].get(data_key, "")
if not text_content:
continue
# Check if the specific data_key exists for the cell # Calculate absolute position for the text's alignment point
if data_key in cell_data: # abs_x = table_start + column_start + text_start_in_column
content = str(cell_data[data_key]) abs_x = start_pos.x + col_start_x_rel + text_def['relative_pos'][0]
abs_y = row_bottom_y + text_def['relative_pos'][1]
# --- Calculate Absolute Position ---
# Text's relative position is relative to the column's start add_aligned_text(msp, text_content, (abs_x, abs_y), text_def)
abs_x = start_pos.x + col_start_x_rel + text_def['relative_pos'][0]
abs_y = row_y_bottom + text_def['relative_pos'][1]
alignment_str = text_def.get("alignment", "BOTTOM_LEFT")
alignment = ALIGNMENT_MAP.get(alignment_str, TextEntityAlignment.BOTTOM_LEFT)
dxfattribs = { current_y = row_top_y
'style': text_def['style'],
'height': text_def['height'],
'color': text_def['color'],
'width': 0.7 # Ensure width factor is applied
}
# Add the text entity with correct placement
msp.add_text(
content,
dxfattribs=dxfattribs
).set_placement(
(abs_x, abs_y),
align=alignment
)
# --- Draw Row and Column Lines ---
# (This part seems correct, but we'll double check if text fix doesn't solve all issues)
num_data_rows = len(data_rows)
table_height = header_height + num_data_rows * row_height
table_base_y = start_pos.y
# Draw horizontal lines for each data row
for i in range(num_data_rows + 1):
y = table_base_y + header_height + i * row_height
msp.add_line((start_pos.x, y), (start_pos.x + table_width, y))
# Draw vertical lines based on column boundaries
for x_rel in col_boundaries:
x_abs = start_pos.x + x_rel
# CORRECTED: Vertical lines should start from the top of the header, not the bottom of the table.
msp.add_line((x_abs, table_base_y + header_height), (x_abs, table_base_y + table_height))
def add_aligned_text(msp, content, point, text_def): def add_aligned_text(msp, content, point, text_def):
"""Adds a TEXT entity with specified alignment.""" """Adds a TEXT entity with specified alignment."""
@ -184,23 +172,21 @@ def add_aligned_text(msp, content, point, text_def):
# ============================================================================== # ==============================================================================
def main(): def main():
parser = argparse.ArgumentParser(description="Draw a BOM table in a DXF file based on JSON templates.") parser = argparse.ArgumentParser(description="Draw a BOM table in a DXF file based on JSON templates.")
# Input files
parser.add_argument("source_dxf", help="Path to the source DXF file to read.") parser.add_argument("source_dxf", help="Path to the source DXF file to read.")
parser.add_argument("header_template", help="Path to the header template JSON file.")
parser.add_argument("columns_template", help="Path to the columns template JSON file.")
parser.add_argument("data_json", help="Path to the BOM data JSON file.")
# Output file
parser.add_argument("output_dxf", help="Path to the output DXF file to write.") parser.add_argument("output_dxf", help="Path to the output DXF file to write.")
# Optional coordinates
parser.add_argument("--x", type=float, default=260.0, help="The X coordinate for the table's bottom-left insertion point.")
parser.add_argument("--y", type=float, default=50.0, help="The Y coordinate for the table's bottom-left insertion point.")
args = parser.parse_args() args = parser.parse_args()
# Get the absolute path to the directory where this script is located
script_dir = os.path.dirname(os.path.abspath(__file__))
header_template_path = os.path.join(script_dir, "header_template.json")
columns_template_path = os.path.join(script_dir, "columns_template.json")
# --- Load Templates --- # --- Load Templates ---
try: try:
with open(args.header_template, 'r', encoding='utf-8') as f: with open(header_template_path, 'r', encoding='utf-8') as f:
header_template = json.load(f) header_template = json.load(f)
with open(args.columns_template, 'r', encoding='utf-8') as f: with open(columns_template_path, 'r', encoding='utf-8') as f:
columns_template = json.load(f) columns_template = json.load(f)
except (IOError, json.JSONDecodeError) as e: except (IOError, json.JSONDecodeError) as e:
print(f"Error reading template files: {e}") print(f"Error reading template files: {e}")
@ -224,15 +210,11 @@ def main():
print(f"An unexpected error occurred: {e}") print(f"An unexpected error occurred: {e}")
return return
# --- Load Data ---
bom_data = load_bom_data(args.data_json)
if bom_data is None:
return
# --- Draw Table --- # --- Draw Table ---
print("Drawing table from templates...") print("Drawing table from templates...")
start_position = Vec3(args.x, args.y) # Using a fixed start position for predictability
draw_table_from_template(msp, start_position, header_template, columns_template, bom_data) start_position = Vec3(260, 50)
draw_table_from_template(msp, start_position, header_template, columns_template, BOM_DATA)
# --- Save Output --- # --- Save Output ---
try: try:

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,5 @@
import json import json
import os import os
import argparse
def find_table_boundaries(lines): def find_table_boundaries(lines):
""" """
@ -60,129 +59,6 @@ def find_table_boundaries(lines):
# Return boundaries and the absolute X coords of vertical lines # Return boundaries and the absolute X coords of vertical lines
return boundaries, vert_lines_x return boundaries, vert_lines_x
def find_table_boundaries_from_texts(texts, lines, y_cluster_tolerance=2.0, expansion_margin=2.0, header_cluster_gap_tolerance=5.0):
"""
Finds table boundaries by identifying the densest group of adjacent text clusters (multi-line header),
then finds the closest data row cluster (either above or below).
"""
if not texts:
return None, None
# 1. Cluster texts by their Y-coordinate to find "rows" of text.
texts.sort(key=lambda t: t['insert_point'][1])
y_clusters = []
if texts:
current_cluster = [texts[0]]
for i in range(1, len(texts)):
if abs(texts[i]['insert_point'][1] - current_cluster[-1]['insert_point'][1]) < y_cluster_tolerance:
current_cluster.append(texts[i])
else:
y_clusters.append(current_cluster)
current_cluster = [texts[i]]
y_clusters.append(current_cluster)
if not y_clusters:
return None, None
# 2. Find the densest *group* of adjacent clusters (our multi-line header).
best_header_group = []
max_density = 0
for i in range(len(y_clusters)):
current_group = [y_clusters[i]]
current_density = len(y_clusters[i])
# Look ahead to see if the next clusters are close enough to be part of the same header
for j in range(i + 1, len(y_clusters)):
# Calculate vertical gap between the last cluster in the group and the next one
last_cluster_avg_y = sum(t['insert_point'][1] for t in current_group[-1]) / len(current_group[-1])
next_cluster_avg_y = sum(t['insert_point'][1] for t in y_clusters[j]) / len(y_clusters[j])
if abs(next_cluster_avg_y - last_cluster_avg_y) < header_cluster_gap_tolerance:
current_group.append(y_clusters[j])
current_density += len(y_clusters[j])
else:
break # The gap is too large, this block has ended
if current_density > max_density:
max_density = current_density
best_header_group = current_group
if not best_header_group:
print("Warning: Could not identify a header group.")
return None, None
# 3. All texts within the identified header group belong to the header.
all_header_texts = [text for cluster in best_header_group for text in cluster]
# 4. Find the closest data row (can be above or below the header).
header_indices = {y_clusters.index(cluster) for cluster in best_header_group}
first_data_row_cluster = None
min_dist = float('inf')
for i, cluster in enumerate(y_clusters):
if i not in header_indices:
# It's a data row candidate. Find its distance to the header block.
header_min_y = min(t['insert_point'][1] for t in all_header_texts)
header_max_y = max(t['insert_point'][1] for t in all_header_texts)
cluster_avg_y = sum(t['insert_point'][1] for t in cluster) / len(cluster)
dist = min(abs(cluster_avg_y - header_min_y), abs(cluster_avg_y - header_max_y))
if dist < min_dist:
min_dist = dist
first_data_row_cluster = cluster
data_start_y = None
if first_data_row_cluster:
data_start_y = first_data_row_cluster[0]['insert_point'][1]
else:
print("Warning: Could not automatically detect a data row near the header.")
# 5. Define boundaries based on the multi-line header text block.
min_x = min(t['insert_point'][0] for t in all_header_texts)
max_x = max(t['insert_point'][0] for t in all_header_texts)
min_y = min(t['insert_point'][1] for t in all_header_texts)
max_y = max(t['insert_point'][1] + t['height'] for t in all_header_texts)
# ... (The rest of the logic to find lines and define final bounds remains largely the same,
# but it will now operate on the correct header_texts and boundaries)
# Re-using the line-finding logic from the previous implementation
expansion_margin = 5.0 # Increase margin slightly for complex layouts
bbox_min_x, bbox_max_x = min_x - expansion_margin, max_x + expansion_margin
bbox_min_y, bbox_max_y = min_y - expansion_margin, max_y + expansion_margin
table_h_lines = [l for l in lines if (bbox_min_y < l['start'][1] < bbox_max_y and
bbox_min_y < l['end'][1] < bbox_max_y)]
table_v_lines = [l for l in lines if (bbox_min_x < l['start'][0] < bbox_max_x and
bbox_min_x < l['end'][0] < bbox_max_x)]
if not table_h_lines or not table_v_lines:
print("Warning: Could not find enough lines near the identified text header.")
return None, None
final_min_y = min(l['start'][1] for l in table_h_lines)
final_max_y = max(l['start'][1] for l in table_h_lines)
col_x_coords = set()
for line in table_v_lines:
if min(line['start'][1], line['end'][1]) < final_min_y + 1 and \
max(line['start'][1], line['end'][1]) > final_max_y - 1:
col_x_coords.add(round(line['start'][0], 2))
sorted_col_x = sorted(list(col_x_coords))
if not sorted_col_x:
return None, None
bounds = {
'y_min': final_min_y,
'y_max': final_max_y,
'x_min': sorted_col_x[0],
'x_max': sorted_col_x[-1],
'header_total_height': final_max_y - final_min_y,
'data_start_y': data_start_y
}
return bounds, sorted_col_x
def generate_header_template(data, bounds, col_x_coords_abs): def generate_header_template(data, bounds, col_x_coords_abs):
""" """
Generates the header part of the template from extracted entity data, Generates the header part of the template from extracted entity data,
@ -279,12 +155,7 @@ def generate_column_definitions(data, bounds, col_x_coords_abs, header_template)
col_names[i] = main_text['content'].strip() col_names[i] = main_text['content'].strip()
# --- Find text patterns in the first data row --- # --- Find text patterns in the first data row ---
first_data_row_y = bounds.get("data_start_y") first_data_row_y = bounds["data_start_y"]
if first_data_row_y is None:
print("Warning: No data row was found in the source DXF. No column definitions will be generated.")
return []
data_row_texts = [ data_row_texts = [
t for t in texts t for t in texts
if first_data_row_y < t['insert_point'][1] < first_data_row_y + 8.0 if first_data_row_y < t['insert_point'][1] < first_data_row_y + 8.0
@ -333,57 +204,52 @@ def generate_column_definitions(data, bounds, col_x_coords_abs, header_template)
def main(): def main():
parser = argparse.ArgumentParser(description="Generate modular header and column templates from a DXF entities JSON file.") source_json_path = os.path.join("03_Python_OpenSource_DXF", "Drawing1_entities.json")
parser.add_argument("source_json", help="Path to the source JSON file (digital snapshot).") header_template_path = os.path.join("03_Python_OpenSource_DXF", "header_template.json")
parser.add_argument("output_header_template", help="Path to write the output header_template.json.") columns_template_path = os.path.join("03_Python_OpenSource_DXF", "columns_template.json")
parser.add_argument("output_columns_template", help="Path to write the output columns_template.json.")
args = parser.parse_args()
if not os.path.exists(args.source_json): if not os.path.exists(source_json_path):
print(f"Error: Source JSON file not found at {args.source_json}") print(f"Error: Source JSON file not found at {source_json_path}")
return return
print(f"Reading entity data from {args.source_json}...") print(f"Reading entity data from {source_json_path}...")
with open(args.source_json, 'r', encoding='utf-8') as f: with open(source_json_path, 'r', encoding='utf-8') as f:
entity_data = json.load(f) entity_data = json.load(f)
print("Generating templates using text-based detection...") print("Generating templates...")
# USE THE NEW, ROBUST FUNCTION bounds, col_x_coords_abs = find_table_boundaries(entity_data["lines"])
bounds, col_x_coords_abs = find_table_boundaries_from_texts(entity_data.get("texts", []), entity_data.get("lines", []))
if not bounds or not col_x_coords_abs: if bounds and col_x_coords_abs:
print("Error: Could not determine table boundaries from the provided snapshot.") # 1. Generate and save the header template
print("Attempting to fall back to the old line-based method...") header_template = generate_header_template(entity_data, bounds, col_x_coords_abs)
bounds, col_x_coords_abs = find_table_boundaries(entity_data.get("lines", [])) if header_template:
if not bounds or not col_x_coords_abs: try:
print("Fallback method also failed. Aborting.") with open(header_template_path, 'w', encoding='utf-8') as f:
return json.dump(header_template, f, ensure_ascii=False, indent=2)
print(f"Successfully generated header template: {header_template_path}")
except IOError as e:
print(f"Error writing header template file: {e}")
# 1. Generate and save the header template # 2. Generate and save the columns template
header_template = generate_header_template(entity_data, bounds, col_x_coords_abs) # We need the header text to name the columns correctly
if header_template: if not header_template:
try: header_template = generate_header_template(entity_data, bounds, col_x_coords_abs)
with open(args.output_header_template, 'w', encoding='utf-8') as f:
json.dump(header_template, f, ensure_ascii=False, indent=2) column_definitions = generate_column_definitions(entity_data, bounds, col_x_coords_abs, header_template)
print(f"Successfully generated header template: {args.output_header_template}")
except IOError as e: # Create the final columns template structure
print(f"Error writing header template file: {e}") columns_template = {
"row_height": 8.0,
# 2. Generate and save the columns template "column_definitions": column_definitions
column_definitions = generate_column_definitions(entity_data, bounds, col_x_coords_abs, header_template) }
columns_template = { if column_definitions:
"row_height": header_template.get("row_height", 8.0), # Get row_height from header or default try:
"column_definitions": column_definitions with open(columns_template_path, 'w', encoding='utf-8') as f:
} json.dump(columns_template, f, ensure_ascii=False, indent=2)
print(f"Successfully generated columns template: {columns_template_path}")
if column_definitions: except IOError as e:
try: print(f"Error writing columns template file: {e}")
with open(args.output_columns_template, 'w', encoding='utf-8') as f:
json.dump(columns_template, f, ensure_ascii=False, indent=2)
print(f"Successfully generated columns template: {args.output_columns_template}")
except IOError as e:
print(f"Error writing columns template file: {e}")
if __name__ == "__main__": if __name__ == "__main__":
main() main()

View File

@ -1,418 +0,0 @@
{
"template_name": "标准物料清单-底部表头",
"row_height": 8.0,
"header_height": 20.0,
"column_boundaries": [
0.0,
45.0,
75.0,
94.99,
109.99,
134.99
],
"header_definition": {
"lines": [
{
"start": [
-20.0,
0.0
],
"end": [
160.0,
-0.0
]
},
{
"start": [
-20.01,
20.0
],
"end": [
159.9,
20.0
]
},
{
"start": [
75.0,
20.0
],
"end": [
75.0,
0.0
]
},
{
"start": [
94.99,
20.0
],
"end": [
94.99,
0.0
]
},
{
"start": [
45.0,
20.0
],
"end": [
45.0,
0.0
]
},
{
"start": [
0.0,
20.0
],
"end": [
0.0,
0.0
]
},
{
"start": [
109.99,
20.0
],
"end": [
109.99,
0.0
]
},
{
"start": [
134.99,
20.0
],
"end": [
134.99,
0.0
]
},
{
"start": [
-20.01,
12.0
],
"end": [
159.99,
12.0
]
},
{
"start": [
-20.0,
20.0
],
"end": [
-20.0,
0.0
]
},
{
"start": [
160.0,
20.0
],
"end": [
160.0,
-0.0
]
}
],
"texts": [
{
"content": "组合件",
"relative_pos": [
59.58,
15.64
],
"alignment": "BOTTOM_CENTER",
"height": 3.5,
"style": "HZ",
"layer": "6文字层",
"color": 256
},
{
"content": "基础模板组件",
"relative_pos": [
1.98,
15.59
],
"alignment": "BOTTOM_LEFT",
"height": 3.5,
"style": "HZ",
"layer": "6文字层",
"color": 256
},
{
"content": "/",
"relative_pos": [
101.68,
14.72
],
"alignment": "BOTTOM_LEFT",
"height": 3.0,
"style": "HZ",
"layer": "6文字层",
"color": 256
},
{
"content": "NCY24-S004-00",
"relative_pos": [
147.19,
14.58
],
"alignment": "BOTTOM_CENTER",
"height": 3.0,
"style": "HZTXT",
"layer": "6文字层",
"color": 256
},
{
"content": "NCY24-S004-04",
"relative_pos": [
122.19,
14.58
],
"alignment": "BOTTOM_CENTER",
"height": 3.0,
"style": "HZTXT",
"layer": "6文字层",
"color": 256
},
{
"content": "1147",
"relative_pos": [
80.74,
14.33
],
"alignment": "BOTTOM_LEFT",
"height": 3.0,
"style": "HZ",
"layer": "6文字层",
"color": 256
},
{
"content": "14",
"relative_pos": [
-9.96,
14.17
],
"alignment": "BOTTOM_CENTER",
"height": 3.0,
"style": "HZ",
"layer": "6文字层",
"color": 256
},
{
"content": "COMP.",
"relative_pos": [
59.58,
13.01
],
"alignment": "BOTTOM_CENTER",
"height": 2.0,
"style": "HZ",
"layer": "6文字层",
"color": 256
},
{
"content": "BASE TEMPLATE COMPONENT",
"relative_pos": [
1.98,
12.99
],
"alignment": "BOTTOM_LEFT",
"height": 2.0,
"style": "HZ",
"layer": "6文字层",
"color": 256
},
{
"content": "装配图号",
"relative_pos": [
142.59,
10.3
],
"alignment": "TOP_LEFT",
"height": 3.499649999999999,
"style": "HZ",
"layer": "6文字层",
"color": 256
},
{
"content": "所在图号",
"relative_pos": [
117.23,
6.25
],
"alignment": "BOTTOM_LEFT",
"height": 3.499649999999999,
"style": "HZ",
"layer": "6文字层",
"color": 256
},
{
"content": "比 例",
"relative_pos": [
99.09,
6.25
],
"alignment": "BOTTOM_LEFT",
"height": 3.499649999999999,
"style": "HZ",
"layer": "6文字层",
"color": 256
},
{
"content": "(kg)",
"relative_pos": [
84.32,
6.25
],
"alignment": "BOTTOM_LEFT",
"height": 2.9997,
"style": "HZ",
"layer": "6文字层",
"color": 256
},
{
"content": "质量",
"relative_pos": [
78.7,
6.25
],
"alignment": "BOTTOM_LEFT",
"height": 3.499649999999999,
"style": "HZ",
"layer": "6文字层",
"color": 256
},
{
"content": "材 料",
"relative_pos": [
55.65,
6.25
],
"alignment": "BOTTOM_LEFT",
"height": 3.499649999999999,
"style": "HZ",
"layer": "6文字层",
"color": 256
},
{
"content": "名 称",
"relative_pos": [
15.6,
6.25
],
"alignment": "BOTTOM_LEFT",
"height": 3.499649999999999,
"style": "HZ",
"layer": "6文字层",
"color": 256
},
{
"content": "件 号",
"relative_pos": [
-13.84,
6.25
],
"alignment": "BOTTOM_LEFT",
"height": 3.499649999999999,
"style": "HZ",
"layer": "6文字层",
"color": 256
},
{
"content": "ASSY. DWG. NO.",
"relative_pos": [
140.5,
2.0
],
"alignment": "BOTTOM_LEFT",
"height": 1.999799999999999,
"style": "HZ",
"layer": "6文字层",
"color": 256
},
{
"content": "DWG. NO.",
"relative_pos": [
118.15,
1.89
],
"alignment": "BOTTOM_LEFT",
"height": 1.999799999999999,
"style": "HZ",
"layer": "6文字层",
"color": 256
},
{
"content": "SCALE",
"relative_pos": [
99.04,
1.89
],
"alignment": "BOTTOM_LEFT",
"height": 1.999799999999999,
"style": "HZ",
"layer": "6文字层",
"color": 256
},
{
"content": "MASS",
"relative_pos": [
82.14,
1.89
],
"alignment": "BOTTOM_LEFT",
"height": 1.999799999999999,
"style": "HZ",
"layer": "6文字层",
"color": 256
},
{
"content": "MAT'L",
"relative_pos": [
56.2,
1.89
],
"alignment": "BOTTOM_LEFT",
"height": 1.999799999999999,
"style": "HZ",
"layer": "6文字层",
"color": 256
},
{
"content": "PARTS. NAME.",
"relative_pos": [
15.45,
1.89
],
"alignment": "BOTTOM_LEFT",
"height": 1.999799999999999,
"style": "HZ",
"layer": "6文字层",
"color": 256
},
{
"content": "PART NO.",
"relative_pos": [
-14.7,
1.89
],
"alignment": "BOTTOM_LEFT",
"height": 1.999799999999999,
"style": "HZ",
"layer": "6文字层",
"color": 256
}
]
},
"column_definitions": {}
}

View File

@ -1,437 +0,0 @@
{
"lines": [
{
"start": [
23.73854350283364,
19.93336115189902
],
"end": [
203.7446562383284,
19.93336115189174
],
"layer": "Cont",
"color": 256,
"linetype": "BYLAYER"
},
{
"start": [
23.72785958692839,
39.9354976031035
],
"end": [
203.6368268983642,
39.9354976031035
],
"layer": "1轮廓实线层",
"color": 256,
"linetype": "BYLAYER"
},
{
"start": [
118.7363650645911,
39.93549760309623
],
"end": [
118.7363650645911,
19.93749760309663
],
"layer": "1轮廓实线层",
"color": 256,
"linetype": "BYLAYER"
},
{
"start": [
138.7343650645907,
39.93549760309623
],
"end": [
138.7343650645907,
19.93749760309663
],
"layer": "1轮廓实线层",
"color": 256,
"linetype": "BYLAYER"
},
{
"start": [
88.73936506458813,
39.93549760309623
],
"end": [
88.73936506458813,
19.93749760309663
],
"layer": "1轮廓实线层",
"color": 256,
"linetype": "BYLAYER"
},
{
"start": [
43.74386506459086,
39.9354976031035
],
"end": [
43.74386506459086,
19.93749760309663
],
"layer": "1轮廓实线层",
"color": 256,
"linetype": "BYLAYER"
},
{
"start": [
153.7328650645922,
39.93549760309623
],
"end": [
153.7328650645922,
19.93749760309663
],
"layer": "1轮廓实线层",
"color": 256,
"linetype": "BYLAYER"
},
{
"start": [
178.7303650645899,
39.93549760309623
],
"end": [
178.7303650645899,
19.93749760309663
],
"layer": "1轮廓实线层",
"color": 256,
"linetype": "BYLAYER"
},
{
"start": [
23.72785958692839,
31.93829694832027
],
"end": [
203.7278612820573,
31.93829694832027
],
"layer": "1轮廓实线层",
"color": 256,
"linetype": "BYLAYER"
},
{
"start": [
23.73854350283364,
39.93136115190589
],
"end": [
23.73854350283364,
19.93336115189902
],
"layer": "1轮廓实线层",
"color": 256,
"linetype": "BYLAYER"
},
{
"start": [
203.7446562383284,
39.93136115189861
],
"end": [
203.7446562383284,
19.93336115189174
],
"layer": "1轮廓实线层",
"color": 256,
"linetype": "BYLAYER"
}
],
"texts": [
{
"content": "1147",
"insert_point": [
124.4764950550452,
34.2627779988801
],
"alignment": "BOTTOM_LEFT",
"height": 3.0,
"style": "HZ",
"layer": "6文字层",
"color": 256
},
{
"content": "14",
"insert_point": [
33.77901289916735,
34.10529139576181
],
"alignment": "BOTTOM_CENTER",
"height": 3.0,
"style": "HZ",
"layer": "6文字层",
"color": 256
},
{
"content": "质量",
"insert_point": [
122.4403602387228,
26.18136891473921
],
"alignment": "BOTTOM_LEFT",
"height": 3.499649999999999,
"style": "HZ",
"layer": "6文字层",
"color": 256
},
{
"content": "PART NO.",
"insert_point": [
29.0369009548358,
21.82782019717786
],
"alignment": "BOTTOM_LEFT",
"height": 1.999799999999999,
"style": "HZ",
"layer": "6文字层",
"color": 256
},
{
"content": "件 号",
"insert_point": [
29.89970835921667,
26.18136891473921
],
"alignment": "BOTTOM_LEFT",
"height": 3.499649999999999,
"style": "HZ",
"layer": "6文字层",
"color": 256
},
{
"content": "PARTS. NAME.",
"insert_point": [
59.19042413651005,
21.82782019717786
],
"alignment": "BOTTOM_LEFT",
"height": 1.999799999999999,
"style": "HZ",
"layer": "6文字层",
"color": 256
},
{
"content": "名 称",
"insert_point": [
59.33564142979412,
26.18136891473921
],
"alignment": "BOTTOM_LEFT",
"height": 3.499649999999999,
"style": "HZ",
"layer": "6文字层",
"color": 256
},
{
"content": "材 料",
"insert_point": [
99.3943741164021,
26.18136891473921
],
"alignment": "BOTTOM_LEFT",
"height": 3.499649999999999,
"style": "HZ",
"layer": "6文字层",
"color": 256
},
{
"content": "MAT'L",
"insert_point": [
99.9354408084455,
21.82782019717786
],
"alignment": "BOTTOM_LEFT",
"height": 1.999799999999999,
"style": "HZ",
"layer": "6文字层",
"color": 256
},
{
"content": "DWG. NO.",
"insert_point": [
161.8937869091223,
21.82782019717786
],
"alignment": "BOTTOM_LEFT",
"height": 1.999799999999999,
"style": "HZ",
"layer": "6文字层",
"color": 256
},
{
"content": "所在图号",
"insert_point": [
160.9703478429856,
26.18136891474649
],
"alignment": "BOTTOM_LEFT",
"height": 3.499649999999999,
"style": "HZ",
"layer": "6文字层",
"color": 256
},
{
"content": "SCALE",
"insert_point": [
142.7826683415896,
21.82782019717786
],
"alignment": "BOTTOM_LEFT",
"height": 1.999799999999999,
"style": "HZ",
"layer": "6文字层",
"color": 256
},
{
"content": "比 例",
"insert_point": [
142.8276929091498,
26.18136891473921
],
"alignment": "BOTTOM_LEFT",
"height": 3.499649999999999,
"style": "HZ",
"layer": "6文字层",
"color": 256
},
{
"content": "MASS",
"insert_point": [
125.8844440770899,
21.82782019717786
],
"alignment": "BOTTOM_LEFT",
"height": 1.999799999999999,
"style": "HZ",
"layer": "6文字层",
"color": 256
},
{
"content": "(kg)",
"insert_point": [
128.0551982187298,
26.18136891473921
],
"alignment": "BOTTOM_LEFT",
"height": 2.9997,
"style": "HZ",
"layer": "6文字层",
"color": 256
},
{
"content": "ASSY. DWG. NO.",
"insert_point": [
184.2388684480884,
21.92933849586632
],
"alignment": "BOTTOM_LEFT",
"height": 1.999799999999999,
"style": "HZ",
"layer": "6文字层",
"color": 256
},
{
"content": "装配图号",
"insert_point": [
186.3276523504228,
30.2371262225214
],
"alignment": "TOP_LEFT",
"height": 3.499649999999999,
"style": "HZ",
"layer": "6文字层",
"color": 256
},
{
"content": "/",
"insert_point": [
145.4193566768135,
34.65570350136466
],
"alignment": "BOTTOM_LEFT",
"height": 3.0,
"style": "HZ",
"layer": "6文字层",
"color": 256
},
{
"content": "组合件",
"insert_point": [
103.3210070273762,
35.57388864299799
],
"alignment": "BOTTOM_CENTER",
"height": 3.5,
"style": "HZ",
"layer": "6文字层",
"color": 256
},
{
"content": "COMP.",
"insert_point": [
103.3210070273762,
32.93840666469703
],
"alignment": "BOTTOM_CENTER",
"height": 2.0,
"style": "HZ",
"layer": "6文字层",
"color": 256
},
{
"content": "BASE TEMPLATE COMPONENT",
"insert_point": [
45.72193970487933,
32.92432577639636
],
"alignment": "BOTTOM_LEFT",
"height": 2.0,
"style": "HZ",
"layer": "6文字层",
"color": 256
},
{
"content": "基础模板组件",
"insert_point": [
45.72193970487933,
35.52149788173199
],
"alignment": "BOTTOM_LEFT",
"height": 3.5,
"style": "HZ",
"layer": "6文字层",
"color": 256
},
{
"content": "NCY24-S004-04",
"insert_point": [
165.9288823862643,
34.51250068956631
],
"alignment": "BOTTOM_CENTER",
"height": 3.0,
"style": "HZTXT",
"layer": "6文字层",
"color": 256
},
{
"content": "NCY24-S004-00",
"insert_point": [
190.926382386262,
34.51250068956631
],
"alignment": "BOTTOM_CENTER",
"height": 3.0,
"style": "HZTXT",
"layer": "6文字层",
"color": 256
}
]
}

View File

@ -1,4 +1,4 @@
ezdxf==1.1.4 ezdxf>=1.0.0
pytest>=7.0.0 pytest>=7.0.0
pytest-cov>=4.0.0 pytest-cov>=4.0.0
black>=22.0.0 black>=22.0.0
@ -6,6 +6,4 @@ flake8>=6.0.0
mypy>=1.0.0 mypy>=1.0.0
pandas>=1.5.0 # 如果需要处理Excel数据 pandas>=1.5.0 # 如果需要处理Excel数据
openpyxl>=3.0.0 # Excel文件处理 openpyxl>=3.0.0 # Excel文件处理
matplotlib==3.7.5 # 如果需要图表功能 matplotlib>=3.6.0 # 如果需要图表功能
beautifulsoup4==4.12.3
lxml==5.2.2

View File

@ -1,52 +0,0 @@
[
{
"件 号": {"main": "1"},
"图号或标准号": {"main": "JB/T XXXX"},
"名 称": {"chinese_name": "新零件-A", "english_name": "NEW PART-A"},
"数量": {"main": "4"},
"材 料": {"main": "Q345R"},
"单": {"main": "12.5"},
"总": {"main": "50.0"},
"备 注": {"main": "自定义备注A"}
},
{
"件 号": {"main": "2"},
"图号或标准号": {"main": "GB/T YYYY"},
"名 称": {"chinese_name": "新零件-B", "english_name": "NEW PART-B"},
"数量": {"main": "2"},
"材 料": {"main": "S30408"},
"单": {"main": "8.2"},
"总": {"main": "16.4"},
"备 注": {"main": ""}
},
{
"件 号": {"main": "3"},
"图号或标准号": {"main": "JB/T ZZZZ"},
"名 称": {"chinese_name": "新零件-C", "english_name": "NEW PART-C"},
"数量": {"main": "10"},
"材 料": {"main": "20#"},
"单": {"main": "1.5"},
"总": {"main": "15.0"},
"备 注": {"main": "自定义备注C"}
},
{
"件 号": {"main": "4"},
"图号或标准号": {"main": "GB/T WWWW"},
"名 称": {"chinese_name": "新零件-D", "english_name": "NEW PART-D"},
"数量": {"main": "8"},
"材 料": {"main": "35CrMo"},
"单": {"main": "3.0"},
"总": {"main": "24.0"},
"备 注": {"main": ""}
},
{
"件 号": {"main": "5"},
"图号或标准号": {"main": "HG/T VVVV"},
"名 称": {"chinese_name": "新零件-E", "english_name": "NEW PART-E"},
"数量": {"main": "1"},
"材 料": {"main": "HT250"},
"单": {"main": "150.0"},
"总": {"main": "150.0"},
"备 注": {"main": "自定义备注E"}
}
]

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -13,12 +13,11 @@ TEST_OUTPUT_DIR = os.path.join("03_Python_OpenSource_DXF", "test_results")
TEST_DXF_OUTPUT = os.path.join(TEST_OUTPUT_DIR, "test_table_output.dxf") TEST_DXF_OUTPUT = os.path.join(TEST_OUTPUT_DIR, "test_table_output.dxf")
TEST_JSON_SNAPSHOT = os.path.join(TEST_OUTPUT_DIR, "test_table_snapshot.json") TEST_JSON_SNAPSHOT = os.path.join(TEST_OUTPUT_DIR, "test_table_snapshot.json")
EMPTY_DXF_TEMPLATE = os.path.join("03_Python_OpenSource_DXF", "empty_template.dxf") EMPTY_DXF_TEMPLATE = os.path.join("03_Python_OpenSource_DXF", "empty_template.dxf")
TEST_DATA_FILE = os.path.join("03_Python_OpenSource_DXF", "test_bom_data.json") # New data file
# Expected values for verification # Expected values for verification
EXPECTED_DATA_ROWS = 5 EXPECTED_DATA_ROWS = 5
EXPECTED_HEADER_TEXTS = 18 # As counted from the snapshot EXPECTED_HEADER_TEXTS = 13
EXPECTED_COLUMNS = 8 # As defined in the columns template EXPECTED_COLUMNS = 7
def setup_test_environment(): def setup_test_environment():
"""Create an empty DXF for drawing and ensure the test output directory exists.""" """Create an empty DXF for drawing and ensure the test output directory exists."""
@ -50,25 +49,31 @@ def run_tests():
print("\n--- Phase 1: Setup Test Environment ---") print("\n--- Phase 1: Setup Test Environment ---")
setup_test_environment() setup_test_environment()
print(f"\n--- Phase 2: Drawing Table into a Clean DXF using Test Data ---") # We need to modify the draw script to accept input/output paths
# Add coordinates to the drawing command for the test # For now, let's assume it's modified. We will edit it next.
draw_args = [ # This test will fail initially.
EMPTY_DXF_TEMPLATE,
TEST_DXF_OUTPUT, print(f"\n--- Phase 2: Drawing Table into a Clean DXF ---")
TEST_DATA_FILE, # We will need to modify draw_table_from_template.py to accept arguments
"--x", "100", # for input and output files to make it testable.
"--y", "150" # Let's add a placeholder for this call. This part will be implemented next.
]
success = run_script(DRAW_SCRIPT, draw_args) # For the purpose of creating the file, I'll temporarily modify the draw script
if not success: # in the next step to point to our test files.
print("Test Failed: Drawing script failed to execute.") # After that, we'll parameterize it.
return
# Placeholder for the drawing call
# success = run_script(DRAW_SCRIPT, [EMPTY_DXF_TEMPLATE, TEST_DXF_OUTPUT])
# if not success:
# print("Test Failed: Drawing script failed to execute.")
# return
print(f"\n--- Phase 3: Extracting Entities from Generated DXF ---") print(f"\n--- Phase 3: Extracting Entities from Generated DXF ---")
success = run_script(EXTRACT_SCRIPT, [TEST_DXF_OUTPUT, TEST_JSON_SNAPSHOT]) # We also need to modify extract_entities_to_json.py to be parameterizable
if not success: # success = run_script(EXTRACT_SCRIPT, [TEST_DXF_OUTPUT, TEST_JSON_SNAPSHOT])
print("Test Failed: Extraction script failed to execute.") # if not success:
return # print("Test Failed: Extraction script failed to execute.")
# return
print("\n--- Phase 4: Verifying the Extracted Data ---") print("\n--- Phase 4: Verifying the Extracted Data ---")
try: try:
@ -79,29 +84,17 @@ def run_tests():
return return
num_texts = len(data.get("texts", [])) num_texts = len(data.get("texts", []))
num_lines = len(data.get("lines", []))
# --- Verification Logic --- # Verification logic will be added here once we have a generated file.
# The '名称' column has two text entities per row. print("Verification logic pending implementation.")
# Total columns with text definitions: 件号, 图号, 名称(2), 数量, 材料, 总. (7 text fields per row)
# The template defines 8 columns, but '单' and '备注' have no text_definitions. Let's adjust.
# A manual check of columns_template shows 7 text fields are defined per data row.
# Let's adjust EXPECTED_COLUMNS logic
expected_texts_per_row = 7 # Manually counted from columns_template.json
expected_total_texts = EXPECTED_HEADER_TEXTS + (EXPECTED_DATA_ROWS * expected_texts_per_row)
print(f"Found {num_texts} text entities in the output.")
print(f"Expected {expected_total_texts} text entities ({EXPECTED_HEADER_TEXTS} header + {EXPECTED_DATA_ROWS} rows * {expected_texts_per_row} fields/row).")
assert num_texts == expected_total_texts, \
f"Assertion Failed: Expected {expected_total_texts} texts, but found {num_texts}."
print("✅ Assertion Passed: The number of text entities is correct.")
# We can add more assertions here, e.g., for lines
# For now, verifying text count is a strong indicator of success.
print("\n--- Test Workflow Complete ---") print("\n--- Test Workflow Complete ---")
if __name__ == "__main__": if __name__ == "__main__":
run_tests() # This is just the initial structure. We will make the other scripts
# CLI-friendly and then complete this test runner.
print("This is a placeholder test script structure.")
# run_tests()

View File

@ -1,3 +1,3 @@
83500 83500
数智化吴鑫 数智化吴鑫
2025年9月10日 16:04:48 2025年9月9日 15:50:29

View File

@ -3,5 +3,5 @@
<username>83500</username> <username>83500</username>
<machinename>数智化吴鑫 </machinename> <machinename>数智化吴鑫 </machinename>
<fullname></fullname> <fullname></fullname>
<datetime>2025年9月10日 16:04:48</datetime> <datetime>2025年9月9日 15:50:29</datetime>
</whprops> </whprops>