diff --git a/backend/agent/skills/cad-engine/SKILL.md b/backend/agent/skills/cad-engine/SKILL.md index 9427d021..234460ee 100644 --- a/backend/agent/skills/cad-engine/SKILL.md +++ b/backend/agent/skills/cad-engine/SKILL.md @@ -51,3 +51,16 @@ Help an AI agent generate CAD models through the repository's CDSL engine. value shapes in `cdsl_schema.json`. In particular, hole positions must be objects such as `{"mm": [u_mm, v_mm, w_mm]}`, never bare coordinate arrays. - Preserve the original CDSL and write revisions as separate artifacts. + +## CADFS Engine Maintenance + +When changing CADFS lowering, the CDSL schema, runtime, geometry adapter, +selector binding, or their tests, update +`cadfs_to_cdsl/ENGINE_CAPABILITY_GAPS_PROGRESS.local.md` in the same change. + +- Record the end-to-end capability state, remaining semantic boundary, and + regression evidence. A successful rebuild is not geometric similarity. +- Update a capability to completed only when lowering, contract, execution, + geometry, and regression coverage all exist. +- The progress ledger is local development state and is intentionally ignored + by Git. Do not add it to a commit. diff --git a/backend/agent/skills/cdsl-author-guidance/00-author-contract.md b/backend/agent/skills/cdsl-author-guidance/00-author-contract.md new file mode 100644 index 00000000..92470134 --- /dev/null +++ b/backend/agent/skills/cdsl-author-guidance/00-author-contract.md @@ -0,0 +1 @@ +只使用当前工具 schema、`operation_contract` 和服务端提供的 topology/reference token;它们高于本指引、示例和任何经验。每次只完成被调度的一个原子操作,不编造 atom、字段、selector 或能力。参数须为有限 mm/deg 数值。已有可执行 checkpoint 是应保留的最佳结果;能力缺口、未验证项和视觉疑点要如实交给服务端证据流程,不能用虚构几何掩盖。 diff --git a/backend/agent/skills/cdsl-author-guidance/01-brief-and-assumptions.md b/backend/agent/skills/cdsl-author-guidance/01-brief-and-assumptions.md new file mode 100644 index 00000000..1c850d90 --- /dev/null +++ b/backend/agent/skills/cdsl-author-guidance/01-brief-and-assumptions.md @@ -0,0 +1 @@ +先区分显式事实、图像观察、工程默认值和未知项。提取单位、外形、功能面、孔/槽、配合关系、关键尺寸及可验证目标。默认值只能补足常见零件的非关键构造,不能把未说明尺寸伪装成用户要求或确定性验收值。只有安全、配合、合规或可建模性确实取决于一个缺失事实时,才提出一个聚焦澄清;其余不确定性记录为假设或风险。 diff --git a/backend/agent/skills/cdsl-author-guidance/02-parameters-and-derived-dimensions.md b/backend/agent/skills/cdsl-author-guidance/02-parameters-and-derived-dimensions.md new file mode 100644 index 00000000..035276fe --- /dev/null +++ b/backend/agent/skills/cdsl-author-guidance/02-parameters-and-derived-dimensions.md @@ -0,0 +1 @@ +把尺寸当作模型契约:先识别主控的长度、宽度、厚度、直径、中心距、节距、数量、半径和角度,再从它们推导重复位置、对称偏移和余量。所有尺寸明确使用 mm,角度使用 deg;长度、直径、深度、节距和圆角半径必须为合理正有限值。阵列优先由中心线、数量、节距、半径或角度推导,避免难以追溯的点坐标常数。提交前以包围盒、比例、壁厚/材料余量和目标特征数量做常识检查。 diff --git a/backend/agent/skills/cdsl-author-guidance/03-coordinate-system-and-datums.md b/backend/agent/skills/cdsl-author-guidance/03-coordinate-system-and-datums.md new file mode 100644 index 00000000..0e85183c --- /dev/null +++ b/backend/agent/skills/cdsl-author-guidance/03-coordinate-system-and-datums.md @@ -0,0 +1 @@ +世界坐标默认是右手 mm。根 `XY` 与 `+Z` 挤出只用于当前 contract 允许的根挤出。原点按功能基准选取:对称件取中心,板件取占地中心,轴对称件取轴线,存在安装或配合接口时取安装面、配合轴或明确接口基准。`workplane.origin_mm` 是局部 `(0,0)` 的世界点,`x_dir` 是局部 `+X`,`normal` 指向正挤出;草图局部坐标不是世界坐标。孔 `positions[].mm` 是宿主面上的绝对世界点,不是面内偏移。非根特征依据当前 contract、测量宿主和 token 建立 frame,不能猜测最后生成面或全局平面。`reference_plane`/`reference_axis` 是唯一支持的命名定位上下文;先创建基准,再创建依赖它的旋转、镜像、阵列和草图。位置必须由基准、中心线、偏移、节距或半径导出;定位失败时改 frame、偏移或方向,不修改已发布工件。 diff --git a/backend/agent/skills/cdsl-author-guidance/04-construction-and-feature-order.md b/backend/agent/skills/cdsl-author-guidance/04-construction-and-feature-order.md new file mode 100644 index 00000000..28e2331d --- /dev/null +++ b/backend/agent/skills/cdsl-author-guidance/04-construction-and-feature-order.md @@ -0,0 +1 @@ +优先把零件身份和主控尺寸写进稳定根特征:根体、主要增材体、主要切除、孔/槽、重复特征、最后的圆角/倒角。每个节点只承担一个原子意图,依赖边只表示直接几何前提。默认形成连通单体;确需多体时必须由目标和 contract 支持。避免把视觉装饰、细小倒角或易碎布尔放在主形体之前。重规划时保留已完成节点和可执行检查点,只替换最小必要子图。 diff --git a/backend/agent/skills/cdsl-author-guidance/05-profiles-workplanes-and-cuts.md b/backend/agent/skills/cdsl-author-guidance/05-profiles-workplanes-and-cuts.md new file mode 100644 index 00000000..289c3ecf --- /dev/null +++ b/backend/agent/skills/cdsl-author-guidance/05-profiles-workplanes-and-cuts.md @@ -0,0 +1 @@ +轮廓必须闭合、不自交、无零长或重叠边,并清楚区分外环和内环。先验证 workplane 的原点、`x_dir`、`normal` 与局部轮廓方向;翻转方向使用 contract 允许的字段,不凭视觉猜测。切除从实际材料面进入,深度覆盖目标材料并满足当前预检;避免刚好停在共面边界。对薄壁、近相切、重叠工具和零厚度结果保持余量。切除失败先检查宿主、方向、深度和轮廓,再考虑更换建模顺序。 diff --git a/backend/agent/skills/cdsl-author-guidance/06-hosted-features-selectors-and-topology.md b/backend/agent/skills/cdsl-author-guidance/06-hosted-features-selectors-and-topology.md new file mode 100644 index 00000000..56109b58 --- /dev/null +++ b/backend/agent/skills/cdsl-author-guidance/06-hosted-features-selectors-and-topology.md @@ -0,0 +1 @@ +宿主特征只能使用当前 revision 的测量 topology 和服务端给出的不透明 selector token;不得按边/面列表下标、历史名称或“最后一个面”猜选。选择前核对 token 的 kind、中心、法向、包围盒和 surface_type 是否覆盖预期材料区域。布尔、孔、阵列、圆角后拓扑可能变化,旧 token 和 reference 不可假定仍有效;依赖新拓扑时重新观察。reference token 只按当前 contract 放入允许槽位。选择不确定时请求 topology,而不是提交模糊 selector。 diff --git a/backend/agent/skills/cdsl-author-guidance/07-patterns-symmetry-and-repetition.md b/backend/agent/skills/cdsl-author-guidance/07-patterns-symmetry-and-repetition.md new file mode 100644 index 00000000..6a1fa24b --- /dev/null +++ b/backend/agent/skills/cdsl-author-guidance/07-patterns-symmetry-and-repetition.md @@ -0,0 +1 @@ +对称和重复优先通过 `pattern_linear`、`pattern_mirror` 及其 contract 参数表达。先完成一个正确的源特征,再用中心面、中心线、方向、数量、节距、半径或角度定义重复关系;不要用零散手填坐标代替可追溯模式。镜像平面和阵列方向应来自已建立的 datum 或当前测量 token。阵列前确认源特征、间距和数量不会重叠、越界或使材料变成零厚度。 diff --git a/backend/agent/skills/cdsl-author-guidance/08-finishing-and-boolean-risk.md b/backend/agent/skills/cdsl-author-guidance/08-finishing-and-boolean-risk.md new file mode 100644 index 00000000..3ab5a436 --- /dev/null +++ b/backend/agent/skills/cdsl-author-guidance/08-finishing-and-boolean-risk.md @@ -0,0 +1 @@ +圆角和倒角仅在主形体、切除和孔稳定后执行,并只选择唯一、当前有效的边 token;禁止“所有边”式回退。半径/距离必须小于邻近材料可容纳范围,避免相邻圆角相交。布尔操作避开共面终止、近相切和重复工具重叠;若风险高,优先以更稳定的主轮廓、顺序或足够余量表达。失败时不要重复原片段,先诊断受影响的面、边、深度和拓扑。 diff --git a/backend/agent/skills/cdsl-author-guidance/09-evidence-visual-review-and-validation.md b/backend/agent/skills/cdsl-author-guidance/09-evidence-visual-review-and-validation.md new file mode 100644 index 00000000..8183f587 --- /dev/null +++ b/backend/agent/skills/cdsl-author-guidance/09-evidence-visual-review-and-validation.md @@ -0,0 +1 @@ +确定性几何事实与视觉审查职责不同:包围盒、实体数、孔深或贯穿状态只能证明已测量的 claim,不能证明整体设计语义。使用当前 contract、预检结果、claim evidence、render manifest 和 recent failures 作决定。视觉不符时给出具体的形状、位置、方向或比例差异作为修复依据,不能把它伪装成确定性通过。仅在几何改变后重新审查;STEP/checkpoint 是主工件,GLB 和渲染是派生审查证据,不能替代 CAD 几何。 diff --git a/backend/agent/skills/cdsl-author-guidance/10-repair-and-best-effort.md b/backend/agent/skills/cdsl-author-guidance/10-repair-and-best-effort.md new file mode 100644 index 00000000..145d9f5f --- /dev/null +++ b/backend/agent/skills/cdsl-author-guidance/10-repair-and-best-effort.md @@ -0,0 +1 @@ +修复先读错误和证据,定位最小责任点,再改最小的 CDSL/计划部分并重新执行依赖检查。常见原因包括开环/自交轮廓、零或负尺寸、切除方向或深度错误、错误 host frame、布尔后的旧 selector、过大圆角和直径/半径混淆。不要原样重试已失败片段。运行时不支持的能力应作为风险或缺口保留并继续发布最佳可执行模型,不能发明新 atom 或删除有效 checkpoint。 diff --git a/backend/agent/skills/cdsl-author-guidance/README.md b/backend/agent/skills/cdsl-author-guidance/README.md new file mode 100644 index 00000000..96da5a74 --- /dev/null +++ b/backend/agent/skills/cdsl-author-guidance/README.md @@ -0,0 +1,63 @@ +# CDSL Author Guidance Corpus + +This corpus is a Chinese-first, non-authoritative author aid. The runtime +operation contract, fragment schema, topology/reference tokens, preflight and +verifier evidence always win over these Markdown files. The manifest maps +only workflow phase, scheduled atomic operation and repair state; it never +classifies the user's part request. + +## Source Migration + +| Source reference | CDSL target sections | Intentionally excluded | +| --- | --- | --- | +| `cad-brief.md` | `01`, `02`, `09` | Python/file workflow | +| `parameters.md` | `02`, `07` | sidecars, animation, viewer control | +| `positioning.md` | `03`, `06`, `op-reference` | assemblies, joints, `Location`, imported STEP placement | +| `build123d-modeling.md` | `03` through `08`, operation appendices | build123d APIs, labels, colors and assembly source | +| `build123d-modeling.zh-CN.md` | all Chinese terminology and rule review | a duplicate competing rule set | +| `inspection-and-validation.md` | `09`, `10` | CLI paths and selector syntax | +| `snapshot-review.md` | `09` | renderer commands | +| `repair-loop.md` | `10`, `05`, `06`, `08` | build123d-only remediation syntax | +| `step-generation.md` | `00`, `09` | Python generator commands | +| `supported-exports.md` | `09` | mesh tolerance and exporter-specific flags | + +## Selection Contract + +- Requirements authoring selects `00` to `03`. +- Feature planning selects `00`, `02`, `03`, `04`, `07`, and `08`. +- A scheduled feature selects `00`, `03` to `06`, `08`, and its current + operation appendix. +- Repair selects `00`, `03`, `06`, `09`, `10`, and its operation appendix. +- Final validation selects `00`, `09`, and `10`. + +At a bounded prompt budget, contract, coordinate/datum, and the scheduled +operation appendix are mandatory. Other sections are included in stable +priority order. A malformed corpus or an unsupported operation registry +falls back to the original short author prompt and records fallback metadata +with the author usage record. + +## Evaluation Commands + +Run the six matched control scenarios three times each, first without and +then with guidance: + +```bash +PYTHONPATH=backend python -m app.cad_agent.evals.live --suite comprehensive --repetitions 3 --author-guidance off \ + --scenario rectangular_mounting_plate --scenario circular_flange_pcd \ + --scenario obround_slot_plate --scenario rounded_rectangular_pocket \ + --scenario double_hole_linkage_arm --scenario l_bracket + +PYTHONPATH=backend python -m app.cad_agent.evals.live --suite comprehensive --repetitions 3 --author-guidance on \ + --scenario rectangular_mounting_plate --scenario circular_flange_pcd \ + --scenario obround_slot_plate --scenario rounded_rectangular_pocket \ + --scenario double_hole_linkage_arm --scenario l_bracket + +PYTHONPATH=backend python -m app.cad_agent.evals.live --compare-guidance-reports CONTROL/report.json TREATMENT/report.json +``` + +The comparator excludes declared validation gaps and engine-declared +`unsupported_*` capability gaps from prompt quality metrics, checks paired +model/runtime/contract/budget equivalence, and +requires the treatment's checkpoint/completion rates not to regress, median +author calls to stay within 10 percent, and either schema/decision or CDSL +expression failures to improve. diff --git a/backend/agent/skills/cdsl-author-guidance/manifest.json b/backend/agent/skills/cdsl-author-guidance/manifest.json new file mode 100644 index 00000000..f82f8911 --- /dev/null +++ b/backend/agent/skills/cdsl-author-guidance/manifest.json @@ -0,0 +1,67 @@ +{ + "schema_version": "cdsl.author-guidance.manifest.v1", + "version": "2026-09-03.1", + "sections": [ + {"id": "00-author-contract", "file": "00-author-contract.md", "title": "00 Author Contract", "priority": 100, "mandatory": true}, + {"id": "01-brief-and-assumptions", "file": "01-brief-and-assumptions.md", "title": "01 Brief And Assumptions", "priority": 70, "mandatory": false}, + {"id": "02-parameters-and-derived-dimensions", "file": "02-parameters-and-derived-dimensions.md", "title": "02 Parameters And Derived Dimensions", "priority": 80, "mandatory": false}, + {"id": "03-coordinate-system-and-datums", "file": "03-coordinate-system-and-datums.md", "title": "03 Coordinate System And Datums", "priority": 100, "mandatory": true}, + {"id": "04-construction-and-feature-order", "file": "04-construction-and-feature-order.md", "title": "04 Construction And Feature Order", "priority": 70, "mandatory": false}, + {"id": "05-profiles-workplanes-and-cuts", "file": "05-profiles-workplanes-and-cuts.md", "title": "05 Profiles Workplanes And Cuts", "priority": 90, "mandatory": false}, + {"id": "06-hosted-features-selectors-and-topology", "file": "06-hosted-features-selectors-and-topology.md", "title": "06 Hosted Features Selectors And Topology", "priority": 90, "mandatory": false}, + {"id": "07-patterns-symmetry-and-repetition", "file": "07-patterns-symmetry-and-repetition.md", "title": "07 Patterns Symmetry And Repetition", "priority": 60, "mandatory": false}, + {"id": "08-finishing-and-boolean-risk", "file": "08-finishing-and-boolean-risk.md", "title": "08 Finishing And Boolean Risk", "priority": 60, "mandatory": false}, + {"id": "09-evidence-visual-review-and-validation", "file": "09-evidence-visual-review-and-validation.md", "title": "09 Evidence Visual Review And Validation", "priority": 80, "mandatory": false}, + {"id": "10-repair-and-best-effort", "file": "10-repair-and-best-effort.md", "title": "10 Repair And Best Effort", "priority": 80, "mandatory": false}, + {"id": "op-extrude-add", "file": "op-extrude-add.md", "title": "Operation Appendix Extrude Add", "priority": 100, "mandatory": true}, + {"id": "op-extrude-cut", "file": "op-extrude-cut.md", "title": "Operation Appendix Extrude Cut", "priority": 100, "mandatory": true}, + {"id": "op-loft", "file": "op-loft.md", "title": "Operation Appendix Loft", "priority": 100, "mandatory": true}, + {"id": "op-revolve", "file": "op-revolve.md", "title": "Operation Appendix Revolve", "priority": 100, "mandatory": true}, + {"id": "op-hole", "file": "op-hole.md", "title": "Operation Appendix Hole", "priority": 100, "mandatory": true}, + {"id": "op-reference", "file": "op-reference.md", "title": "Operation Appendix Reference", "priority": 100, "mandatory": true}, + {"id": "op-pattern", "file": "op-pattern.md", "title": "Operation Appendix Pattern", "priority": 100, "mandatory": true}, + {"id": "op-finish", "file": "op-finish.md", "title": "Operation Appendix Finish", "priority": 100, "mandatory": true}, + {"id": "op-sphere", "file": "op-sphere.md", "title": "Operation Appendix Sphere", "priority": 100, "mandatory": true}, + {"id": "op-primitives", "file": "op-primitives.md", "title": "Operation Appendix Primitives", "priority": 100, "mandatory": true}, + {"id": "op-thread", "file": "op-thread.md", "title": "Operation Appendix Thread", "priority": 100, "mandatory": true} + ], + "phase_sections": { + "DEFAULT": ["00-author-contract", "03-coordinate-system-and-datums", "09-evidence-visual-review-and-validation"], + "DRAFTING_REQUIREMENTS_DOCUMENT": ["00-author-contract", "01-brief-and-assumptions", "02-parameters-and-derived-dimensions", "03-coordinate-system-and-datums"], + "DRAFTING_COMPLETION_TARGET": ["00-author-contract", "01-brief-and-assumptions", "02-parameters-and-derived-dimensions", "03-coordinate-system-and-datums"], + "COMPILING_REQUIREMENTS": ["00-author-contract", "01-brief-and-assumptions", "02-parameters-and-derived-dimensions", "03-coordinate-system-and-datums"], + "COMPILING_FEATURE_PLAN": ["00-author-contract", "02-parameters-and-derived-dimensions", "03-coordinate-system-and-datums", "04-construction-and-feature-order", "07-patterns-symmetry-and-repetition", "08-finishing-and-boolean-risk"], + "REPLANNING_FEATURE_SUBGRAPH": ["00-author-contract", "02-parameters-and-derived-dimensions", "03-coordinate-system-and-datums", "04-construction-and-feature-order", "07-patterns-symmetry-and-repetition", "08-finishing-and-boolean-risk"], + "FEATURE_PENDING": ["00-author-contract", "03-coordinate-system-and-datums", "04-construction-and-feature-order", "05-profiles-workplanes-and-cuts", "06-hosted-features-selectors-and-topology", "08-finishing-and-boolean-risk"], + "AWAITING_ACTION": ["00-author-contract", "03-coordinate-system-and-datums", "06-hosted-features-selectors-and-topology", "09-evidence-visual-review-and-validation", "10-repair-and-best-effort"], + "ACTION_PENDING": ["00-author-contract", "03-coordinate-system-and-datums", "06-hosted-features-selectors-and-topology", "09-evidence-visual-review-and-validation", "10-repair-and-best-effort"] + }, + "repair_sections": ["00-author-contract", "03-coordinate-system-and-datums", "06-hosted-features-selectors-and-topology", "09-evidence-visual-review-and-validation", "10-repair-and-best-effort"], + "final_sections": ["00-author-contract", "09-evidence-visual-review-and-validation", "10-repair-and-best-effort"], + "operation_sections": { + "extrude_add_blind": ["op-extrude-add"], + "extrude_add_two_sided": ["op-extrude-add"], + "extrude_cut_blind": ["op-extrude-cut"], + "extrude_cut_two_sided": ["op-extrude-cut"], + "extrude_cut_through": ["op-extrude-cut"], + "loft_add": ["op-loft"], + "revolve_add": ["op-revolve"], + "revolve_cut": ["op-revolve"], + "hole_blind": ["op-hole"], + "hole_counterbore": ["op-hole"], + "hole_countersink": ["op-hole"], + "hole_wizard": ["op-hole"], + "reference_plane": ["op-reference"], + "reference_axis": ["op-reference"], + "pattern_linear": ["op-pattern"], + "pattern_mirror": ["op-pattern"], + "pattern_circular": ["op-pattern"], + "fillet": ["op-finish"], + "chamfer": ["op-finish"], + "sphere_add": ["op-sphere"], + "box_add": ["op-primitives"], + "cylinder_add": ["op-primitives"], + "thread_add": ["op-thread"], + "thread_cut": ["op-thread"] + } +} diff --git a/backend/agent/skills/cdsl-author-guidance/op-extrude-add.md b/backend/agent/skills/cdsl-author-guidance/op-extrude-add.md new file mode 100644 index 00000000..754cacb0 --- /dev/null +++ b/backend/agent/skills/cdsl-author-guidance/op-extrude-add.md @@ -0,0 +1 @@ +`extrude_add_blind` 和 `extrude_add_two_sided` 必须使用闭合草图和 contract 允许的正距离。根挤出遵守根 `XY` datum;后续增材先确认草图 frame 与已有实体的连接。双向挤出分别核对两个方向的长度与材料范围;`reverse` 只用于当前 frame 的方向修正,不能代替错误的 workplane。 diff --git a/backend/agent/skills/cdsl-author-guidance/op-extrude-cut.md b/backend/agent/skills/cdsl-author-guidance/op-extrude-cut.md new file mode 100644 index 00000000..f88a19b2 --- /dev/null +++ b/backend/agent/skills/cdsl-author-guidance/op-extrude-cut.md @@ -0,0 +1 @@ +`extrude_cut_blind` 使用闭合草图、当前允许的正距离和正确宿主 frame。从实际材料面进入,方向由 workplane normal 与 contract 的 `reverse` 决定;深度应覆盖目标材料,不能刚好停在共面边界。`extrude_cut_two_sided` 必须分别提供正向和反向的距离与终止条件,不能以单侧深度近似双向切除。`extrude_cut_through` 只接受明确的 `end_condition`,由现有主体跨度决定穿透距离,不能伪造盲向深度。切除失败时先检查轮廓、宿主、方向、深度和材料覆盖,而不是盲目加大距离。 diff --git a/backend/agent/skills/cdsl-author-guidance/op-finish.md b/backend/agent/skills/cdsl-author-guidance/op-finish.md new file mode 100644 index 00000000..728dc7c1 --- /dev/null +++ b/backend/agent/skills/cdsl-author-guidance/op-finish.md @@ -0,0 +1 @@ +`fillet` 与 `chamfer` 仅接受当前 revision 中唯一且合格的 edge selector token。先完成影响这些边的布尔、孔和阵列,再按 contract 使用正半径或距离。局部材料不足、相邻过渡相交或 token 已失效时,不用全局边选择兜底;保留主体并报告该收尾特征的风险。 diff --git a/backend/agent/skills/cdsl-author-guidance/op-hole.md b/backend/agent/skills/cdsl-author-guidance/op-hole.md new file mode 100644 index 00000000..455e5be8 --- /dev/null +++ b/backend/agent/skills/cdsl-author-guidance/op-hole.md @@ -0,0 +1 @@ +孔 atom 需要当前宿主面的有效 selector token。`positions[].mm` 使用该宿主面上的绝对世界坐标,先核对点在面区域内与法向方向。直径、深度、沉孔/沉头参数以 contract 为准,深度覆盖预期材料;多孔共享一个原子操作时保持同一规格和同一宿主。不要把点写成面局部偏移或裸数组。 diff --git a/backend/agent/skills/cdsl-author-guidance/op-loft.md b/backend/agent/skills/cdsl-author-guidance/op-loft.md new file mode 100644 index 00000000..99deb7c9 --- /dev/null +++ b/backend/agent/skills/cdsl-author-guidance/op-loft.md @@ -0,0 +1 @@ +`loft_add` 在 `params.profile_sketch_ids` 中按放样方向列出至少两条不同的闭合草图。每条截面必须解析为一条无孔外轮廓;截面拓扑和 workplane frame 必须稳定对应。不要以挤出替代放样,也不要把 selector 选中的实体面当作放样截面,除非 operation contract 明确支持。 diff --git a/backend/agent/skills/cdsl-author-guidance/op-pattern.md b/backend/agent/skills/cdsl-author-guidance/op-pattern.md new file mode 100644 index 00000000..8bf48548 --- /dev/null +++ b/backend/agent/skills/cdsl-author-guidance/op-pattern.md @@ -0,0 +1 @@ +`pattern_linear` 只复制当前 contract 允许且存在的源 feature reference;方向是明确世界/基准方向,数量和 spacing 为合理值。`pattern_mirror` 使用存在的镜像 plane reference,先确认源与平面关系以及复制后不会重叠或意外合并。pattern 不代替新的宿主选择;下游特征若依赖新面,重新读取 topology。 diff --git a/backend/agent/skills/cdsl-author-guidance/op-primitives.md b/backend/agent/skills/cdsl-author-guidance/op-primitives.md new file mode 100644 index 00000000..09e10d85 --- /dev/null +++ b/backend/agent/skills/cdsl-author-guidance/op-primitives.md @@ -0,0 +1 @@ +`box_add` 和 `cylinder_add` 是世界坐标原生图元。按 operation contract 提供正尺寸以及明确的 `center_mm` 或 axis。仅在目标确为长方体或圆柱体时使用;由轮廓驱动的几何保留草图、放样等历史表达。 diff --git a/backend/agent/skills/cdsl-author-guidance/op-reference.md b/backend/agent/skills/cdsl-author-guidance/op-reference.md new file mode 100644 index 00000000..20e1e31f --- /dev/null +++ b/backend/agent/skills/cdsl-author-guidance/op-reference.md @@ -0,0 +1 @@ +`reference_plane` 用有限非零 `normal` 和与其不平行的 `x_dir` 定义局部 frame;`origin_mm` 是世界点。`reference_axis` 用有限非零 `direction` 和世界原点定义。它们只建立可追溯 datum,不直接制造实体;先于依赖它的旋转、镜像、阵列或定位特征,并依照 contract 的 reference token 规则引用。 diff --git a/backend/agent/skills/cdsl-author-guidance/op-revolve.md b/backend/agent/skills/cdsl-author-guidance/op-revolve.md new file mode 100644 index 00000000..161997dc --- /dev/null +++ b/backend/agent/skills/cdsl-author-guidance/op-revolve.md @@ -0,0 +1 @@ +`revolve_add` 和 `revolve_cut` 的轴必须由明确 datum 或 contract 中的世界坐标轴表达,并按预检要求位于正确的草图关系中。核对 axis origin、direction、角度和 `reverse`;完整回转避免轮廓跨轴造成自交,局部回转避免与现有材料近相切。切除回转仍必须覆盖目标材料。 diff --git a/backend/agent/skills/cdsl-author-guidance/op-sphere.md b/backend/agent/skills/cdsl-author-guidance/op-sphere.md new file mode 100644 index 00000000..99c82b4b --- /dev/null +++ b/backend/agent/skills/cdsl-author-guidance/op-sphere.md @@ -0,0 +1 @@ +`sphere_add` 用明确的世界中心和正有限半径定义。确认它与目标实体的连接意图:需要单体时应有足够相交,独立体仅在需求允许多体时使用。球体位置从 datum 或主尺寸导出,不把视图坐标误当世界坐标。 diff --git a/backend/agent/skills/cdsl-author-guidance/op-thread.md b/backend/agent/skills/cdsl-author-guidance/op-thread.md new file mode 100644 index 00000000..38e932da --- /dev/null +++ b/backend/agent/skills/cdsl-author-guidance/op-thread.md @@ -0,0 +1 @@ +`thread_add` 和 `thread_cut` 需要明确的 axis、正的大小径、螺距和长度,且各参数必须物理一致。螺纹切除必须有已有宿主实体;需求为实体螺纹时,不能以光滑孔替代。 diff --git a/backend/app/cad_agent/adapters/author_guidance.py b/backend/app/cad_agent/adapters/author_guidance.py new file mode 100644 index 00000000..4cae1ef5 --- /dev/null +++ b/backend/app/cad_agent/adapters/author_guidance.py @@ -0,0 +1,191 @@ +"""Bounded, file-backed author guidance for the CDSL workflow. + +The corpus is deliberately non-authoritative: contracts, schemas, topology +tokens, and server preflight always remain the executable source of truth. +Loading errors return an empty selection so authoring continues with the +pre-guidance prompt instead of turning documentation into an availability +dependency. +""" + +from __future__ import annotations + +import json +from dataclasses import dataclass +from pathlib import Path +from typing import Any + +from app.cad_agent.domain.state import TaskPhase +from app.cad_agent.ports import AuthorGuidanceSelection + + +_MIN_CHARS = 1_200 +_MAX_CHARS = 6_000 + + +@dataclass(frozen=True, slots=True) +class _Section: + section_id: str + title: str + priority: int + mandatory: bool + content: str + + @property + def block(self) -> str: + return f"## {self.title}\n{self.content.strip()}" + + +@dataclass(frozen=True, slots=True) +class _Corpus: + version: str + sections: dict[str, _Section] + phase_sections: dict[str, tuple[str, ...]] + repair_sections: tuple[str, ...] + final_sections: tuple[str, ...] + operation_sections: dict[str, tuple[str, ...]] + + +class FileAuthorGuidance: + """Read and select the checked-in corpus deterministically. + + Selection depends exclusively on workflow state and the runtime operation + registry. It intentionally receives neither the user's request nor image + observations, so it cannot become an implicit part-family classifier. + """ + + def __init__(self, root: Path, *, enabled: bool = True, max_chars: int = 3_600) -> None: + self.root = root + self.enabled = enabled + self.max_chars = min(_MAX_CHARS, max(_MIN_CHARS, max_chars)) + self._corpus: _Corpus | None = None + self._load_error = "" + + def select( + self, + *, + phase: TaskPhase, + atomic_id: str, + repair_required: bool, + supported_atomic_ids: tuple[str, ...], + ) -> AuthorGuidanceSelection: + if not self.enabled: + return AuthorGuidanceSelection(fallback_reason="guidance_disabled") + corpus = self._load() + if corpus is None: + return AuthorGuidanceSelection(fallback_reason=self._load_error or "guidance_unavailable") + supported = set(supported_atomic_ids) + if set(corpus.operation_sections) != supported: + return AuthorGuidanceSelection(fallback_reason="guidance_operation_coverage_mismatch") + if atomic_id and atomic_id not in supported: + return AuthorGuidanceSelection(fallback_reason="guidance_unknown_atomic_id") + + if repair_required: + requested = list(corpus.repair_sections) + elif phase == TaskPhase.FINAL_VALIDATION: + requested = list(corpus.final_sections) + else: + requested = list(corpus.phase_sections.get(phase.value, corpus.phase_sections.get("DEFAULT", ()))) + if atomic_id: + requested.extend(corpus.operation_sections[atomic_id]) + requested = list(dict.fromkeys(requested)) + if not requested: + return AuthorGuidanceSelection(fallback_reason="guidance_no_matching_sections") + + mandatory = [section_id for section_id in requested if corpus.sections[section_id].mandatory] + optional = [section_id for section_id in requested if not corpus.sections[section_id].mandatory] + optional.sort(key=lambda section_id: (-corpus.sections[section_id].priority, requested.index(section_id))) + selected: list[str] = [] + text = "" + for section_id in [*mandatory, *optional]: + block = corpus.sections[section_id].block + candidate = block if not text else f"{text}\n\n{block}" + if len(candidate) <= self.max_chars: + text = candidate + selected.append(section_id) + elif section_id in mandatory: + # Do not silently drop contract, datum, or operation guidance. + return AuthorGuidanceSelection(fallback_reason="guidance_required_sections_exceed_budget") + return AuthorGuidanceSelection( + version=corpus.version, + section_ids=tuple(selected), + content=text, + enabled=True, + ) + + def _load(self) -> _Corpus | None: + if self._corpus is not None: + return self._corpus + if self._load_error: + return None + try: + manifest_path = self.root / "manifest.json" + raw = json.loads(manifest_path.read_text(encoding="utf-8")) + if not isinstance(raw, dict): + raise ValueError("manifest is not an object") + version = raw.get("version") + if raw.get("schema_version") != "cdsl.author-guidance.manifest.v1" or not isinstance(version, str) or not version: + raise ValueError("manifest version is invalid") + raw_sections = raw.get("sections") + if not isinstance(raw_sections, list) or not raw_sections: + raise ValueError("manifest sections are invalid") + sections: dict[str, _Section] = {} + root = self.root.resolve() + for item in raw_sections: + if not isinstance(item, dict): + raise ValueError("section declaration is invalid") + section_id = item.get("id") + filename = item.get("file") + title = item.get("title") + priority = item.get("priority") + mandatory = item.get("mandatory", False) + if ( + not isinstance(section_id, str) or not section_id + or not isinstance(filename, str) or not filename + or not isinstance(title, str) or not title + or not isinstance(priority, int) or isinstance(priority, bool) + or not isinstance(mandatory, bool) + or section_id in sections + ): + raise ValueError("section metadata is invalid") + path = (self.root / filename).resolve() + if root not in path.parents or not path.is_file(): + raise ValueError("section file is unavailable") + content = path.read_text(encoding="utf-8").strip() + if not content: + raise ValueError("section content is empty") + sections[section_id] = _Section(section_id, title, priority, mandatory, content) + + def identifiers(value: Any, field: str) -> tuple[str, ...]: + if not isinstance(value, list) or not value or not all(isinstance(item, str) and item in sections for item in value): + raise ValueError(f"{field} is invalid") + return tuple(dict.fromkeys(value)) + + raw_phases = raw.get("phase_sections") + if not isinstance(raw_phases, dict) or "DEFAULT" not in raw_phases: + raise ValueError("phase sections are invalid") + phase_sections = { + phase: identifiers(section_ids, f"phase {phase}") + for phase, section_ids in raw_phases.items() + if isinstance(phase, str) + } + if len(phase_sections) != len(raw_phases): + raise ValueError("phase name is invalid") + operation_sections = { + atomic_id: identifiers(section_ids, f"operation {atomic_id}") + for atomic_id, section_ids in (raw.get("operation_sections") or {}).items() + if isinstance(atomic_id, str) + } + if not operation_sections or len(operation_sections) != len(raw.get("operation_sections") or {}): + raise ValueError("operation sections are invalid") + self._corpus = _Corpus( + version=version, + sections=sections, + phase_sections=phase_sections, + repair_sections=identifiers(raw.get("repair_sections"), "repair sections"), + final_sections=identifiers(raw.get("final_sections"), "final sections"), + operation_sections=operation_sections, + ) + return self._corpus + except (OSError, ValueError, TypeError, json.JSONDecodeError) as error: + self._load_error = f"guidance_load_failed:{type(error).__name__}" + return None diff --git a/backend/app/cad_agent/adapters/structured_llm.py b/backend/app/cad_agent/adapters/structured_llm.py index 9d1f1054..53532ef8 100644 --- a/backend/app/cad_agent/adapters/structured_llm.py +++ b/backend/app/cad_agent/adapters/structured_llm.py @@ -46,13 +46,67 @@ class StructuredModelGateway: async def call_tool(self, *, messages: list[dict[str, Any]], tool: dict[str, Any], provider_id: str, model_id: str, required_tool_name: str) -> dict[str, Any]: provider, model = self._provider_model(provider_id, model_id) - payload = self._payload(provider, model.id, messages, [tool], required_tool_name) - response = await self._request(provider, payload) + response, compatibility_mode = await self._request_tool_call( + provider, model.id, messages, [tool], required_tool_name, + ) try: - return self._normalized_response(provider, response) + normalized = self._normalized_response(provider, response) + normalized["usage"]["structured_compatibility_mode"] = compatibility_mode + return normalized except (KeyError, TypeError, ValueError) as error: raise StructuredModelError("Provider response cannot be normalized as a structured tool-call result") from error + async def _request_tool_call( + self, + provider: ProviderConfig, + model_id: str, + messages: list[dict[str, Any]], + tools: list[dict[str, Any]], + required_tool_name: str, + ) -> tuple[dict[str, Any], str]: + """Keep forced tool choice unless a provider explicitly rejects thinking mode. + + Some OpenAI-compatible gateways reject an otherwise valid forced + ``tool_choice`` whenever reasoning is enabled or implicit. This is a + request compatibility issue rather than an author error. First retry + without the client-requested reasoning option; only if that exact + rejection remains do we allow automatic choice among the single tool + already exposed to the model. The workflow's one-call/schema checks + still reject free-form or wrong-tool responses. + """ + try: + return await self._request( + provider, + self._payload(provider, model_id, messages, tools, required_tool_name), + ), "forced" + except StructuredModelError as first_error: + if not self._thinking_tool_choice_rejection(first_error): + raise + try: + return await self._request( + provider, + self._payload( + provider, model_id, messages, tools, required_tool_name, + include_reasoning=False, + ), + ), "reasoning_disabled" + except StructuredModelError as second_error: + if not self._thinking_tool_choice_rejection(second_error): + raise + return await self._request( + provider, + self._payload( + provider, model_id, messages, tools, required_tool_name, + include_reasoning=False, + force_tool_name=False, + ), + ), "single_tool_auto" + + @staticmethod + def _thinking_tool_choice_rejection(error: StructuredModelError) -> bool: + message = str(error).casefold() + return "thinking" in message and ("tool_choice" in message or "tool choice" in message) + async def conformance(self, *, provider_id: str, model_id: str, tools: list[dict[str, Any]]) -> dict[str, Any]: failures: list[dict[str, str]] = [] usage = {"prompt_tokens": 0, "completion_tokens": 0} @@ -190,20 +244,31 @@ class StructuredModelGateway: }) return values - def _payload(self, provider: ProviderConfig, model_id: str, messages: list[dict[str, Any]], tools: list[dict[str, Any]], required_tool_name: str) -> dict[str, Any]: + def _payload( + self, + provider: ProviderConfig, + model_id: str, + messages: list[dict[str, Any]], + tools: list[dict[str, Any]], + required_tool_name: str, + *, + include_reasoning: bool = True, + force_tool_name: bool = True, + ) -> dict[str, Any]: if len(tools) != 1 or not required_tool_name: raise StructuredModelError("The v3 protocol requires exactly one named tool per provider call.") + request_options = provider.request_options if include_reasoning else {} if provider.api_style == "responses": payload: dict[str, Any] = { "model": model_id, "input": self._responses_input(messages), "tools": self._responses_tools(tools), - "tool_choice": {"type": "function", "name": required_tool_name}, - **provider.request_options, + "tool_choice": {"type": "function", "name": required_tool_name} if force_tool_name else "auto", + **request_options, } return payload return { "model": model_id, "messages": messages, "tools": tools, - "tool_choice": {"type": "function", "function": {"name": required_tool_name}}, - "temperature": 0, **provider.request_options, + "tool_choice": {"type": "function", "function": {"name": required_tool_name}} if force_tool_name else "auto", + "temperature": 0, **request_options, } @staticmethod diff --git a/backend/app/cad_agent/application/workflow.py b/backend/app/cad_agent/application/workflow.py index f92e33d0..73e9839b 100644 --- a/backend/app/cad_agent/application/workflow.py +++ b/backend/app/cad_agent/application/workflow.py @@ -33,7 +33,17 @@ from app.cad_agent.domain.feature_plan import FeaturePlan, plan_hash from app.cad_agent.domain.errors import ErrorCode, WorkflowError from app.cad_agent.domain.operation_contract import fragment_schema from app.cad_agent.domain.state import TaskPhase, TaskState, retry_resume_event, transition -from app.cad_agent.ports import AdapterUnavailable, ArtifactStore, CadRuntime, ModelGateway, ReviewGateway, TaskRepository +from app.cad_agent.ports import ( + AdapterUnavailable, + ArtifactStore, + AuthorGuidance, + AuthorGuidanceSelection, + CadRuntime, + ModelGateway, + NullAuthorGuidance, + ReviewGateway, + TaskRepository, +) T = TypeVar("T", bound=BaseModel) @@ -116,6 +126,7 @@ class WorkflowCoordinator: review_gateway: ReviewGateway, requirements: RequirementsCommandHandler, actions: ActionCommandHandler, + author_guidance: AuthorGuidance | None = None, ) -> None: self.config = config self.repository = repository @@ -125,6 +136,7 @@ class WorkflowCoordinator: self.review_gateway = review_gateway self.requirements = requirements self.actions = actions + self.author_guidance = author_guidance or NullAuthorGuidance() def create_task( self, @@ -1078,14 +1090,14 @@ class WorkflowCoordinator: "message": message[:1000], } - async def _author_turn(self, task_id: str, author: ModelIdentity, tools: list[dict[str, Any]], feedback: list[dict[str, Any]]) -> tuple[str, str, dict[str, int]] | WorkflowError: + async def _author_turn(self, task_id: str, author: ModelIdentity, tools: list[dict[str, Any]], feedback: list[dict[str, Any]]) -> tuple[str, str, dict[str, Any]] | WorkflowError: if len(tools) != 1: raise RuntimeError("Workflow state must expose exactly one author tool.") tool = tools[0] name = str((tool.get("function") or {}).get("name") or "") if not name: raise RuntimeError("Workflow exposed an unnamed author tool.") - messages = self._author_context(task_id, feedback) + messages, guidance = self._author_context(task_id, feedback) try: response = await self.model_gateway.call_tool( messages=messages, tool=tool, provider_id=author.provider_id, @@ -1103,10 +1115,26 @@ class WorkflowCoordinator: "model_id": author.model_id, "retry_reason": "provider_unavailable", "cache_hit": False, + **guidance.usage_metadata(), }) return WorkflowError(ErrorCode.AUTHOR_TRANSPORT_UNAVAILABLE, str(error)[:1000], retryable=True) call = validate_one_tool_call(response["tool_calls"], name) if isinstance(call, WorkflowError): + # A provider response is still a billable author attempt even when + # it violates the one-tool-call protocol. Keep guidance audit + # metadata on that record so A/B reports do not silently omit the + # failures this corpus is intended to reduce. + self.repository.record_usage(task_id, { + **response["usage"], + "context_chars": len(json.dumps(messages, ensure_ascii=False)), + "tool": name, + "provider_id": author.provider_id, + "model_id": author.model_id, + "raw_arguments_hash": "", + "retry_reason": "invalid_tool_call", + "cache_hit": False, + **guidance.usage_metadata(), + }) self._record_rejected_tool_calls( task_id, actor="author", @@ -1132,6 +1160,7 @@ class WorkflowCoordinator: "raw_arguments_hash": raw_arguments_hash(raw), "retry_reason": "", "cache_hit": False, + **guidance.usage_metadata(), } self.repository.record_usage(task_id, usage) return name, raw, usage @@ -1400,10 +1429,10 @@ class WorkflowCoordinator: return [] return [self._tool("record_geometry_conclusion", StatelessGeometryConclusion)] - def _author_context(self, task_id: str, feedback: list[dict[str, Any]]) -> list[dict[str, Any]]: + def _author_context(self, task_id: str, feedback: list[dict[str, Any]]) -> tuple[list[dict[str, Any]], AuthorGuidanceSelection]: state = self.repository.get_state(task_id) if state is None: - return [] + return [], AuthorGuidanceSelection(fallback_reason="task_state_unavailable") if state.phase == TaskPhase.DRAFTING_REQUIREMENTS_DOCUMENT: content = { "protocol": "cad.v3.2.feature-dag", @@ -1557,8 +1586,17 @@ class WorkflowCoordinator: "feature_plan_hash": state.feature_plan_hash, "feature_node_statuses": self._feature_node_statuses(task_id, None), }) - messages: list[dict[str, Any]] = [{"role": "system", "content": "You are the autonomous CAD author. Use exactly one offered structured tool call. Never emit Markdown plans or free-form JSON."}, {"role": "user", "content": json.dumps(content, ensure_ascii=False)}] - return [*messages, *feedback[-2:]] + guidance = self.author_guidance.select( + phase=state.phase, + atomic_id=state.pending_feature.atomic_id if state.pending_feature is not None else "", + repair_required=state.repair_required, + supported_atomic_ids=self.runtime.supported_atomic_ids(), + ) + system = "You are the autonomous CAD author. Use exactly one offered structured tool call. Never emit Markdown plans or free-form JSON." + if guidance.content: + system += "\n\nThe following is non-authoritative CDSL author guidance. The current tool schema, operation contract, and server facts take precedence.\n\n" + guidance.content + messages: list[dict[str, Any]] = [{"role": "system", "content": system}, {"role": "user", "content": json.dumps(content, ensure_ascii=False)}] + return [*messages, *feedback[-2:]], guidance def _user_clarifications(self, task_id: str) -> list[dict[str, str]]: clarifications: list[dict[str, str]] = [] diff --git a/backend/app/cad_agent/composition.py b/backend/app/cad_agent/composition.py index d8f99944..13287c9c 100644 --- a/backend/app/cad_agent/composition.py +++ b/backend/app/cad_agent/composition.py @@ -6,6 +6,7 @@ from dataclasses import dataclass import shutil from app.cad_agent.adapters.artifact_store import FileArtifactStore +from app.cad_agent.adapters.author_guidance import FileAuthorGuidance from app.cad_agent.adapters.event_publisher import IdempotentInProcessPublisher from app.cad_agent.adapters.runtime import ProfileCadRuntime from app.cad_agent.adapters.review_gateway import RenderedReviewGateway @@ -17,7 +18,7 @@ from app.cad_agent.application.outbox import OutboxDispatcher from app.cad_agent.application.requirements import RequirementsCommandHandler from app.cad_agent.application.workflow import ModelIdentity, WorkflowConfig, WorkflowCoordinator from app.cad_agent.domain.verifier_registry import default_registry -from app.settings import Settings +from app.settings import BACKEND_ROOT, Settings from app.services.storage import WorkspaceStore @@ -65,5 +66,10 @@ def compose_v3(settings: Settings) -> V3Services: RenderedReviewGateway(models), requirements, actions, + FileAuthorGuidance( + BACKEND_ROOT / "agent" / "skills" / "cdsl-author-guidance", + enabled=settings.agent_author_guidance_enabled, + max_chars=settings.agent_author_guidance_max_chars, + ), ) return V3Services(repository, artifacts, workflow, models, outbox) diff --git a/backend/app/cad_agent/evals/live.py b/backend/app/cad_agent/evals/live.py index 7b6656ee..593767b1 100644 --- a/backend/app/cad_agent/evals/live.py +++ b/backend/app/cad_agent/evals/live.py @@ -15,6 +15,7 @@ from hashlib import sha256 import json from pathlib import Path import secrets +from statistics import median import subprocess import sys from typing import Any @@ -100,12 +101,15 @@ def _arguments() -> argparse.Namespace: parser.add_argument("--author-model") parser.add_argument("--review-provider") parser.add_argument("--review-model") - parser.add_argument("--scenario", help="Run one fixture scenario by its stable ID for targeted regression validation.") + parser.add_argument("--scenario", action="append", dest="scenarios", help="Run a fixture scenario by stable ID. Repeat this option to select a comparison set.") + parser.add_argument("--repetitions", type=int, help="Run every selected scenario this many times.") + parser.add_argument("--author-guidance", choices=("on", "off"), help="Override CDSL author guidance for this run.") + parser.add_argument("--compare-guidance-reports", nargs=2, type=Path, metavar=("CONTROL", "TREATMENT"), help="Compare matched --author-guidance off/on report.json files without invoking providers.") parser.add_argument("--baseline-report", type=Path, help="Measured pre-v3 token baseline JSON for a release run.") return parser.parse_args() -def _fixture(suite: str, scenario_id: str | None = None) -> list[dict[str, Any]]: +def _fixture(suite: str, scenario_ids: list[str] | None = None) -> list[dict[str, Any]]: fixture_name = "comprehensive.json" if suite == "comprehensive" else "release.json" value = json.loads((Path(__file__).parent / "fixtures" / fixture_name).read_text(encoding="utf-8")) if fixture_name == "comprehensive.json": @@ -122,11 +126,14 @@ def _fixture(suite: str, scenario_id: str | None = None) -> list[dict[str, Any]] raise ValueError("Comprehensive fixture is not synchronized with its source document.") values = [item for item in value.get("scenarios") or () if isinstance(item, dict)] values = values[:2] if suite == "smoke" else values - if scenario_id is None: + if not scenario_ids: return values - selected = [item for item in values if str(item.get("id") or "") == scenario_id] - if not selected: - raise ValueError(f"Unknown scenario {scenario_id!r} for suite {suite!r}") + requested = list(dict.fromkeys(scenario_ids)) + available = {str(item.get("id") or "") for item in values} + unknown = [scenario_id for scenario_id in requested if scenario_id not in available] + if unknown: + raise ValueError(f"Unknown scenario {unknown[0]!r} for suite {suite!r}") + selected = [item for item in values if str(item.get("id") or "") in set(requested)] return selected @@ -567,12 +574,180 @@ def _safe_artifact_manifest(artifact_root: Path) -> dict[str, Any]: return {"schema_version": "cad.live-eval-artifact-manifest.v1", "artifact_root": str(artifact_root), "files": files} +def _guidance_metadata(usage: dict[str, Any]) -> dict[str, Any]: + """Summarize author-only guidance audit metadata without retaining prompts.""" + records = [ + item for item in usage.get("records") or () + if isinstance(item, dict) and item.get("role") != "reviewer" + ] + sections = sorted({ + section_id + for item in records + for section_id in item.get("guidance_section_ids") or () + if isinstance(section_id, str) + }) + versions = sorted({ + str(item.get("guidance_version") or "") + for item in records + if str(item.get("guidance_version") or "") + }) + return { + "enabled": bool(records) and any(item.get("guidance_enabled") is True for item in records), + "versions": versions, + "section_ids": sections, + "chars_total": sum(int(item.get("guidance_chars") or 0) for item in records), + "fallback_reasons": sorted({ + str(item.get("guidance_fallback_reason") or "") + for item in records + if str(item.get("guidance_fallback_reason") or "") + }), + } + + +def _has_unsupported_capability(row: dict[str, Any]) -> bool: + """Recognize engine-declared unsupported capability without hiding model errors.""" + for event in row.get("ledger") or (): + if not isinstance(event, dict): + continue + for failure in event.get("operation_failures") or (): + if isinstance(failure, dict) and "unsupported_" in str(failure.get("message") or ""): + return True + if "unsupported_" in str(event.get("message") or ""): + return True + return False + + +def _guidance_metric_rows(report: dict[str, Any]) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]: + results = [item for item in report.get("results") or () if isinstance(item, dict)] + capability_gaps = [ + item for item in results + if item.get("outcome") == "validation_capability_gap" or _has_unsupported_capability(item) + ] + return [item for item in results if item not in capability_gaps], capability_gaps + + +def _guidance_metrics(rows: list[dict[str, Any]]) -> dict[str, Any]: + if not rows: + return {"eligible_runs": 0} + author_calls = [ + sum(1 for item in (row.get("usage") or {}).get("records") or () if isinstance(item, dict) and item.get("role") != "reviewer") + for row in rows + ] + context_chars = [ + sum(int(item.get("context_chars") or 0) for item in (row.get("usage") or {}).get("records") or () if isinstance(item, dict) and item.get("role") != "reviewer") + for row in rows + ] + prompt_tokens = [ + sum(int(item.get("prompt_tokens") or 0) for item in (row.get("usage") or {}).get("records") or () if isinstance(item, dict) and item.get("role") != "reviewer") + for row in rows + ] + failure_layers: dict[str, int] = {} + for row in rows: + layer = str((row.get("failure_attribution") or {}).get("layer") or "passed") + failure_layers[layer] = failure_layers.get(layer, 0) + 1 + def rate(predicate: Any) -> float: + return sum(1 for row in rows if predicate(row)) / len(rows) + return { + "eligible_runs": len(rows), + "executable_checkpoint_rate": rate(lambda row: bool(row.get("revision_ids"))), + "completion_rate": rate(lambda row: str((row.get("projection") or {}).get("phase") or "") == "COMPLETED"), + "deterministic_claim_success_rate": rate(lambda row: bool((row.get("checks") or {}).get("deterministic_claims_pass"))), + "schema_or_decision_rejections": sum(int(row.get("schema_rejection_count") or 0) for row in rows), + "cdsl_expression_failures": sum(1 for row in rows if str((row.get("failure_attribution") or {}).get("layer") or "") == "cdsl_expression"), + "median_author_calls": median(author_calls), + "median_author_context_chars": median(context_chars), + "total_author_prompt_tokens": sum(prompt_tokens), + "median_author_prompt_tokens": median(prompt_tokens), + "failure_layers": dict(sorted(failure_layers.items())), + } + + +def compare_guidance_reports(control: dict[str, Any], treatment: dict[str, Any]) -> dict[str, Any]: + """Compare paired guidance-off/on runs without treating engine gaps as prompt results.""" + control_rows, control_gaps = _guidance_metric_rows(control) + treatment_rows, treatment_gaps = _guidance_metric_rows(treatment) + control_by_key = {(str(row.get("scenario") or ""), int(row.get("repetition") or 0)): row for row in control_rows} + treatment_by_key = {(str(row.get("scenario") or ""), int(row.get("repetition") or 0)): row for row in treatment_rows} + paired = sorted(set(control_by_key).intersection(treatment_by_key)) + control_only = sorted(set(control_by_key).difference(treatment_by_key)) + treatment_only = sorted(set(treatment_by_key).difference(control_by_key)) + control_pairs = [control_by_key[key] for key in paired] + treatment_pairs = [treatment_by_key[key] for key in paired] + control_metrics = _guidance_metrics(control_pairs) + treatment_metrics = _guidance_metrics(treatment_pairs) + same_runtime = ( + control.get("author") == treatment.get("author") + and control.get("reviewer") == treatment.get("reviewer") + and control.get("runtime_profile_sha256") == treatment.get("runtime_profile_sha256") + and control.get("operation_contracts") == treatment.get("operation_contracts") + ) + control_guidance = bool((control.get("author_guidance") or {}).get("enabled")) + treatment_guidance = bool((treatment.get("author_guidance") or {}).get("enabled")) + same_budgets = all( + control_by_key[key].get("scenario_budget") == treatment_by_key[key].get("scenario_budget") + for key in paired + ) + calls_control = control_metrics.get("median_author_calls") + calls_treatment = treatment_metrics.get("median_author_calls") + calls_within_limit = ( + isinstance(calls_control, (int, float)) + and isinstance(calls_treatment, (int, float)) + and calls_treatment <= calls_control * 1.10 + ) + improved = ( + treatment_metrics.get("schema_or_decision_rejections", 0) < control_metrics.get("schema_or_decision_rejections", 0) + or treatment_metrics.get("cdsl_expression_failures", 0) < control_metrics.get("cdsl_expression_failures", 0) + ) + gates = { + "complete_pairing": bool(paired) and not control_only and not treatment_only, + "control_off_treatment_on": not control_guidance and treatment_guidance, + "same_author_reviewer_runtime_and_contracts": same_runtime, + "same_per_scenario_budgets": same_budgets, + "checkpoint_rate_not_lower": treatment_metrics.get("executable_checkpoint_rate", -1) >= control_metrics.get("executable_checkpoint_rate", 0), + "completion_rate_not_lower": treatment_metrics.get("completion_rate", -1) >= control_metrics.get("completion_rate", 0), + "median_author_calls_within_ten_percent": calls_within_limit, + "model_or_cdsl_failure_improved": improved, + } + return { + "schema_version": "cad.author-guidance-comparison.v1", + "status": "passed" if all(gates.values()) else "failed", + "gates": gates, + "paired_runs": [{"scenario": scenario, "repetition": repetition} for scenario, repetition in paired], + "unpaired_runs": { + "control_only": [{"scenario": scenario, "repetition": repetition} for scenario, repetition in control_only], + "treatment_only": [{"scenario": scenario, "repetition": repetition} for scenario, repetition in treatment_only], + }, + "control": control_metrics, + "treatment": treatment_metrics, + "excluded_capability_gaps": { + "control": [{"scenario": item.get("scenario"), "repetition": item.get("repetition")} for item in control_gaps], + "treatment": [{"scenario": item.get("scenario"), "repetition": item.get("repetition")} for item in treatment_gaps], + }, + } + + +def compare_guidance_report_paths(control_path: Path, treatment_path: Path) -> dict[str, Any]: + try: + control = json.loads(control_path.read_text(encoding="utf-8")) + treatment = json.loads(treatment_path.read_text(encoding="utf-8")) + except (OSError, json.JSONDecodeError) as error: + return {"status": "failed", "error": f"GUIDANCE_COMPARISON_INPUT_INVALID: {type(error).__name__}"} + if not isinstance(control, dict) or not isinstance(treatment, dict): + return {"status": "failed", "error": "GUIDANCE_COMPARISON_INPUT_INVALID: report must be an object"} + return compare_guidance_reports(control, treatment) + + async def _run(arguments: argparse.Namespace, report_root: Path) -> dict[str, Any]: try: - scenarios = _fixture(arguments.suite, arguments.scenario) + scenarios = _fixture(arguments.suite, arguments.scenarios) except ValueError as error: return {"status": "LIVE_EVAL_BLOCKED", "error": str(error)} + repetitions = arguments.repetitions if arguments.repetitions is not None else 3 if arguments.suite == "release" else 1 + if repetitions < 1: + return {"status": "LIVE_EVAL_BLOCKED", "error": "--repetitions must be at least 1"} settings = get_settings() + if arguments.author_guidance is not None: + settings = replace(settings, agent_author_guidance_enabled=arguments.author_guidance == "on") try: author_provider, author_model = settings.resolve_model(arguments.author_provider, arguments.author_model) if arguments.review_provider or arguments.review_model: @@ -628,7 +803,7 @@ async def _run(arguments: argparse.Namespace, report_root: Path) -> dict[str, An } results: list[dict[str, Any]] = [] for scenario in scenarios: - for repetition in range(1, 4 if arguments.suite == "release" else 2): + for repetition in range(1, repetitions + 1): services.workflow.config = replace( services.workflow.config, # State transitions include local candidate recovery, so the @@ -716,6 +891,13 @@ async def _run(arguments: argparse.Namespace, report_root: Path) -> dict[str, An "terminal": terminal, "projection": projection, "usage": usage, "checks": checks, "acceptance_coverage": acceptance, "failure_attribution": failure_attribution, + "guidance": _guidance_metadata(usage), + "scenario_budget": { + "max_author_turns": int(scenario["max_author_turns"]), + "max_reviewer_turns": int(scenario["max_reviewer_turns"]), + "max_total_calls": int(scenario["max_total_calls"]), + "max_total_tokens": int(scenario["max_total_tokens"]), + }, "rejection_codes": _rejection_codes(events), "schema_rejection_count": len(_rejection_codes(events)), "retry_count": sum(1 for event in events if (event.get("payload") or {}).get("status") == "error"), @@ -745,6 +927,10 @@ async def _run(arguments: argparse.Namespace, report_root: Path) -> dict[str, An "author": {"provider": author_provider.id, "model": author_model.id}, "author_request_identity": current_author_identity, "reviewer": {"provider": review_provider.id, "model": review_model.id}, + "author_guidance": { + "enabled": settings.agent_author_guidance_enabled, + "max_chars": settings.agent_author_guidance_max_chars, + }, "author_capability": author_capability, "reviewer_capability": reviewer_capability, "structured_output_mode": { @@ -759,6 +945,7 @@ async def _run(arguments: argparse.Namespace, report_root: Path) -> dict[str, An "verifier_schema_hash": verifier_schema_hash, "token_comparison": token_comparison, "baseline_report": str(baseline_path) if baseline_path else "", + "repetitions": repetitions, "results": results, } @@ -768,7 +955,11 @@ def main() -> int: report_root = BACKEND_ROOT / "live-evals" / datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ") report_root.mkdir(parents=True, exist_ok=True) try: - result = asyncio.run(_run(arguments, report_root)) + result = ( + compare_guidance_report_paths(*arguments.compare_guidance_reports) + if arguments.compare_guidance_reports + else asyncio.run(_run(arguments, report_root)) + ) except KeyboardInterrupt: # Let an explicit operator interruption retain its normal CLI # semantics. An external kill cannot be reported reliably either. diff --git a/backend/app/cad_agent/ports.py b/backend/app/cad_agent/ports.py index 5c8809bc..dc88cff5 100644 --- a/backend/app/cad_agent/ports.py +++ b/backend/app/cad_agent/ports.py @@ -5,13 +5,60 @@ from __future__ import annotations from dataclasses import dataclass from typing import Any, Protocol -from .domain.state import TaskState +from .domain.state import TaskPhase, TaskState class AdapterUnavailable(RuntimeError): """A bounded external-service outage; handlers must preserve checkpoints.""" +@dataclass(frozen=True, slots=True) +class AuthorGuidanceSelection: + """Non-authoritative author context selected from the local guidance corpus.""" + + version: str = "" + section_ids: tuple[str, ...] = () + content: str = "" + enabled: bool = False + fallback_reason: str = "" + + def usage_metadata(self) -> dict[str, object]: + return { + "guidance_version": self.version, + "guidance_section_ids": list(self.section_ids), + "guidance_chars": len(self.content), + "guidance_enabled": self.enabled, + "guidance_fallback_reason": self.fallback_reason, + } + + +class AuthorGuidance(Protocol): + """Select bounded local author guidance without interpreting user intent.""" + + def select( + self, + *, + phase: TaskPhase, + atomic_id: str, + repair_required: bool, + supported_atomic_ids: tuple[str, ...], + ) -> AuthorGuidanceSelection: ... + + +class NullAuthorGuidance: + """Compatibility default that retains the pre-guidance author prompt.""" + + def select( + self, + *, + phase: TaskPhase, + atomic_id: str, + repair_required: bool, + supported_atomic_ids: tuple[str, ...], + ) -> AuthorGuidanceSelection: + return AuthorGuidanceSelection(fallback_reason="guidance_not_configured") + + @dataclass(frozen=True, slots=True) class InvocationRecord: invocation_id: str diff --git a/backend/app/settings.py b/backend/app/settings.py index 8eafe639..49a6c07d 100644 --- a/backend/app/settings.py +++ b/backend/app/settings.py @@ -71,6 +71,8 @@ class Settings: agent_consecutive_no_progress_limit: int = 6 agent_format_error_repeat_limit: int = 3 agent_context_char_limit: int = 14000 + agent_author_guidance_enabled: bool = True + agent_author_guidance_max_chars: int = 3600 agent_render_cache: bool = True autonomous_generation: bool = True resume_running_tasks_on_startup: bool = True @@ -211,6 +213,8 @@ def get_settings() -> Settings: agent_consecutive_no_progress_limit=max(1, int(os.getenv("CDSL_AGENT_CONSECUTIVE_NO_PROGRESS_LIMIT", "6"))), agent_format_error_repeat_limit=max(1, int(os.getenv("CDSL_AGENT_FORMAT_ERROR_REPEAT_LIMIT", "3"))), agent_context_char_limit=max(4000, int(os.getenv("CDSL_AGENT_CONTEXT_CHAR_LIMIT", "14000"))), + agent_author_guidance_enabled=_env_flag("CDSL_AGENT_AUTHOR_GUIDANCE_ENABLED", True), + agent_author_guidance_max_chars=min(6000, max(1200, int(os.getenv("CDSL_AGENT_AUTHOR_GUIDANCE_MAX_CHARS", "3600")))), agent_render_cache=_env_flag("CDSL_AGENT_RENDER_CACHE", True), autonomous_generation=True, # Production instances recover durable runs by default. Test workers diff --git a/backend/engine/cdsl_engine/build123d_adapter.py b/backend/engine/cdsl_engine/build123d_adapter.py index d9ae6558..28969a7b 100644 --- a/backend/engine/cdsl_engine/build123d_adapter.py +++ b/backend/engine/cdsl_engine/build123d_adapter.py @@ -66,9 +66,15 @@ class Build123dGeometryAdapter: @staticmethod def _wire(edges: list[dict[str, Any]]) -> Wire: - # 将边字典列表(直线/圆弧)组装成 build123d 的 Wire 线框。 + # 将边字典列表(直线/圆弧/插值 B 样条)组装成 build123d 的 Wire 线框。 built: list[Edge] = [] for edge in edges: + if edge.get("type") == "bspline": + points = [_vector(point) for point in edge.get("points_mm") or []] + if len(points) < 3: + raise ValueError("bspline contour edge needs at least 3 points") + built.append(Edge.make_spline(points, periodic=False)) + continue start = _vector(edge["start_mm"]) end = _vector(edge["end_mm"]) if edge.get("type") == "arc" and edge.get("center_mm") is not None: @@ -134,20 +140,38 @@ class Build123dGeometryAdapter: result: list[Face] = [] for region in regions: outer = region.get("outer") or [] - if len(outer) < 2: + # 闭合插值样条仅有一条边;直线/圆弧轮廓通常由多条边组成。 + if len(outer) < 1: continue face = Face(self._wire(outer)) - holes = [self._wire(hole) for hole in region.get("holes") or [] if len(hole) >= 2] + holes = [self._wire(hole) for hole in region.get("holes") or [] if len(hole) >= 1] result.append(face.make_holes(holes) if holes else face) return result # 2. 退化:仅有单组轮廓边时,直接作为外轮廓建面。 edges = sketch.get("contour_edges_mm") or [] - if len(edges) >= 2: + if len(edges) >= 1: return [Face(self._wire(edges))] # 3. 最终回退:由工作平面与实体圆生成面(圆环/孔洞处理见 _faces_from_circles)。 plane = PlaneSpec.from_mapping(sketch.get("workplane") or {}) return self._faces_from_circles(sketch.get("entities") or [], plane) + def loft(self, sketches: list[dict[str, Any]]) -> Solid: + """由多条简单闭合草图轮廓生成实体放样。""" + wires: list[Wire] = [] + for index, sketch in enumerate(sketches): + # Solid.make_loft 接收 Wire;复用 faces_for_sketch 保持放样、 + # 拉伸和回转的 profile resolver 一致。多区域/内环的截面对应关系 + # 尚未由 CDSL 表达,必须显式拒绝而非猜测。 + faces = self.faces_for_sketch(sketch) + if len(faces) != 1: + raise ValueError(f"loft profile {index} must resolve to exactly one closed region") + if faces[0].inner_wires(): + raise ValueError(f"loft profile {index} must not contain inner loops") + wires.append(faces[0].outer_wire()) + if len(wires) < 2: + raise ValueError("loft requires at least two profile sketches") + return Solid.make_loft(wires) + @staticmethod def _coerce_single_or_compound(result: Any, *, empty_error: str | None = None) -> Any: """规整一次布尔结果:None/空视为失败(可选报错),多成员合并为 Compound。""" @@ -189,14 +213,22 @@ class Build123dGeometryAdapter: """ # 1. 采样点到目标的最远命中距离决定穿透余量;没有任何采样点命中 # 说明 profile 与目标面无交叠,无法裁剪(保留 extent_target_not_reached)。 + # through_next 的 target 是当前主体,必须先从其面中选出实际命中的 + # 下一终止面,不能把整个 body 当作待拉伸的 Face。 unit = _vector(direction).normalized() - hits = [ - Build123dGeometryAdapter._forward_intersection_distance(target, point, unit) - for point in Build123dGeometryAdapter.profile_sample_points(face) - ] - distances = [value for value in hits if value is not None] - if not distances: + points = Build123dGeometryAdapter.profile_sample_points(face) + targets = [target] if isinstance(target, Face) else list(target.faces()) + candidates = [] + for candidate in targets: + hits = [Build123dGeometryAdapter._forward_intersection_distance(candidate, point, unit) for point in points] + distances = [value for value in hits if value is not None] + if distances: + candidates.append((len(distances), min(distances), candidate, distances)) + if not candidates: raise ValueError("extent target is not reached by the profile") + # 覆盖最多 profile 采样点的面就是本次实体的下一终止面;覆盖数相同 + # 时取最近的正向交点,确保相邻面交界处的选择稳定。 + _, _, target, distances = max(candidates, key=lambda item: (item[0], -item[1])) margin = max(distances) + 2.0 # 2. 穿透拉伸 profile,同时把目标面向回推生成体层,二者求交即裁剪体。 # build123d 的布尔交方法名是 intersect(不是 OCC 的 common), diff --git a/backend/engine/cdsl_engine/capabilities.py b/backend/engine/cdsl_engine/capabilities.py index 219db982..88c21a99 100644 --- a/backend/engine/cdsl_engine/capabilities.py +++ b/backend/engine/cdsl_engine/capabilities.py @@ -22,21 +22,22 @@ _SKETCH_ATOM_PREFIXES = ("extrude_", "revolve_") # 物理意义:仅 extrude 直切类原子支持;add/回转对开放轮廓会造出无意义的封块。 _OPEN_PROFILE_ATOMICS = frozenset({"extrude_cut_blind", "extrude_cut_through"}) _PRIMARY_ATOMICS = frozenset({ - "extrude_add_blind", "extrude_add_two_sided", "extrude_cut_blind", + "extrude_add_blind", "extrude_add_two_sided", "extrude_cut_blind", "extrude_cut_two_sided", "extrude_cut_through", "revolve_add", "revolve_cut", "hole_blind", "hole_countersink", "hole_counterbore", "sphere_add", "box_add", "cylinder_add", }) _HOLE_ATOMICS = frozenset({"hole_blind", "hole_countersink", "hole_counterbore", "hole_wizard"}) _ACTIVE_BODY_REQUIRED = frozenset({ - "extrude_cut_blind", "extrude_cut_through", "revolve_cut", *_HOLE_ATOMICS, "fillet", "chamfer", + "extrude_cut_blind", "extrude_cut_two_sided", "extrude_cut_through", + "revolve_cut", *_HOLE_ATOMICS, "fillet", "chamfer", # thread_cut 是 cut 型特征:必须在已有主体(宿主)上做布尔差,不能凭空 # 造实体;无宿主时按 active_body 前置阻止而非让 executor 在 None 上崩溃。 "thread_cut", }) _BODY_MUTATING_ATOMICS = frozenset({ - "extrude_add_blind", "extrude_add_two_sided", "extrude_cut_blind", - "extrude_cut_through", + "extrude_add_blind", "extrude_add_two_sided", "extrude_cut_blind", "extrude_cut_two_sided", + "extrude_cut_through", "loft_add", "revolve_add", "revolve_cut", "sphere_add", "box_add", "cylinder_add", "thread_add", "thread_cut", "bend_add", *_HOLE_ATOMICS, "fillet", "chamfer", }) @@ -148,27 +149,32 @@ def _schema_contract() -> dict[str, dict[str, Any]]: def sketch_ids_required_by_contract(cdsl: dict[str, Any]) -> frozenset[str]: - """Return only sketches consumed by a feature with a sketch contract. + """Return only sketches consumed by executable feature contracts. CAD documents commonly preserve construction or abandoned sketches whose contours are incomplete. They are semantic data, but must not make an - otherwise independent feature history ineligible for execution. + otherwise independent feature history ineligible for execution. Most + operations declare ``sketch_id``; loft declares its ordered section set + in ``params.profile_sketch_ids``. """ contracts = _schema_contract() - return frozenset( - str(feature["sketch_id"]) - for feature in cdsl.get("features") or () - if feature.get("sketch_id") is not None - and (contracts.get(str(feature.get("atomic_id") or "")) or {}).get("requires_sketch") - ) + required: set[str] = set() + for feature in cdsl.get("features") or (): + atomic_id = str(feature.get("atomic_id") or "") + if feature.get("sketch_id") is not None and (contracts.get(atomic_id) or {}).get("requires_sketch"): + required.add(str(feature["sketch_id"])) + if atomic_id == "loft_add": + for sketch_id in (feature.get("params") or {}).get("profile_sketch_ids") or (): + required.add(str(sketch_id)) + return frozenset(required) def _has_closed_region(sketch: dict[str, Any]) -> bool: """Mirror the adapter's input contract without importing the geometry kernel.""" regions = sketch.get("contour_regions_mm") or [] - if any(len(region.get("outer") or []) >= 2 for region in regions if isinstance(region, dict)): + if any(len(region.get("outer") or []) >= 1 for region in regions if isinstance(region, dict)): return True - if len(sketch.get("contour_edges_mm") or []) >= 2: + if len(sketch.get("contour_edges_mm") or []) >= 1: return True return any( entity.get("type") == "circle" and not entity.get("construction") @@ -178,6 +184,18 @@ def _has_closed_region(sketch: dict[str, Any]) -> bool: ) +def _has_single_loft_region(sketch: dict[str, Any]) -> bool: + """Whether a resolved profile maps exactly to one solid loft section.""" + regions = sketch.get("contour_regions_mm") or [] + if regions: + return len(regions) == 1 and not (regions[0].get("holes") or []) + circles = [ + entity for entity in sketch.get("entities") or [] + if isinstance(entity, dict) and entity.get("type") == "circle" and not entity.get("construction") + ] + return len(circles) == 1 + + @dataclass(frozen=True) class CapabilityAnalysis: plan: tuple[FeaturePlanNode, ...] @@ -295,6 +313,54 @@ class CapabilityAnalyzer: sketch_id=node.sketch_id, atomic_id=node.atomic_id, )) + if node.atomic_id == "loft_add": + profile_ids = params.get("profile_sketch_ids") + if not isinstance(profile_ids, list) or len(profile_ids) < 2: + blockers.append(self._blocker( + node.feature_id, "invalid_loft_profiles", + "Loft requires at least two profile sketch ids", + )) + elif len({str(sketch_id) for sketch_id in profile_ids}) != len(profile_ids): + blockers.append(self._blocker( + node.feature_id, "invalid_loft_profiles", + "Loft profile sketch ids must be distinct", + )) + else: + for sketch_id in profile_ids: + sketch_id = str(sketch_id) + profile = sketches.get(sketch_id) + if profile is None: + blockers.append(self._blocker( + node.feature_id, "missing_loft_profile", + "Loft profile sketch does not exist", sketch_id=sketch_id, + )) + continue + profile_type = str((profile.get("profile") or {}).get("type") or "") + required.append(f"loft_profile:{profile_type}") + resolution_error = sketch_errors.get(sketch_id) + if resolution_error: + blockers.append(self._blocker( + node.feature_id, "profile_resolution_failed", + "A loft profile sketch could not be resolved into executable regions", + sketch_id=sketch_id, reason=resolution_error, + )) + elif profile_type not in self.profile_types: + blockers.append(self._blocker( + node.feature_id, "unsupported_profile", + "The current runtime cannot resolve a loft profile", + sketch_id=sketch_id, profile_type=profile_type, + )) + elif not _has_closed_region(profile): + blockers.append(self._blocker( + node.feature_id, "profile_no_closed_region", + "A loft profile contains no closed region", sketch_id=sketch_id, + )) + elif not _has_single_loft_region(profile): + blockers.append(self._blocker( + node.feature_id, "unsupported_loft_profile_regions", + "Loft currently requires exactly one outer profile without holes", + sketch_id=sketch_id, + )) if node.atomic_id.startswith(_SKETCH_ATOM_PREFIXES): # #2 draft:extrudeParams.draft 在 cdsl_schema.json 中被允许, # 但 runtime 的拉伸执行器(build123d Solid.extrude)没有锥形 @@ -332,7 +398,7 @@ class CapabilityAnalyzer: node.feature_id, "missing_offset_distance", "Offset-from-surface requires a non-zero captured offset distance", )) - if node.atomic_id == "extrude_add_two_sided": + if node.atomic_id in {"extrude_add_two_sided", "extrude_cut_two_sided"}: reverse_condition = params.get("reverse_end_condition") or {"type": "blind"} reverse_type = reverse_condition.get("type") required.append(f"extent:reverse:{reverse_type}") @@ -463,7 +529,8 @@ class CapabilityAnalyzer: if node.atomic_id in _BODY_MUTATING_ATOMICS: body_available = True body_producers = { - "extrude_add_blind", "extrude_add_two_sided", "extrude_cut_blind", + "extrude_add_blind", "extrude_add_two_sided", "extrude_cut_blind", "extrude_cut_two_sided", + "extrude_cut_through", "loft_add", "revolve_add", "revolve_cut", "sphere_add", "box_add", "cylinder_add", "thread_add", "bend_add", # thread_cut 与 extrude_cut_blind/revolve_cut 一致:无宿主时由 diff --git a/backend/engine/cdsl_engine/cdsl_schema.json b/backend/engine/cdsl_engine/cdsl_schema.json index b3aad2b2..2b15522b 100644 --- a/backend/engine/cdsl_engine/cdsl_schema.json +++ b/backend/engine/cdsl_engine/cdsl_schema.json @@ -108,6 +108,20 @@ "required": ["end_condition"], "additionalProperties": false }, + "loftParams": { + "type": "object", + "properties": { + "profile_sketch_ids": { + "type": "array", + "minItems": 2, + "maxItems": 16, + "uniqueItems": true, + "items": {"type": "string", "pattern": "^[A-Za-z0-9_-]{1,80}$"} + } + }, + "required": ["profile_sketch_ids"], + "additionalProperties": false + }, "revolveParams": { "type": "object", "properties": {"angle_deg": {"type": "number", "minimum": 0, "maximum": 360}, "axis": {"$ref": "#/$defs/axis"}, "reverse": {"type": "boolean"}, "end_condition": {"$ref": "#/$defs/endCondition"}}, @@ -367,18 +381,20 @@ "analyticSegment": { "type": "object", "properties": { - "type": {"enum": ["line", "arc", "circle"]}, + "type": {"enum": ["line", "arc", "circle", "bspline"]}, "start": {"$ref": "#/$defs/point2"}, "end": {"$ref": "#/$defs/point2"}, "center": {"$ref": "#/$defs/point2"}, "radius_mm": {"$ref": "#/$defs/positive"}, + "points": {"type": "array", "minItems": 3, "items": {"$ref": "#/$defs/point2"}}, "clockwise": {"type": "boolean"} }, "required": ["type"], "allOf": [ {"if": {"properties": {"type": {"const": "line"}}}, "then": {"required": ["start", "end"]}}, {"if": {"properties": {"type": {"const": "arc"}}}, "then": {"required": ["start", "end", "center", "radius_mm"]}}, - {"if": {"properties": {"type": {"const": "circle"}}}, "then": {"required": ["center", "radius_mm"]}} + {"if": {"properties": {"type": {"const": "circle"}}}, "then": {"required": ["center", "radius_mm"]}}, + {"if": {"properties": {"type": {"const": "bspline"}}}, "then": {"required": ["start", "end", "points"]}} ], "additionalProperties": false }, @@ -421,7 +437,7 @@ "required": ["type", "contours"], "additionalProperties": false }, - "feature_atomic_ids": {"enum": ["extrude_add_blind", "extrude_add_two_sided", "extrude_cut_blind", "extrude_cut_through", "revolve_add", "revolve_cut", "hole_blind", "hole_countersink", "hole_counterbore", "sphere_add", "box_add", "cylinder_add", "thread_add", "thread_cut", "bend_add", "fillet", "chamfer", "pattern_linear", "pattern_mirror", "pattern_circular", "reference_plane", "reference_axis", "hole_wizard"]}, + "feature_atomic_ids": {"enum": ["extrude_add_blind", "extrude_add_two_sided", "extrude_cut_blind", "extrude_cut_two_sided", "extrude_cut_through", "loft_add", "revolve_add", "revolve_cut", "hole_blind", "hole_countersink", "hole_counterbore", "sphere_add", "box_add", "cylinder_add", "thread_add", "thread_cut", "fillet", "chamfer", "pattern_linear", "pattern_mirror", "pattern_circular", "reference_plane", "reference_axis", "hole_wizard"]}, "feature": { "type": "object", "properties": { @@ -442,6 +458,8 @@ {"if": {"properties": {"atomic_id": {"const": "extrude_add_two_sided"}}}, "then": {"properties": {"params": {"$ref": "#/$defs/extrudeParams"}}}}, {"if": {"properties": {"atomic_id": {"const": "extrude_cut_blind"}}}, "then": {"properties": {"params": {"$ref": "#/$defs/extrudeParams"}}}}, {"if": {"properties": {"atomic_id": {"const": "extrude_cut_through"}}}, "then": {"properties": {"params": {"$ref": "#/$defs/extrudeCutThroughParams"}}}}, + {"if": {"properties": {"atomic_id": {"const": "extrude_cut_two_sided"}}}, "then": {"properties": {"params": {"$ref": "#/$defs/extrudeParams"}}}}, + {"if": {"properties": {"atomic_id": {"const": "loft_add"}}}, "then": {"properties": {"params": {"$ref": "#/$defs/loftParams"}}}}, {"if": {"properties": {"atomic_id": {"const": "revolve_add"}}}, "then": {"properties": {"params": {"$ref": "#/$defs/revolveParams"}}}}, {"if": {"properties": {"atomic_id": {"const": "revolve_cut"}}}, "then": {"properties": {"params": {"$ref": "#/$defs/revolveParams"}}}}, {"if": {"properties": {"atomic_id": {"const": "sphere_add"}}}, "then": {"properties": {"params": {"$ref": "#/$defs/sphereParams"}}}}, diff --git a/backend/engine/cdsl_engine/phase_pools.py b/backend/engine/cdsl_engine/phase_pools.py index 7e274f06..83a5dd5c 100644 --- a/backend/engine/cdsl_engine/phase_pools.py +++ b/backend/engine/cdsl_engine/phase_pools.py @@ -18,7 +18,7 @@ from .sketch_solver import resolve_required_sketches P3_ATOMIC_IDS = frozenset({ "reference_plane", "reference_axis", "extrude_add_blind", "extrude_add_two_sided", - "extrude_cut_blind", "revolve_add", "revolve_cut", + "extrude_cut_blind", "extrude_cut_two_sided", "revolve_add", "revolve_cut", }) P4_ATOMIC_IDS = P3_ATOMIC_IDS | frozenset({"hole_wizard"}) P6_ATOMIC_IDS = P4_ATOMIC_IDS | frozenset({"pattern_linear", "pattern_mirror"}) @@ -27,7 +27,7 @@ P3_PROFILE_TYPES = frozenset({"analytic_contours", "circle", "polygon"}) # revolve history. Whether a first cut has a preceding active body remains a # runtime preflight question, not a reason to erase it from the input pool. _P3_PRIMARY_ATOMICS = frozenset({ - "extrude_add_blind", "extrude_add_two_sided", "extrude_cut_blind", "revolve_add", "revolve_cut", + "extrude_add_blind", "extrude_add_two_sided", "extrude_cut_blind", "extrude_cut_two_sided", "revolve_add", "revolve_cut", }) @@ -47,8 +47,8 @@ def _p3_profile_ready(cdsl: dict[str, Any]) -> bool: if profile.get("type") not in P3_PROFILE_TYPES: return False if not ( - any(len(region.get("outer") or []) >= 2 for region in sketch.get("contour_regions_mm") or () if isinstance(region, dict)) - or len(sketch.get("contour_edges_mm") or ()) >= 2 + any(len(region.get("outer") or []) >= 1 for region in sketch.get("contour_regions_mm") or () if isinstance(region, dict)) + or len(sketch.get("contour_edges_mm") or ()) >= 1 or any( entity.get("type") == "circle" and not entity.get("construction") and float(entity.get("radius_mm") or 0.0) > 0 for entity in sketch.get("entities") or () diff --git a/backend/engine/cdsl_engine/profile_schema.json b/backend/engine/cdsl_engine/profile_schema.json index e85dae94..8e25ef9d 100644 --- a/backend/engine/cdsl_engine/profile_schema.json +++ b/backend/engine/cdsl_engine/profile_schema.json @@ -1,15 +1,17 @@ { "schema": "cdsl.engine.schema.v1", - "schema_version": "1.3.1", + "schema_version": "1.3.2", "cdsl_json_schema_file": "cdsl_schema.json", "maintenance_rule": "The CDSL-only runtime contract is limited to direct generic profiles and runtime.py EXECUTORS. Legacy macro profiles are importer compatibility syntax and must be lowered by cdsl_importer.legacy_profile_adapter before generic runtime validation.", "coordinate_convention": "All profile dimensions use millimetres. Two-dimensional points are [u, v] in the sketch workplane.", "runtime_supported_profiles": ["circle", "polygon", "analytic_contours"], "operation_contracts": { "extrude_add_blind": {"atomic_id":"extrude_add_blind","contract_version":"3.0","fragment_shape":{"sketch":"required","params":"required_object","selector_tokens":"forbidden"},"author_params_schema":{"type":"object","properties":{"distance_mm":{"type":"number","exclusiveMinimum":0},"reverse":{"type":"boolean"}},"required":["distance_mm"],"additionalProperties":false},"selector_policy":{"slot":null,"token_kind":null,"min_items":0,"max_items":0,"snapshot_bound":false},"server_injected_paths":[],"reference_policy":{"mode":"none"},"semantic_preflight":["sketch_workplane","profile_non_self_intersecting"],"candidate_verifiers":["single_connected_body"]}, + "loft_add": {"atomic_id":"loft_add","contract_version":"3.0","fragment_shape":{"sketch":"forbidden","params":"required_object","selector_tokens":"forbidden"},"author_params_schema":{"type":"object","properties":{"profile_sketch_ids":{"type":"array","items":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,80}$"},"minItems":2,"maxItems":16,"uniqueItems":true}},"required":["profile_sketch_ids"],"additionalProperties":false},"selector_policy":{"slot":null,"token_kind":null,"min_items":0,"max_items":0,"snapshot_bound":false},"server_injected_paths":[],"reference_policy":{"mode":"none"},"semantic_preflight":["loft_profiles_exist","loft_profiles_closed","loft_profiles_single_region"],"candidate_verifiers":["single_connected_body"]}, "extrude_add_two_sided": {"atomic_id":"extrude_add_two_sided","contract_version":"3.0","fragment_shape":{"sketch":"required","params":"required_object","selector_tokens":"forbidden"},"author_params_schema":{"type":"object","properties":{"distance_mm":{"type":"number","exclusiveMinimum":0},"reverse_distance_mm":{"type":"number","exclusiveMinimum":0},"reverse":{"type":"boolean"},"end_condition":{"type":"object","properties":{"type":{"type":"string","minLength":1,"maxLength":48},"solidworks_code":{"type":"integer"}},"required":["type","solidworks_code"],"additionalProperties":false},"reverse_end_condition":{"type":"object","properties":{"type":{"type":"string","minLength":1,"maxLength":48},"solidworks_code":{"type":"integer"}},"required":["type","solidworks_code"],"additionalProperties":false}},"required":["distance_mm","reverse_distance_mm"],"additionalProperties":false},"selector_policy":{"slot":null,"token_kind":null,"min_items":0,"max_items":0,"snapshot_bound":false},"server_injected_paths":[],"reference_policy":{"mode":"none"},"semantic_preflight":["sketch_workplane","profile_non_self_intersecting"],"candidate_verifiers":["single_connected_body"]}, "extrude_cut_blind": {"atomic_id":"extrude_cut_blind","contract_version":"3.0","fragment_shape":{"sketch":"required","params":"required_object","selector_tokens":"forbidden"},"author_params_schema":{"type":"object","properties":{"distance_mm":{"type":"number","exclusiveMinimum":0},"reverse":{"type":"boolean"}},"required":["distance_mm"],"additionalProperties":false},"selector_policy":{"slot":null,"token_kind":null,"min_items":0,"max_items":0,"snapshot_bound":false},"server_injected_paths":[],"reference_policy":{"mode":"none"},"semantic_preflight":["requires_active_solid","sketch_workplane","profile_non_self_intersecting","cut_exit_distance"],"candidate_verifiers":["single_connected_body","volume_decreased"]}, "extrude_cut_through": {"atomic_id":"extrude_cut_through","contract_version":"3.0","fragment_shape":{"sketch":"required","params":"required_object","selector_tokens":"forbidden"},"author_params_schema":{"type":"object","properties":{"reverse":{"type":"boolean"},"end_condition":{"type":"object","properties":{"type":{"type":"string","minLength":1,"maxLength":48},"solidworks_code":{"type":"integer"}},"required":["type","solidworks_code"],"additionalProperties":false}},"required":["end_condition"],"additionalProperties":false},"selector_policy":{"slot":null,"token_kind":null,"min_items":0,"max_items":0,"snapshot_bound":false},"server_injected_paths":[],"reference_policy":{"mode":"none"},"semantic_preflight":["requires_active_solid","sketch_workplane","profile_non_self_intersecting"],"candidate_verifiers":["single_connected_body","volume_decreased"]}, + "extrude_cut_two_sided": {"atomic_id":"extrude_cut_two_sided","contract_version":"3.0","fragment_shape":{"sketch":"required","params":"required_object","selector_tokens":"forbidden"},"author_params_schema":{"type":"object","properties":{"distance_mm":{"type":"number","exclusiveMinimum":0},"reverse_distance_mm":{"type":"number","exclusiveMinimum":0},"reverse":{"type":"boolean"},"end_condition":{"type":"object","properties":{"type":{"type":"string","minLength":1,"maxLength":48},"solidworks_code":{"type":"integer"}},"required":["type","solidworks_code"],"additionalProperties":false},"reverse_end_condition":{"type":"object","properties":{"type":{"type":"string","minLength":1,"maxLength":48},"solidworks_code":{"type":"integer"}},"required":["type","solidworks_code"],"additionalProperties":false}},"required":["distance_mm","reverse_distance_mm"],"additionalProperties":false},"selector_policy":{"slot":null,"token_kind":null,"min_items":0,"max_items":0,"snapshot_bound":false},"server_injected_paths":[],"reference_policy":{"mode":"none"},"semantic_preflight":["requires_active_solid","sketch_workplane","profile_non_self_intersecting","cut_exit_distance"],"candidate_verifiers":["single_connected_body","volume_decreased"]}, "revolve_add": {"atomic_id":"revolve_add","contract_version":"3.0","fragment_shape":{"sketch":"required","params":"required_object","selector_tokens":"forbidden"},"author_params_schema":{"type":"object","properties":{"angle_deg":{"type":"number","exclusiveMinimum":0,"maximum":360},"axis":{"type":"object","properties":{"origin_mm":{"type":"array","items":{"type":"number"},"minItems":3,"maxItems":3},"direction":{"type":"array","items":{"type":"number"},"minItems":3,"maxItems":3}},"required":["origin_mm","direction"],"additionalProperties":false},"reverse":{"type":"boolean"}},"required":["angle_deg","axis"],"additionalProperties":false},"selector_policy":{"slot":null,"token_kind":null,"min_items":0,"max_items":0,"snapshot_bound":false},"server_injected_paths":[],"reference_policy":{"mode":"none"},"semantic_preflight":["sketch_workplane","revolve_axis_on_sketch"],"candidate_verifiers":["single_connected_body"]}, "revolve_cut": {"atomic_id":"revolve_cut","contract_version":"3.0","fragment_shape":{"sketch":"required","params":"required_object","selector_tokens":"forbidden"},"author_params_schema":{"type":"object","properties":{"angle_deg":{"type":"number","exclusiveMinimum":0,"maximum":360},"axis":{"type":"object","properties":{"origin_mm":{"type":"array","items":{"type":"number"},"minItems":3,"maxItems":3},"direction":{"type":"array","items":{"type":"number"},"minItems":3,"maxItems":3}},"required":["origin_mm","direction"],"additionalProperties":false},"reverse":{"type":"boolean"}},"required":["angle_deg","axis"],"additionalProperties":false},"selector_policy":{"slot":null,"token_kind":null,"min_items":0,"max_items":0,"snapshot_bound":false},"server_injected_paths":[],"reference_policy":{"mode":"none"},"semantic_preflight":["requires_active_solid","sketch_workplane","revolve_axis_on_sketch"],"candidate_verifiers":["single_connected_body","volume_decreased"]}, "hole_blind": {"atomic_id":"hole_blind","contract_version":"3.0","fragment_shape":{"sketch":"forbidden","params":"required_object","selector_tokens":"required"},"author_params_schema":{"type":"object","properties":{"diameter_mm":{"type":"number","exclusiveMinimum":0},"depth_mm":{"type":"number","exclusiveMinimum":0},"positions":{"type":"array","minItems":1,"maxItems":64,"items":{"type":"object","properties":{"mm":{"type":"array","items":{"type":"number"},"minItems":3,"maxItems":3}},"required":["mm"],"additionalProperties":false}},"drill_angle_rad":{"type":"number","exclusiveMinimum":0,"maximum":3.141592653589793}},"required":["diameter_mm","depth_mm","positions"],"additionalProperties":false},"selector_policy":{"slot":"params.host_face","token_kind":"face","min_items":1,"max_items":1,"snapshot_bound":true},"server_injected_paths":["params.host_face"],"reference_policy":{"mode":"none"},"semantic_preflight":["host_face_exists","hole_positions_on_host_plane","cut_exit_distance"],"candidate_verifiers":["cylindrical_bore","through_cylindrical_bore"]}, @@ -72,7 +74,7 @@ "constraints": ["vertices contains at least three [u, v] points"] }, "analytic_contours": { - "summary": "Closed executable line, arc and circle contours. B-splines are unsupported in contours; imported construction B-splines are retained as non-executable audit geometry." + "summary": "Closed executable line, arc, circle and interpolation B-spline contours. Executable B-splines preserve ordered interpolation points and must be closed; imported construction B-splines remain non-executable audit geometry." } } } diff --git a/backend/engine/cdsl_engine/runtime.py b/backend/engine/cdsl_engine/runtime.py index 1e3a8e0a..49769822 100644 --- a/backend/engine/cdsl_engine/runtime.py +++ b/backend/engine/cdsl_engine/runtime.py @@ -20,8 +20,8 @@ from .sketch_solver import CORE_SHAPE_GENERATORS, resolve_required_sketches ALL_ATOMIC_IDS = frozenset({ - "extrude_add_blind", "extrude_add_two_sided", "extrude_cut_blind", - "extrude_cut_through", + "extrude_add_blind", "extrude_add_two_sided", "extrude_cut_blind", "extrude_cut_two_sided", + "extrude_cut_through", "loft_add", "revolve_add", "revolve_cut", "hole_blind", "hole_countersink", "hole_counterbore", "sphere_add", "box_add", "cylinder_add", "reference_plane", "reference_axis", @@ -83,6 +83,7 @@ class GeometryAdapter(Protocol): def body_solids(self, body: Any) -> list[Any]: ... def body_geometry(self, body: Any) -> dict[str, Any]: ... def faces_for_sketch(self, sketch: dict[str, Any]) -> list[Any]: ... + def loft(self, sketches: list[dict[str, Any]]) -> Any: ... def extrude(self, face: Any, direction: Vector3) -> Any: ... def extrude_trimmed(self, face: Any, target: Any, direction: Vector3) -> Any: ... def revolve(self, face: Any, angle_deg: float, axis: AxisSpec) -> Any: ... @@ -238,7 +239,7 @@ def _targeted_extent_vector( except ValueError as error: message = str(error) code = "non_uniform_extent_target" if "non-uniform" in message else "extent_target_not_reached" - if condition == "up_to_surface": + if condition in {"up_to_surface", "through_next"}: # #5 高级终止条件:profile 与目标面非均匀相交(部分采样点未 # 命中目标 → 悬空;或各点命中距离不一 → 斜目标面)时不再整体 # 拒绝,而是"裁剪"——只保留从 profile 到目标面之间的材料。 @@ -246,6 +247,7 @@ def _targeted_extent_vector( # 目标的部分被切掉(CAD "拉伸到面"标准语义)。若全部采样点都 # 未命中(profile 与目标面无交叠),extrude_trimmed 内部仍抛 # "not reached",保持显式拒绝。 + # through_next 从当前主体中选取实际命中的下一张面; # up_to_vertex/up_to_body/offset_from_surface 无 face 可构造 # 裁剪体层,仍保持显式拒绝。 return ExtentVector(vector_scale(direction, 1.0), trim_to=target) @@ -273,7 +275,7 @@ def _side_extent_vectors( ) -> list[ExtentVector]: """Resolve one directional extent without borrowing the opposite side. - ``extrude_add_two_sided`` calls this once for each independently captured + ``extrude_add_two_sided`` and ``extrude_cut_two_sided`` call this once for each independently captured termination. The regular one-sided executor also uses it for all simple termination modes, keeping the geometry adapter interface uniform. """ @@ -321,7 +323,7 @@ def _extent_vectors( end_condition = params.get("end_condition") or {"type": "blind"} condition = end_condition.get("type", "blind") distance = abs(float(params.get("distance_mm") or 0.0)) - if node.atomic_id == "extrude_add_two_sided": + if node.atomic_id in {"extrude_add_two_sided", "extrude_cut_two_sided"}: reverse_condition = params.get("reverse_end_condition") or {"type": "blind"} reverse_distance = abs(float(params.get("reverse_distance_mm") or 0.0)) if reverse_distance <= 0: @@ -459,6 +461,21 @@ def _shape_from_primary(node: FeaturePlanNode, session: ExecutionSession, *, ske return session.result(node) +def _execute_loft_add(node: FeaturePlanNode, session: ExecutionSession) -> FeatureResult: + # 放样截面不占用 feature.sketch_id;按有序 profile_sketch_ids 取已解析 + # 草图,并由 adapter 统一校验单闭环、无内环等内核输入约束。 + profile_ids = node.params.get("profile_sketch_ids") or [] + profiles: list[dict[str, Any]] = [] + for sketch_id in profile_ids: + sketch = session.sketches.get(str(sketch_id)) + if sketch is None: + raise ValueError(f"loft profile sketch {sketch_id!r} is not resolved") + profiles.append(sketch) + solid = session.adapter.loft(profiles) + session.register_body(node.feature_id, session.adapter.fuse(session.body, solid), replay_node=node) + return session.result(node) + + def _execute_reference_plane(node: FeaturePlanNode, session: ExecutionSession) -> FeatureResult: # 基准面特征(reference_plane)执行入口:从参数解析平面并登记为拓扑上下文。 @@ -782,6 +799,11 @@ def _translated_sketch(sketch: dict[str, Any], offset: Vector3) -> dict[str, Any for point_key in ("start_mm", "end_mm", "center_mm"): if point_key in value: value[point_key] = [float(value[point_key][index]) + components[index] for index in range(3)] + if "points_mm" in value: + value["points_mm"] = [ + [float(point[index]) + components[index] for index in range(3)] + for point in value["points_mm"] + ] for child in value.values(): translate(child) elif isinstance(value, list): @@ -791,6 +813,30 @@ def _translated_sketch(sketch: dict[str, Any], offset: Vector3) -> dict[str, Any return output +def _transformed_loft_profiles( + node: FeaturePlanNode, + params: dict[str, Any], + instance_id: str, + session: ExecutionSession, + transform: Callable[[dict[str, Any]], dict[str, Any]], +) -> None: + """为 pattern replay 创建放样截面的变换副本。""" + if node.atomic_id != "loft_add": + return + profile_ids = params.get("profile_sketch_ids") or [] + transformed_ids: list[str] = [] + for index, sketch_id in enumerate(profile_ids): + source = session.sketches.get(str(sketch_id)) + if source is None: + raise ValueError(f"loft profile sketch {sketch_id!r} has no replay definition") + transformed_id = f"{instance_id}.profile.{index}" + # 不复用原 profile:pattern 中的每个截面都必须与 source feature + # 使用相同的平移、镜像或旋转,才能保持放样的真实空间位置。 + session.sketches[transformed_id] = transform(source) + transformed_ids.append(transformed_id) + params["profile_sketch_ids"] = transformed_ids + + def _owner_plane_frame(session: ExecutionSession, selector: dict[str, Any]) -> dict[str, Any] | None: """解析 selector 的 owner 特征(reference_plane)注册的显式平面 frame。 @@ -831,6 +877,10 @@ def _translated_node(node: FeaturePlanNode, instance_id: str, offset: Vector3, s # box_add/sphere_add 以世界坐标几何中心定位;平移重放必须随实例移动该中心, # 否则阵列副本会静默重合在原位置。 params["center_mm"] = [float(center[index]) + components[index] for index in range(3)] + _transformed_loft_profiles( + node, params, instance_id, session, + lambda sketch: _translated_sketch(sketch, offset), + ) mirror_plane = params.get("mirror_plane") if isinstance(mirror_plane, dict) and node.atomic_id == "pattern_mirror": # #6 pattern 引用重解析:镜像面是 reference_plane 引用,随实例平移 @@ -938,6 +988,12 @@ def _mirrored_sketch(sketch: dict[str, Any], plane: PlaneSpec) -> dict[str, Any] point = value.get(point_key) if isinstance(point, list) and len(point) == 2: value[point_key] = [float(point[0]), -float(point[1])] + if isinstance(value.get("points"), list): + value["points"] = [ + [float(point[0]), -float(point[1])] + for point in value["points"] + if isinstance(point, list) and len(point) == 2 + ] for child in value.values(): mirror_local_coordinates(child) elif isinstance(value, list): @@ -954,6 +1010,8 @@ def _mirrored_sketch(sketch: dict[str, Any], plane: PlaneSpec) -> dict[str, Any] for point_key in ("start_mm", "end_mm", "center_mm"): if point_key in value: value[point_key] = _reflect_point(value[point_key], plane) + if "points_mm" in value: + value["points_mm"] = [_reflect_point(point, plane) for point in value["points_mm"]] if value.get("normal"): value["normal"] = _reflect_point(value["normal"], plane, vector=True) for child in value.values(): @@ -1013,6 +1071,10 @@ def _mirrored_node(node: FeaturePlanNode, instance_id: str, plane: PlaneSpec, se # 世界轴对齐,跨坐标平面镜像后仍保持朝向(斜镜像面在 _execute_mirror_pattern # 中已被显式拒绝)。 params["center_mm"] = _reflect_point(center, plane) + _transformed_loft_profiles( + node, params, instance_id, session, + lambda sketch: _mirrored_sketch(sketch, plane), + ) mirror_plane = params.get("mirror_plane") if isinstance(mirror_plane, dict) and node.atomic_id == "pattern_mirror": # #6 pattern 引用重解析:镜像重放 mirror source 时,其镜像面引用 @@ -1121,7 +1183,7 @@ def _rotated_sketch(sketch: dict[str, Any], axis: AxisSpec, angle_rad: float) -> # 环形阵列实例的草图:工作平面 frame(原点为点、x/y/normal 为向量)绕轴旋转; # 2D 局部实体坐标不动(frame 旋转后由草图求解器映射到新世界位置)。与 # _translated_sketch 对"世界坐标轮廓点"的处理对称,这里把 start/end/center - # 世界坐标点绕轴旋转。 + # 世界坐标点和圆弧法向绕轴旋转。 output = deepcopy(sketch) workplane = output.get("workplane") or {} if workplane.get("origin_mm"): @@ -1136,6 +1198,10 @@ def _rotated_sketch(sketch: dict[str, Any], axis: AxisSpec, angle_rad: float) -> for point_key in ("start_mm", "end_mm", "center_mm"): if point_key in value: value[point_key] = _rotated_point(value[point_key], axis, angle_rad) + if "points_mm" in value: + value["points_mm"] = [_rotated_point(point, axis, angle_rad) for point in value["points_mm"]] + if "normal" in value: + value["normal"] = list(_rotated_vector(tuple(float(v) for v in value["normal"]), axis, angle_rad)) for child in value.values(): rotate(child) elif isinstance(value, list): @@ -1187,6 +1253,10 @@ def _rotated_node(node: FeaturePlanNode, instance_id: str, axis: AxisSpec, angle center = params.get("center_mm") if isinstance(center, list) and len(center) == 3: params["center_mm"] = _rotated_point(center, axis, angle_rad) + _transformed_loft_profiles( + node, params, instance_id, session, + lambda sketch: _rotated_sketch(sketch, axis, angle_rad), + ) if node.atomic_id in {"pattern_mirror", "pattern_circular"}: # pattern 引用旋转重解析:镜像面 / 内层源随本实例一起旋转,否则嵌套 # pattern 作为 circular source 时重放会退化成错误几何(见 _translated_node)。 @@ -1300,6 +1370,11 @@ def _primary_executor(node: FeaturePlanNode, session: ExecutionSession, sketch: return _shape_from_primary(node, session, sketch=sketch) +def _loft_executor(node: FeaturePlanNode, session: ExecutionSession, sketch: dict[str, Any] | None) -> FeatureResult: + del sketch + return _execute_loft_add(node, session) + + def _reference_plane_executor(node: FeaturePlanNode, session: ExecutionSession, sketch: dict[str, Any] | None) -> FeatureResult: del sketch return _execute_reference_plane(node, session) @@ -1367,7 +1442,9 @@ EXECUTORS: dict[str, ExecutorFunction] = { "extrude_add_blind": _primary_executor, "extrude_add_two_sided": _primary_executor, "extrude_cut_blind": _primary_executor, + "extrude_cut_two_sided": _primary_executor, "extrude_cut_through": _primary_executor, + "loft_add": _loft_executor, "revolve_add": _primary_executor, "revolve_cut": _primary_executor, "hole_blind": _hole_executor, diff --git a/backend/engine/cdsl_engine/sketch_solver.py b/backend/engine/cdsl_engine/sketch_solver.py index 8f1d3093..b9429afc 100644 --- a/backend/engine/cdsl_engine/sketch_solver.py +++ b/backend/engine/cdsl_engine/sketch_solver.py @@ -1,9 +1,9 @@ """Core CDSL sketch resolver. The runtime accepts only direct geometric descriptions: circles, straight-edge -polygons, and closed analytic line/arc/circle contours. Semantic shapes and -historical profile macros belong to the importer compatibility layer and must -be lowered before this module is invoked. +polygons, and closed analytic line/arc/circle/B-spline contours. Semantic +shapes and historical profile macros belong to the importer compatibility +layer and must be lowered before this module is invoked. """ from __future__ import annotations @@ -81,6 +81,11 @@ def _transform_contours(contours: list[_Ctx], workplane: _Ctx) -> list[_Ctx]: if edge["type"] == "arc": output["center_mm"] = _to_3d(workplane, edge["center_mm"][0], edge["center_mm"][1]) output["normal"] = list(normal) + elif edge["type"] == "bspline": + output["points_mm"] = [ + _to_3d(workplane, point[0], point[1]) + for point in edge["points_mm"] + ] transformed.append(output) return transformed @@ -118,6 +123,8 @@ def _reverse(edge: _Ctx) -> _Ctx: output["start_mm"], output["end_mm"] = output["end_mm"], output["start_mm"] if output.get("type") == "arc" and "clockwise" in output: output["clockwise"] = not bool(output["clockwise"]) + if output.get("type") == "bspline": + output["points_mm"] = list(reversed(output["points_mm"])) return output @@ -176,7 +183,16 @@ def _segment_edges(segment: _Ctx) -> list[_Ctx]: if kind == "circle": return _circle_edges(segment) if kind == "bspline": - raise ValueError("analytic_contours: bspline requires an explicit approximation capability") + points = segment.get("points") or [] + if len(points) < 3: + raise ValueError("analytic_contours: bspline needs at least 3 interpolation points") + converted = [_point(point) for point in points] + return [{ + "type": "bspline", + "start_mm": converted[0], + "end_mm": converted[-1], + "points_mm": converted, + }] raise ValueError(f"analytic_contours: unsupported segment type {kind!r}") @@ -185,6 +201,9 @@ def _sample_loop(edges: list[_Ctx]) -> list[tuple[float, float]]: for edge in edges: start = edge["start_mm"] points.append((float(start[0]), float(start[1]))) + if edge.get("type") == "bspline": + points.extend((float(point[0]), float(point[1])) for point in edge["points_mm"][1:-1]) + continue if edge.get("type") != "arc": continue center, end = edge["center_mm"], edge["end_mm"] diff --git a/backend/tests/test_author_guidance.py b/backend/tests/test_author_guidance.py new file mode 100644 index 00000000..54af4727 --- /dev/null +++ b/backend/tests/test_author_guidance.py @@ -0,0 +1,158 @@ +from __future__ import annotations + +import asyncio +import json +from pathlib import Path +import sys +import tempfile +import unittest + + +ROOT = Path(__file__).resolve().parents[2] +sys.path.insert(0, str(ROOT / "backend")) + +from app.cad_agent.adapters.author_guidance import FileAuthorGuidance # noqa: E402 +from app.cad_agent.application.workflow import ModelIdentity, WorkflowConfig, WorkflowCoordinator # noqa: E402 +from app.cad_agent.domain.errors import ErrorCode, WorkflowError # noqa: E402 +from app.cad_agent.domain.state import TaskPhase, TaskState # noqa: E402 + + +GUIDANCE_ROOT = ROOT / "backend" / "agent" / "skills" / "cdsl-author-guidance" +PROFILE = ROOT / "backend" / "engine" / "cdsl_engine" / "profile_schema.json" + + +def atomic_ids() -> tuple[str, ...]: + return tuple(json.loads(PROFILE.read_text(encoding="utf-8"))["operation_contracts"]) + + +class _Repository: + def __init__(self, state: TaskState) -> None: + self.state = state + self.usage_records: list[dict] = [] + + def get_state(self, _task_id: str) -> TaskState: + return self.state + + def ledger_events(self, _task_id: str) -> list[dict]: + return [] + + def record_usage(self, _task_id: str, payload: dict) -> None: + self.usage_records.append(payload) + + def record_tool_audit(self, _task_id: str, _payload: dict) -> None: + pass + + +class _Artifacts: + def read_source_requirements(self, _task_id: str) -> str: + return "Create a symmetric mounting plate." + + def read_json(self, *_args: object) -> None: + return None + + +class _Runtime: + def supported_atomic_ids(self) -> tuple[str, ...]: + return atomic_ids() + + +class AuthorGuidanceTests(unittest.TestCase): + def test_manifest_covers_every_runtime_atomic_and_keeps_coordinate_core_at_minimum_budget(self) -> None: + guidance = FileAuthorGuidance(GUIDANCE_ROOT, max_chars=1_200) + covered: set[str] = set() + for atomic_id in atomic_ids(): + selection = guidance.select( + phase=TaskPhase.FEATURE_PENDING, + atomic_id=atomic_id, + repair_required=False, + supported_atomic_ids=atomic_ids(), + ) + self.assertTrue(selection.enabled, selection.fallback_reason) + self.assertIn("00-author-contract", selection.section_ids) + self.assertIn("03-coordinate-system-and-datums", selection.section_ids) + self.assertLessEqual(len(selection.content), 1_200) + self.assertIn("世界坐标", selection.content) + covered.update(section_id for section_id in selection.section_ids if section_id.startswith("op-")) + self.assertEqual(covered, {"op-extrude-add", "op-extrude-cut", "op-loft", "op-revolve", "op-hole", "op-reference", "op-pattern", "op-finish", "op-sphere", "op-primitives", "op-thread"}) + + def test_phase_repair_and_budget_selection_are_stable(self) -> None: + guidance = FileAuthorGuidance(GUIDANCE_ROOT, max_chars=3_600) + planning = guidance.select( + phase=TaskPhase.COMPILING_FEATURE_PLAN, + atomic_id="", + repair_required=False, + supported_atomic_ids=atomic_ids(), + ) + repair = guidance.select( + phase=TaskPhase.AWAITING_ACTION, + atomic_id="fillet", + repair_required=True, + supported_atomic_ids=atomic_ids(), + ) + self.assertEqual(planning.section_ids[:2], ("00-author-contract", "03-coordinate-system-and-datums")) + self.assertIn("02-parameters-and-derived-dimensions", planning.section_ids) + self.assertEqual(repair.section_ids[:3], ("00-author-contract", "03-coordinate-system-and-datums", "op-finish")) + self.assertIn("10-repair-and-best-effort", repair.section_ids) + + def test_disabled_missing_and_invalid_corpus_fall_back_without_authoring_failure(self) -> None: + common = { + "phase": TaskPhase.FEATURE_PENDING, + "atomic_id": "extrude_add_blind", + "repair_required": False, + "supported_atomic_ids": atomic_ids(), + } + self.assertEqual(FileAuthorGuidance(GUIDANCE_ROOT, enabled=False).select(**common).fallback_reason, "guidance_disabled") + with tempfile.TemporaryDirectory() as temporary: + root = Path(temporary) + self.assertEqual(FileAuthorGuidance(root).select(**common).fallback_reason, "guidance_load_failed:FileNotFoundError") + (root / "manifest.json").write_text("{}", encoding="utf-8") + self.assertEqual(FileAuthorGuidance(root).select(**common).fallback_reason, "guidance_load_failed:ValueError") + + def test_author_context_receives_guidance_but_keeps_the_existing_tool_instruction(self) -> None: + state = TaskState("cad_123456abcdef", TaskPhase.DRAFTING_REQUIREMENTS_DOCUMENT, 1) + workflow = WorkflowCoordinator( + WorkflowConfig(max_turns=8, format_error_limit=2), + _Repository(state), + _Artifacts(), + _Runtime(), + object(), + object(), + object(), + object(), + FileAuthorGuidance(GUIDANCE_ROOT), + ) + messages, selection = workflow._author_context(state.task_id, []) + system = str(messages[0]["content"]) + self.assertTrue(selection.enabled) + self.assertIn("Use exactly one offered structured tool call", system) + self.assertIn("Coordinate System And Datums", system) + self.assertIn("世界坐标", system) + + def test_invalid_author_tool_call_retains_guidance_usage_metadata(self) -> None: + state = TaskState("cad_123456abcdef", TaskPhase.DRAFTING_REQUIREMENTS_DOCUMENT, 1) + repository = _Repository(state) + class _Models: + async def call_tool(self, **_kwargs: object) -> dict: + return {"tool_calls": [], "usage": {"prompt_tokens": 3, "completion_tokens": 1, "total_tokens": 4}} + workflow = WorkflowCoordinator( + WorkflowConfig(max_turns=8, format_error_limit=2), + repository, + _Artifacts(), + _Runtime(), + _Models(), + object(), + object(), + object(), + FileAuthorGuidance(GUIDANCE_ROOT), + ) + tool = {"type": "function", "function": {"name": "write_requirements_document", "parameters": {"type": "object"}}} + result = asyncio.run(workflow._author_turn(state.task_id, ModelIdentity("provider", "model"), [tool], [])) + self.assertIsInstance(result, WorkflowError) + self.assertEqual(result.code, ErrorCode.AUTHOR_FORMAT_INVALID) + self.assertEqual(repository.usage_records[0]["guidance_enabled"], True) + self.assertIn("03-coordinate-system-and-datums", repository.usage_records[0]["guidance_section_ids"]) + self.assertEqual(repository.usage_records[0]["retry_reason"], "invalid_tool_call") + + +if __name__ == "__main__": + unittest.main() diff --git a/backend/tests/test_engine_loft_geometry.py b/backend/tests/test_engine_loft_geometry.py new file mode 100644 index 00000000..b40b92e6 --- /dev/null +++ b/backend/tests/test_engine_loft_geometry.py @@ -0,0 +1,117 @@ +from __future__ import annotations + +import tempfile +import unittest +from pathlib import Path + +from engine.cdsl_engine.runtime import rebuild_cdsl + + +def _spline_profile(sketch_id: str, z: float) -> dict: + return { + "id": sketch_id, + "workplane": {"origin_mm": [0, 0, z], "x_dir": [1, 0, 0], "normal": [0, 0, 1]}, + "profile": { + "type": "analytic_contours", + "contours": [{ + "role": "outer", + "closed": True, + "segments": [{ + "type": "bspline", + "start": [0, 0], + "end": [0, 0], + "points": [[0, 0], [20, 0], [25, 10], [15, 18], [0, 10], [0, 0]], + }], + }], + }, + } + + +class LoftGeometryTests(unittest.TestCase): + def test_open_bspline_segment_can_close_with_other_contour_edges(self): + cdsl = { + "schema": "cad.cdsl.llm.v1", + "schema_version": "1.1.0", + "kind": "part", + "part_id": "open-bspline-contour", + "meta": {"unit": "mm"}, + "geometry": {"sketches": [{ + "id": "profile", + "workplane": {"origin_mm": [0, 0, 0], "x_dir": [1, 0, 0], "normal": [0, 0, 1]}, + "profile": {"type": "analytic_contours", "contours": [{ + "role": "outer", "closed": True, + "segments": [ + {"type": "line", "start": [0, 0], "end": [0, 10]}, + { + "type": "bspline", "start": [0, 10], "end": [0, 0], + "points": [[0, 10], [4, 10], [4, 0], [0, 0]], + }, + ], + }]}, + }]}, + "features": [{ + "id": "add", "atomic_id": "extrude_add_blind", "depends_on": [], + "sketch_id": "profile", "params": {"distance_mm": 5}, + }], + } + with tempfile.TemporaryDirectory() as tmp: + rebuilt = rebuild_cdsl(cdsl, Path(tmp) / "open-bspline-contour.step") + self.assertGreater(rebuilt["volume_mm3"], 1.0) + + def test_bspline_loft_and_mirror_replay_produce_step(self): + cdsl = { + "schema": "cad.cdsl.llm.v1", + "schema_version": "1.1.0", + "kind": "part", + "part_id": "bspline-loft-mirror", + "meta": {"unit": "mm"}, + "geometry": {"sketches": [_spline_profile("lower", 0), _spline_profile("upper", 20)]}, + "features": [ + { + "id": "mirror_plane", + "atomic_id": "reference_plane", + "depends_on": [], + "params": {"plane": {"origin_mm": [35, 0, 0], "x_dir": [0, 1, 0], "normal": [1, 0, 0]}}, + "execution_status": "supported", + }, + { + "id": "loft", + "atomic_id": "loft_add", + "depends_on": [], + "params": {"profile_sketch_ids": ["lower", "upper"]}, + "execution_status": "supported", + }, + { + "id": "mirror", + "atomic_id": "pattern_mirror", + "depends_on": ["mirror_plane", "loft"], + "params": { + "source_feature_ids": ["loft"], + "mirror_plane": { + "kind": "plane", + "owner_feature_id": "mirror_plane", + "stable_id": "mirror-plane", + "source": "runtime_snapshot", + "confidence": 1.0, + }, + }, + "selectors": [{ + "kind": "plane", + "owner_feature_id": "mirror_plane", + "stable_id": "mirror-plane", + "source": "runtime_snapshot", + "confidence": 1.0, + }], + "execution_status": "supported", + }, + ], + } + with tempfile.TemporaryDirectory() as tmp: + rebuilt = rebuild_cdsl(cdsl, Path(tmp) / "loft.step") + self.assertGreater(rebuilt["volume_mm3"], 1.0) + self.assertEqual(rebuilt["solid_count"], 2) + self.assertEqual([item["feature_id"] for item in rebuilt["feature_results"]], ["mirror_plane", "loft", "mirror.m.loft", "mirror"]) + + +if __name__ == "__main__": + unittest.main() diff --git a/backend/tests/test_engine_pattern_transform_contract.py b/backend/tests/test_engine_pattern_transform_contract.py index 2aba2e95..0c21b28f 100644 --- a/backend/tests/test_engine_pattern_transform_contract.py +++ b/backend/tests/test_engine_pattern_transform_contract.py @@ -316,6 +316,55 @@ class PatternTransformContractTests(unittest.TestCase): self.assertAlmostEqual(rebuilt["volume_mm3"], 1000 + 72 + 24, places=5) + def test_circular_pattern_rotates_analytic_arc_normals_with_the_sketch(self) -> None: + """倾斜圆草图的环形阵列必须保持每条圆弧处于旋转后的工作平面。 + + ``analytic_contours`` 在运行时会预先展开为世界坐标的圆弧边。环形阵列 + 不能只旋转边端点和圆心:Build123d 依据 edge.normal 计算三点圆弧中点, + 法向遗留在源草图平面会让中点偏离 wire 平面,最终报 + ``Cannot build face(s): wires not planar``。该 fixture 使用绕 Z 轴的 + 垂直圆草图,保证每个实例都实际变换该法向。 + """ + cdsl = { + "schema": "cad.cdsl.llm.v1", "schema_version": "1.1.0", "kind": "part", + "part_id": "circular-arc-normal", "meta": {"unit": "mm"}, + "geometry": {"sketches": [ + { + "id": "base", "workplane": _workplane(origin=[0, 0, 0], normal=[0, 0, 1]), + "profile": {"type": "circle", "center": [0, 0], "radius_mm": 10}, + }, + { + "id": "boss", "workplane": _workplane(origin=[0, -10, 0], normal=[0, 1, 0]), + "profile": {"type": "analytic_contours", "contours": [{ + "role": "outer", "closed": True, + "segments": [{"type": "circle", "center": [0, -5], "radius_mm": 1}], + }]}, + }, + ]}, + "features": [ + { + "id": "base_add", "atomic_id": "extrude_add_blind", "depends_on": [], + "sketch_id": "base", "params": {"distance_mm": 10}, + }, + { + "id": "boss_add", "atomic_id": "extrude_add_blind", "depends_on": ["base_add"], + "sketch_id": "boss", "params": {"distance_mm": 2}, + }, + { + "id": "repeat", "atomic_id": "pattern_circular", "depends_on": ["boss_add"], + "params": { + "source_feature_ids": ["boss_add"], + "axis": {"origin_mm": [0, 0, 0], "direction": [0, 0, 1]}, + "pattern_count": 4, "sweep_angle_deg": 360, + }, + }, + ], + } + with tempfile.TemporaryDirectory() as directory: + rebuilt = rebuild_cdsl(cdsl, Path(directory) / "circular-arc-normal.step") + self.assertGreater(rebuilt["volume_mm3"], math.pi * 1000) + self.assertEqual(rebuilt["solid_count"], 1) + if __name__ == "__main__": unittest.main() diff --git a/backend/tests/test_engine_runtime_foundation.py b/backend/tests/test_engine_runtime_foundation.py index 43565df2..d31ae60f 100644 --- a/backend/tests/test_engine_runtime_foundation.py +++ b/backend/tests/test_engine_runtime_foundation.py @@ -918,6 +918,25 @@ class EngineRuntimeFoundationTests(unittest.TestCase): analysis = analyze_cdsl(cdsl) self.assertIn("missing_parameter", [item.code for item in analysis.feature_results[0].blockers]) + def test_two_sided_cut_uses_independent_forward_and_reverse_distances(self) -> None: + from cdsl_engine.runtime import rebuild_cdsl + + cdsl = self._base_block() + cdsl["geometry"]["sketches"].append({ + "id": "cut", "workplane": {**_workplane(), "origin_mm": [0, 0, 5]}, + "profile": {"type": "circle", "center": [0, 0], "radius_mm": 1}, + }) + cdsl["features"].append({ + "id": "cut_1", "atomic_id": "extrude_cut_two_sided", "depends_on": ["base_add"], + "params": { + "distance_mm": 3, "reverse_distance_mm": 5, + "end_condition": {"type": "blind"}, "reverse_end_condition": {"type": "blind"}, + }, "sketch_id": "cut", + }) + with tempfile.TemporaryDirectory() as directory: + result = rebuild_cdsl(cdsl, Path(directory) / "two-sided-cut.step") + self.assertAlmostEqual(result["volume_mm3"], 1000.0 - 8.0 * 3.141592653589793, places=5) + def test_revolve_can_resolve_an_owner_qualified_reference_axis(self) -> None: from cdsl_engine.runtime import rebuild_cdsl @@ -1038,6 +1057,22 @@ class EngineRuntimeFoundationTests(unittest.TestCase): result = rebuild_cdsl(cdsl, root / f"{name}.step") self.assertAlmostEqual(result["volume_mm3"], 1000 - expected_depth * 3.141592653589793, places=5) + def test_through_next_trims_a_partially_overlapping_profile_to_the_next_body_face(self) -> None: + from cdsl_engine.runtime import rebuild_cdsl + + base = self._base_block() + base["geometry"]["sketches"].append({ + "id": "partial", "workplane": {"origin_mm": [0, 0, 12], "x_dir": [1, 0, 0], "normal": [0, 0, -1]}, + "profile": {"type": "polygon", "vertices": [[3, -3], [9, -3], [9, 3], [3, 3]]}, + }) + base["features"].append({ + "id": "partial_add", "atomic_id": "extrude_add_blind", "depends_on": ["base_add"], + "sketch_id": "partial", "params": {"distance_mm": 0, "end_condition": {"type": "through_next"}}, + }) + with tempfile.TemporaryDirectory() as directory: + result = rebuild_cdsl(base, Path(directory) / "partial-through-next.step") + self.assertAlmostEqual(result["volume_mm3"], 1000 + 24, places=5) + def test_up_to_body_extent_uses_a_uniquely_resolved_body_record(self) -> None: from cdsl_engine.runtime import rebuild_cdsl diff --git a/backend/tests/test_live_guidance_comparison.py b/backend/tests/test_live_guidance_comparison.py new file mode 100644 index 00000000..eb1224e0 --- /dev/null +++ b/backend/tests/test_live_guidance_comparison.py @@ -0,0 +1,71 @@ +from __future__ import annotations + +from pathlib import Path +import sys +import unittest + + +ROOT = Path(__file__).resolve().parents[2] +sys.path.insert(0, str(ROOT / "backend")) + +from app.cad_agent.evals.live import _fixture, compare_guidance_reports # noqa: E402 + + +def _result(*, scenario: str, repetition: int, author_calls: int, context_chars: int, schema_rejections: int, failure_layer: str = "") -> dict: + return { + "scenario": scenario, + "repetition": repetition, + "outcome": "passed", + "revision_ids": ["revision_001"], + "projection": {"phase": "COMPLETED"}, + "checks": {"deterministic_claims_pass": True}, + "schema_rejection_count": schema_rejections, + "failure_attribution": {"layer": failure_layer} if failure_layer else None, + "scenario_budget": {"max_author_turns": 8, "max_reviewer_turns": 2, "max_total_calls": 10, "max_total_tokens": 1000}, + "usage": {"records": [{"context_chars": context_chars} for _ in range(author_calls)]}, + } + + +def _report(results: list[dict]) -> dict: + return { + "author": {"provider": "author", "model": "model"}, + "reviewer": {"provider": "reviewer", "model": "review"}, + "runtime_profile_sha256": "a" * 64, + "operation_contracts": [{"atomic_id": "extrude_add_blind", "contract_hash": "b" * 64}], + "author_guidance": {"enabled": False, "max_chars": 3600}, + "results": results, + } + + +class LiveGuidanceComparisonTests(unittest.TestCase): + def test_fixture_accepts_multiple_stable_scenarios_in_fixture_order(self) -> None: + selected = _fixture("comprehensive", ["l_bracket", "circular_flange_pcd"]) + self.assertEqual([item["id"] for item in selected], ["circular_flange_pcd", "l_bracket"]) + + def test_comparison_enforces_matched_budget_and_quality_gates(self) -> None: + control = _report([ + _result(scenario="part_a", repetition=1, author_calls=10, context_chars=1000, schema_rejections=2, failure_layer="cdsl_expression"), + _result(scenario="part_a", repetition=2, author_calls=10, context_chars=1000, schema_rejections=1), + ]) + treatment = _report([ + _result(scenario="part_a", repetition=1, author_calls=11, context_chars=1300, schema_rejections=0), + _result(scenario="part_a", repetition=2, author_calls=11, context_chars=1300, schema_rejections=0), + ]) + treatment["author_guidance"]["enabled"] = True + comparison = compare_guidance_reports(control, treatment) + self.assertEqual(comparison["status"], "passed") + self.assertTrue(comparison["gates"]["median_author_calls_within_ten_percent"]) + self.assertTrue(comparison["gates"]["model_or_cdsl_failure_improved"]) + + def test_comparison_excludes_explicit_unsupported_runtime_capability(self) -> None: + control = _report([_result(scenario="part_a", repetition=1, author_calls=10, context_chars=1000, schema_rejections=1)]) + treatment = _report([_result(scenario="part_a", repetition=1, author_calls=10, context_chars=1200, schema_rejections=0)]) + treatment["author_guidance"]["enabled"] = True + treatment["results"][0]["ledger"] = [{"operation_failures": [{"message": "unsupported_draft"}]}] + comparison = compare_guidance_reports(control, treatment) + self.assertEqual(comparison["treatment"]["eligible_runs"], 0) + self.assertEqual(comparison["excluded_capability_gaps"]["treatment"], [{"scenario": "part_a", "repetition": 1}]) + + +if __name__ == "__main__": + unittest.main() diff --git a/backend/tests/test_structured_llm.py b/backend/tests/test_structured_llm.py new file mode 100644 index 00000000..ccb50777 --- /dev/null +++ b/backend/tests/test_structured_llm.py @@ -0,0 +1,123 @@ +from __future__ import annotations + +import asyncio +from pathlib import Path +import sys +import unittest + + +ROOT = Path(__file__).resolve().parents[2] +sys.path.insert(0, str(ROOT / "backend")) + +from app.cad_agent.adapters.structured_llm import StructuredModelError, StructuredModelGateway # noqa: E402 +from app.settings import ProviderConfig, ProviderModel, Settings # noqa: E402 + + +def _settings(*, api_style: str = "responses", reasoning_effort: str = "medium") -> Settings: + provider = ProviderConfig( + "provider", "Provider", "https://example.invalid/v1", "key", + (ProviderModel("model"),), reasoning_effort=reasoning_effort, api_style=api_style, + ) + return Settings( + task_root=ROOT / "tmp-tasks", + conversation_root=ROOT / "tmp-conversations", + library_root=ROOT / "backend" / "cdsl_library", + engine_root=ROOT / "backend" / "engine" / "cdsl_engine", + llm_base_url=provider.base_url, + llm_api_key=provider.api_key, + llm_model="model", + llm_timeout_s=1, + default_provider_id="provider", + providers=(provider,), + ) + + +def _tool() -> dict: + return { + "type": "function", + "function": { + "name": "write_document", + "description": "Write one document.", + "parameters": {"type": "object", "properties": {}, "additionalProperties": False}, + }, + } + + +def _response(api_style: str) -> dict: + if api_style == "responses": + return { + "output": [{"type": "function_call", "name": "write_document", "arguments": "{}"}], + "usage": {"input_tokens": 5, "output_tokens": 3, "total_tokens": 8}, + } + return { + "choices": [{"message": {"tool_calls": [{"function": {"name": "write_document", "arguments": "{}"}}]}}], + "usage": {"prompt_tokens": 5, "completion_tokens": 3, "total_tokens": 8}, + } + + +class StructuredModelGatewayCompatibilityTests(unittest.TestCase): + def test_thinking_tool_choice_retries_without_reasoning_before_relaxing_tool_choice(self) -> None: + gateway = StructuredModelGateway(_settings()) + payloads: list[dict] = [] + + async def request(_provider: object, payload: dict) -> dict: + payloads.append(payload) + if len(payloads) == 1: + raise StructuredModelError("Provider rejected structured request (400): Thinking mode does not support this tool_choice") + return _response("responses") + + gateway._request = request # type: ignore[method-assign] + result = asyncio.run(gateway.call_tool( + messages=[{"role": "system", "content": "Call the tool."}], + tool=_tool(), provider_id="provider", model_id="model", required_tool_name="write_document", + )) + + self.assertEqual(len(payloads), 2) + self.assertEqual(payloads[0]["tool_choice"], {"type": "function", "name": "write_document"}) + self.assertEqual(payloads[0]["reasoning"], {"effort": "medium"}) + self.assertEqual(payloads[1]["tool_choice"], {"type": "function", "name": "write_document"}) + self.assertNotIn("reasoning", payloads[1]) + self.assertEqual(result["usage"]["structured_compatibility_mode"], "reasoning_disabled") + + def test_persistent_thinking_rejection_uses_auto_with_the_same_single_tool(self) -> None: + gateway = StructuredModelGateway(_settings(api_style="chat_completions", reasoning_effort="")) + payloads: list[dict] = [] + + async def request(_provider: object, payload: dict) -> dict: + payloads.append(payload) + if len(payloads) < 3: + raise StructuredModelError("Thinking mode does not support this tool_choice") + return _response("chat_completions") + + gateway._request = request # type: ignore[method-assign] + result = asyncio.run(gateway.call_tool( + messages=[{"role": "system", "content": "Call the tool."}], + tool=_tool(), provider_id="provider", model_id="model", required_tool_name="write_document", + )) + + self.assertEqual(len(payloads), 3) + self.assertEqual(payloads[0]["tool_choice"]["function"]["name"], "write_document") + self.assertEqual(payloads[1]["tool_choice"]["function"]["name"], "write_document") + self.assertEqual(payloads[2]["tool_choice"], "auto") + self.assertEqual(len(payloads[2]["tools"]), 1) + self.assertEqual(result["usage"]["structured_compatibility_mode"], "single_tool_auto") + + def test_unrelated_provider_rejection_is_not_retried(self) -> None: + gateway = StructuredModelGateway(_settings()) + payloads: list[dict] = [] + + async def request(_provider: object, payload: dict) -> dict: + payloads.append(payload) + raise StructuredModelError("Provider rejected structured request (400): invalid model") + + gateway._request = request # type: ignore[method-assign] + with self.assertRaisesRegex(StructuredModelError, "invalid model"): + asyncio.run(gateway.call_tool( + messages=[{"role": "system", "content": "Call the tool."}], + tool=_tool(), provider_id="provider", model_id="model", required_tool_name="write_document", + )) + self.assertEqual(len(payloads), 1) + + +if __name__ == "__main__": + unittest.main() diff --git a/cadfs_to_cdsl/ENGINE_CAPABILITY_GAPS_BASELINE.md b/cadfs_to_cdsl/ENGINE_CAPABILITY_GAPS_BASELINE.md new file mode 100644 index 00000000..5b651567 --- /dev/null +++ b/cadfs_to_cdsl/ENGINE_CAPABILITY_GAPS_BASELINE.md @@ -0,0 +1,103 @@ +# CADFS 基线 Engine 能力缺口与补齐清单 + +基于原始 `cadfs_to_cdsl/output` 全量报告。受影响样本数可重叠,不可相加。 + +## P0:主体、草图与核心特征 + +| 能力缺口 | 受影响样本 | 需要补足的能力 | +| --- | ---: | --- | +| B 样条草图 / `skFitSpline` | 未单独统计 | 控制点、闭合 wire、B-spline edge,以及供 extrude、loft、sweep 使用的 profile | +| 复杂草图轮廓 | 未单独统计 | 直线、圆弧、圆、椭圆、样条、多 wire 和孔洞的稳定闭合与排序 | +| `shell` | 696 | 移除面、壁厚、内/外方向、多实体和失败诊断 | +| `sweep` | 326 | 截面、路径、导轨、实体/曲面模式、扭转和过渡策略 | +| `loft` | 309 | 多 profile、闭合/开口 profile、导轨、实体/曲面模式和 profile 对齐 | +| `booleanBodies` | 187 | union、subtract、intersect、目标/工具 body 和保留工具体策略 | +| `circularPattern` | 232 | source feature 重放、旋转轴、数量、角度范围和嵌套 pattern | + +## P0:拉伸语义 + +| 能力缺口 | 受影响样本 | 需要补足的能力 | +| --- | ---: | --- | +| `extrude_cut_through_all` | 303 | 按目标 body 实际交段执行穿透全部切除 | +| `extrude_cut_two_sided` | 262 | 正反独立距离和终止条件的双向切除 | +| `extrude_extent:up_to_surface` | 95 | 到面终止与非平面 profile 裁剪 | +| `extrude_extent:up_to_next` | 49 | 到下一实体终止 | +| `extrude_add_through_all` | 4 | 穿透全部加料拉伸 | +| `extrude_extent:up_to_vertex` | 4 | 到顶点终止 | +| `extrude_surface_or_mixed` | 4 | 曲面拉伸和 surface/solid 混合拓扑 | +| `extrude_extent:up_to_body` | 2 | 到指定 body 终止 | + +## P1:selector 与基准面 + +| 能力缺口 | 受影响样本 | 需要补足的能力 | +| --- | ---: | --- | +| `extrude_profile_topology:intersect` | 382 | 面/边交集 selector 与持久化 | +| `extrude_profile_topology:cap_face` | 200 | 拉伸端盖面 selector | +| `extrude_profile_topology:cap_edge` | 133 | 拉伸端盖边 selector | +| `reference_plane:line_angle` | 123 | 线-角度基准面 | +| `extrude_profile_topology:swept_face` | 99 | 侧壁面 selector | +| `reference_plane:plane_point` | 47 | 平面-点基准面 | +| `reference_plane:three_point` | 35 | 三点基准面 | +| `reference_plane:mid_plane` | 25 | 两平面中面 | +| `reference_plane:line_point` | 23 | 线-点基准面 | +| `extrude_profile_topology:offset_face` | 18 | 偏移面 selector | +| `extrude_profile_topology:swept_edge` | 16 | 侧壁边 selector | +| `reference_plane:curve_point` | 13 | 曲线-点基准面 | +| `extrude_profile_topology:mid_cap_edge` | 2 | 中性面端盖边 selector | + +## P1:已有特征的未覆盖语义 + +| 特征 | 受影响样本 | 需要补足的能力 | +| --- | ---: | --- | +| `extrude` | 3,863 | 复杂 profile、拓扑引用和曲面/混合实体语义 | +| `fillet` | 1,913 | 稳定边选择、切线传播、半径可行性 | +| `revolve` | 742 | 轴选择、方向/角度、曲面模式和 profile 表达 | +| `hole` | 623 | 孔型、螺纹、沉头/沉孔、终止条件和宿主面引用 | +| `chamfer` | 588 | 距离-角度、双距离等参数及稳定边选择 | +| `revolve_surface` | 367 | 曲面旋转及其与实体布尔的混合策略 | +| `mirror` | 263 | source replay、镜像面引用和嵌套 pattern | +| `cPlane` | 145 | 除 OFFSET 外的基准面构造和可重用 frame | + +## P2:未覆盖的 FeatureScript 操作 + +| 能力缺口 | 需要补足的能力 | +| --- | --- | +| `draft` | 拔模面与中性面定义 | +| `thicken` | 曲面加厚和方向控制 | +| `split` | 面/实体分割与结果 body 管理 | +| `moveFace` | 偏移、旋转、平移现有面 | +| `deleteFace` | 删除面及修补策略 | +| `replaceFace` | 面替换与拓扑更新 | +| `derive` | 受控外部派生模型协议 | +| `import` | 受控外部几何导入协议 | + +## 补齐清单 + +### 第一阶段 + +- [ ] 支持 B 样条草图和闭合 profile。 +- [ ] 新增 `loft_add`。 +- [ ] 新增 `shell`。 +- [ ] 新增 `sweep_add`。 +- [ ] 新增多 body boolean:union、subtract、intersect。 +- [ ] 为以上 FeatureScript 操作完成 lowering、schema、executor、adapter、selector 和端到端回归。 + +### 第二阶段 + +- [ ] 补齐 extrude 的 through-all、two-sided cut、up-to-surface、up-to-next、up-to-body、up-to-vertex。 +- [ ] 支持 `revolve_surface` 和 surface/solid 混合策略。 +- [ ] 补齐 `hole_wizard` 的 CADFS 孔型、螺纹和终止条件。 +- [ ] 补齐 `fillet`/`chamfer` 的参数变体、可行性预检查和稳定失败诊断。 +- [ ] 补齐 `circularPattern` 和 `mirror` 的 source replay、基准引用和嵌套 pattern。 + +### 第三阶段 + +- [ ] 补齐 CAP、SWEPT、OFFSET、INTERSECT、MID_CAP 等面/边 selector。 +- [ ] 补齐 line-angle、plane-point、three-point、mid-plane、line-point、curve-point 基准面。 +- [ ] 补齐 selector 的 owner、几何签名和歧义解析。 + +### 第四阶段 + +- [ ] 支持 `draft`、`thicken`、`split`。 +- [ ] 支持 `moveFace`、`deleteFace`、`replaceFace`。 +- [ ] 定义并实现 `derive`、`import` 的受控资产协议。 diff --git a/cadfs_to_cdsl/README.md b/cadfs_to_cdsl/README.md index 7cdb1019..28f2a28f 100644 --- a/cadfs_to_cdsl/README.md +++ b/cadfs_to_cdsl/README.md @@ -14,3 +14,28 @@ PYTHONPATH=backend:. python -m cadfs_to_cdsl pipeline All stages are resumable. Use `--force` after changing converter behavior. The original CADFS directory is read-only; generated evidence is written under `cadfs_to_cdsl/output/samples//`. + +## Representative regression pool + +Generate the checked-in representative manifest after a full corpus conversion: + +```bash +PYTHONPATH=backend:. python -m cadfs_to_cdsl regression-select +``` + +The selector deterministically covers every observed FeatureScript modeling +operation, sketch entity type, lowered CDSL atomic operation, unsupported +operation, and unsupported engine-capability variant. It records which atomic +operations have a successful engine baseline and which are diagnostic-only; +the latter are never reported as passing builds. A normal engine change has a +small, executable STEP regression command: + +```bash +PYTHONPATH=backend:. python -m cadfs_to_cdsl regression --tier engine --stage rebuild +``` + +The command reruns by default; use `--resume` only to inspect cached results. +Use `--tier conversion --stage convert` to verify feature lowering and +diagnostics, or `--tier all --stage pipeline` for the complete representative +conversion/rebuild/compare pass. Unsupported CADFS features remain diagnostic +coverage, not expected successful engine builds. diff --git a/cadfs_to_cdsl/cli.py b/cadfs_to_cdsl/cli.py index 0374c8c0..378449a1 100644 --- a/cadfs_to_cdsl/cli.py +++ b/cadfs_to_cdsl/cli.py @@ -2,7 +2,9 @@ from __future__ import annotations import argparse, json from pathlib import Path +from .describe import describe_samples from .pipeline import load_samples, run_stage, scan, select_samples +from .regression import regression_sample_ids, summarize_regression, write_regression_manifest from .reports import generate_markdown_report, generate_reports, read_json DEFAULT_INPUT = Path("data/cadfs-sample/CADFS_test") @@ -12,11 +14,30 @@ DEFAULT_OUTPUT = Path("cadfs_to_cdsl/output") def _parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser(description="Convert CADFS FeatureScript to CDSL and validate against STEP") commands = parser.add_subparsers(dest="command", required=True) - for name in ("scan", "convert", "rebuild", "compare", "report", "pipeline"): + for name in ("scan", "convert", "rebuild", "compare", "report", "pipeline", "describe", "regression-select", "regression"): command = commands.add_parser(name) command.add_argument("--input", type=Path, default=DEFAULT_INPUT) command.add_argument("--output", type=Path, default=DEFAULT_OUTPUT) - if name != "scan": + if name == "regression-select": + command.add_argument("--manifest", type=Path, default=Path("cadfs_to_cdsl/regression/manifest.json")) + elif name == "regression": + command.add_argument("--manifest", type=Path, default=Path("cadfs_to_cdsl/regression/manifest.json")) + command.add_argument("--tier", choices=("engine", "conversion", "all"), default="engine") + command.add_argument("--stage", choices=("convert", "rebuild", "compare", "pipeline"), default="rebuild") + command.add_argument("--workers", type=int, default=1) + command.add_argument("--compare-mode", choices=("rp", "strict"), default="rp") + command.add_argument("--timeout-seconds", type=float, default=30.0, help="per-model OCC timeout (default: 30)") + command.add_argument("--resume", action="store_true", help="reuse cached per-sample stage results instead of rerunning them") + elif name == "describe": + command.add_argument("--shard", help="input shard directory to describe, for example 0005") + command.add_argument("--sample-id", action="append") + command.add_argument("--offset", type=int, default=0) + command.add_argument("--limit", type=int) + command.add_argument("--seed", type=int) + command.add_argument("--workers", type=int, default=1) + command.add_argument("--mode", choices=("local", "hybrid", "vision"), default="hybrid") + command.add_argument("--force", action="store_true") + elif name != "scan": command.add_argument("--sample-id", action="append") command.add_argument("--offset", type=int, default=0) command.add_argument("--limit", type=int) @@ -31,9 +52,33 @@ def _parser() -> argparse.ArgumentParser: def main(argv: list[str] | None = None) -> int: - args = _parser().parse_args(argv); args.output.mkdir(parents=True, exist_ok=True) + args = _parser().parse_args(argv) + if args.command != "describe": + args.output.mkdir(parents=True, exist_ok=True) if args.command == "scan": records = scan(args.input, args.output); result = {"sample_count": len(records), "output": str(args.output / "dataset_index.json")} + elif args.command == "regression-select": + manifest = write_regression_manifest(args.output, args.manifest) + result = {"manifest": str(args.manifest), "selected_sample_count": manifest["selected_sample_count"], "engine_sample_count": manifest["engine_sample_count"]} + elif args.command == "regression": + if not 1 <= args.workers <= 8: raise ValueError("--workers must be between 1 and 8") + if args.timeout_seconds <= 0: raise ValueError("--timeout-seconds must be positive") + sample_ids = regression_sample_ids(args.manifest, args.tier) + samples = select_samples(load_samples(args.input, args.output), sample_ids=sample_ids) + records = run_stage(args.stage, samples, args.output, force=not args.resume, compare_mode=args.compare_mode, timeout_seconds=args.timeout_seconds, workers=args.workers) + result = {"tier": args.tier, "stage": args.stage, "manifest": str(args.manifest), **summarize_regression(records)} + elif args.command == "describe": + records, result = describe_samples( + args.input, + shard=args.shard, + sample_ids=args.sample_id, + mode=args.mode, + offset=args.offset, + limit=args.limit, + seed=args.seed, + force=args.force, + workers=args.workers, + ) elif args.command == "report": manifest = args.output / "manifest.jsonl"; records = [json.loads(line) for line in manifest.read_text().splitlines() if line.strip()] result = generate_reports(args.output, records) diff --git a/cadfs_to_cdsl/describe.py b/cadfs_to_cdsl/describe.py new file mode 100644 index 00000000..0ded9d08 --- /dev/null +++ b/cadfs_to_cdsl/describe.py @@ -0,0 +1,976 @@ +from __future__ import annotations + +import asyncio +import base64 +from collections import Counter +from concurrent.futures import ThreadPoolExecutor +import json +import math +from pathlib import Path +import random +import re +import struct +import threading +from typing import Any, Protocol + +from .dataset import Sample, scan_dataset +from .featurescript_parser import parse_featurescript +from .lowering import _number, _point, plain + + +DESCRIPTION_SCHEMA_VERSION = "cadfs_to_cdsl.description.v1" +MANIFEST_NAME = "description_manifest.jsonl" + +ENTITY_LABELS = { + "skLineSegment": "线段", + "skCircle": "圆", + "skArc": "圆弧", + "skEllipse": "椭圆", + "skFitSpline": "样条", + "skPoint": "点", +} + +OPERATION_LABELS = { + "extrude": "拉伸", + "revolve": "旋转", + "fillet": "圆角", + "chamfer": "倒角", + "hole": "孔", + "linearPattern": "线性阵列", + "circularPattern": "圆周阵列", + "mirror": "镜像", + "shell": "抽壳", + "loft": "放样", + "sweep": "扫掠", + "booleanBodies": "布尔", + "cPlane": "参考平面", + "referenceAxis": "参考轴", +} + +VISION_DESCRIPTION_TOOL = { + "type": "function", + "function": { + "name": "describe_cad_model", + "description": "Return a conservative semantic and geometric description for one CAD model.", + "parameters": { + "type": "object", + "additionalProperties": False, + "required": [ + "category", + "category_confidence", + "candidate_names", + "summary_zh", + "possible_functions", + "applications", + "structural_features", + "geometric_features", + "keywords_zh", + "keywords_en", + "uncertainties", + ], + "properties": { + "category": {"type": "string", "minLength": 1, "maxLength": 160}, + "category_confidence": {"type": "number", "minimum": 0, "maximum": 1}, + "candidate_names": {"type": "array", "items": {"type": "string", "minLength": 1, "maxLength": 120}, "maxItems": 8}, + "summary_zh": {"type": "string", "minLength": 1, "maxLength": 1200}, + "possible_functions": {"type": "array", "items": {"type": "string", "minLength": 1, "maxLength": 240}, "maxItems": 8}, + "applications": {"type": "array", "items": {"type": "string", "minLength": 1, "maxLength": 240}, "maxItems": 8}, + "structural_features": {"type": "array", "items": {"type": "string", "minLength": 1, "maxLength": 240}, "maxItems": 16}, + "geometric_features": {"type": "array", "items": {"type": "string", "minLength": 1, "maxLength": 240}, "maxItems": 16}, + "keywords_zh": {"type": "array", "items": {"type": "string", "minLength": 1, "maxLength": 80}, "maxItems": 32}, + "keywords_en": {"type": "array", "items": {"type": "string", "minLength": 1, "maxLength": 80}, "maxItems": 32}, + "uncertainties": {"type": "array", "items": {"type": "string", "minLength": 1, "maxLength": 240}, "maxItems": 12}, + }, + }, + }, +} + + +class VisionDescriptionClient(Protocol): + def describe(self, *, sample_id: str, image_path: Path, local_facts: dict[str, Any]) -> dict[str, Any]: + ... + + +class ConfiguredVisionDescriptionClient: + def __init__(self) -> None: + from app.cad_agent.adapters.structured_llm import StructuredModelGateway + from app.settings import get_settings + + self.settings = get_settings() + provider, model = self.settings.resolve_review_model() + if not model.vision: + raise ValueError("configured review model is not vision-capable") + self.provider_id = provider.id + self.model_id = model.id + self.gateway = StructuredModelGateway(self.settings) + + def describe(self, *, sample_id: str, image_path: Path, local_facts: dict[str, Any]) -> dict[str, Any]: + try: + loop = asyncio.get_running_loop() + except RuntimeError: + loop = None + if loop and loop.is_running(): + raise RuntimeError("vision description cannot run inside an active event loop") + return asyncio.run(self._describe(sample_id=sample_id, image_path=image_path, local_facts=local_facts)) + + async def _describe(self, *, sample_id: str, image_path: Path, local_facts: dict[str, Any]) -> dict[str, Any]: + payload = { + "sample_id": sample_id, + "local_category": local_facts.get("category"), + "local_candidate_names": local_facts.get("candidate_names"), + "local_geometric_features": local_facts.get("geometric_features"), + "local_operations": local_facts.get("operations"), + "local_dimensions": local_facts.get("dimensions"), + "annotation_excerpt": str(local_facts.get("annotation_excerpt") or "")[:4000], + "instruction": ( + "Use the image and deterministic CAD facts to identify likely model class and retrieval terms. " + "Keep product identity and use cases as candidates when the evidence is not definitive. " + "Do not invent exact dimensions beyond the supplied facts." + ), + } + content: list[dict[str, Any]] = [{"type": "text", "text": json.dumps(payload, ensure_ascii=False)}] + if image_path.is_file(): + content.append(self._image_part(image_path)) + response = await self.gateway.call_tool( + messages=[ + { + "role": "system", + "content": ( + "You describe CAD parts for vector search. Return only the required tool call. " + "Separate visible geometry from inferred semantics, and mark uncertain real-world identity conservatively." + ), + }, + {"role": "user", "content": content}, + ], + tool=VISION_DESCRIPTION_TOOL, + provider_id=self.provider_id, + model_id=self.model_id, + required_tool_name="describe_cad_model", + ) + calls = response.get("tool_calls") or [] + if len(calls) != 1: + raise ValueError("vision provider did not return exactly one tool call") + function = calls[0].get("function") if isinstance(calls[0], dict) else None + if not isinstance(function, dict) or function.get("name") != "describe_cad_model": + raise ValueError("vision provider returned an unexpected tool call") + arguments = json.loads(str(function.get("arguments") or "{}")) + if not isinstance(arguments, dict): + raise ValueError("vision provider arguments are not an object") + arguments["usage"] = response.get("usage") or {} + return arguments + + @staticmethod + def _image_part(path: Path) -> dict[str, Any]: + media_type = "image/jpeg" if path.suffix.lower() in {".jpg", ".jpeg"} else "image/png" + data = base64.b64encode(path.read_bytes()).decode("ascii") + return {"type": "image_url", "image_url": {"url": f"data:{media_type};base64,{data}"}} + + +class VisionCallState: + def __init__(self, client: VisionDescriptionClient, *, failure_limit: int = 3) -> None: + self.client = client + self.failure_limit = failure_limit + self._lock = threading.Lock() + self._consecutive_failures = 0 + self._disabled_reason = "" + + def describe(self, *, sample_id: str, image_path: Path, local_facts: dict[str, Any]) -> tuple[dict[str, Any] | None, dict[str, Any] | None]: + with self._lock: + disabled = self._disabled_reason + if disabled: + return None, {"code": "vision_disabled", "message": disabled} + try: + result = self.client.describe(sample_id=sample_id, image_path=image_path, local_facts=local_facts) + except Exception as exc: + with self._lock: + self._consecutive_failures += 1 + if self._consecutive_failures >= self.failure_limit: + self._disabled_reason = f"vision disabled after {self._consecutive_failures} consecutive failures" + return None, {"code": "vision_failed", "message": str(exc), "type": type(exc).__name__} + with self._lock: + self._consecutive_failures = 0 + return result, None + + +def sample_shard(sample: Sample) -> str: + for modality in ("image", "featurescript", "annotation", "step", "stl"): + path = sample.files.get(modality) + if path: + return Path(path).parent.name + return sample.sample_id[:4] + + +def select_description_samples( + input_root: Path, + *, + shard: str | None = None, + sample_ids: list[str] | None = None, + offset: int = 0, + limit: int | None = None, + seed: int | None = None, +) -> list[Sample]: + samples = scan_dataset(input_root, include_hashes=False) + if shard: + samples = [sample for sample in samples if sample_shard(sample) == shard] + if sample_ids: + wanted = set(sample_ids) + samples = [sample for sample in samples if sample.sample_id in wanted] + missing = wanted - {sample.sample_id for sample in samples} + if missing: + raise ValueError("unknown sample ids: " + ", ".join(sorted(missing))) + if seed is not None and limit is not None: + samples = random.Random(seed).sample(samples, min(limit, len(samples))) + return sorted(samples, key=lambda sample: sample.sample_id) + return samples[offset:None if limit is None else offset + limit] + + +def describe_samples( + input_root: Path, + *, + shard: str | None = None, + sample_ids: list[str] | None = None, + mode: str = "hybrid", + offset: int = 0, + limit: int | None = None, + seed: int | None = None, + force: bool = False, + workers: int = 1, + vision_client: VisionDescriptionClient | None = None, +) -> tuple[list[dict[str, Any]], dict[str, Any]]: + if mode not in {"local", "hybrid", "vision"}: + raise ValueError("--mode must be local, hybrid, or vision") + if not shard and not sample_ids and limit is None: + raise ValueError("describe requires --shard, --sample-id, or --limit to avoid accidental full-corpus generation") + if not 1 <= workers <= 8: + raise ValueError("--workers must be between 1 and 8") + + samples = select_description_samples(input_root, shard=shard, sample_ids=sample_ids, offset=offset, limit=limit, seed=seed) + existing_manifest = _read_manifest(input_root / "description_txt" / MANIFEST_NAME) + shared_diagnostics: list[dict[str, Any]] = [] + vision_state = VisionCallState(vision_client) if vision_client is not None else None + if mode != "local" and vision_client is None: + try: + vision_client = ConfiguredVisionDescriptionClient() + vision_state = VisionCallState(vision_client) + except Exception as exc: + shared_diagnostics.append({"code": "vision_unavailable", "message": str(exc), "type": type(exc).__name__}) + + def process(sample: Sample) -> dict[str, Any]: + return describe_one( + sample, + input_root, + mode=mode, + force=force, + vision_state=vision_state, + existing_record=existing_manifest.get(sample.sample_id), + shared_diagnostics=shared_diagnostics, + ) + + if workers == 1: + records = [process(sample) for sample in samples] + else: + with ThreadPoolExecutor(max_workers=workers, thread_name_prefix="cadfs-describe") as executor: + records = list(executor.map(process, samples)) + manifest_rows = _write_manifest(input_root, records) + return records, _summary(input_root, records, manifest_rows) + + +def describe_one( + sample: Sample, + input_root: Path, + *, + mode: str, + force: bool = False, + vision_state: VisionCallState | None = None, + existing_record: dict[str, Any] | None = None, + shared_diagnostics: list[dict[str, Any]] | None = None, +) -> dict[str, Any]: + shard = sample_shard(sample) + txt_path = input_root / "description_txt" / shard / f"{sample.sample_id}.txt" + if txt_path.exists() and not force: + return existing_record or _skipped_record(sample, input_root, txt_path, shard) + + diagnostics = list(shared_diagnostics or []) + try: + local_facts = _local_facts(sample) + diagnostics.extend(local_facts.pop("diagnostics", [])) + vision_result = None + if mode != "local": + if vision_state is None: + diagnostics.append({"code": "vision_unavailable", "message": "no configured vision client"}) + else: + vision_result, vision_diagnostic = vision_state.describe( + sample_id=sample.sample_id, + image_path=Path(sample.files.get("image", "")), + local_facts=local_facts, + ) + if vision_diagnostic is not None: + diagnostics.append(vision_diagnostic) + record = _compose_record(sample, input_root, shard, txt_path, local_facts, vision_result, diagnostics, mode) + _atomic_write_text(txt_path, render_description_text(record)) + return record + except Exception as exc: + diagnostics.append({"code": "description_failed", "message": str(exc), "type": type(exc).__name__}) + record = _failed_record(sample, input_root, txt_path, shard, diagnostics) + _atomic_write_text(txt_path, render_description_text(record)) + return record + + +def render_description_text(record: dict[str, Any]) -> str: + confidence = _format_float(float(record.get("category_confidence") or 0.0)) + keywords = _unique([*(record.get("keywords_zh") or []), *(record.get("keywords_en") or [])], limit=48) + evidence = [ + "确定事实来自 FeatureScript、原始 CAD 操作说明、STL/STEP 文件存在性和可解析的几何范围。", + *_as_text_list(record.get("uncertainties")), + ] + diagnostics = record.get("diagnostics") or [] + if diagnostics: + codes = _unique([str(item.get("code") or "diagnostic") for item in diagnostics if isinstance(item, dict)], limit=8) + if codes: + evidence.append("诊断:" + ",".join(codes)) + lines = [ + f"样本ID:{record.get('sample_id', '')}", + f"分类:{record.get('category', '通用机械 CAD 零件(候选)')}", + "候选名称:" + _join_or_unknown(record.get("candidate_names")), + "模型概述:" + str(record.get("summary_zh") or "该模型为缺少语义上下文的 CAD 几何样本,描述以可见结构和建模特征为主。"), + "可能作用:" + _join_or_unknown(record.get("possible_functions")), + "典型应用:" + _join_or_unknown(record.get("applications")), + "结构特征:" + _join_or_unknown(record.get("structural_features")), + "建模与几何特征:" + _join_or_unknown(record.get("geometric_features")), + "检索关键词:" + _join_or_unknown(keywords), + "证据与不确定性:" + ";".join(evidence), + f"置信度:{confidence}(几何事实置信度较高;真实零件类别、用途和装配位置为候选判断)", + ] + return "\n".join(lines) + "\n" + + +def _local_facts(sample: Sample) -> dict[str, Any]: + feature_path = Path(sample.files["featurescript"]) + annotation_path = Path(sample.files.get("annotation", "")) + stl_path = Path(sample.files.get("stl", "")) + image_path = Path(sample.files.get("image", "")) + source = feature_path.read_text(encoding="utf-8") + annotation = annotation_path.read_text(encoding="utf-8") if annotation_path.is_file() else "" + model = parse_featurescript(source, sample.sample_id) + diagnostics: list[dict[str, Any]] = [] + sketch_info, sketch_features, sketch_dimensions, entity_counts = _describe_sketches(model) + operations, operation_dimensions = _describe_operations(model) + stl_info = _stl_bbox(stl_path) if stl_path.is_file() else None + image_info = _image_metadata(image_path) if image_path.is_file() else None + shape_cues = _shape_cues(annotation, entity_counts, operations) + geometric_features = _unique( + [ + f"包含 {len(model.sketches)} 个草图和 {len(model.features)} 个建模/修饰特征", + *sketch_features, + *shape_cues, + *_stl_features(stl_info), + ], + limit=32, + ) + dimensions = _unique([*sketch_dimensions, *operation_dimensions, *_stl_dimensions(stl_info)], limit=32) + classification = _classify_local(annotation, operations, geometric_features, stl_info) + keywords_zh, keywords_en = _keywords(classification, operations, geometric_features) + uncertainties = [ + "CADFS 样本未提供真实装配上下文,类别、用途和安装位置只能作为候选语义。", + "确定描述优先依据几何、草图和特征操作;视觉判断仅作为补充证据。", + ] + summary = _local_summary(classification, operations, geometric_features, dimensions) + if not source.strip(): + diagnostics.append({"code": "empty_featurescript", "message": "FeatureScript file is empty"}) + return { + "category": classification["category"], + "category_confidence": classification["confidence"], + "candidate_names": classification["candidate_names"], + "summary_zh": summary, + "possible_functions": classification["possible_functions"], + "applications": classification["applications"], + "structural_features": _unique([*classification["structural_features"], *sketch_info], limit=24), + "geometric_features": geometric_features, + "operations": operations, + "dimensions": dimensions, + "keywords_zh": keywords_zh, + "keywords_en": keywords_en, + "uncertainties": uncertainties, + "annotation_excerpt": annotation[:4000], + "image_metadata": image_info, + "diagnostics": diagnostics, + } + + +def _describe_sketches(model: Any) -> tuple[list[str], list[str], list[str], Counter[str]]: + sketch_info: list[str] = [] + features: list[str] = [] + dimensions: list[str] = [] + entity_counts: Counter[str] = Counter() + for sketch in model.sketches: + counts = Counter(entity.operation for entity in sketch.entities) + entity_counts.update(counts) + labels = [f"{ENTITY_LABELS.get(name, name)} {count} 个" for name, count in sorted(counts.items())] + plane = _plane_name(sketch.workplane) + if labels: + sketch_info.append(f"{sketch.feature_id} 位于 {plane} 平面,包含" + "、".join(labels)) + else: + sketch_info.append(f"{sketch.feature_id} 位于 {plane} 平面,没有可解析草图实体") + if counts: + features.append("草图包含" + "、".join(labels)) + bbox = _sketch_bbox(sketch) + if bbox: + width = bbox[2] - bbox[0] + height = bbox[3] - bbox[1] + dimensions.append(f"{sketch.feature_id} 二维范围约 {_format_float(width)} × {_format_float(height)} mm") + return sketch_info, _unique(features, limit=12), dimensions, entity_counts + + +def _describe_operations(model: Any) -> tuple[list[str], list[str]]: + operations: list[str] = [] + dimensions: list[str] = [] + for feature in model.features: + params = feature.params + operation = feature.operation + if operation == "extrude": + operation_type = _enum_tail(params.get("operationType") or "NEW").upper() + action = "extrude_cut" if operation_type in {"REMOVE", "CUT"} else "extrude_add" + depth = _safe_number(params.get("depth")) + label = f"{action}" + if depth is not None: + label += f" depth_mm={_format_float(depth)}" + dimensions.append(f"{feature.feature_id} 拉伸深度 {_format_mm(depth)}") + if _truthy(params.get("hasSecondDirection")): + label += " two_sided=true" + operations.append(label) + elif operation == "revolve": + operation_type = _enum_tail(params.get("operationType") or params.get("surfaceOperationType") or "NEW").upper() + action = "revolve_cut" if operation_type in {"REMOVE", "CUT"} else "revolve_add" + angle = 360.0 if "FULL" in _enum_tail(params.get("revolveType") or "FULL").upper() else _safe_number(params.get("angle")) + operations.append(f"{action} angle_deg={_format_float(angle or 360.0)}") + elif operation == "hole": + diameter = _safe_number(params.get("holeDiameter")) + style = _enum_tail(params.get("style") or "simple").lower() + label = f"hole {style}" + if diameter is not None: + label += f" diameter_mm={_format_float(diameter)}" + dimensions.append(f"{feature.feature_id} 孔径 {_format_mm(diameter)}") + operations.append(label) + elif operation == "fillet": + radius = _safe_number(params.get("radius")) + operations.append("fillet" + (f" radius_mm={_format_float(radius)}" if radius is not None else "")) + if radius is not None: + dimensions.append(f"{feature.feature_id} 圆角半径 {_format_mm(radius)}") + elif operation == "chamfer": + width = _safe_number(params.get("width") or params.get("width1")) + operations.append("chamfer" + (f" distance_mm={_format_float(width)}" if width is not None else "")) + if width is not None: + dimensions.append(f"{feature.feature_id} 倒角距离 {_format_mm(width)}") + else: + operations.append(OPERATION_LABELS.get(operation, operation)) + return _unique(operations, limit=32), _unique(dimensions, limit=32) + + +def _shape_cues(annotation: str, entity_counts: Counter[str], operations: list[str]) -> list[str]: + text = annotation.lower() + joined_ops = " ".join(operations).lower() + cues: list[str] = [] + if "triangle" in text or "triangular" in text: + cues.append("具有三角形轮廓或三角截面") + if "rectangle" in text or entity_counts.get("skLineSegment", 0) >= 4: + cues.append("包含矩形/多边形直线轮廓") + if "circle" in text or entity_counts.get("skCircle", 0): + cues.append("包含圆形轮廓或圆孔候选特征") + if "arc" in text or entity_counts.get("skArc", 0): + cues.append("包含圆弧边界") + if "slot" in text or "notch" in text or "cutout" in text or "开口" in text: + cues.append("包含槽、缺口或开口候选结构") + if "hole" in text or "hole" in joined_ops: + cues.append("包含孔加工候选结构") + if "fillet" in joined_ops: + cues.append("包含圆角过渡") + if "chamfer" in joined_ops: + cues.append("包含倒角边") + if "pattern" in joined_ops: + cues.append("包含阵列重复特征") + if "mirror" in joined_ops: + cues.append("包含镜像对称特征") + if any(item.startswith("revolve") for item in operations): + cues.append("绕轴旋转形成回转体") + if any(item.startswith("extrude") for item in operations): + cues.append("由二维草图拉伸形成实体") + return _unique(cues, limit=16) + + +def _classify_local(annotation: str, operations: list[str], geometric_features: list[str], stl_info: dict[str, Any] | None) -> dict[str, Any]: + text = " ".join([annotation.lower(), " ".join(operations).lower(), " ".join(geometric_features)]) + slender = _is_slender(stl_info) + has_extrude = any(item.startswith("extrude") for item in operations) + has_revolve = any(item.startswith("revolve") for item in operations) + has_hole = "hole" in text or "孔" in text + has_triangle = "triangle" in text or "triangular" in text or "三角" in text + has_rectangle = "rectangle" in text or "矩形" in text + has_cutout = any(word in text for word in ("slot", "notch", "cutout", "开口", "缺口")) + + if has_revolve: + return { + "category": "回转轴套/法兰类零件(候选)" if has_hole else "回转体机械零件(候选)", + "confidence": 0.5, + "candidate_names": ["回转体", "轴套", "法兰盘", "轮毂状零件", "revolved part", "flange", "bushing"], + "possible_functions": ["可能用于同轴定位、连接、支承、隔套或旋转类结构的几何占位。"], + "applications": ["机械传动、夹具、管路连接、轴承座周边或需要轴线对称零件的装配场景。"], + "structural_features": ["回转外形", "轴向轮廓", "圆柱/圆盘候选结构"], + } + if has_triangle and has_extrude: + return { + "category": "三角棱柱/楔形梁类零件(候选)", + "confidence": 0.56, + "candidate_names": ["长条三角棱柱", "楔形梁", "三角截面导轨", "triangular prism", "wedge beam"], + "possible_functions": ["可能作为楔块、导向条、支撑肋、定位块或三角截面结构件使用。"], + "applications": ["夹具定位、机械支撑、导向结构、教育/仿真几何库或需要楔形截面的 CAD 检索场景。"], + "structural_features": ["三角截面", "长条拉伸体", "棱柱体", "斜面侧壁"], + } + if has_hole and has_rectangle: + return { + "category": "带孔板/安装板类零件(候选)", + "confidence": 0.52, + "candidate_names": ["安装板", "连接板", "带孔支架板", "mounting plate", "bracket plate"], + "possible_functions": ["可能用于螺钉安装、定位连接、固定支撑或作为装配转接板。"], + "applications": ["设备框架、夹具底板、连接支架、外壳内部固定件。"], + "structural_features": ["板状主体", "孔特征", "平面安装面"], + } + if has_cutout and has_extrude: + return { + "category": "开槽板/叉形支架类零件(候选)", + "confidence": 0.48, + "candidate_names": ["开槽板", "叉形支架", "U 形支架", "slotted plate", "fork bracket"], + "possible_functions": ["可能用于避让、卡接、导向、夹持或作为插槽式连接件。"], + "applications": ["支架、夹具、连接耳、导向槽结构或板件开口检索场景。"], + "structural_features": ["开口/缺口", "板状拉伸体", "平直侧壁"], + } + if has_extrude and slender: + return { + "category": "拉伸型梁/导轨类零件(候选)", + "confidence": 0.46, + "candidate_names": ["拉伸梁", "导轨条", "长条棱柱", "extruded beam", "rail"], + "possible_functions": ["可能用于导向、支撑、隔距、边框或长条结构件。"], + "applications": ["框架、滑轨、夹具、机械结构支撑或型材检索场景。"], + "structural_features": ["长条外形", "恒定截面候选", "拉伸成型"], + } + if has_extrude: + return { + "category": "拉伸棱柱/板块类零件(候选)", + "confidence": 0.42, + "candidate_names": ["拉伸实体", "板块", "棱柱体", "extruded solid", "prismatic part"], + "possible_functions": ["可能作为基础块、板件、支撑件或后续加工毛坯。"], + "applications": ["通用机械零件、夹具、支架、CAD 几何检索和相似形状匹配。"], + "structural_features": ["二维轮廓拉伸", "平面端面", "直壁结构"], + } + return { + "category": "通用机械 CAD 零件(候选)", + "confidence": 0.34, + "candidate_names": ["机械零件", "CAD 几何样本", "mechanical part", "CAD model"], + "possible_functions": ["可能作为机械结构、连接、支撑或几何检索样本,实际用途需结合装配上下文确认。"], + "applications": ["CAD 数据集检索、几何相似度匹配、零件分类训练和工程知识库索引。"], + "structural_features": ["可解析 CAD 特征组合"], + } + + +def _keywords(classification: dict[str, Any], operations: list[str], geometric_features: list[str]) -> tuple[list[str], list[str]]: + text = " ".join([classification["category"], " ".join(classification["candidate_names"]), " ".join(operations), " ".join(geometric_features)]).lower() + zh = ["CAD模型", "机械零件", "几何检索", "相似特征", *classification["candidate_names"][:4]] + en = ["cad model", "mechanical part", "geometry retrieval", "similar features"] + mappings = [ + ("三角", "三角棱柱", "triangular prism"), + ("wedge", "楔形", "wedge"), + ("extrude", "拉伸", "extruded"), + ("revolve", "回转", "revolved"), + ("hole", "孔", "hole"), + ("fillet", "圆角", "fillet"), + ("chamfer", "倒角", "chamfer"), + ("slot", "槽", "slot"), + ("flange", "法兰", "flange"), + ("bushing", "轴套", "bushing"), + ("plate", "板件", "plate"), + ("bracket", "支架", "bracket"), + ("rail", "导轨", "rail"), + ] + for needle, zh_value, en_value in mappings: + if needle in text or zh_value in text: + zh.append(zh_value) + en.append(en_value) + return _unique(zh, limit=32), _unique(en, limit=32) + + +def _compose_record( + sample: Sample, + input_root: Path, + shard: str, + txt_path: Path, + local_facts: dict[str, Any], + vision_result: dict[str, Any] | None, + diagnostics: list[dict[str, Any]], + mode: str, +) -> dict[str, Any]: + vision = _normalize_vision(vision_result) + status = "described_local" + if mode != "local": + status = "described_hybrid" if vision else "local_fallback" + category = vision.get("category") or local_facts["category"] + category_confidence = _clamp_float(vision.get("category_confidence"), local_facts["category_confidence"]) + candidate_names = _unique([*vision.get("candidate_names", []), *local_facts["candidate_names"]], limit=12) + structural_features = _unique([*local_facts["structural_features"], *vision.get("structural_features", [])], limit=32) + geometric_features = _unique([*local_facts["geometric_features"], *vision.get("geometric_features", [])], limit=40) + uncertainties = _unique([*local_facts["uncertainties"], *vision.get("uncertainties", [])], limit=20) + return { + "schema_version": DESCRIPTION_SCHEMA_VERSION, + "sample_id": sample.sample_id, + "shard": shard, + "status": status, + "txt_path": str(txt_path), + "category": category, + "category_confidence": category_confidence, + "candidate_names": candidate_names, + "summary_zh": vision.get("summary_zh") or local_facts["summary_zh"], + "possible_functions": _unique([*vision.get("possible_functions", []), *local_facts["possible_functions"]], limit=12), + "applications": _unique([*vision.get("applications", []), *local_facts["applications"]], limit=12), + "structural_features": structural_features, + "geometric_features": geometric_features, + "operations": local_facts["operations"], + "dimensions": local_facts["dimensions"], + "keywords_zh": _unique([*vision.get("keywords_zh", []), *local_facts["keywords_zh"]], limit=40), + "keywords_en": _unique([*vision.get("keywords_en", []), *local_facts["keywords_en"]], limit=40), + "uncertainties": uncertainties, + "source_files": _source_files(sample, input_root), + "diagnostics": diagnostics, + } + + +def _normalize_vision(value: dict[str, Any] | None) -> dict[str, Any]: + if not isinstance(value, dict): + return {} + result: dict[str, Any] = {} + for key in ( + "candidate_names", + "possible_functions", + "applications", + "structural_features", + "geometric_features", + "keywords_zh", + "keywords_en", + "uncertainties", + ): + result[key] = _as_text_list(value.get(key)) + for key in ("category", "summary_zh"): + if isinstance(value.get(key), str) and value[key].strip(): + result[key] = value[key].strip() + result["category_confidence"] = value.get("category_confidence") + return result + + +def _local_summary(classification: dict[str, Any], operations: list[str], geometric_features: list[str], dimensions: list[str]) -> str: + operation_text = "、".join(operations[:4]) if operations else "可解析 CAD 特征" + feature_text = ";".join(geometric_features[:4]) if geometric_features else "几何结构待进一步识别" + dimension_text = ";".join(dimensions[:3]) if dimensions else "未提取到稳定尺寸摘要" + return ( + f"该模型可保守识别为{classification['category']},主要由 {operation_text} 构成。" + f"确定结构包括:{feature_text}。尺寸线索:{dimension_text}。" + "真实产品身份和装配用途缺少上下文,因此以候选名称和相似几何特征用于检索。" + ) + + +def _failed_record(sample: Sample, input_root: Path, txt_path: Path, shard: str, diagnostics: list[dict[str, Any]]) -> dict[str, Any]: + return { + "schema_version": DESCRIPTION_SCHEMA_VERSION, + "sample_id": sample.sample_id, + "shard": shard, + "status": "failed", + "txt_path": str(txt_path), + "category": "通用机械 CAD 零件(描述失败)", + "category_confidence": 0.0, + "candidate_names": ["CAD 模型"], + "summary_zh": "该样本描述生成失败,仅保留源文件索引和诊断信息。", + "possible_functions": ["无法可靠判断。"], + "applications": ["需重新运行描述生成或人工复核后再进入向量库。"], + "structural_features": [], + "geometric_features": [], + "operations": [], + "dimensions": [], + "keywords_zh": ["CAD模型", "描述失败"], + "keywords_en": ["cad model", "description failed"], + "uncertainties": ["描述生成过程失败,不能据此判断模型类别或用途。"], + "source_files": _source_files(sample, input_root), + "diagnostics": diagnostics, + } + + +def _skipped_record(sample: Sample, input_root: Path, txt_path: Path, shard: str) -> dict[str, Any]: + return { + "schema_version": DESCRIPTION_SCHEMA_VERSION, + "sample_id": sample.sample_id, + "shard": shard, + "status": "skipped_existing", + "txt_path": str(txt_path), + "category": "", + "category_confidence": 0.0, + "candidate_names": [], + "geometric_features": [], + "operations": [], + "dimensions": [], + "keywords_zh": [], + "keywords_en": [], + "uncertainties": ["已有描述文件,未使用 --force,因此本次未覆盖。"], + "source_files": _source_files(sample, input_root), + "diagnostics": [{"code": "existing_output_skipped"}], + } + + +def _source_files(sample: Sample, input_root: Path) -> dict[str, str]: + return {key: str(Path(value)) for key, value in sorted(sample.files.items()) if Path(value).is_file() or input_root} + + +def _read_manifest(path: Path) -> dict[str, dict[str, Any]]: + if not path.exists(): + return {} + records: dict[str, dict[str, Any]] = {} + for line in path.read_text(encoding="utf-8").splitlines(): + if not line.strip(): + continue + try: + value = json.loads(line) + except json.JSONDecodeError: + continue + sample_id = str(value.get("sample_id") or "") + if sample_id: + records[sample_id] = value + return records + + +def _write_manifest(input_root: Path, records: list[dict[str, Any]]) -> int: + path = input_root / "description_txt" / MANIFEST_NAME + existing = _read_manifest(path) + for record in records: + existing[str(record["sample_id"])] = record + ordered = [existing[key] for key in sorted(existing)] + text = "".join(json.dumps(record, ensure_ascii=False, sort_keys=True) + "\n" for record in ordered) + _atomic_write_text(path, text) + return len(ordered) + + +def _summary(input_root: Path, records: list[dict[str, Any]], manifest_rows: int) -> dict[str, Any]: + counts = Counter(str(record.get("status") or "unknown") for record in records) + txt_count = sum(1 for record in records if Path(str(record.get("txt_path") or "")).is_file()) + return { + "sample_count": len(records), + "statuses": dict(sorted(counts.items())), + "txt_count": txt_count, + "manifest": str(input_root / "description_txt" / MANIFEST_NAME), + "manifest_rows": manifest_rows, + } + + +def _atomic_write_text(path: Path, text: str) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + tmp = path.with_name(path.name + ".tmp") + tmp.write_text(text, encoding="utf-8") + tmp.replace(path) + + +def _safe_number(value: Any) -> float | None: + try: + result = _number(value, True) + except Exception: + return None + return result if math.isfinite(result) else None + + +def _safe_point(value: Any) -> list[float] | None: + try: + point = _point(value) + except Exception: + return None + return point if len(point) >= 2 and all(math.isfinite(item) for item in point[:2]) else None + + +def _sketch_bbox(sketch: Any) -> list[float] | None: + xs: list[float] = [] + ys: list[float] = [] + for entity in sketch.entities: + params = entity.params + if entity.operation == "skLineSegment": + for key in ("start", "end"): + point = _safe_point(params.get(key)) + if point: + xs.append(point[0]) + ys.append(point[1]) + elif entity.operation == "skCircle": + center = _safe_point(params.get("center")) + radius = _safe_number(params.get("radius")) + if center and radius is not None: + xs.extend([center[0] - radius, center[0] + radius]) + ys.extend([center[1] - radius, center[1] + radius]) + elif entity.operation == "skArc": + for key in ("start", "mid", "end"): + point = _safe_point(params.get(key)) + if point: + xs.append(point[0]) + ys.append(point[1]) + elif entity.operation == "skPoint": + point = _safe_point(params.get("position")) + if point: + xs.append(point[0]) + ys.append(point[1]) + if not xs or not ys: + return None + return [min(xs), min(ys), max(xs), max(ys)] + + +def _stl_bbox(path: Path) -> dict[str, Any] | None: + try: + data = path.read_bytes() + except OSError: + return None + vertices = _binary_stl_vertices(data) or _ascii_stl_vertices(data) + if not vertices: + return None + mins = [min(vertex[i] for vertex in vertices) for i in range(3)] + maxs = [max(vertex[i] for vertex in vertices) for i in range(3)] + size = [maxs[i] - mins[i] for i in range(3)] + return {"min_mm": mins, "max_mm": maxs, "size_mm": size, "vertex_count": len(vertices)} + + +def _binary_stl_vertices(data: bytes) -> list[tuple[float, float, float]]: + if len(data) < 84: + return [] + triangle_count = struct.unpack(" len(data): + return [] + vertices: list[tuple[float, float, float]] = [] + offset = 84 + for _ in range(triangle_count): + offset += 12 + for _ in range(3): + vertices.append(struct.unpack(" list[tuple[float, float, float]]: + try: + text = data[:5_000_000].decode("utf-8", errors="ignore") + except Exception: + return [] + pattern = re.compile(r"\bvertex\s+([-+0-9.eE]+)\s+([-+0-9.eE]+)\s+([-+0-9.eE]+)") + vertices = [] + for match in pattern.finditer(text): + try: + vertices.append((float(match.group(1)), float(match.group(2)), float(match.group(3)))) + except ValueError: + continue + return vertices + + +def _image_metadata(path: Path) -> dict[str, Any]: + try: + from PIL import Image + + with Image.open(path) as image: + return {"width": image.width, "height": image.height, "format": str(image.format or "").lower()} + except Exception as exc: + return {"error": f"image metadata unavailable: {type(exc).__name__}"} + + +def _stl_features(stl_info: dict[str, Any] | None) -> list[str]: + if not stl_info: + return [] + size = stl_info.get("size_mm") or [] + if len(size) != 3: + return [] + features = ["STL 网格提供三维包围盒"] + if _is_slender(stl_info): + features.append("整体呈长条比例") + if _is_plate_like(stl_info): + features.append("整体呈薄板比例") + return features + + +def _stl_dimensions(stl_info: dict[str, Any] | None) -> list[str]: + if not stl_info: + return [] + size = stl_info.get("size_mm") or [] + if len(size) != 3: + return [] + return [f"STL 三维包围盒约 {_format_float(size[0])} × {_format_float(size[1])} × {_format_float(size[2])} mm"] + + +def _is_slender(stl_info: dict[str, Any] | None) -> bool: + if not stl_info: + return False + values = [abs(float(item)) for item in stl_info.get("size_mm") or [] if abs(float(item)) > 1e-9] + return len(values) >= 2 and max(values) / max(min(values), 1e-9) >= 3.0 + + +def _is_plate_like(stl_info: dict[str, Any] | None) -> bool: + if not stl_info: + return False + values = sorted(abs(float(item)) for item in stl_info.get("size_mm") or [] if abs(float(item)) > 1e-9) + return len(values) == 3 and values[0] * 4 <= values[1] + + +def _plane_name(value: Any) -> str: + text = json.dumps(plain(value), ensure_ascii=False) + for name in ("Top", "Front", "Right"): + if f"{name}.planeOp" in text: + return name + return "未知" + + +def _enum_tail(value: Any) -> str: + return str(value or "").split(".")[-1] + + +def _truthy(value: Any) -> bool: + return value is True or (isinstance(value, str) and value.lower() == "true") + + +def _format_float(value: float) -> str: + rounded = round(float(value), 4) + if rounded == int(rounded): + return str(int(rounded)) + return f"{rounded:.4f}".rstrip("0").rstrip(".") + + +def _format_mm(value: float) -> str: + return f"{_format_float(value)} mm" + + +def _clamp_float(value: Any, fallback: float) -> float: + try: + number = float(value) + except (TypeError, ValueError): + number = float(fallback) + if not math.isfinite(number): + number = float(fallback) + return max(0.0, min(1.0, number)) + + +def _as_text_list(value: Any) -> list[str]: + if not isinstance(value, list): + return [] + return [str(item).strip() for item in value if str(item).strip()] + + +def _unique(values: list[Any], *, limit: int | None = None) -> list[str]: + result: list[str] = [] + seen: set[str] = set() + for value in values: + text = str(value).strip() + if not text or text in seen: + continue + seen.add(text) + result.append(text) + if limit is not None and len(result) >= limit: + break + return result + + +def _join_or_unknown(values: Any) -> str: + items = _as_text_list(values) if isinstance(values, list) else _unique(list(values or [])) if isinstance(values, tuple) else [] + return "、".join(items) if items else "无法可靠判断" diff --git a/cadfs_to_cdsl/featurescript_parser.py b/cadfs_to_cdsl/featurescript_parser.py index 3a360bd2..7dde2219 100644 --- a/cadfs_to_cdsl/featurescript_parser.py +++ b/cadfs_to_cdsl/featurescript_parser.py @@ -119,7 +119,7 @@ def parse_featurescript(source: str, sample_id: str = "unknown") -> ModelIR: if model.sketches: args = _arg_map(call); eid = str(call.args[1]) if len(call.args) > 1 else f"E{len(model.sketches[-1].entities)}" model.sketches[-1].entities.append(FeatureIR(eid, call.name, args, line_start=call.line, raw_source=call.name)) - elif call.name in {"extrude", "revolve", "fillet", "chamfer", "hole", "linearPattern", "mirror", "cPlane", "referenceAxis", "shell", "loft", "sweep", "circularPattern", "booleanBodies"}: + elif call.name in {"extrude", "revolve", "fillet", "chamfer", "hole", "linearPattern", "mirror", "cPlane", "referenceAxis", "shell", "loft", "sweep", "circularPattern", "booleanBodies", "transform"}: fid = symbolic_string(call.args[1]) if len(call.args) > 1 else f"feature_{len(model.features)}" feature_ir = FeatureIR(fid, call.name, _arg_map(call), line_start=call.line, raw_source=call.name) model.features.append(feature_ir); model.steps.append(feature_ir) diff --git a/cadfs_to_cdsl/lowering.py b/cadfs_to_cdsl/lowering.py index 0d7fd0d8..ce37a358 100644 --- a/cadfs_to_cdsl/lowering.py +++ b/cadfs_to_cdsl/lowering.py @@ -8,7 +8,7 @@ from .ir import Call, FeatureIR, ModelIR, SketchIR from .query_parser import parse_query, walk_calls -UNSUPPORTED = {"shell", "loft", "sweep", "draft", "thicken", "split", "booleanBodies", "circularPattern", "moveFace", "replaceFace", "deleteFace", "import", "derive"} +UNSUPPORTED = {"shell", "sweep", "draft", "thicken", "split", "booleanBodies", "moveFace", "replaceFace", "deleteFace", "import", "derive"} PLANES = { "Top": {"origin_mm": [0., 0., 0.], "x_dir": [1., 0., 0.], "normal": [0., 0., 1.]}, "Front": {"origin_mm": [0., 0., 0.], "x_dir": [1., 0., 0.], "normal": [0., -1., 0.]}, @@ -65,6 +65,43 @@ def _cross(a: list[float], b: list[float]) -> list[float]: return [a[1]*b[2]-a[2]*b[1], a[2]*b[0]-a[0]*b[2], a[0]*b[1]-a[1]*b[0]] +def _dot(a: list[float], b: list[float]) -> float: return sum(left * right for left, right in zip(a, b)) + + +def _unit(value: list[float], message: str) -> list[float]: + length = math.sqrt(_dot(value, value)) + if length <= 1e-9: raise ValueError(message) + return [component / length for component in value] + + +def _sub(a: list[float], b: list[float]) -> list[float]: return [a[index] - b[index] for index in range(3)] + + +def _rotate(value: list[float], axis: list[float], angle_rad: float) -> list[float]: + axis = _unit(axis, "rotation axis is degenerate") + cosine, sine = math.cos(angle_rad), math.sin(angle_rad) + cross = _cross(axis, value); projection = _dot(axis, value) * (1.0 - cosine) + return [value[index] * cosine + cross[index] * sine + axis[index] * projection for index in range(3)] + + +def _translate_frame(frame: dict[str, Any], offset: list[float]) -> dict[str, Any]: + return {**frame, "origin_mm": [frame["origin_mm"][index] + offset[index] for index in range(3)]} + + +def _rotate_point(point: list[float], axis: dict[str, list[float]], angle_rad: float) -> list[float]: + relative = _rotate(_sub(point, axis["origin_mm"]), axis["direction"], angle_rad) + return [axis["origin_mm"][index] + relative[index] for index in range(3)] + + +def _rotate_frame(frame: dict[str, Any], axis: dict[str, list[float]], angle_rad: float) -> dict[str, Any]: + return { + **frame, + "origin_mm": _rotate_point(frame["origin_mm"], axis, angle_rad), + "x_dir": _rotate(frame["x_dir"], axis["direction"], angle_rad), + "normal": _rotate(frame["normal"], axis["direction"], angle_rad), + } + + def _y_dir(plane: dict[str, Any]) -> list[float]: return _cross(plane["normal"], plane["x_dir"]) @@ -81,6 +118,12 @@ def _oriented_plane(plane: dict[str, Any], normal_sign: float, distance: float = return {**shifted, "normal": [normal_sign * value for value in plane["normal"]]} +def _frame(origin: list[float], x_dir: list[float], normal: list[float]) -> dict[str, list[float]]: + normal = _unit(normal, "reference plane normal is degenerate") + x_dir = _sub(x_dir, [normal[index] * _dot(x_dir, normal) for index in range(3)]) + return {"origin_mm": origin, "x_dir": _unit(x_dir, "reference plane x direction is degenerate"), "normal": normal} + + def _plane_from_query(value: Any, feature_frames: dict[str, dict[str, Any]], sketch_by_source: dict[str, dict[str, Any]] | None = None) -> dict[str, Any]: for call in walk_calls(value): if call.name in {"makeId", "qCreatedBy"}: @@ -107,6 +150,7 @@ def _end_condition(value: Any) -> dict[str, Any]: if name == "BLIND": return {"type": "blind", "solidworks_code": 0} if name == "SYMMETRIC": return {"type": "mid_plane", "solidworks_code": 8} if name == "THROUGH_ALL": return {"type": "through_all", "solidworks_code": 1} + if name == "UP_TO_NEXT": return {"type": "through_next", "solidworks_code": 4} raise UnsupportedCapability(f"extrude_extent:{name.lower()}", f"current CDSL atomic set has no exact extrusion operation for {name}") @@ -137,12 +181,13 @@ def _contours(segments: list[dict[str, Any]]) -> tuple[list[dict[str, Any]], lis if _endpoint(item, True) == tail: item["start"], item["end"] = item["end"], item["start"] if item["type"] == "arc": item["clockwise"] = not item["clockwise"] + elif item["type"] == "bspline": item["points"] = list(reversed(item["points"])) contour.append(item); tail = _endpoint(item, True) if tail == first: contours.append({"role": "unknown", "closed": True, "segments": contour}) return contours + circles, construction -def _lower_sketch(sketch: SketchIR, plane: dict[str, Any]) -> tuple[dict[str, Any], dict[str, dict[str, Any]]]: +def _lower_sketch(sketch: SketchIR, plane: dict[str, Any], allow_open: bool = False) -> tuple[dict[str, Any], dict[str, dict[str, Any]]]: segments: list[dict[str, Any]] = []; explicit_construction: list[dict[str, Any]] = []; entities: dict[str, dict[str, Any]] = {}; unsupported = [] for entity in sketch.entities: p = entity.params @@ -150,6 +195,10 @@ def _lower_sketch(sketch: SketchIR, plane: dict[str, Any]) -> tuple[dict[str, An if entity.operation == "skLineSegment": item = {"type": "line", "start": _point(p["start"]), "end": _point(p["end"])} elif entity.operation == "skCircle": item = {"type": "circle", "center": _point(p["center"]), "radius_mm": _number(p["radius"], True)} elif entity.operation == "skArc": item = _arc(_point(p["start"]), _point(p["mid"]), _point(p["end"])) + elif entity.operation == "skFitSpline": + points = [_point(point) for point in p.get("points") or []] + if len(points) < 3: raise ValueError("fit spline needs at least 3 points") + item = {"type": "bspline", "start": points[0], "end": points[-1], "points": points} else: unsupported.append(entity.operation); continue (explicit_construction if _bool(p.get("construction")) else segments).append(item); entities[entity.feature_id] = item if unsupported: raise ValueError("unsupported sketch entities: " + ",".join(sorted(set(unsupported)))) @@ -162,7 +211,9 @@ def _lower_sketch(sketch: SketchIR, plane: dict[str, Any]) -> tuple[dict[str, An else: contours, open_segments = _contours(segments) if open_segments: - raise ValueError(f"sketch has {len(open_segments)} open non-construction segment(s)") + if not allow_open: raise ValueError(f"sketch has {len(open_segments)} open non-construction segment(s)") + profile = {"type": "analytic_contours", "contours": [], "construction": explicit_construction + open_segments} + return {"id": f"sketch_{sketch.feature_id}", "name": sketch.feature_id, "workplane": plane, "profile": profile, "role": "reference"}, entities construction = list(explicit_construction) if not contours: profile = {"type": "analytic_contours", "contours": [], "construction": construction} @@ -193,6 +244,89 @@ def _source_sketch(params: dict[str, Any]) -> str | None: return None +def _pattern_source_features(value: Any, previous: list[str]) -> list[str]: + sources = [] + for call in walk_calls(value): + if call.name != "makeQuery" or not call.args: + continue + owner = symbolic_string(call.args[0]) + if "F" not in owner: + continue + source = "f_" + owner[owner.find("F"):].split(".", 1)[0] + if source in previous and source not in sources: + sources.append(source) + if not sources: + raise ValueError("pattern source features are unresolved") + return sources + + +def _transform_source_features(value: Any, previous: list[str]) -> list[str]: + sources = [] + for call in walk_calls(value): + if call.name not in {"makeQuery", "qCreatedBy"} or not call.args: + continue + owner = symbolic_string(call.args[0]) + if "F" not in owner: + continue + source = "f_" + owner[owner.find("F"):].split(".", 1)[0] + if source in previous and source not in sources: + sources.append(source) + if not sources: + raise ValueError("transform source features are unresolved") + return sources + + +def _circular_pattern_axis( + value: Any, + feature_frames: dict[str, dict[str, Any]], + sketch_by_source: dict[str, dict[str, Any]], + entity_by_sketch: dict[str, dict[str, dict[str, Any]]], +) -> dict[str, list[float]]: + query = parse_query(value) + entity = (entity_by_sketch.get(query.source_sketch or "") or {}).get(query.source_entity or "") + if entity is None or query.source_sketch not in sketch_by_source: + raise ValueError("circular pattern axis is unresolved") + plane = sketch_by_source[query.source_sketch]["workplane"] + if entity["type"] == "line": + start = _global(plane, entity["start"]); end = _global(plane, entity["end"]) + direction = [end[index] - start[index] for index in range(3)] + norm = math.sqrt(sum(component * component for component in direction)) + if norm <= 1e-9: + raise ValueError("circular pattern axis line is degenerate") + return {"origin_mm": start, "direction": [component / norm for component in direction]} + if entity["type"] == "circle": + frame = feature_frames.get(query.owner_feature or "") + if frame and query.is_start is not None: + plane = frame["start" if query.is_start else "end"] + return {"origin_mm": _global(plane, entity["center"]), "direction": list(plane["normal"])} + raise ValueError("circular pattern axis must be a sketch line or circular edge") + + +def _loft_profile_sketches(params: dict[str, Any]) -> list[str]: + # CADFS loft 的 profile 是草图 IMPRINT 面;几何仍来自原始闭合草图, + # 保留草图 source,不能把前序实体的选中面近似为新的放样轮廓。 + profiles = params.get("sheetProfilesArray") + if not isinstance(profiles, list): + raise ValueError("loft sheetProfilesArray is unresolved") + sources: list[str] = [] + for profile in profiles: + query_value = profile.get("sheetProfileEntities") if isinstance(profile, dict) else profile + query = parse_query(query_value) + if query.topology_type and query.topology_type != "IMPRINT": + raise UnsupportedCapability( + f"loft_profile_topology:{query.topology_type.lower()}", + f"current CDSL loft only supports sketch-imprint profiles, not {query.topology_type}", + ) + if not query.source_sketch: + raise ValueError("loft profile sketch query is unresolved") + sources.append(query.source_sketch) + if len(sources) < 2: + raise ValueError("loft requires at least two profile sketches") + if len(set(sources)) != len(sources): + raise ValueError("loft profile sketches must be distinct") + return sources + + def _profile_query_kind(params: dict[str, Any]) -> str | None: for key in ("entities", "sheetProfilesArray"): if key in params: @@ -215,18 +349,243 @@ def _default_plane(value: Any) -> dict[str, Any] | None: return None +def _entity_from_query( + query: Any, + sketch_by_source: dict[str, dict[str, Any]], + entity_by_sketch: dict[str, dict[str, dict[str, Any]]], +) -> tuple[dict[str, Any], dict[str, Any], str]: + info = parse_query(query); source = info.source_sketch or ""; token = info.source_entity or "" + available = entity_by_sketch.get(source) or {} + entity = available.get(token) + if entity is None: + entity_id = max((key for key in available if token.startswith(key + ".")), key=len, default="") + entity = available.get(entity_id) + sketch = sketch_by_source.get(source) + if entity is None or sketch is None: + raise ValueError("reference geometry source is unresolved") + return entity, sketch["workplane"], token + + +def _entity_point(entity: dict[str, Any], plane: dict[str, Any], token: str) -> list[float]: + if entity["type"] == "point": return _global(plane, entity["point"]) + if entity["type"] == "line": + local = entity["end"] if ".end" in token else entity["start"] + return _global(plane, local) + if entity["type"] == "bspline": + index = next((int(part) - 1 for part in token.split(".") if part.isdigit()), 0) + points = entity.get("points") or [] + if not points: raise ValueError("B-spline reference point is unresolved") + return _global(plane, points[max(0, min(index, len(points) - 1))]) + raise ValueError("reference entity does not define a point") + + +def _entity_line(entity: dict[str, Any], plane: dict[str, Any]) -> tuple[list[float], list[float]]: + if entity["type"] != "line": raise ValueError("reference entity is not a line") + return _global(plane, entity["start"]), _global(plane, entity["end"]) + + +def _query_plane( + query: Any, + feature_frames: dict[str, dict[str, Any]], + sketch_by_source: dict[str, dict[str, Any]], + entity_by_sketch: dict[str, dict[str, dict[str, Any]]], +) -> dict[str, Any]: + try: + return _plane_from_query(query, feature_frames, sketch_by_source) + except ValueError: + info = parse_query(query) + if info.topology_type != "SWEPT_FACE" or not info.owner_feature: + raise + entity, source_plane, _ = _entity_from_query(query, sketch_by_source, entity_by_sketch) + start, end = _entity_line(entity, source_plane); frame = feature_frames.get(info.owner_feature) + if frame is None: raise ValueError("swept face owner frame is unresolved") + direction = _unit(_sub(end, start), "swept face source line is degenerate") + normal = _cross(direction, frame["end"]["normal"]) + return _frame(start, direction, normal) + + +def _query_line( + query: Any, + feature_frames: dict[str, dict[str, Any]], + sketch_by_source: dict[str, dict[str, Any]], + entity_by_sketch: dict[str, dict[str, dict[str, Any]]], +) -> tuple[list[float], list[float]]: + info = parse_query(query) + entity, plane, _ = _entity_from_query(query, sketch_by_source, entity_by_sketch) + if info.topology_type == "CAP_EDGE" and info.owner_feature in feature_frames: + frame = feature_frames[info.owner_feature]["start" if info.is_start else "end"] + plane = frame + return _entity_line(entity, plane) + + +def _transform_axis( + query: Any, + feature_frames: dict[str, dict[str, Any]], + sketch_by_source: dict[str, dict[str, Any]], + entity_by_sketch: dict[str, dict[str, dict[str, Any]]], +) -> dict[str, list[float]]: + start, end = _query_line(query, feature_frames, sketch_by_source, entity_by_sketch) + return {"origin_mm": start, "direction": _unit(_sub(end, start), "transform axis is degenerate")} + + +def _bake_transform( + params: dict[str, Any], + previous: list[str], + feature_by_id: dict[str, dict[str, Any]], + feature_source_by_id: dict[str, str], + sketches_by_id: dict[str, dict[str, Any]], + feature_frames: dict[str, dict[str, Any]], + sketch_by_source: dict[str, dict[str, Any]], + entity_by_sketch: dict[str, dict[str, dict[str, Any]]], +) -> None: + if _bool(params.get("makeCopy")): + raise UnsupportedCapability("transform", "current CDSL engine cannot exactly copy transformed CADFS source bodies") + sources = _transform_source_features(params.get("entities"), previous) + if len(sources) != 1: + raise UnsupportedCapability("transform", "current CDSL engine cannot exactly transform multiple selected CADFS source bodies") + source_id = sources[0]; source = feature_by_id.get(source_id) + if source is None or source.get("atomic_id") not in {"extrude_add_blind", "extrude_add_two_sided", "revolve_add"}: + raise UnsupportedCapability("transform", "current CDSL engine can only bake a direct additive extrusion or revolve transform") + sketch = sketches_by_id.get(str(source.get("sketch_id") or "")) + source_feature_id = feature_source_by_id.get(source_id) + if sketch is None or source_feature_id is None: + raise UnsupportedCapability("transform", "current CDSL engine cannot resolve the transformed source feature geometry") + transform_type = str(params.get("transformType") or "").split(".")[-1].upper() + if transform_type == "TRANSLATION_3D": + offset = [_number(params.get(key, 0.0), True) for key in ("dx", "dy", "dz")] + transform_frame = lambda frame: _translate_frame(frame, offset) + transform_axis = lambda axis: {**axis, "origin_mm": [axis["origin_mm"][index] + offset[index] for index in range(3)]} + elif transform_type == "ROTATION": + axis = _transform_axis(params.get("transformAxis"), feature_frames, sketch_by_source, entity_by_sketch) + angle_rad = math.radians(_number(params.get("angle"), True)) + transform_frame = lambda frame: _rotate_frame(frame, axis, angle_rad) + transform_axis = lambda value: { + **value, + "origin_mm": _rotate_point(value["origin_mm"], axis, angle_rad), + "direction": _rotate(value["direction"], axis["direction"], angle_rad), + } + else: + raise UnsupportedCapability("transform", f"current CDSL engine cannot exactly bake {transform_type or 'unknown'} transform") + sketch["workplane"] = transform_frame(sketch["workplane"]) + frame = feature_frames.get(source_feature_id) + if frame is not None: + feature_frames[source_feature_id] = {key: transform_frame(value) for key, value in frame.items()} + source_axis = source.get("params", {}).get("axis") + if isinstance(source_axis, dict) and source_axis.get("origin_mm") and source_axis.get("direction"): + source["params"]["axis"] = transform_axis(source_axis) + + +def _query_point( + query: Any, + feature_frames: dict[str, dict[str, Any]], + sketch_by_source: dict[str, dict[str, Any]], + entity_by_sketch: dict[str, dict[str, dict[str, Any]]], +) -> list[float]: + info = parse_query(query) + if info.topology_type == "CAP_VERTEX" and info.owner_feature in feature_frames: + references = _source_refs(query) + if len(references) >= 2: + plane = feature_frames[info.owner_feature]["start" if info.is_start else "end"] + lines = [] + for source, token in references: + available = entity_by_sketch.get(source) or {} + entity = available.get(token) + if entity is None: + entity_id = max((key for key in available if token.startswith(key + ".")), key=len, default="") + entity = available.get(entity_id) + if entity and entity.get("type") == "line": lines.append(_entity_line(entity, plane)) + if len(lines) >= 2: + pairs = [(math.dist(left, right), left) for left in lines[0] for right in lines[1]] + distance, point = min(pairs, key=lambda item: item[0]) + if distance <= 1e-5: return point + entity, plane, token = _entity_from_query(query, sketch_by_source, entity_by_sketch) + if info.topology_type == "CAP_VERTEX" and info.owner_feature in feature_frames: + plane = feature_frames[info.owner_feature]["start" if info.is_start else "end"] + return _entity_point(entity, plane, token) + + +def _cplane( + params: dict[str, Any], + feature_frames: dict[str, dict[str, Any]], + sketch_by_source: dict[str, dict[str, Any]], + entity_by_sketch: dict[str, dict[str, dict[str, Any]]], +) -> dict[str, Any]: + plane_type = str(params.get("cplaneType") or "OFFSET").split(".")[-1].upper() + entities = _queries(params.get("entities")) + if plane_type == "OFFSET": + return _shift_plane(_query_plane(entities[0], feature_frames, sketch_by_source, entity_by_sketch), _number(params.get("offset", 0), True)) + if plane_type == "LINE_ANGLE": + line_query = next((item for item in entities if parse_query(item).source_entity), None) + if line_query is None: raise ValueError("line-angle reference line is unresolved") + base_query = next((item for item in entities if item is not line_query), line_query) + try: + base = _query_plane(base_query, feature_frames, sketch_by_source, entity_by_sketch) + except ValueError: + _, base, _ = _entity_from_query(line_query, sketch_by_source, entity_by_sketch) + start, end = _query_line(line_query, feature_frames, sketch_by_source, entity_by_sketch) + axis = _sub(end, start); angle = _number(params.get("angle", 0.0)) + if _bool(params.get("oppositeDirection")): angle = -angle + return _frame(start, _rotate(base["x_dir"], axis, math.radians(angle)), _rotate(base["normal"], axis, math.radians(angle))) + if plane_type == "PLANE_POINT": + base_query = next((item for item in entities if _default_plane(item) or "qCreatedBy" in parse_query(item).calls), None) + point_query = next((item for item in entities if item is not base_query), None) + if base_query is None or point_query is None: raise ValueError("plane-point references are unresolved") + base = _query_plane(base_query, feature_frames, sketch_by_source, entity_by_sketch) + return _frame(_query_point(point_query, feature_frames, sketch_by_source, entity_by_sketch), base["x_dir"], base["normal"]) + if plane_type == "CURVE_POINT": + point_query = next((item for item in entities if parse_query(item).kind and "vertex" in parse_query(item).kind), None) + curve_query = next((item for item in entities if item is not point_query), None) + if point_query is None or curve_query is None: raise ValueError("curve-point references are unresolved") + point = _query_point(point_query, feature_frames, sketch_by_source, entity_by_sketch) + start, end = _query_line(curve_query, feature_frames, sketch_by_source, entity_by_sketch) + _, source_plane, _ = _entity_from_query(curve_query, sketch_by_source, entity_by_sketch) + return _frame(point, source_plane["normal"], _sub(end, start)) + if plane_type == "THREE_POINT": + if len(entities) != 3: raise ValueError("three-point plane requires exactly three points") + first, second, third = [_query_point(item, feature_frames, sketch_by_source, entity_by_sketch) for item in entities] + normal = _cross(_sub(second, first), _sub(third, first)) + if _bool(params.get("oppositeDirection")): normal = [-value for value in normal] + return _frame(first, _sub(second, first), normal) + if plane_type == "LINE_POINT": + line_query = next((item for item in entities if "edge" in (parse_query(item).kind or "")), None) + point_query = next((item for item in entities if item is not line_query), None) + if line_query is None or point_query is None: raise ValueError("line-point references are unresolved") + start, end = _query_line(line_query, feature_frames, sketch_by_source, entity_by_sketch) + point = _query_point(point_query, feature_frames, sketch_by_source, entity_by_sketch) + direction = _sub(end, start); normal = _cross(direction, _sub(point, start)) + if _bool(params.get("oppositeDirection")): normal = [-value for value in normal] + return _frame(start, direction, normal) + if plane_type == "MID_PLANE": + if len(entities) != 2: raise ValueError("mid-plane requires exactly two reference planes") + first, second = [_query_plane(item, feature_frames, sketch_by_source, entity_by_sketch) for item in entities] + alignment = 1.0 if _dot(first["normal"], second["normal"]) >= 0 else -1.0 + offset = _dot(_sub(second["origin_mm"], first["origin_mm"]), first["normal"]) * alignment + return _shift_plane(first, offset / 2.0) + raise UnsupportedCapability(f"reference_plane:{plane_type.lower()}", f"current converter has no exact {plane_type} reference plane") + + def lower_model(model: ModelIR, provenance: dict[str, Any]) -> LoweringResult: diagnostics: list[dict[str, Any]] = []; history = [] - sketches: list[dict[str, Any]] = []; sketch_by_source: dict[str, dict[str, Any]] = {}; entity_by_sketch: dict[str, dict[str, dict[str, Any]]] = {} + sketches: list[dict[str, Any]] = []; sketches_by_id: dict[str, dict[str, Any]] = {}; sketch_by_source: dict[str, dict[str, Any]] = {}; entity_by_sketch: dict[str, dict[str, dict[str, Any]]] = {} feature_frames: dict[str, dict[str, Any]] = {} features: list[dict[str, Any]] = []; complete = True; previous: list[str] = [] + feature_by_id: dict[str, dict[str, Any]] = {}; feature_source_by_id: dict[str, str] = {} for step in model.steps: if isinstance(step, SketchIR): history.append({"feature_id": step.feature_id, "operation": "newSketch", "parameters": {"sketchPlane": plain(step.workplane)}, "entities": [{"entity_id": e.feature_id, "operation": e.operation, "parameters": plain(e.params)} for e in step.entities]}) try: plane = _plane_from_query(step.workplane, feature_frames, sketch_by_source) - lowered, entities = _lower_sketch(step, plane); sketches.append(lowered); sketch_by_source[step.feature_id] = lowered; entity_by_sketch[step.feature_id] = entities + lowered, entities = _lower_sketch(step, plane); sketches.append(lowered); sketches_by_id[lowered["id"]] = lowered; sketch_by_source[step.feature_id] = lowered; entity_by_sketch[step.feature_id] = entities except Exception as exc: + # 开放草图不能作为实体 profile,但其几何仍可能是后续基准面、 + # 阵列轴或旋转轴的精确引用。保留为 reference 草图,后续实体 + # 特征仍由 _profile_executable 明确拒绝,不能静默把开放轮廓实体化。 + try: + plane = _plane_from_query(step.workplane, feature_frames, sketch_by_source) + lowered, entities = _lower_sketch(step, plane, allow_open=True) + sketches.append(lowered); sketches_by_id[lowered["id"]] = lowered; sketch_by_source[step.feature_id] = lowered; entity_by_sketch[step.feature_id] = entities + except Exception: + pass diagnostics.append({"code": "sketch_deferred", "feature_id": step.feature_id, "message": str(exc)}); complete = False continue item = step @@ -235,12 +594,14 @@ def lower_model(model: ModelIR, provenance: dict[str, Any]) -> LoweringResult: diagnostics.append({"code": "unsupported_operation", "feature_id": item.feature_id, "operation": item.operation}); complete = False; continue try: fid = f"f_{item.feature_id}"; depends = list(previous[-1:]); p = item.params; feature: dict[str, Any] - if item.operation == "cPlane": - plane_type = str(p.get("cplaneType") or "OFFSET").split(".")[-1].upper() - if plane_type != "OFFSET": - raise UnsupportedCapability(f"reference_plane:{plane_type.lower()}", f"current converter only supports exact OFFSET reference planes, not {plane_type}") - base = _plane_from_query(p.get("entities"), feature_frames, sketch_by_source); offset = _number(p.get("offset", 0), True); plane = _shift_plane(base, offset) - feature = {"id": fid, "name": item.feature_id, "atomic_id": "reference_plane", "depends_on": depends, "params": {"plane": plane, "offset_mm": offset}, "execution_status": "supported"} + if item.operation == "transform": + # 仅将单一、直接的原始实体变换烘焙回其输入几何。不能移动当前 + # 聚合主体:CADFS transform 可能只选择 pattern copy 或多 body。 + _bake_transform(p, previous, feature_by_id, feature_source_by_id, sketches_by_id, feature_frames, sketch_by_source, entity_by_sketch) + continue + elif item.operation == "cPlane": + plane = _cplane(p, feature_frames, sketch_by_source, entity_by_sketch) + feature = {"id": fid, "name": item.feature_id, "atomic_id": "reference_plane", "depends_on": depends, "params": {"plane": plane}, "execution_status": "supported"} feature_frames[item.feature_id] = {"start": plane, "end": plane} elif item.operation == "extrude": if p.get("surfaceOperationType") is not None: @@ -255,19 +616,21 @@ def lower_model(model: ModelIR, provenance: dict[str, Any]) -> LoweringResult: depth_value = p.get("depth"); depth = _number(depth_value, True) if depth_value is not None else 1.0 operation = str(p.get("operationType") or "NEW").upper(); reverse = _bool(p.get("oppositeDirection")); cutting = any(x in operation for x in ("REMOVE", "CUT")) second = _bool(p.get("hasSecondDirection")) - if cutting and (second or end["type"] != "blind"): - capability = "extrude_cut_two_sided" if second or end["type"] == "mid_plane" else f"extrude_cut_{end['type']}" + if cutting and not second and end["type"] not in {"blind", "mid_plane", "through_all", "through_next"}: + capability = f"extrude_cut_{end['type']}" raise UnsupportedCapability(capability, f"current CDSL atomic set has no exact {capability} operation") - if not cutting and not second and end["type"] not in {"blind", "mid_plane"}: + if not cutting and not second and end["type"] not in {"blind", "mid_plane", "through_all", "through_next"}: capability = f"extrude_add_{end['type']}" raise UnsupportedCapability(capability, f"current CDSL atomic set has no exact {capability} operation") if second: - atomic = "extrude_add_two_sided"; params = {"distance_mm": depth, "reverse": reverse, "end_condition": end} + atomic = "extrude_cut_two_sided" if cutting else "extrude_add_two_sided" + params = {"distance_mm": depth, "reverse": reverse, "end_condition": end} reverse_depth = _number(p.get("secondDirectionDepth", depth), True) params.update({"reverse_distance_mm": reverse_depth, "reverse_end_condition": _end_condition(p.get("secondDirectionBound"))}) - elif end["type"] == "mid_plane" and not cutting: + elif end["type"] == "mid_plane": blind = _end_condition("BLIND") - atomic = "extrude_add_two_sided"; params = {"distance_mm": depth / 2, "reverse_distance_mm": depth / 2, "reverse": reverse, "end_condition": blind, "reverse_end_condition": dict(blind)} + atomic = "extrude_cut_two_sided" if cutting else "extrude_add_two_sided" + params = {"distance_mm": depth / 2, "reverse_distance_mm": depth / 2, "reverse": reverse, "end_condition": blind, "reverse_end_condition": dict(blind)} else: atomic = "extrude_cut_blind" if cutting else "extrude_add_blind" params = {"distance_mm": depth, "reverse": reverse, "end_condition": end} @@ -279,6 +642,22 @@ def lower_model(model: ModelIR, provenance: dict[str, Any]) -> LoweringResult: elif end["type"] == "mid_plane": direction = -1 if reverse else 1 feature_frames[item.feature_id] = {"start": _shift_plane(plane, -direction * depth / 2), "end": _shift_plane(plane, direction * depth / 2)} + elif item.operation == "loft": + sources = _loft_profile_sketches(p) + missing = [source for source in sources if source not in sketch_by_source] + if missing: + raise ValueError("loft profile sketches are unresolved: " + ", ".join(missing)) + non_closed = [source for source in sources if not _profile_executable(sketch_by_source[source])] + if non_closed: + raise ValueError("loft profile sketches have no closed profile: " + ", ".join(non_closed)) + feature = { + "id": fid, + "name": item.feature_id, + "atomic_id": "loft_add", + "depends_on": depends, + "params": {"profile_sketch_ids": [sketch_by_source[source]["id"] for source in sources]}, + "execution_status": "supported", + } elif item.operation == "revolve": if p.get("surfaceOperationType") is not None and p.get("operationType") is None: raise UnsupportedCapability("revolve_surface", "current CDSL engine has no exact surface-revolve operation") @@ -365,6 +744,25 @@ def lower_model(model: ModelIR, provenance: dict[str, Any]) -> LoweringResult: hole_params["counterbore"] = {"diameter_mm": _number(p.get("counterboreDiameter") or p.get("cBoreDiameter") or p.get("majorDiameter"), True), "depth_mm": _number(p.get("counterboreDepth") or p.get("cBoreDepth"), True)} if _bool(p.get("isTappedThrough")) or p.get("tapSize") is not None: hole_params["thread"] = {"source": "CADFS", "decorative": True} feature = {"id": fid, "name": item.feature_id, "atomic_id": "hole_wizard", "depends_on": depends, "params": hole_params, "execution_status": "supported"} + elif item.operation == "circularPattern": + sources = _pattern_source_features(p.get("entities"), previous) + axis = _circular_pattern_axis(p.get("axis"), feature_frames, sketch_by_source, entity_by_sketch) + count = int(_number(p.get("instanceCount"))) + if count < 1: + raise ValueError("circular pattern instanceCount must be positive") + feature = { + "id": fid, + "name": item.feature_id, + "atomic_id": "pattern_circular", + "depends_on": list(dict.fromkeys(sources + depends)), + "params": { + "source_feature_ids": sources, + "axis": axis, + "pattern_count": count, + "sweep_angle_deg": _number(p.get("angle", 360.0)), + }, + "execution_status": "supported", + } elif item.operation == "mirror": owners = [] for call in walk_calls(p.get("entities")): @@ -387,7 +785,7 @@ def lower_model(model: ModelIR, provenance: dict[str, Any]) -> LoweringResult: feature = {"id": fid, "name": item.feature_id, "atomic_id": "pattern_mirror", "depends_on": list(dict.fromkeys(owners + [plane_owner])), "params": {"source_feature_ids": owners, "mirror_plane": mirror_plane}, "selectors": [mirror_plane], "execution_status": "supported"} else: raise ValueError(f"operation mapping not implemented: {item.operation}") - features.append(feature); previous.append(fid) + features.append(feature); feature_by_id[fid] = feature; feature_source_by_id[fid] = item.feature_id; previous.append(fid) except UnsupportedCapability as exc: diagnostics.append({"code": "unsupported_engine_capability", "capability": exc.capability, "feature_id": item.feature_id, "operation": item.operation, "message": str(exc)}); complete = False except Exception as exc: diff --git a/cadfs_to_cdsl/pipeline.py b/cadfs_to_cdsl/pipeline.py index 5e7655f6..0cee6328 100644 --- a/cadfs_to_cdsl/pipeline.py +++ b/cadfs_to_cdsl/pipeline.py @@ -103,7 +103,12 @@ def _isolated(target: Any, args: tuple[str, ...], result_path: Path, timeout_sec def rebuild_one(sample: Sample, output: Path, *, force: bool = False, timeout_seconds: float = 30.0) -> dict[str, Any]: directory = _sample_dir(output, sample.sample_id); status_path = directory / "status.json" status = read_json(status_path) if status_path.exists() else convert_one(sample, output, force=force) - if status.get("conversion_status") != "converted_complete": return status + # A partial conversion may still retain a self-contained, semantically + # executable CDSL prefix. That prefix is valuable engine evidence and + # must be rebuilt instead of being hidden behind the conversion label. + # ``rebuild_candidate`` keeps the two outcomes distinct by reporting a + # runtime-ineligible candidate when no executable body can be produced. + if not (directory / "candidate.cdsl.json").exists(): return status rebuild_path = directory / "rebuild.json"; step_path = directory / "rebuild.step" if not force and rebuild_path.exists() and status.get("rebuild_status"): if status.get("rebuild_status") != "rebuilt" or step_path.exists(): return status diff --git a/cadfs_to_cdsl/regression.py b/cadfs_to_cdsl/regression.py new file mode 100644 index 00000000..8ca6e393 --- /dev/null +++ b/cadfs_to_cdsl/regression.py @@ -0,0 +1,216 @@ +"""Deterministic, feature-covering CADFS regression pools. + +The converted CADFS corpus is deliberately kept outside version control. This +module turns the artifacts already produced in ``output/samples`` into a small, +versionable manifest that records why every selected source sample is needed. +""" + +from __future__ import annotations + +from collections import Counter +from pathlib import Path +from typing import Any + +from .reports import read_json, write_json + + +REGRESSION_SCHEMA = "cadfs_to_cdsl.regression.v1" + + +def _tag(kind: str, value: str) -> str: + return f"{kind}:{value}" + + +def _split_tag(tag: str) -> tuple[str, str]: + kind, separator, value = tag.partition(":") + if not separator: + raise ValueError(f"Malformed regression coverage tag: {tag!r}") + return kind, value + + +def _sample_features(sample_dir: Path) -> dict[str, Any]: + """Read coverage signals from a converted sample without reparsing CADFS.""" + history = read_json(sample_dir / "history.json") if (sample_dir / "history.json").exists() else [] + candidate = read_json(sample_dir / "candidate.cdsl.json") if (sample_dir / "candidate.cdsl.json").exists() else {} + diagnostics = read_json(sample_dir / "diagnostics.json") if (sample_dir / "diagnostics.json").exists() else [] + status = read_json(sample_dir / "status.json") if (sample_dir / "status.json").exists() else {} + + source_operations: set[str] = set() + sketch_entities: set[str] = set() + for item in history: + if not isinstance(item, dict): + continue + operation = item.get("operation") + if isinstance(operation, str): + source_operations.add(operation) + for entity in item.get("entities") or (): + if isinstance(entity, dict) and isinstance(entity.get("operation"), str): + sketch_entities.add(str(entity["operation"])) + + atomic_ids = { + str(feature["atomic_id"]) + for feature in candidate.get("features") or () + if isinstance(feature, dict) and isinstance(feature.get("atomic_id"), str) + } + unsupported_capabilities = { + str(item["capability"]) + for item in diagnostics + if isinstance(item, dict) + and item.get("code") == "unsupported_engine_capability" + and isinstance(item.get("capability"), str) + } + unsupported_operations = { + str(item["operation"]) + for item in diagnostics + if isinstance(item, dict) + and item.get("code") == "unsupported_operation" + and isinstance(item.get("operation"), str) + } + return { + "sample_id": sample_dir.name, + "source_operations": sorted(source_operations), + "sketch_entities": sorted(sketch_entities), + "engine_atomic_ids": sorted(atomic_ids), + "unsupported_capabilities": sorted(unsupported_capabilities), + "unsupported_operations": sorted(unsupported_operations), + "has_candidate": bool(candidate), + "baseline": { + key: status[key] + for key in ("conversion_status", "rebuild_status", "comparison_decision", "status") + if key in status + }, + } + + +def _tags(record: dict[str, Any]) -> set[str]: + return { + *(_tag("source_operation", item) for item in record["source_operations"]), + *(_tag("sketch_entity", item) for item in record["sketch_entities"]), + *(_tag("engine_atomic", item) for item in record["engine_atomic_ids"]), + *(_tag("unsupported_capability", item) for item in record["unsupported_capabilities"]), + *(_tag("unsupported_operation", item) for item in record["unsupported_operations"]), + } + + +def _greedy_cover(records: list[dict[str, Any]], wanted: set[str], selected: list[dict[str, Any]]) -> dict[str, list[str]]: + """Cover ``wanted`` with stable maximum-coverage selection. + + The sample id breaks ties, so a corpus refresh is reviewable and never + changes pool membership due to directory iteration order. + """ + coverage: dict[str, list[str]] = {} + selected_ids = {record["sample_id"] for record in selected} + remaining = set(wanted) + while remaining: + choices = [record for record in records if record["sample_id"] not in selected_ids] + if not choices: + break + choice = min( + choices, + key=lambda record: (-len(_tags(record) & remaining), record["sample_id"]), + ) + gained = _tags(choice) & remaining + if not gained: + break + selected.append(choice) + selected_ids.add(choice["sample_id"]) + for tag in sorted(gained): + coverage.setdefault(tag, []).append(choice["sample_id"]) + remaining -= gained + return coverage + + +def build_regression_manifest(output: Path) -> dict[str, Any]: + """Build a compact pool covering every observed CADFS modeling signal.""" + records = [_sample_features(directory) for directory in sorted((output / "samples").glob("*")) if directory.is_dir()] + if not records: + raise FileNotFoundError(f"No converted CADFS samples found under {output / 'samples'}") + + all_tags = set().union(*(_tags(record) for record in records)) + selected: list[dict[str, Any]] = [] + + # Give the engine pool a current successful baseline per atomic operation + # whenever the corpus has one. Unsupported/failed atoms remain covered by + # the full conversion pool and are never represented as passing builds. + engine_records = [ + record for record in records + if record["has_candidate"] and record["baseline"].get("rebuild_status") == "rebuilt" + ] + engine_tags = {_tag("engine_atomic", atom) for record in engine_records for atom in record["engine_atomic_ids"]} + _greedy_cover(engine_records, engine_tags, selected) + # Samples chosen for the executable baseline can also cover source and + # converter signals. Count those signals before the general pass so the + # final fixture remains genuinely representative rather than redundant. + coverage = { + tag: sorted(record["sample_id"] for record in selected if tag in _tags(record)) + for tag in sorted(set().union(*(_tags(record) for record in selected))) + } + all_coverage = _greedy_cover(records, all_tags - set(coverage), selected) + for tag, sample_ids in all_coverage.items(): + coverage[tag] = sorted(set(coverage.get(tag, []) + sample_ids)) + + selected_ids = {record["sample_id"] for record in selected} + missing = sorted(all_tags - set(coverage)) + if missing: + raise RuntimeError("Unable to cover CADFS regression features: " + ", ".join(missing)) + + entries = [] + for record in sorted(selected, key=lambda item: item["sample_id"]): + tags = _tags(record) + tiers = ["conversion"] + if record["sample_id"] in {item["sample_id"] for item in engine_records}: + tiers.append("engine") + entries.append({ + **record, + "tiers": tiers, + "selection_reasons": sorted(tag for tag, sample_ids in coverage.items() if record["sample_id"] in sample_ids), + }) + + inventory: dict[str, dict[str, int]] = {} + for kind, _ in map(_split_tag, sorted(all_tags)): + inventory.setdefault(kind, {}) + for tag in all_tags: + kind, value = _split_tag(tag) + inventory[kind][value] = sum(tag in _tags(record) for record in records) + + return { + "schema": REGRESSION_SCHEMA, + "description": "Feature-covering representative CADFS regression pool. Engine samples have a prior successful rebuild; conversion samples retain unsupported-feature diagnostics.", + "source_output": str(output), + "source_sample_count": len(records), + "selected_sample_count": len(entries), + "engine_sample_count": sum("engine" in entry["tiers"] for entry in entries), + "conversion_sample_count": len(entries), + "engine_baseline_atomic_ids": sorted(tag.removeprefix("engine_atomic:") for tag in engine_tags), + "engine_diagnostic_only_atomic_ids": sorted( + tag.removeprefix("engine_atomic:") for tag in all_tags - engine_tags if tag.startswith("engine_atomic:") + ), + "feature_inventory": {kind: dict(sorted(values.items())) for kind, values in sorted(inventory.items())}, + "coverage": coverage, + "entries": entries, + "selected_ids": sorted(selected_ids), + } + + +def write_regression_manifest(output: Path, manifest_path: Path) -> dict[str, Any]: + manifest = build_regression_manifest(output) + write_json(manifest_path, manifest) + return manifest + + +def regression_sample_ids(manifest_path: Path, tier: str) -> list[str]: + manifest = read_json(manifest_path) + if manifest.get("schema") != REGRESSION_SCHEMA: + raise ValueError(f"Unsupported regression manifest schema: {manifest.get('schema')!r}") + if tier not in {"engine", "conversion", "all"}: + raise ValueError(f"Unknown regression tier: {tier!r}") + entries = manifest.get("entries") or [] + selected = [item["sample_id"] for item in entries if tier == "all" or tier in (item.get("tiers") or ())] + if not selected: + raise ValueError(f"Regression manifest has no samples for tier {tier!r}") + return sorted(selected) + + +def summarize_regression(records: list[dict[str, Any]]) -> dict[str, Any]: + statuses = Counter(str(record.get("status") or "unknown") for record in records) + return {"sample_count": len(records), "statuses": dict(sorted(statuses.items()))} diff --git a/cadfs_to_cdsl/regression/manifest.json b/cadfs_to_cdsl/regression/manifest.json new file mode 100644 index 00000000..f866186c --- /dev/null +++ b/cadfs_to_cdsl/regression/manifest.json @@ -0,0 +1,942 @@ +{ + "conversion_sample_count": 17, + "coverage": { + "engine_atomic:chamfer": [ + "00002243", + "00111611" + ], + "engine_atomic:extrude_add_blind": [ + "00111611" + ], + "engine_atomic:extrude_add_two_sided": [ + "00002243", + "00111611" + ], + "engine_atomic:extrude_cut_blind": [ + "00002243", + "00129362" + ], + "engine_atomic:fillet": [ + "00111611" + ], + "engine_atomic:hole_wizard": [ + "00002243" + ], + "engine_atomic:pattern_mirror": [ + "00925274" + ], + "engine_atomic:reference_plane": [ + "00129362" + ], + "engine_atomic:revolve_add": [ + "00111611", + "00129362" + ], + "engine_atomic:revolve_cut": [ + "00129362" + ], + "sketch_entity:skArc": [ + "00111611", + "00129362" + ], + "sketch_entity:skCircle": [ + "00002243", + "00111611", + "00129362" + ], + "sketch_entity:skEllipse": [ + "00287955" + ], + "sketch_entity:skFitSpline": [ + "00542223" + ], + "sketch_entity:skLineSegment": [ + "00002243", + "00111611", + "00129362" + ], + "sketch_entity:skPoint": [ + "00002243", + "00129362" + ], + "source_operation:booleanBodies": [ + "00835610" + ], + "source_operation:cPlane": [ + "00129362" + ], + "source_operation:chamfer": [ + "00002243", + "00111611" + ], + "source_operation:circularPattern": [ + "00542223" + ], + "source_operation:extrude": [ + "00002243", + "00111611", + "00129362" + ], + "source_operation:fillet": [ + "00111611" + ], + "source_operation:hole": [ + "00002243" + ], + "source_operation:loft": [ + "00287955" + ], + "source_operation:mirror": [ + "00925274" + ], + "source_operation:newSketch": [ + "00002243", + "00111611", + "00129362" + ], + "source_operation:revolve": [ + "00111611", + "00129362" + ], + "source_operation:shell": [ + "00542223" + ], + "source_operation:sweep": [ + "00542223" + ], + "unsupported_capability:extrude_add_through_all": [ + "00159804" + ], + "unsupported_capability:extrude_cut_through_all": [ + "00542223" + ], + "unsupported_capability:extrude_cut_two_sided": [ + "00212904" + ], + "unsupported_capability:extrude_extent:up_to_body": [ + "00694309" + ], + "unsupported_capability:extrude_extent:up_to_next": [ + "00192744" + ], + "unsupported_capability:extrude_extent:up_to_surface": [ + "00925274" + ], + "unsupported_capability:extrude_extent:up_to_vertex": [ + "00423838" + ], + "unsupported_capability:extrude_profile_topology:cap_edge": [ + "00287955" + ], + "unsupported_capability:extrude_profile_topology:cap_face": [ + "00835610" + ], + "unsupported_capability:extrude_profile_topology:intersect": [ + "00835610" + ], + "unsupported_capability:extrude_profile_topology:mid_cap_edge": [ + "00612529" + ], + "unsupported_capability:extrude_profile_topology:offset_face": [ + "00789939" + ], + "unsupported_capability:extrude_profile_topology:swept_edge": [ + "00054089" + ], + "unsupported_capability:extrude_profile_topology:swept_face": [ + "00789939" + ], + "unsupported_capability:extrude_surface_or_mixed": [ + "00710855" + ], + "unsupported_capability:reference_plane:curve_point": [ + "00192744" + ], + "unsupported_capability:reference_plane:line_angle": [ + "00925274" + ], + "unsupported_capability:reference_plane:line_point": [ + "00035682" + ], + "unsupported_capability:reference_plane:mid_plane": [ + "00287955" + ], + "unsupported_capability:reference_plane:plane_point": [ + "00542223" + ], + "unsupported_capability:reference_plane:three_point": [ + "00212904" + ], + "unsupported_capability:revolve_surface": [ + "00835610" + ], + "unsupported_operation:booleanBodies": [ + "00835610" + ], + "unsupported_operation:circularPattern": [ + "00542223" + ], + "unsupported_operation:loft": [ + "00287955" + ], + "unsupported_operation:shell": [ + "00542223" + ], + "unsupported_operation:sweep": [ + "00542223" + ] + }, + "description": "Feature-covering representative CADFS regression pool. Engine samples have a prior successful rebuild; conversion samples retain unsupported-feature diagnostics.", + "engine_baseline_atomic_ids": [ + "chamfer", + "extrude_add_blind", + "extrude_add_two_sided", + "extrude_cut_blind", + "fillet", + "hole_wizard", + "reference_plane", + "revolve_add", + "revolve_cut" + ], + "engine_diagnostic_only_atomic_ids": [ + "pattern_mirror" + ], + "engine_sample_count": 3, + "entries": [ + { + "baseline": { + "comparison_decision": "rejected", + "conversion_status": "converted_complete", + "rebuild_status": "rebuilt", + "status": "rebuilt" + }, + "engine_atomic_ids": [ + "chamfer", + "extrude_add_two_sided", + "extrude_cut_blind", + "hole_wizard" + ], + "has_candidate": true, + "sample_id": "00002243", + "selection_reasons": [ + "engine_atomic:chamfer", + "engine_atomic:extrude_add_two_sided", + "engine_atomic:extrude_cut_blind", + "engine_atomic:hole_wizard", + "sketch_entity:skCircle", + "sketch_entity:skLineSegment", + "sketch_entity:skPoint", + "source_operation:chamfer", + "source_operation:extrude", + "source_operation:hole", + "source_operation:newSketch" + ], + "sketch_entities": [ + "skCircle", + "skLineSegment", + "skPoint" + ], + "source_operations": [ + "chamfer", + "extrude", + "hole", + "newSketch" + ], + "tiers": [ + "conversion", + "engine" + ], + "unsupported_capabilities": [], + "unsupported_operations": [] + }, + { + "baseline": { + "conversion_status": "deferred_no_executable_feature", + "status": "deferred_no_executable_feature" + }, + "engine_atomic_ids": [ + "extrude_cut_blind", + "revolve_add" + ], + "has_candidate": true, + "sample_id": "00035682", + "selection_reasons": [ + "unsupported_capability:reference_plane:line_point" + ], + "sketch_entities": [ + "skCircle", + "skLineSegment" + ], + "source_operations": [ + "cPlane", + "extrude", + "newSketch", + "revolve" + ], + "tiers": [ + "conversion" + ], + "unsupported_capabilities": [ + "reference_plane:line_point" + ], + "unsupported_operations": [] + }, + { + "baseline": { + "conversion_status": "deferred_no_executable_feature", + "status": "deferred_no_executable_feature" + }, + "engine_atomic_ids": [ + "extrude_add_blind", + "extrude_cut_blind", + "revolve_add" + ], + "has_candidate": true, + "sample_id": "00054089", + "selection_reasons": [ + "unsupported_capability:extrude_profile_topology:swept_edge" + ], + "sketch_entities": [ + "skArc", + "skLineSegment" + ], + "source_operations": [ + "extrude", + "newSketch", + "revolve" + ], + "tiers": [ + "conversion" + ], + "unsupported_capabilities": [ + "extrude_profile_topology:swept_edge" + ], + "unsupported_operations": [] + }, + { + "baseline": { + "comparison_decision": "rejected", + "conversion_status": "converted_complete", + "rebuild_status": "rebuilt", + "status": "rebuilt" + }, + "engine_atomic_ids": [ + "chamfer", + "extrude_add_blind", + "extrude_add_two_sided", + "fillet", + "revolve_add" + ], + "has_candidate": true, + "sample_id": "00111611", + "selection_reasons": [ + "engine_atomic:chamfer", + "engine_atomic:extrude_add_blind", + "engine_atomic:extrude_add_two_sided", + "engine_atomic:fillet", + "engine_atomic:revolve_add", + "sketch_entity:skArc", + "sketch_entity:skCircle", + "sketch_entity:skLineSegment", + "source_operation:chamfer", + "source_operation:extrude", + "source_operation:fillet", + "source_operation:newSketch", + "source_operation:revolve" + ], + "sketch_entities": [ + "skArc", + "skCircle", + "skLineSegment" + ], + "source_operations": [ + "chamfer", + "extrude", + "fillet", + "newSketch", + "revolve" + ], + "tiers": [ + "conversion", + "engine" + ], + "unsupported_capabilities": [], + "unsupported_operations": [] + }, + { + "baseline": { + "conversion_status": "converted_complete", + "rebuild_status": "rebuilt", + "status": "rebuilt" + }, + "engine_atomic_ids": [ + "extrude_cut_blind", + "reference_plane", + "revolve_add", + "revolve_cut" + ], + "has_candidate": true, + "sample_id": "00129362", + "selection_reasons": [ + "engine_atomic:extrude_cut_blind", + "engine_atomic:reference_plane", + "engine_atomic:revolve_add", + "engine_atomic:revolve_cut", + "sketch_entity:skArc", + "sketch_entity:skCircle", + "sketch_entity:skLineSegment", + "sketch_entity:skPoint", + "source_operation:cPlane", + "source_operation:extrude", + "source_operation:newSketch", + "source_operation:revolve" + ], + "sketch_entities": [ + "skArc", + "skCircle", + "skLineSegment", + "skPoint" + ], + "source_operations": [ + "cPlane", + "extrude", + "newSketch", + "revolve" + ], + "tiers": [ + "conversion", + "engine" + ], + "unsupported_capabilities": [], + "unsupported_operations": [] + }, + { + "baseline": { + "conversion_status": "converted_partial", + "status": "converted_partial" + }, + "engine_atomic_ids": [ + "reference_plane", + "revolve_add" + ], + "has_candidate": true, + "sample_id": "00159804", + "selection_reasons": [ + "unsupported_capability:extrude_add_through_all" + ], + "sketch_entities": [ + "skArc", + "skCircle", + "skLineSegment" + ], + "source_operations": [ + "cPlane", + "circularPattern", + "extrude", + "newSketch", + "revolve" + ], + "tiers": [ + "conversion" + ], + "unsupported_capabilities": [ + "extrude_add_through_all", + "reference_plane:line_angle" + ], + "unsupported_operations": [ + "circularPattern" + ] + }, + { + "baseline": { + "conversion_status": "converted_partial", + "status": "converted_partial" + }, + "engine_atomic_ids": [ + "extrude_add_blind", + "reference_plane" + ], + "has_candidate": true, + "sample_id": "00192744", + "selection_reasons": [ + "unsupported_capability:extrude_extent:up_to_next", + "unsupported_capability:reference_plane:curve_point" + ], + "sketch_entities": [ + "skCircle", + "skLineSegment" + ], + "source_operations": [ + "cPlane", + "circularPattern", + "extrude", + "newSketch" + ], + "tiers": [ + "conversion" + ], + "unsupported_capabilities": [ + "extrude_extent:up_to_next", + "reference_plane:curve_point" + ], + "unsupported_operations": [ + "circularPattern" + ] + }, + { + "baseline": { + "conversion_status": "converted_partial", + "status": "converted_partial" + }, + "engine_atomic_ids": [ + "extrude_add_blind" + ], + "has_candidate": true, + "sample_id": "00212904", + "selection_reasons": [ + "unsupported_capability:extrude_cut_two_sided", + "unsupported_capability:reference_plane:three_point" + ], + "sketch_entities": [ + "skLineSegment", + "skPoint" + ], + "source_operations": [ + "cPlane", + "extrude", + "newSketch", + "shell" + ], + "tiers": [ + "conversion" + ], + "unsupported_capabilities": [ + "extrude_cut_two_sided", + "reference_plane:three_point" + ], + "unsupported_operations": [ + "shell" + ] + }, + { + "baseline": { + "conversion_status": "converted_partial", + "status": "converted_partial" + }, + "engine_atomic_ids": [ + "extrude_add_blind" + ], + "has_candidate": true, + "sample_id": "00287955", + "selection_reasons": [ + "sketch_entity:skEllipse", + "source_operation:loft", + "unsupported_capability:extrude_profile_topology:cap_edge", + "unsupported_capability:reference_plane:mid_plane", + "unsupported_operation:loft" + ], + "sketch_entities": [ + "skEllipse", + "skFitSpline", + "skLineSegment" + ], + "source_operations": [ + "cPlane", + "extrude", + "loft", + "newSketch", + "revolve" + ], + "tiers": [ + "conversion" + ], + "unsupported_capabilities": [ + "extrude_profile_topology:cap_edge", + "reference_plane:mid_plane" + ], + "unsupported_operations": [ + "loft" + ] + }, + { + "baseline": { + "conversion_status": "converted_partial", + "status": "converted_partial" + }, + "engine_atomic_ids": [ + "extrude_add_blind" + ], + "has_candidate": true, + "sample_id": "00423838", + "selection_reasons": [ + "unsupported_capability:extrude_extent:up_to_vertex" + ], + "sketch_entities": [ + "skArc", + "skCircle", + "skLineSegment" + ], + "source_operations": [ + "circularPattern", + "extrude", + "newSketch" + ], + "tiers": [ + "conversion" + ], + "unsupported_capabilities": [ + "extrude_cut_two_sided", + "extrude_extent:up_to_vertex" + ], + "unsupported_operations": [ + "circularPattern" + ] + }, + { + "baseline": { + "conversion_status": "converted_partial", + "status": "converted_partial" + }, + "engine_atomic_ids": [ + "extrude_add_blind" + ], + "has_candidate": true, + "sample_id": "00542223", + "selection_reasons": [ + "sketch_entity:skFitSpline", + "source_operation:circularPattern", + "source_operation:shell", + "source_operation:sweep", + "unsupported_capability:extrude_cut_through_all", + "unsupported_capability:reference_plane:plane_point", + "unsupported_operation:circularPattern", + "unsupported_operation:shell", + "unsupported_operation:sweep" + ], + "sketch_entities": [ + "skCircle", + "skFitSpline" + ], + "source_operations": [ + "cPlane", + "circularPattern", + "extrude", + "fillet", + "newSketch", + "shell", + "sweep" + ], + "tiers": [ + "conversion" + ], + "unsupported_capabilities": [ + "extrude_cut_through_all", + "reference_plane:plane_point" + ], + "unsupported_operations": [ + "circularPattern", + "shell", + "sweep" + ] + }, + { + "baseline": { + "conversion_status": "converted_partial", + "status": "converted_partial" + }, + "engine_atomic_ids": [ + "reference_plane" + ], + "has_candidate": true, + "sample_id": "00612529", + "selection_reasons": [ + "unsupported_capability:extrude_profile_topology:mid_cap_edge" + ], + "sketch_entities": [ + "skFitSpline" + ], + "source_operations": [ + "cPlane", + "extrude", + "loft", + "mirror", + "newSketch" + ], + "tiers": [ + "conversion" + ], + "unsupported_capabilities": [ + "extrude_profile_topology:mid_cap_edge" + ], + "unsupported_operations": [ + "loft" + ] + }, + { + "baseline": { + "conversion_status": "converted_partial", + "status": "converted_partial" + }, + "engine_atomic_ids": [ + "extrude_add_blind", + "fillet" + ], + "has_candidate": true, + "sample_id": "00694309", + "selection_reasons": [ + "unsupported_capability:extrude_extent:up_to_body" + ], + "sketch_entities": [ + "skCircle" + ], + "source_operations": [ + "extrude", + "fillet", + "newSketch" + ], + "tiers": [ + "conversion" + ], + "unsupported_capabilities": [ + "extrude_extent:up_to_body" + ], + "unsupported_operations": [] + }, + { + "baseline": { + "conversion_status": "converted_partial", + "status": "converted_partial" + }, + "engine_atomic_ids": [ + "extrude_add_blind" + ], + "has_candidate": true, + "sample_id": "00710855", + "selection_reasons": [ + "unsupported_capability:extrude_surface_or_mixed" + ], + "sketch_entities": [ + "skCircle" + ], + "source_operations": [ + "chamfer", + "extrude", + "newSketch" + ], + "tiers": [ + "conversion" + ], + "unsupported_capabilities": [ + "extrude_surface_or_mixed" + ], + "unsupported_operations": [] + }, + { + "baseline": { + "conversion_status": "converted_partial", + "status": "converted_partial" + }, + "engine_atomic_ids": [ + "extrude_add_blind", + "fillet" + ], + "has_candidate": true, + "sample_id": "00789939", + "selection_reasons": [ + "unsupported_capability:extrude_profile_topology:offset_face", + "unsupported_capability:extrude_profile_topology:swept_face" + ], + "sketch_entities": [ + "skArc", + "skCircle", + "skLineSegment", + "skPoint" + ], + "source_operations": [ + "extrude", + "fillet", + "newSketch", + "shell" + ], + "tiers": [ + "conversion" + ], + "unsupported_capabilities": [ + "extrude_profile_topology:offset_face", + "extrude_profile_topology:swept_face" + ], + "unsupported_operations": [ + "shell" + ] + }, + { + "baseline": { + "conversion_status": "converted_partial", + "status": "converted_partial" + }, + "engine_atomic_ids": [ + "extrude_add_blind" + ], + "has_candidate": true, + "sample_id": "00835610", + "selection_reasons": [ + "source_operation:booleanBodies", + "unsupported_capability:extrude_profile_topology:cap_face", + "unsupported_capability:extrude_profile_topology:intersect", + "unsupported_capability:revolve_surface", + "unsupported_operation:booleanBodies" + ], + "sketch_entities": [ + "skCircle", + "skLineSegment", + "skPoint" + ], + "source_operations": [ + "booleanBodies", + "extrude", + "newSketch", + "revolve" + ], + "tiers": [ + "conversion" + ], + "unsupported_capabilities": [ + "extrude_profile_topology:cap_face", + "extrude_profile_topology:intersect", + "revolve_surface" + ], + "unsupported_operations": [ + "booleanBodies" + ] + }, + { + "baseline": { + "conversion_status": "converted_partial", + "status": "converted_partial" + }, + "engine_atomic_ids": [ + "extrude_add_blind", + "pattern_mirror", + "reference_plane" + ], + "has_candidate": true, + "sample_id": "00925274", + "selection_reasons": [ + "engine_atomic:pattern_mirror", + "source_operation:mirror", + "unsupported_capability:extrude_extent:up_to_surface", + "unsupported_capability:reference_plane:line_angle" + ], + "sketch_entities": [ + "skCircle", + "skLineSegment" + ], + "source_operations": [ + "cPlane", + "extrude", + "mirror", + "newSketch" + ], + "tiers": [ + "conversion" + ], + "unsupported_capabilities": [ + "extrude_cut_through_all", + "extrude_extent:up_to_surface", + "reference_plane:line_angle" + ], + "unsupported_operations": [] + } + ], + "feature_inventory": { + "engine_atomic": { + "chamfer": 1011, + "extrude_add_blind": 6215, + "extrude_add_two_sided": 924, + "extrude_cut_blind": 1302, + "fillet": 2292, + "hole_wizard": 343, + "pattern_mirror": 152, + "reference_plane": 1023, + "revolve_add": 1062, + "revolve_cut": 99 + }, + "sketch_entity": { + "skArc": 2777, + "skCircle": 5556, + "skEllipse": 131, + "skFitSpline": 897, + "skLineSegment": 7962, + "skPoint": 4419 + }, + "source_operation": { + "booleanBodies": 187, + "cPlane": 1188, + "chamfer": 1432, + "circularPattern": 232, + "extrude": 8238, + "fillet": 3639, + "hole": 894, + "loft": 309, + "mirror": 411, + "newSketch": 9347, + "revolve": 1798, + "shell": 696, + "sweep": 326 + }, + "unsupported_capability": { + "extrude_add_through_all": 4, + "extrude_cut_through_all": 303, + "extrude_cut_two_sided": 262, + "extrude_extent:up_to_body": 2, + "extrude_extent:up_to_next": 49, + "extrude_extent:up_to_surface": 95, + "extrude_extent:up_to_vertex": 4, + "extrude_profile_topology:cap_edge": 133, + "extrude_profile_topology:cap_face": 200, + "extrude_profile_topology:intersect": 382, + "extrude_profile_topology:mid_cap_edge": 2, + "extrude_profile_topology:offset_face": 18, + "extrude_profile_topology:swept_edge": 16, + "extrude_profile_topology:swept_face": 99, + "extrude_surface_or_mixed": 4, + "reference_plane:curve_point": 13, + "reference_plane:line_angle": 123, + "reference_plane:line_point": 23, + "reference_plane:mid_plane": 25, + "reference_plane:plane_point": 47, + "reference_plane:three_point": 35, + "revolve_surface": 367 + }, + "unsupported_operation": { + "booleanBodies": 187, + "circularPattern": 232, + "loft": 309, + "shell": 696, + "sweep": 326 + } + }, + "schema": "cadfs_to_cdsl.regression.v1", + "selected_ids": [ + "00002243", + "00035682", + "00054089", + "00111611", + "00129362", + "00159804", + "00192744", + "00212904", + "00287955", + "00423838", + "00542223", + "00612529", + "00694309", + "00710855", + "00789939", + "00835610", + "00925274" + ], + "selected_sample_count": 17, + "source_output": "cadfs_to_cdsl/output", + "source_sample_count": 9347 +} diff --git a/cadfs_to_cdsl/reports.py b/cadfs_to_cdsl/reports.py index 53543bb9..c72ad908 100644 --- a/cadfs_to_cdsl/reports.py +++ b/cadfs_to_cdsl/reports.py @@ -184,11 +184,12 @@ def generate_markdown_report( ) unsupported_ops = { - "shell", "loft", "sweep", "draft", "thicken", "split", "booleanBodies", "circularPattern", + "shell", "sweep", "draft", "thicken", "split", "booleanBodies", "circularPattern", "moveFace", "replaceFace", "deleteFace", "import", "derive", } exact_mappings = { "extrude": "extrude_add_blind / extrude_add_two_sided / extrude_cut_blind", + "loft": "loft_add (simple closed sketch profiles only)", "revolve": "revolve_add / revolve_cut", "fillet": "fillet", "chamfer": "chamfer", diff --git a/cadfs_to_cdsl/selector_binding.py b/cadfs_to_cdsl/selector_binding.py index 93ab6ef0..7baafa3c 100644 --- a/cadfs_to_cdsl/selector_binding.py +++ b/cadfs_to_cdsl/selector_binding.py @@ -14,6 +14,12 @@ def _score(expected: dict[str, Any], actual: dict[str, Any]) -> float | None: left, right = expected[key], actual.get(key) if not isinstance(right, (list, tuple)) or len(left) != len(right): return None delta = math.sqrt(sum((float(a) - float(b)) ** 2 for a, b in zip(left, right))) + if key == "normal": + # A source CAP_FACE identifies a geometric plane, not the OCC + # orientation of the resulting face. The two kernels may + # report the same cap with inverse normals, especially for a + # start cap. Keep axis direction orientation-sensitive. + delta = min(delta, math.sqrt(sum((float(a) + float(b)) ** 2 for a, b in zip(left, right)))) scores.append(max(0.0, 1.0 - delta / 0.05)) for key in ("radius_mm", "plane_offset_mm"): if key in expected: @@ -75,7 +81,16 @@ def bind_candidate_selectors(cdsl: dict[str, Any]) -> tuple[dict[str, Any], list if not prefix["features"]: raise ValueError(f"{feature['id']}: selector has no executable prefix") report = rebuild_cdsl(prefix, Path(temporary) / f"prefix-{index}.step", strict=True) body_id = next((item.get("body_id") for item in reversed(report.get("feature_results") or []) if item.get("body_id")), None) - records = [item for item in report.get("topology_records") or [] if not body_id or item.get("body_id") == body_id or str(item.get("body_id") or "").startswith(f"{body_id}:")] + records = [ + item for item in report.get("topology_records") or [] + # Reference planes and axes are session context, not body + # topology. They must remain available while binding a mirror + # or extent selector against a body-bearing prefix. + if item.get("kind") in {"plane", "axis"} + or not body_id + or item.get("body_id") == body_id + or str(item.get("body_id") or "").startswith(f"{body_id}:") + ] resolved = [] for placeholder in placeholders: geometry = placeholder.get("geometry") or {} @@ -85,14 +100,21 @@ def bind_candidate_selectors(cdsl: dict[str, Any]) -> tuple[dict[str, Any], list owner = placeholder.get("owner_feature_id") owner_matches = [record for record in same_kind if owner in (record.get("owner_feature_ids") or [record.get("feature_id")])] pool = owner_matches or same_kind - if not geometry and len(pool) != 1: - raise ValueError(f"{feature['id']}: selector_ambiguous after prefix rebuild") - scored = [(score, record) for record in pool if (score := _score(geometry, record.get("geometry") or {})) is not None and score >= 0.8] - scored.sort(key=lambda value: (-value[0], str(value[1].get("record_id")))) - if scored: - if len(scored) > 1 and abs(scored[0][0] - scored[1][0]) <= 1e-9: + if not geometry: + # Context selectors (notably a generated mirror plane) + # may have no geometric snapshot. Their owner-qualified + # singleton identity is sufficient and must not be scored + # as a zero-information geometric match. + if len(pool) != 1: raise ValueError(f"{feature['id']}: selector_ambiguous after prefix rebuild") - candidates = [scored[0][1]] + candidates = [pool[0]] + else: + scored = [(score, record) for record in pool if (score := _score(geometry, record.get("geometry") or {})) is not None and score >= 0.8] + scored.sort(key=lambda value: (-value[0], str(value[1].get("record_id")))) + if scored: + if len(scored) > 1 and abs(scored[0][0] - scored[1][0]) <= 1e-9: + raise ValueError(f"{feature['id']}: selector_ambiguous after prefix rebuild") + candidates = [scored[0][1]] if not candidates: raise ValueError(f"{feature['id']}: selector_not_found after prefix rebuild") for record in candidates: owners = record.get("owner_feature_ids") or [record.get("feature_id")] diff --git a/cadfs_to_cdsl/tests/test_describe.py b/cadfs_to_cdsl/tests/test_describe.py new file mode 100644 index 00000000..e229d29d --- /dev/null +++ b/cadfs_to_cdsl/tests/test_describe.py @@ -0,0 +1,147 @@ +from __future__ import annotations + +import json +import tempfile +import unittest +from pathlib import Path + +from cadfs_to_cdsl.describe import describe_samples, select_description_samples + + +SAMPLE_FS = r''' +FeatureScript 1511; +import(path : "onshape/std/geometry.fs", version : "1511.0"); +const mm = millimeter; +const FACE = EntityType.FACE; +function v(x, y){return vector(x, y);} +function sQuery(a, b, c) {return sketchEntityQuery(a, b, c);} +export const myFeature = defineFeature(function(context is Context, id is Id, definition is map) + precondition{} + { + { + var Q0; + Q0=qCreatedBy(makeId("Top.planeOp"),FACE); + var sketch = newSketch(context, id + "F0", { "sketchPlane" : qUnion([Q0])}); + skLineSegment(sketch, "E0", {"start": v(-269.41, -156.6) * mm, "end": v(-0.92, 311.62) * mm}); + skLineSegment(sketch, "E1", {"start": v(-0.92, 311.62) * mm, "end": v(270.33, -155.02) * mm}); + skLineSegment(sketch, "E2", {"start": v(270.33, -155.02) * mm, "end": v(-269.41, -156.6) * mm}); + skSolve(sketch); + } + { + var Q0; + Q0 = qSketchRegion(id + "F0", true); + extrude(context, id + "F1", {"entities" : qUnion([Q0]), "depth" : 1828.8 * mm}); + } + }); +''' + + +SAMPLE_ANNOTATION = """Step 1 - Sketch +Draw a closed triangle. + +Step 2 - Extrude NEW +Extrude the triangular area upward a distance of 1828.8 mm. +""" + + +class FailingVision: + def describe(self, *, sample_id: str, image_path: Path, local_facts: dict[str, object]) -> dict[str, object]: + raise RuntimeError("vision offline") + + +class StaticVision: + def describe(self, *, sample_id: str, image_path: Path, local_facts: dict[str, object]) -> dict[str, object]: + return { + "category": "视觉增强三角楔块(候选)", + "category_confidence": 0.7, + "candidate_names": ["视觉楔块"], + "summary_zh": "视觉模型认为该模型是三角楔块候选件。", + "possible_functions": ["可能用于定位。"], + "applications": ["夹具。"], + "structural_features": ["斜面"], + "geometric_features": ["三角截面"], + "keywords_zh": ["视觉识别"], + "keywords_en": ["vision wedge"], + "uncertainties": ["视觉语义仍需人工确认。"], + } + + +class DescribeTests(unittest.TestCase): + def test_selects_only_requested_shard(self): + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + self._write_sample(root, "0005", "00050089") + self._write_sample(root, "0006", "00060001") + + selected = select_description_samples(root, shard="0005") + + self.assertEqual([sample.sample_id for sample in selected], ["00050089"]) + + def test_hybrid_falls_back_and_writes_txt_manifest(self): + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + self._write_sample(root, "0005", "00050089") + + records, summary = describe_samples(root, shard="0005", mode="hybrid", vision_client=FailingVision()) + + self.assertEqual(summary["sample_count"], 1) + self.assertEqual(summary["statuses"], {"local_fallback": 1}) + txt_path = root / "description_txt/0005/00050089.txt" + self.assertTrue(txt_path.is_file()) + text = txt_path.read_text(encoding="utf-8") + self.assertIn("样本ID:00050089", text) + self.assertIn("三角棱柱", text) + self.assertIn("证据与不确定性", text) + manifest = root / "description_txt/description_manifest.jsonl" + rows = [json.loads(line) for line in manifest.read_text(encoding="utf-8").splitlines()] + self.assertEqual(len(rows), 1) + self.assertEqual(rows[0]["sample_id"], "00050089") + self.assertEqual(rows[0]["status"], "local_fallback") + self.assertIn("vision_failed", [item["code"] for item in records[0]["diagnostics"]]) + + def test_hybrid_uses_vision_when_available(self): + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + self._write_sample(root, "0005", "00050089") + + records, _summary = describe_samples(root, shard="0005", mode="hybrid", vision_client=StaticVision()) + + self.assertEqual(records[0]["status"], "described_hybrid") + self.assertEqual(records[0]["category"], "视觉增强三角楔块(候选)") + self.assertIn("视觉楔块", records[0]["candidate_names"]) + + def test_existing_txt_is_skipped_without_force(self): + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + self._write_sample(root, "0005", "00050089") + first, _ = describe_samples(root, shard="0005", mode="local") + txt_path = root / "description_txt/0005/00050089.txt" + before = txt_path.read_text(encoding="utf-8") + + second, _ = describe_samples(root, shard="0005", mode="local") + + self.assertEqual(second[0]["status"], first[0]["status"]) + self.assertEqual(txt_path.read_text(encoding="utf-8"), before) + + @staticmethod + def _write_sample(root: Path, shard: str, sample_id: str) -> None: + for directory, suffix, content in ( + ("featurescript_rp", ".txt", SAMPLE_FS), + ("text_annotations", ".txt", SAMPLE_ANNOTATION), + ("step_abc", ".step", "ISO-10303-21;\nEND-ISO-10303-21;\n"), + ("stl_abc", ".stl", "solid sample\nendsolid sample\n"), + ): + path = root / directory / shard / f"{sample_id}{suffix}" + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(content, encoding="utf-8") + image = root / "multiview_images_abc" / shard / f"{sample_id}.png" + image.parent.mkdir(parents=True, exist_ok=True) + image.write_bytes( + b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR" + b"\x00\x00\x00\x01\x00\x00\x00\x01\x08\x02\x00\x00\x00" + b"\x90wS\xde\x00\x00\x00\x00IEND\xaeB`\x82" + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/cadfs_to_cdsl/tests/test_lowering.py b/cadfs_to_cdsl/tests/test_lowering.py index 6f958a82..6ca5cc11 100644 --- a/cadfs_to_cdsl/tests/test_lowering.py +++ b/cadfs_to_cdsl/tests/test_lowering.py @@ -7,7 +7,7 @@ from cadfs_to_cdsl.lowering import lower_model from cadfs_to_cdsl.pipeline import convert_one from cadfs_to_cdsl.dataset import Sample from cadfs_to_cdsl.dataset import scan_dataset -from cadfs_to_cdsl.tests.test_parser import SOURCE +from cadfs_to_cdsl.tests.test_parser import SOURCE, TRANSFORM_SOURCE class LoweringTests(unittest.TestCase): @@ -30,12 +30,61 @@ class LoweringTests(unittest.TestCase): self.assertIsNone(result.cdsl) self.assertEqual(result.diagnostics[0]["code"], "unsupported_operation") - def test_symmetric_cut_is_not_disguised_as_blind_cut(self): + def test_direct_translation_transform_is_baked_into_the_source_feature(self): + result = lower_model(parse_featurescript(TRANSFORM_SOURCE, "transform"), {}) + self.assertEqual(result.status, "converted_complete") + self.assertEqual([item["id"] for item in result.cdsl["features"]], ["f_F1"]) + self.assertEqual(result.history[-1]["feature_id"], "F2") + self.assertFalse(result.diagnostics) + self.assertEqual(result.cdsl["geometry"]["sketches"][0]["workplane"]["origin_mm"], [10.0, 0.0, 0.0]) + + def test_copy_transform_remains_an_explicit_engine_capability_gap(self): + source = TRANSFORM_SOURCE.replace('"makeCopy":false', '"makeCopy":true') + result = lower_model(parse_featurescript(source, "transform-copy"), {}) + self.assertEqual(result.status, "converted_partial") + self.assertEqual(result.diagnostics[-1]["code"], "unsupported_engine_capability") + self.assertEqual(result.diagnostics[-1]["capability"], "transform") + + def test_symmetric_cut_lowers_to_two_sided_cut(self): source = SOURCE.replace('"depth":120 * mm', '"operationType":NewBodyOperationType.REMOVE, "depth":120 * mm, "symmetric":true') result = lower_model(parse_featurescript(source, "symmetric-cut"), {}) - self.assertIsNone(result.cdsl) - self.assertEqual(result.diagnostics[0]["code"], "unsupported_engine_capability") - self.assertEqual(result.diagnostics[0]["capability"], "extrude_cut_two_sided") + self.assertEqual(result.status, "converted_complete") + feature = result.cdsl["features"][0] + self.assertEqual(feature["atomic_id"], "extrude_cut_two_sided") + self.assertEqual(feature["params"]["distance_mm"], 60.0) + self.assertEqual(feature["params"]["reverse_distance_mm"], 60.0) + + def test_through_all_extrudes_reuse_the_engine_extent_contract(self): + for operation_type, expected_atomic in [ + ("NewBodyOperationType.NEW", "extrude_add_blind"), + ("NewBodyOperationType.REMOVE", "extrude_cut_blind"), + ]: + with self.subTest(operation_type=operation_type): + source = SOURCE.replace( + '"depth":120 * mm', + f'"operationType":{operation_type}, "depth":120 * mm, "endBound":BoundingType.THROUGH_ALL', + ) + result = lower_model(parse_featurescript(source, f"through-all-{operation_type}"), {}) + self.assertEqual(result.status, "converted_complete") + feature = result.cdsl["features"][0] + self.assertEqual(feature["atomic_id"], expected_atomic) + self.assertEqual(feature["params"]["end_condition"]["type"], "through_all") + + def test_up_to_next_extrudes_reuse_the_engine_extent_contract(self): + for operation_type, expected_atomic in [ + ("NewBodyOperationType.NEW", "extrude_add_blind"), + ("NewBodyOperationType.REMOVE", "extrude_cut_blind"), + ]: + with self.subTest(operation_type=operation_type): + source = SOURCE.replace( + '"depth":120 * mm', + f'"operationType":{operation_type}, "depth":120 * mm, "endBound":BoundingType.UP_TO_NEXT', + ) + result = lower_model(parse_featurescript(source, f"up-to-next-{operation_type}"), {}) + self.assertEqual(result.status, "converted_complete") + feature = result.cdsl["features"][0] + self.assertEqual(feature["atomic_id"], expected_atomic) + self.assertEqual(feature["params"]["end_condition"]["type"], "through_next") def test_open_nonconstruction_geometry_is_not_silently_dropped(self): source = SOURCE.replace('skSolve(sketch);', 'skLineSegment(sketch, "open", {"start":v(0, 0) * mm, "end":v(20, 0) * mm}); skSolve(sketch);', 1) @@ -53,6 +102,83 @@ class LoweringTests(unittest.TestCase): self.assertEqual(gap["capability"], "revolve_surface") self.assertIsNone(result.cdsl) + def test_fit_spline_loft_lowers_to_executable_loft_add(self): + root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test" + feature = root / "featurescript_rp/0061/00612529.txt" + if not feature.exists(): self.skipTest("CADFS sample is not installed") + result = lower_model(parse_featurescript(feature.read_text(), "00612529"), {}) + self.assertIsNotNone(result.cdsl) + features = {item["id"]: item for item in result.cdsl["features"]} + self.assertEqual(features["f_F3"]["atomic_id"], "loft_add") + self.assertEqual(features["f_F3"]["params"]["profile_sketch_ids"], ["sketch_F2", "sketch_F0"]) + self.assertEqual(features["f_F4"]["atomic_id"], "pattern_mirror") + self.assertEqual(features["f_F4"]["params"]["source_feature_ids"], ["f_F3"]) + sketches = {item["id"]: item for item in result.cdsl["geometry"]["sketches"]} + self.assertEqual(sketches["sketch_F0"]["profile"]["contours"][0]["segments"][0]["type"], "bspline") + self.assertNotIn("loft", {item.get("operation") for item in result.diagnostics}) + + def test_fit_spline_loft_candidate_rebuilds(self): + root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test" + feature = root / "featurescript_rp/0061/00612529.txt" + if not feature.exists(): self.skipTest("CADFS sample is not installed") + result = lower_model(parse_featurescript(feature.read_text(), "00612529"), {}) + with tempfile.TemporaryDirectory() as tmp: + from cadfs_to_cdsl.rebuild import rebuild_candidate + rebuilt = rebuild_candidate(result.cdsl, Path(tmp) / "rebuilt.step") + self.assertEqual(rebuilt["status"], "rebuilt") + self.assertGreater(rebuilt["result"]["volume_mm3"], 0) + + def test_direct_translation_transform_updates_the_source_revolve(self): + root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test" + feature = root / "featurescript_rp/0011/00111611.txt" + if not feature.exists(): self.skipTest("CADFS sample is not installed") + result = lower_model(parse_featurescript(feature.read_text(), "00111611"), {}) + self.assertNotIn("F10", {item.get("feature_id") for item in result.diagnostics}) + features = {item["id"]: item for item in result.cdsl["features"]} + sketches = {item["id"]: item for item in result.cdsl["geometry"]["sketches"]} + self.assertEqual(sketches[features["f_F9"]["sketch_id"]]["workplane"]["origin_mm"], [77.16, -11.67, -63.0]) + for actual, expected in zip(features["f_F9"]["params"]["axis"]["origin_mm"], [0.13, -11.67, 56.11]): + self.assertAlmostEqual(actual, expected) + + def test_circular_pattern_lowers_to_existing_engine_contract(self): + root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test" + feature = root / "featurescript_rp/0042/00423838.txt" + if not feature.exists(): self.skipTest("CADFS sample is not installed") + result = lower_model(parse_featurescript(feature.read_text(), "00423838"), {}) + self.assertIsNotNone(result.cdsl) + features = {item["id"]: item for item in result.cdsl["features"]} + pattern = features["f_F4"] + self.assertEqual(pattern["atomic_id"], "pattern_circular") + self.assertEqual(pattern["params"]["source_feature_ids"], ["f_F1"]) + self.assertEqual(pattern["params"]["pattern_count"], 6) + self.assertEqual(pattern["params"]["sweep_angle_deg"], 360.0) + self.assertEqual(pattern["params"]["axis"]["direction"], [0.0, 0.0, -1.0]) + self.assertNotIn("F2", {item.get("feature_id") for item in result.diagnostics}) + sketches = {item["id"]: item for item in result.cdsl["geometry"]["sketches"]} + self.assertNotEqual(sketches["sketch_F0"]["workplane"]["normal"], [0.0, 0.0, 1.0]) + self.assertNotIn("circularPattern", {item.get("operation") for item in result.diagnostics}) + + def test_reference_plane_variants_lower_to_explicit_frames(self): + root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test" + cases = { + "00159804": {"F3", "F5"}, + "00192744": {"F3"}, + "00212904": {"F3"}, + "00542223": {"F3"}, + } + for sample_id, expected in cases.items(): + with self.subTest(sample_id=sample_id): + feature = root / "featurescript_rp" / sample_id[:4] / f"{sample_id}.txt" + if not feature.exists(): self.skipTest("CADFS sample is not installed") + result = lower_model(parse_featurescript(feature.read_text(), sample_id), {}) + features = {item["name"]: item for item in result.cdsl["features"]} + for feature_id in expected: + plane = features[feature_id] + self.assertEqual(plane["atomic_id"], "reference_plane") + self.assertEqual(set(plane["params"]["plane"]), {"origin_mm", "x_dir", "normal"}) + diagnostics = [item for item in result.diagnostics if item.get("operation") == "cPlane"] + self.assertFalse(diagnostics) + def test_feature_face_profile_is_not_reused_as_original_sketch(self): source = SOURCE.replace('qSketchRegion(id + "F0", true)', 'makeQuery(id+"F1.opExtrude","CAP_FACE",FACE,{"isStart":false})') result = lower_model(parse_featurescript(source, "face-profile"), {}) diff --git a/cadfs_to_cdsl/tests/test_parser.py b/cadfs_to_cdsl/tests/test_parser.py index a420fd82..127197ad 100644 --- a/cadfs_to_cdsl/tests/test_parser.py +++ b/cadfs_to_cdsl/tests/test_parser.py @@ -19,6 +19,14 @@ export const f = defineFeature(function(context, id, definition) { }); ''' +TRANSFORM_SOURCE = SOURCE.replace( + '\n});\n', + ''' + transform(context, id + "F2", {"entities":qCreatedBy(id + "F1", BODY), "transformType":TransformType.TRANSLATION_3D, "dx":10 * mm, "dy":0 * mm, "dz":0 * mm, "makeCopy":false}); +}); +''', +) + class ParserTests(unittest.TestCase): def test_lexer_ignores_comments_and_preserves_lines(self): @@ -32,6 +40,12 @@ class ParserTests(unittest.TestCase): self.assertEqual(model.sketches[0].entities[0].operation, "skCircle") self.assertIsInstance(model.features[0].params["entities"], Call) + def test_transform_is_preserved_as_a_feature(self): + model = parse_featurescript(TRANSFORM_SOURCE, "transform") + self.assertEqual([step.feature_id for step in model.steps], ["F0", "F1", "F2"]) + self.assertEqual(model.features[-1].operation, "transform") + self.assertEqual(model.features[-1].params["transformType"], "TransformType.TRANSLATION_3D") + def test_query_parser(self): query = Call("makeQuery", [Call("__binary__", ["id", "+", "F1.opExtrude"]), "CAP_EDGE", "EDGE", {"isStart": False, "x": Call("sQuery", [Call("__binary__", ["id", "+", "F0.wireOp"]), "EDGE", "E0"])}]) value = parse_query(query) diff --git a/cadfs_to_cdsl/tests/test_regression.py b/cadfs_to_cdsl/tests/test_regression.py new file mode 100644 index 00000000..6986efa3 --- /dev/null +++ b/cadfs_to_cdsl/tests/test_regression.py @@ -0,0 +1,73 @@ +from __future__ import annotations + +import json +import tempfile +import unittest +from pathlib import Path + +from cadfs_to_cdsl.regression import build_regression_manifest, regression_sample_ids + + +class RegressionSelectionTests(unittest.TestCase): + def _sample( + self, + root: Path, + sample_id: str, + *, + operation: str, + entity: str | None = None, + atomic: str | None = None, + capability: str | None = None, + rebuilt: bool = False, + ) -> None: + directory = root / "samples" / sample_id + directory.mkdir(parents=True) + history = [{"operation": operation}] + if entity: + history[0]["entities"] = [{"operation": entity}] + (directory / "history.json").write_text(json.dumps(history), encoding="utf-8") + if atomic: + (directory / "candidate.cdsl.json").write_text(json.dumps({"features": [{"atomic_id": atomic}]}), encoding="utf-8") + diagnostics = [] if not capability else [{"code": "unsupported_engine_capability", "capability": capability}] + (directory / "diagnostics.json").write_text(json.dumps(diagnostics), encoding="utf-8") + status = {"status": "rebuilt_approximate" if rebuilt else "converted_partial"} + if rebuilt: + status["rebuild_status"] = "rebuilt" + (directory / "status.json").write_text(json.dumps(status), encoding="utf-8") + + def test_manifest_covers_every_observed_signal_and_keeps_engine_pool_buildable(self) -> None: + with tempfile.TemporaryDirectory() as temporary: + output = Path(temporary) + self._sample(output, "00000001", operation="extrude", entity="skLineSegment", atomic="extrude_add_blind", rebuilt=True) + self._sample(output, "00000002", operation="loft", entity="skFitSpline", capability="loft") + self._sample(output, "00000003", operation="revolve", entity="skCircle", atomic="revolve_add", rebuilt=True) + + manifest = build_regression_manifest(output) + + self.assertEqual(manifest["selected_ids"], ["00000001", "00000002", "00000003"]) + self.assertEqual(set(manifest["coverage"]), { + "engine_atomic:extrude_add_blind", "engine_atomic:revolve_add", + "sketch_entity:skCircle", "sketch_entity:skFitSpline", "sketch_entity:skLineSegment", + "source_operation:extrude", "source_operation:loft", "source_operation:revolve", + "unsupported_capability:loft", + }) + self.assertEqual(regression_sample_ids_from_manifest(manifest, "engine"), ["00000001", "00000003"]) + self.assertEqual(manifest["engine_baseline_atomic_ids"], ["extrude_add_blind", "revolve_add"]) + self.assertEqual(manifest["engine_diagnostic_only_atomic_ids"], []) + + def test_manifest_reader_rejects_unknown_tier(self) -> None: + with tempfile.TemporaryDirectory() as temporary: + root = Path(temporary) + self._sample(root, "00000001", operation="extrude", atomic="extrude_add_blind", rebuilt=True) + manifest_path = root / "manifest.json" + manifest_path.write_text(json.dumps(build_regression_manifest(root)), encoding="utf-8") + self.assertEqual(regression_sample_ids(manifest_path, "engine"), ["00000001"]) + with self.assertRaisesRegex(ValueError, "Unknown regression tier"): + regression_sample_ids(manifest_path, "not-a-tier") + + +def regression_sample_ids_from_manifest(manifest: dict, tier: str) -> list[str]: + return sorted( + entry["sample_id"] for entry in manifest["entries"] + if tier == "all" or tier in entry["tiers"] + ) diff --git a/cadfs_to_cdsl/tests/test_selector_binding.py b/cadfs_to_cdsl/tests/test_selector_binding.py new file mode 100644 index 00000000..33fc7ca2 --- /dev/null +++ b/cadfs_to_cdsl/tests/test_selector_binding.py @@ -0,0 +1,24 @@ +from __future__ import annotations + +import unittest + +from cadfs_to_cdsl.selector_binding import _score + + +class SelectorBindingTests(unittest.TestCase): + def test_face_normal_match_is_orientation_independent(self) -> None: + score = _score( + {"normal": [0.0, 0.0, 1.0], "plane_offset_mm": 12.0}, + {"normal": [0.0, 0.0, -1.0], "plane_offset_mm": 12.0}, + ) + self.assertEqual(score, 1.0) + + def test_axis_direction_remains_orientation_sensitive(self) -> None: + score = _score( + {"axis_direction": [0.0, 0.0, 1.0]}, + {"axis_direction": [0.0, 0.0, -1.0]}, + ) + self.assertEqual(score, 0.0) + + def test_empty_snapshot_score_is_not_treated_as_a_match(self) -> None: + self.assertEqual(_score({}, {"normal": [0.0, 0.0, 1.0]}), 0.0) diff --git a/outputs/cadfs_descriptions_0005/cadfs_0005_模型描述.xlsx b/outputs/cadfs_descriptions_0005/cadfs_0005_模型描述.xlsx new file mode 100644 index 00000000..5ccad3bd Binary files /dev/null and b/outputs/cadfs_descriptions_0005/cadfs_0005_模型描述.xlsx differ diff --git a/outputs/cadfs_descriptions_0005/cadfs_0005_模型描述.xlsx.inspect.ndjson b/outputs/cadfs_descriptions_0005/cadfs_0005_模型描述.xlsx.inspect.ndjson new file mode 100644 index 00000000..71243cc4 --- /dev/null +++ b/outputs/cadfs_descriptions_0005/cadfs_0005_模型描述.xlsx.inspect.ndjson @@ -0,0 +1,457 @@ +{"kind":"workbook","id":"wb/5ttgnb","sheets":1,"tables":1} +{"kind":"sheet","id":"ws/pv6qtc","name":"模型描述","index":0,"range":"A1:E93","address":"A1:E93","tables":1,"formulas":1} +{"kind":"table","sheet":"模型描述","address":"A1:E93","rows":93,"cols":5,"values":[["CADFS 0005 模型识别描述索引",null,null,null,null],["样本总数",88,"数据范围","CADFS_test / 0005",null],["说明:分类与用途依据多视图图像及原始建模标注整理;具体产品身份在缺少装配上下文时作为检索候选。",null,null,null,null],[null,null,null,null,null],["样本 ID","分类","模型描述","用途与检索关键词","描述文件路径"],["00050089","长条三角棱柱/楔形结构件。","由三角形截面沿长轴拉伸形成,具有两条长斜面和三角端面,是典型的楔形梁或三角截面导轨几何。","可作为夹具垫楔、导向条、斜面支撑或定位块;适用于治具、机械支撑和滑动导向位置。关键词:楔形梁、三角棱柱、斜面、长条拉伸、导轨。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00050089.txt"],["00050300","带贯通槽的立方开槽块。","方形实体上形成贯通的 U 形开口、两条矩形槽和字母形穿孔,内部留有连续通道与台阶边界。","可作为插接、避让、导向或编码识别块,常见于夹具、拼装件和教学模型。关键词:开槽块、U 形通道、贯通孔、矩形槽、方块。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00050300.txt"],["00050521","圆柱基座与弧形薄壁支架组合件。","主体由大圆柱、带圆弧轮廓的薄壁壳体和三角形连接肋组成,壳体内部另有小圆柱凸台。","可作为旋转件护罩、圆柱件安装座或带加强肋的支撑件;适用于设备外壳、支架和机械连接部位。关键词:圆柱基座、薄壁壳、弧形支架、三角肋、凸台。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00050521.txt"],["00050568","梳齿式薄板连接件/多槽插接板。","两片薄板通过薄桥连接,上层板边缘排列五个等距开槽,形成梳齿状的插接或排线定位结构。","可用于扁平线缆梳理、插片定位、端子排或薄板卡扣;常位于电子装配和小型连接结构中。关键词:梳齿板、五槽、插接、排线定位、薄板连接件。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00050568.txt"],["00050596","多孔薄板安装面板。","长方形薄板上布置四个小圆孔、两个矩形开口、一个边缘狭槽以及局部下凸台,板面为主要安装基准。","可作为设备面板、PCB 固定板、转接板或小型盖板;用于螺钉固定、插口让位和元件定位。关键词:安装面板、四孔板、矩形开口、狭槽、薄板凸台。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00050596.txt"],["00050633","带孔方形铭牌/小型盖板。","方形薄板带内凹矩形台阶、圆形贯通孔和浅刻字母 s,内凹四角带圆角过渡。","可作为标识牌、按钮盖、传感器小盖板或固定垫片;适合面板标记和单点螺钉安装。关键词:方形盖板、刻字、沉槽、圆孔、铭牌。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00050633.txt"],["00051018","双法兰旋转旋钮/线轴形圆盘件。","同轴圆盘和圆筒经完整旋转生成,外缘呈圆润鼓形,中心存在轴向端面,整体接近线轴或旋钮外形。","可作为手拧旋钮、卷线轴、压轮或装饰性端盖;用于轴系、仪器面板和轻载传动位置。关键词:旋钮、线轴、双圆盘、同轴回转体、圆缘。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00051018.txt"],["00051029","细长阶梯轴/定位销。","零件以回转方式形成细长杆身,端部具有小圆盘止挡、局部加粗段和渐缩尖端,轴向比例很大。","可作为定位销、推杆、针状执行件或小型轴杆,常位于夹具、阀件和传动机构内。关键词:细长轴、定位销、止挡盘、阶梯杆、锥尖。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00051029.txt"],["00051031","圆形至椭圆形过渡风管/薄壁转接罩。","底部圆形轮廓与顶部椭圆轮廓经放样连接,端面开口并抽壳,构成截面变化的薄壁过渡体。","可作为通风风道、吸排气转接罩或异形管路接头;用于风机、罩壳和流体管路接口。关键词:圆椭圆过渡、放样、薄壁风管、转接罩、开口壳体。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00051031.txt"],["00051046","圆形密封圈/O 形圈。","由小圆截面绕圆周回转形成的闭合圆环,截面均匀、无安装孔和附加特征。","用于两圆柱面或端面间的密封、缓冲和预紧,常见于液压、气动、管路和机壳配合。关键词:O 形圈、密封圈、圆环、环形垫圈、回转体。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00051046.txt"],["00051065","带法兰的锥形套筒/喷嘴体。","长锥形圆筒底端带圆形法兰,轴线方向设有深孔,外壁还可见两处小孔和倒角过渡。","可作为管嘴、导向套、锥形衬套或带底座的空心接头;用于流体接口、定位和轴套安装。关键词:锥形套筒、法兰、轴向孔、喷嘴、衬套。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00051065.txt"],["00051194","圆角双孔安装板。","长方形板的四条竖边圆角处理,板面有一个贯通圆孔和一个浅矩形沉槽,边缘平直且厚度均匀。","可作为固定板、压板、连接片或面板衬板;用于螺钉连接、定位和局部让位。关键词:圆角板、安装孔、矩形沉槽、压板、连接片。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00051194.txt"],["00051308","开口式梯形壳体/料斗形罩。","矩形底部向上收分并带倒角,顶部为敞开的矩形口,壁厚由抽壳操作形成。","可作为小型料斗、收纳罩、异形导流罩或设备外壳;常用于物料导入、罩壳和容器部位。关键词:料斗、梯形壳体、开口盒、抽壳、收分侧壁。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00051308.txt"],["00051345","四孔圆法兰盘/轮毂盖。","圆盘中心有较大轴孔,外圈均布四个小安装孔,盘面带同轴台阶圆环和下部圆柱颈。","可作为轴承座盖、法兰接头、轮毂端盖或电机安装盘;用于轴向定位与螺钉固定。关键词:圆法兰、四孔、中心轴孔、轮毂、台阶圆盘。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00051345.txt"],["00051361","瓶形回转容器/变径管接头。","由圆弧轮廓回转生成,主体为鼓形大腔体,连接一端较细的圆管颈,外形类似瓶体或变径接头。","可作为储液小壳、消声腔、管路扩缩接头或容器端部;用于流体和真空系统。关键词:瓶形容器、变径接头、鼓形壳、圆管颈、回转体。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00051361.txt"],["00051479","三角开口挂架/带销支撑框。","圆角三角形框架中保留大开口,内部伸出短圆柱销,外环为加厚的连续边框。","可作为挂钩、吊挂扣、轻型支架或连接环,常用于手柄、固定带和装配定位。关键词:三角框、挂架、开口环、圆柱销、圆角支撑。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00051479.txt"],["00051481","锯齿形三角薄板/齿状挡片。","薄板为三角楔形轮廓,一侧排列多个等距尖齿,中心附近有圆孔和小三角切口。","可作为齿形挡片、定位齿板、棘爪配合件或装饰性切齿板;适用于夹具和防滑定位结构。关键词:锯齿板、三角薄板、尖齿、圆孔、棘齿。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00051481.txt"],["00051494","圆头阶梯销/短轴。","小直径杆身一端连接较大圆头,肩部以圆角过渡,整体为无孔的同轴回转实体。","可作为铆钉、定位销、按钮轴或小型止挡件;用于装配定位和轴向限位。关键词:圆头销、阶梯轴、肩部、短轴、定位件。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00051494.txt"],["00051608","阶梯法兰衬套/带通孔轮毂。","多个同轴圆柱台阶组成短轮毂,端面中心有贯通孔,外缘形成明显的法兰和环形肩部。","可作为轴套、隔套、轮毂衬套或小型轴承座;用于导向、间隔和轴向定位。关键词:法兰衬套、阶梯圆筒、中心通孔、轮毂、隔套。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00051608.txt"],["00051957","球头端盖/圆顶插塞。","圆形法兰端连接半球状圆顶,法兰内有较大开口,壳体为回转曲面并带台阶环。","可作为球头保护盖、圆顶塞、喷嘴端帽或装饰端盖;用于封闭、导流和端部保护。关键词:球头端盖、圆顶、法兰、开口端、插塞。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00051957.txt"],["00051960","吸盘形回转件/柔性杯形端盖。","轴对称曲线回转形成浅杯状圆盘,中心带小锥状凸起,外缘具有柔和的环形曲面。","可作为吸盘、柔性端盖、膜片或压紧杯;用于临时吸附、密封和缓冲接触。关键词:吸盘、杯形圆盘、中心锥、膜片、回转曲面。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00051960.txt"],["00051986","平垫圈。","薄的同心圆环,外圆与中心通孔同轴,厚度很小且无其他附加结构。","用于螺钉、螺栓或轴肩下分散压强、调整间隙和保护接触面。关键词:平垫圈、圆环、中心孔、间隔片、紧固件。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00051986.txt"],["00052113","文字标识条/定制铭牌。","细长矩形基条与 ANDREW 文字轮廓共同构成,文字为主要视觉和检索特征,整体为薄板拉伸实体。","可作为设备姓名牌、标签、面板字标或定制标识;用于产品识别和外观标注。关键词:文字铭牌、ANDREW、标识条、字母、定制面板。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00052113.txt"],["00052156","圆角矩形外壳/盒盖。","矩形盒体经过大圆角与圆滑过渡处理,形成封闭或近封闭的厚壁壳状轮廓,外形接近保护罩。","可作为电子外壳、仪器盖、保护盒或缓冲罩;用于封装、遮盖和防碰撞。关键词:圆角外壳、盒盖、圆滑矩形、保护罩、壳体。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00052156.txt"],["00052401","圆角斜面块/楔形实体。","由矩形区域拉伸得到的厚实斜面块,一条长边经过大圆角处理,端面呈不对称梯形或楔形。","可作为支撑楔、导向块、压块或防撞垫;用于夹具、导轨和机械结构的斜面承托。关键词:楔块、斜面块、圆角、梯形实体、支撑件。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00052401.txt"],["00052535","超长薄壁型材/线性导轨条。","横截面较窄的长条实体沿单一直线延伸,端部带台阶轮廓与小圆角,长度远大于截面尺寸。","可作为导轨、装饰压条、密封压条或框架型材;用于建筑收边、设备框架和线性支撑。关键词:长型材、导轨条、薄壁、线性拉伸、压条。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00052535.txt"],["00052598","双孔圆角连接块。","厚实矩形块的四个纵向边圆角化,正面并列两个较大的贯通圆孔,孔轴相互平行。","可作为双螺栓连接块、轴孔座或夹具定位块;用于板件连接、双杆安装和机械固定。关键词:双孔块、圆角连接块、贯通孔、安装座、定位块。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00052598.txt"],["00052681","长圆柱开槽杆/槽形管。","超长圆柱主体沿轴向布置六个分散的矩形切槽,整体比例接近细长管杆或长导向件。","可作为通气杆、长行程导向轴、开槽管或遮罩型材;用于大型设备和线性机构。关键词:长圆杆、轴向槽、开槽管、细长件、导向轴。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00052681.txt"],["00052743","半圆弯管/弧形把手。","圆截面沿约半圆路径回转形成开口弧形杆,两端平齐,曲率连续且杆径恒定。","可作为提手、护栏、弯管、吊环或线缆导向弯;常用于门把、夹具和管路。关键词:半圆弯杆、弧形把手、圆管、U 弯、吊环。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00052743.txt"],["00052858","带孔斜壁箱体/设备壳体。","大矩形箱体一侧延伸出带拔模的斜壁,局部抽壳和底部切除形成空腔,多个圆孔分布于侧板。","可作为电气盒、控制器外壳、支架箱或设备罩;用于容纳部件、布线和螺钉安装。关键词:斜壁箱体、壳体、圆孔、抽壳、拔模、设备盒。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00052858.txt"],["00052980","大型浅盘/嵌入式托盘。","大尺寸方形板在中心区域开出浅矩形凹腔,凹腔边缘带斜角倒角,形成有边框的平面托盘。","可作为仪器托盘、嵌入式面板、模具底板或大型盖板;用于容纳、定位和保护平面组件。关键词:浅盘、矩形凹腔、边框板、倒角、嵌入式面板。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00052980.txt"],["00053435","三孔带长槽安装条。","薄长方形板一端为较大圆孔,另外两端布置两个小圆孔,中部有贯通的长圆槽。","可作为可调安装条、滑槽连接板或传感器安装片;用于位置调节、螺钉固定和导向。关键词:安装条、长圆槽、三孔板、可调支架、滑槽。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00053435.txt"],["00053438","多孔矩形安装板。","厚度均匀的矩形平板上有四个角部小孔、一处大圆孔和一处额外小孔,孔位沿长边方向展开。","可作为面板、法兰板、治具底板或多点固定板;用于连接、穿轴和多螺钉安装。关键词:多孔板、矩形安装板、大圆孔、角孔、法兰板。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00053438.txt"],["00053523","成对的细长穿孔安装条。","两个镜像分离的窄长矩形条构成一组,每根下端排列四个等距圆孔,外侧边略有圆角。","可作为成对导轨、支撑条、绑带固定条或框架连接条;适用于对称安装和多孔调节。关键词:双安装条、镜像、细长孔板、四孔、支撑轨。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00053523.txt"],["00053560","椭圆至方形过渡曲面。","一个椭圆截面与远端方形截面通过导向线放样连接,形成无厚度或薄壁性质的喇叭形过渡面。","可用于风道建模、异形罩壳、流道转接和曲面设计参考。关键词:椭圆方形过渡、放样曲面、喇叭口、异形风道、导向曲线。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00053560.txt"],["00053587","椭圆层叠板/椭圆盖片。","多个不同尺寸的椭圆轮廓叠加拉伸,形成扁平椭圆外形与局部台阶或弧形凸缘。","可作为椭圆盖板、护垫、镜片基座或装饰件;用于封盖、缓冲与外观件。关键词:椭圆盖板、层叠椭圆、台阶、扁平件、镜片基座。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00053587.txt"],["00053597","带圆杆的矩形驱动件/推杆端头。","矩形杆端沿轴向接出细圆柱,主体上方两侧有倒角,根部和棱边带圆角过渡。","可作为推杆端、插入式执行件、传动杆或小型柄部;用于直线运动和连接定位。关键词:推杆、矩形杆、圆柱插杆、倒角、传动端头。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00053597.txt"],["00053748","超长方管/穿孔结构梁。","30 毫米级方形截面沿长度方向延伸,中心贯通圆孔并在端面周围形成多个贯通切口,整体近似长结构管。","可作为框架梁、立柱、方形导轨或穿线结构管;用于大型设备框架和支撑。关键词:方管、结构梁、长方柱、贯通孔、穿孔型材。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00053748.txt"],["00053809","L 形扫掠型材/转角导轨。","复杂薄壁截面沿先竖直后水平的 L 形路径扫掠,转角连续,局部增加矩形让位槽,外棱圆角化。","可作为转角护条、线槽、框架导轨或异形密封条;用于墙角、机柜和管线转向位置。关键词:L 形型材、扫掠、转角导轨、线槽、长条结构。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00053809.txt"],["00053942","异形角板壳体/带孔支架。","五边形外轮廓的厚板在两侧抽壳形成开口边,板面布置三个贯通孔,整体呈折角和加强肋效果。","可作为设备支架、角部连接板、安装罩或轻型壳体;用于多点固定和承托。关键词:异形支架、五边形板、三孔、抽壳、角板。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00053942.txt"],["00054083","大型曲面导流槽/雕塑化壳体。","大尺寸矩形毛坯经两次样条扫掠切除和多级圆角修整,形成双曲线开口、连续曲面的槽形实体。","可作为导流槽、异形护罩、外观壳体或大型曲面模具参考;用于流动导向和工业设计。关键词:曲面导流槽、样条切除、圆角壳体、异形开口、曲面实体。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00054083.txt"],["00054089","带窗口的圆筒套环/回转壳体。","同轴回转体外侧有法兰与环形肩部,前端面和筒壁开出矩形窗口,局部加薄环形边。","可作为通风套、轴承套、旋钮壳或带观察口的圆筒罩;用于包覆、限位和可视检查。关键词:圆筒套环、法兰、矩形窗口、回转壳体、通风孔。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00054089.txt"],["00054409","异形长腔体/曲面导流罩。","复杂封闭截面拉伸成长体,内部带长向空腔、斜向肋和圆角边,端部形成不规则开口。","可作为线缆槽、风道罩、导流壳或异形通道件;用于保护、导向和容纳长条组件。关键词:异形腔体、长向空腔、导流罩、圆角、复杂拉伸。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00054409.txt"],["00054518","圆角矩形托盘/浅凹盖板。","厚矩形板外侧四角圆滑,顶面开出大而浅的矩形凹槽,周边保留连续窄边框。","可作为仪器底托、盖板、嵌入式面板或收纳浅盘;用于容纳、定位和保护元件。关键词:圆角托盘、矩形沉槽、边框板、盖板、浅盘。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00054518.txt"],["00054954","阶梯式管路接头/圆筒联轴件。","多个不同直径的同轴圆筒、法兰环和圆角倒角组合成一端开口的阶梯壳体,具有明显的连接肩部。","可作为软管接头、轴套接头、联轴器外壳或阀体端部;用于两种直径之间的连接、限位和密封。关键词:阶梯接头、法兰环、圆筒壳、联轴件、管路连接。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00054954.txt"],["00055299","带孔曲线薄板/弧形导流片。","细长的样条外轮廓拉伸成薄板,一端圆滑收窄,沿曲线边缘排列多个小贯通孔。","可作为导流叶片、弹性片、装饰条或轻型支撑片;用于空气导向、罩壳内衬和多点固定。关键词:曲线薄板、弧形叶片、小孔、导流片、样条轮廓。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00055299.txt"],["00055355","带顶缘的方形立柱/台座。","高方形主体顶部外扩形成薄法兰,主体与顶缘均带圆角,整体为稳定的垂直台座结构。","可作为设备支脚、支撑柱、底座或保护罩支承;用于抬高、承重和安装面定位。关键词:方形立柱、台座、顶法兰、圆角柱、支撑底座。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00055355.txt"],["00055483","细长空心套管/直管衬套。","长直圆筒一端带短台阶环,沿轴线具有开口内腔,外表面为连续平滑回转面。","可作为隔套、导管、轴套或细长接管;用于导向、保护线缆和形成流体通道。关键词:空心套管、直管、阶梯端、长圆筒、轴套。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00055483.txt"],["00055845","鼓形薄壁喷嘴/瓶颈壳体。","曲线轮廓回转为鼓形腔体,一端为收口颈部,另一端带外翻开口,整体经抽壳形成薄壁内部空间。","可作为喷嘴、导流罩、小容器、吸排气接头或装饰性瓶形壳;用于流体过渡和封装。关键词:鼓形喷嘴、瓶颈、外翻口、薄壁壳、回转容器。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00055845.txt"],["00055851","双缺口薄板/卡接板。","矩形薄板底边附近开有两个小矩形缺口,底部两角圆角化,板面其余区域平直。","可作为卡扣片、盖板、插接定位板或板件让位片;用于装配避让和边缘卡接。关键词:缺口板、双槽、薄板、卡接片、圆角底边。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00055851.txt"],["00055912","S 形曲面导轨/异形夹持件。","复杂样条轮廓拉伸成厚实体,横截面呈连续 S 形起伏,形成一段带内凹通道和外伸边的曲面型材。","可作为卡扣、导向夹、装饰性曲面条或异形流道部件;用于夹持、导向和外观结构。关键词:S 形型材、曲面导轨、内凹通道、异形夹、样条拉伸。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00055912.txt"],["00056025","锥形杯套/回转端帽。","由直线多边形轮廓回转得到,外表从较大开口逐渐收小,形成带厚壁和端部台阶的锥形套状零件。","可作为锥形衬套、杯形端帽、喷口外罩或过渡件;用于导向、封口和异径连接。关键词:锥形杯、锥套、端帽、开口回转体、过渡件。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00056025.txt"],["00056693","单孔矩形连接块。","规则矩形实体中偏置布置一个贯通小圆孔,外轮廓无明显复杂特征,适合作为基础安装块。","可作为定位块、螺钉连接块、传感器安装座或压块;用于单点紧固与支撑。关键词:单孔块、矩形连接件、贯通孔、定位块、安装座。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00056693.txt"],["00056755","开口方盒/方形套筒。","20 毫米级方形实体顶部被移除并抽壳,留下均匀壁厚的矩形内腔和开口边缘。","可作为收纳盒、方形护罩、插接套、容器或壳体原型;用于容纳和保护方形部件。关键词:开口方盒、方形套筒、抽壳、矩形内腔、壳体。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00056755.txt"],["00056858","阶梯回转喷嘴/微型连接头。","复杂轮廓绕轴线回转,形成带曲线肩部、细小端孔和多级台阶的紧凑圆形接头。","可作为微型喷嘴、流体接头、仪器探头端或电连接端头;用于精细导流、插接和定位。关键词:微型喷嘴、阶梯接头、端孔、回转体、仪器连接头。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00056858.txt"],["00056899","带耳环形保持架/小型定位环。","薄圆环上均布六个小圆柱凸台,左右两侧额外伸出带环的轴向耳座,结构具有明显的对称性。","可作为保持环、微型转子框、灯圈支架或定位环;用于限位、安装小件和对称连接。关键词:保持环、定位环、凸台、侧耳、环形支架。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00056899.txt"],["00056993","三叶支撑轮毂/小型转子。","中央圆筒套与三片 120 度均布的薄板叶片相连,外形接近三叶风扇、支撑轮或轻型转子。","可作为风扇轮毂、三爪支撑、叶轮骨架或旋转定位件;用于旋转设备和均布连接。关键词:三叶轮毂、三爪、转子、均布叶片、中心套筒。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00056993.txt"],["00057273","U 形圆线杆/U 形吊环。","恒定圆截面沿两段直线和底部半圆弯曲路径扫掠,形成开口向上的细长 U 形杆。","可作为 U 形销、提环、弹簧钢丝、管路卡或吊挂件;用于悬挂、限位和夹持。关键词:U 形杆、弯线、吊环、圆截面扫掠、U 形销。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00057273.txt"],["00057410","带凸耳的三角安装板。","三角形薄板上附着一个小圆柱耳座和两侧短凸缘,主体为平面板并具有细小连接特征。","可作为角部安装板、铰接耳板、轻型连接片或导流片;用于平面固定和小轴铰接。关键词:三角安装板、凸耳、圆柱销座、角板、连接片。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00057410.txt"],["00057414","基础几何体组合样本。","模型由彼此分离的立方体、圆柱体和球体构成,三个原始实体没有布尔合并或装配关系。","适合作为几何基元、教学示例、形状识别和多实体检索测试;不应推断为完整实际零件。关键词:立方体、圆柱、球体、多实体、几何基元。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00057414.txt"],["00057564","U 槽立式卡脚/叉形板件。","细长梯形薄板的底端开出 U 形缺口,形成两条分叉的支脚,外轮廓向上略收分。","可作为插接卡脚、叉形定位片、夹具支脚或薄板支架;用于插槽配合和卡接定位。关键词:U 槽板、叉形支脚、梯形薄板、卡脚、插接件。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00057564.txt"],["00057726","成对的回转滚轮/双圆盘体。","两个关于基准面对称的鼓形回转实体组成一组,端面有中心孔并带圆滑外缘和同轴肩部。","可作为成对滚轮、滑轮、旋钮体或双侧夹持轮;用于滚动导向和对称装配。关键词:双滚轮、镜像回转体、中心孔、鼓形圆盘、滑轮。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00057726.txt"],["00057781","三孔直角安装支架。","薄底板上垂直拉起一片立板,立板横向排列三个贯通圆孔,底板和立板交界形成典型 L 形。","可作为墙面支架、设备固定角码、导轨支座或小型立板座;用于直角连接与螺钉安装。关键词:L 形支架、三孔立板、角码、底板、安装座。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00057781.txt"],["00057782","圆角角部连接块/带孔支撑块。","矩形实体所有外棱大幅圆角,局部内角进一步圆滑,角部开有贯通圆孔,整体厚实紧凑。","可作为角部支撑块、连接块、护角或夹具压块;用于承托、定位和螺钉固定。关键词:圆角连接块、角部孔、支撑块、护角、厚实体。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00057782.txt"],["00058092","微型圆环耳座/带孔支架。","小圆筒环下方连接窄矩形柄部,环内和柄部端均有小孔,边缘经过细小圆角处理。","可作为微型吊耳、传感器耳座、细线导向件或小铰链支架;用于穿销、悬挂和连接。关键词:圆环耳座、微型支架、小孔、吊耳、圆筒环。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00058092.txt"],["00058106","多孔圆筒卷筒/法兰轮毂。","短圆筒外侧带双端圆盘法兰,前端面围绕中心孔均布六个小孔,整体是可装轴的鼓形部件。","可作为卷筒、转子轮毂、穿线盘或带孔法兰;用于旋转安装、绕线和多螺钉连接。关键词:多孔轮毂、卷筒、法兰圆盘、中心孔、鼓形圆筒。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00058106.txt"],["00058168","法兰头套管销/空心铆钉。","一端为宽圆形头和肩部,另一端为细长空心圆管,管端敞开且整体同轴回转。","可作为空心铆钉、带头套管、轴向隔套或插管端头;用于穿销连接和薄板固定。关键词:空心铆钉、法兰头、套管销、空心管、阶梯轴。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00058168.txt"],["00058173","八孔微型端子条/穿孔薄板。","窄长矩形薄板上分两排布置八个微小圆孔,两端孔径略大,板厚较小。","可作为端子定位条、微型安装片、穿线条或测试治具板;用于多点插针和轻型固定。关键词:八孔板、端子条、微孔、窄长薄板、插针定位。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00058173.txt"],["00058213","微型开槽套管/小型销件。","细长圆筒主体末端连接小方形耳部,筒壁开口,耳部附近有小圆孔和对称矩形切口。","可作为微型定位套、开槽销、导电端子或小型插接件;用于插入、穿销和端部固定。关键词:开槽套管、微型销、方形耳、圆孔、空心杆。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00058213.txt"],["00058244","多孔框架式安装支架。","底板、双侧立板、中心框架和斜向加强片组合为多层支架,多个圆孔分布于底面和立面。","可作为机械安装架、传感器座、电机支架或小型机架节点;用于多方向固定、承托和布置连接件。关键词:框架支架、底板、立板、斜撑、多孔安装。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00058244.txt"],["00058556","弧形薄壁面板/曲面护板。","大圆弧轮廓沿厚度方向拉伸形成曲面板,两个弧形边互相平行,横向宽度均匀。","可作为弧形挡板、护罩片、导流板或装饰面板;用于覆盖圆柱外表、导流和外观件。关键词:弧形面板、曲面护板、圆弧薄板、导流板、罩壳片。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00058556.txt"],["00058560","圆角开口矩形箱/收纳盒。","大型圆角矩形体顶部被抽壳打开,内部为连续腔体,外侧竖边和一条顶边带不同半径的圆角。","可作为储物盒、仪器壳、设备容器或保护罩;用于容纳、隔离与保护内部部件。关键词:开口箱、矩形容器、抽壳、圆角盒体、设备外壳。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00058560.txt"],["00058629","带偏置孔的锥形圆筒壳/端帽。","圆形底座向上延伸为略收分的圆筒,底部带法兰,顶部有偏置大开口和小圆柱凸台。","可作为仪器端帽、管路壳、安装杯或带接口的容器件;用于封装、导流和局部连接。关键词:锥形圆筒、端帽、偏置孔、法兰底座、圆柱凸台。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00058629.txt"],["00058919","胶囊形空心管/椭圆截面套筒。","两个相交圆轮廓与平行边构成胶囊形截面,沿长度拉伸并抽壳,端部保持封闭或半封闭圆弧。","可作为椭圆管、握把壳、缓冲套或异形线缆护管;用于导向、封装和人机接触。关键词:胶囊形管、椭圆套筒、空心壳、圆弧端、异形管。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00058919.txt"],["00058935","双耳连杆/叉形连接臂。","细长板状臂两端形成圆形耳座,一端为单孔,另一端为上下分叉的双耳孔,适合销轴铰接。","可作为连杆、铰链臂、拨叉或运动机构连接件;用于传递力和围绕销轴转动。关键词:叉形连杆、双耳、销孔、铰接臂、拨叉。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00058935.txt"],["00058940","圆角矩形实体块/保护垫块。","无孔矩形块的顶面四周和部分竖边均带连续圆角,外形接近圆润的盒状实体。","可作为垫块、压块、保护块、手持块或基础几何零件;用于支撑、间隔和防碰撞。关键词:圆角方块、保护垫、矩形实体、倒圆、压块。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00058940.txt"],["00059044","锥形过渡管/喇叭形喷口。","大圆截面与远端小圆截面之间平滑放样,随后从大端切出内腔,形成一端敞开的锥形薄壁过渡体。","可作为喷口、漏斗、风管变径段、吸气罩或管路过渡件;用于汇流、扩散和异径连接。关键词:锥形过渡管、喇叭口、放样、内腔、变径喷嘴。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00059044.txt"],["00059097","分离式圆盘垫片组。","一个大直径薄圆盘带中心通孔,旁边独立放置一个小直径圆环盘,属于两件式同轴零件组合。","可作为垫片组、分离盘、绝缘片或装配间隔件;用于轴向间隙调整和定位。关键词:垫片组、圆盘、中心孔、小圆环、多实体。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00059097.txt"],["00059158","薄壁圆管/直套筒。","同心圆环截面拉伸为直线圆筒,端口敞开,顶部和底部外圆边带小圆角。","可作为轴套、隔套、管段、导向管或护线管;用于轴向导向、连接和保护。关键词:圆管、薄壁套筒、同心通孔、直管、隔套。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00059158.txt"],["00059185","带中心孔的锥形垫片/圆形脚垫。","圆盘主体沿厚度方向向内收分,外缘大圆角,中心贯通小孔,截面呈平缓锥形。","可作为锥形垫片、脚垫、压紧盘、锥面止挡或装饰端盖;用于承压、定位和螺钉穿装。关键词:锥形垫片、圆形脚垫、中心孔、圆角圆盘、止挡。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00059185.txt"],["00059306","微型开槽套管/小型销件。","细长空心圆筒在一端带小方形耳部,耳部有微小通孔,筒身邻近端部的矩形切口提供让位。","可作为微型插接套、开槽销、端子接头或小型导向管;用于穿销、插入和轻载连接。关键词:开槽套管、空心销、方形耳、微孔、端子件。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00059306.txt"],["00059380","锥形插杆/带孔扁头销。","长锥形圆杆一端为较大圆形开口端,另一端连接扁平矩形耳头,耳头开有贯通圆孔。","可作为锥销、插入式接头、连杆端或带孔定位杆;用于导向、插接和销轴连接。关键词:锥形插杆、扁头耳、通孔、锥销、连接杆。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00059380.txt"],["00059593","圆角开口矩形托盒。","低矮矩形盒由薄壁抽壳形成,顶部敞开,外轮廓四边带小圆角,内部为大面积容纳腔。","可作为电子元件托盒、收纳盘、浅型外壳或仪器底壳;用于放置、限位和保护部件。关键词:开口托盒、浅壳、矩形容器、圆角、内腔。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00059593.txt"],["00059657","多层机械安装座/带轴支架。","阶梯矩形主体组合底板、上凸圆柱、侧向长圆杆和贯通孔,局部具有台阶边和凸耳,属于多特征支架。","可作为执行机构底座、滑动轴安装座、传感器支架或小型夹具;用于承托、导向和多方向连接。关键词:机械安装座、侧向轴、圆柱凸台、贯通孔、阶梯支架。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00059657.txt"],["00059725","四辐轮毂/带轮。","短圆筒外圈内侧由四条辐条连接中心轴套,前端形成圆形轮缘,中心轴孔贯通。","可作为皮带轮、手轮、滚轮轮芯或小型风扇轮毂;用于传递旋转、支承轴和减重。关键词:四辐轮、轮毂、中心轴套、轮缘、带轮。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00059725.txt"],["00059852","多件式轮毂与轴销组件。","主体为带辐条和中心套的短圆筒轮,旁边分离放置小轴销和圆环垫片,呈待装配的轮毂组件。","可作为小车轮、传动轮、装配式滚轮或轴销连接单元;用于旋转支承、间隔与装配测试。关键词:轮毂组件、辐条轮、轴销、垫片、多实体装配。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00059852.txt"],["00059859","圆角梯形压块/楔形垫块。","厚矩形块沿两条长边施加大圆角,端面呈不规则梯形,主体无孔且具有平整安装面。","可作为压块、楔形垫、支撑块或保护块;用于夹具压紧、支撑和高度补偿。关键词:梯形压块、楔形垫、圆角长边、无孔支撑块、夹具件。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00059859.txt"],["00059941","圆形至圆角方形过渡壳/放样曲面件。","圆形端面与圆角方形端面相隔布置并进行放样,起始端抽壳后形成很薄的异形过渡外壳。","可作为风道转接、异形罩壳、灯具反射罩或曲面设计样件;用于截面转换和轻量化封装。关键词:圆方过渡、放样壳、薄壁、异形风道、圆角方形。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005/00059941.txt"]]} +{"kind":"region","sheet":"模型描述","address":"A5:E93","rows":89,"cols":5,"nonEmpty":445,"text":445,"maxTextLength":90,"preview":[["样本 ID","分类","模型描述","用途与检索关键词","描述文件路径"],["00050089","长条三角棱柱/楔形结构件。","由三角形截面沿长轴拉伸形成,具有两条长斜面和三角端面,是典型的楔形梁或三角截面导轨几何。","可作为夹具垫楔、导向条、斜面支撑或定位块;适用于治具、机械支撑和滑动导向位置。关键词:楔形梁、三角棱柱、斜面、长条拉伸、导轨。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005..."],["00050300","带贯通槽的立方开槽块。","方形实体上形成贯通的 U 形开口、两条矩形槽和字母形穿孔,内部留有连续通道与台阶边界。","可作为插接、避让、导向或编码识别块,常见于夹具、拼装件和教学模型。关键词:开槽块、U 形通道、贯通孔、矩形槽、方块。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005..."],["00050521","圆柱基座与弧形薄壁支架组合件。","主体由大圆柱、带圆弧轮廓的薄壁壳体和三角形连接肋组成,壳体内部另有小圆柱凸台。","可作为旋转件护罩、圆柱件安装座或带加强肋的支撑件;适用于设备外壳、支架和机械连接部位。关键词:圆柱基座、薄壁壳、弧形支架、三角肋、凸台。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005..."],["00050568","梳齿式薄板连接件/多槽插接板。","两片薄板通过薄桥连接,上层板边缘排列五个等距开槽,形成梳齿状的插接或排线定位结构。","可用于扁平线缆梳理、插片定位、端子排或薄板卡扣;常位于电子装配和小型连接结构中。关键词:梳齿板、五槽、插接、排线定位、薄板连接件。","/Users/lk/Projects/cdsl-cad/data/cadfs-sample/CADFS_test/description_txt/0005..."]],"previewAddress":"A5:E9","previewRows":5,"previewCols":5} +{"kind":"region","sheet":"模型描述","address":"A1:E3","rows":3,"cols":5,"nonEmpty":6,"blank":9,"text":6,"maxTextLength":47,"preview":[["CADFS 0005 模型识别描述索引",null,null,null,null],["样本总数","88","数据范围","CADFS_test / 0005",null],["说明:分类与用途依据多视图图像及原始建模标注整理;具体产品身份在缺少装配上下文时作为检索候选。",null,null,null,null]],"previewAddress":"A1:E3","previewRows":3,"previewCols":5} +{"kind":"formula","sheet":"模型描述","address":"B2","formula":"=COUNTA(A6:A93)"} +{"kind":"computedStyle","sheet":"模型描述","for":"A1","style":{"fill":{"type":1,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":16,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":false,"horizontalAlignment":"left","styleId":4}} +{"kind":"computedStyle","sheet":"模型描述","for":"A2","style":{"fill":{"type":1,"color":{"type":1,"value":"EAF2F8"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":false,"horizontalAlignment":"general","styleId":7}} +{"kind":"computedStyle","sheet":"模型描述","for":"B2","style":{"fill":{"type":1,"color":{"type":1,"value":"EAF2F8"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":false,"horizontalAlignment":"general","styleId":7}} +{"kind":"computedStyle","sheet":"模型描述","for":"C2","style":{"fill":{"type":1,"color":{"type":1,"value":"EAF2F8"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":false,"horizontalAlignment":"general","styleId":7}} +{"kind":"computedStyle","sheet":"模型描述","for":"D2","style":{"fill":{"type":1,"color":{"type":1,"value":"EAF2F8"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":false,"horizontalAlignment":"general","styleId":7}} +{"kind":"computedStyle","sheet":"模型描述","for":"A3","style":{"fill":{"type":1,"color":{"type":1,"value":"F7F9FA"},"gradientStops":[],"pictureEffects":[]},"font":{"italic":true,"fontSize":10,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"536471"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":true,"horizontalAlignment":"general","styleId":11}} +{"kind":"computedStyle","sheet":"模型描述","for":"A5","style":{"fill":{"type":1,"color":{"type":1,"value":"1F6F8B"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"15566D"}},"bottom":{"style":"thin","color":{"type":1,"value":"15566D"}},"left":{"style":"thin","color":{"type":1,"value":"15566D"}}},"wrapText":true,"horizontalAlignment":"center","styleId":23}} +{"kind":"computedStyle","sheet":"模型描述","for":"B5","style":{"fill":{"type":1,"color":{"type":1,"value":"1F6F8B"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"15566D"}},"bottom":{"style":"thin","color":{"type":1,"value":"15566D"}}},"wrapText":true,"horizontalAlignment":"center","styleId":24}} +{"kind":"computedStyle","sheet":"模型描述","for":"C5","style":{"fill":{"type":1,"color":{"type":1,"value":"1F6F8B"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"15566D"}},"bottom":{"style":"thin","color":{"type":1,"value":"15566D"}}},"wrapText":true,"horizontalAlignment":"center","styleId":24}} +{"kind":"computedStyle","sheet":"模型描述","for":"D5","style":{"fill":{"type":1,"color":{"type":1,"value":"1F6F8B"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"15566D"}},"bottom":{"style":"thin","color":{"type":1,"value":"15566D"}}},"wrapText":true,"horizontalAlignment":"center","styleId":24}} +{"kind":"computedStyle","sheet":"模型描述","for":"E5","style":{"fill":{"type":1,"color":{"type":1,"value":"1F6F8B"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"15566D"}},"bottom":{"style":"thin","color":{"type":1,"value":"15566D"}},"right":{"style":"thin","color":{"type":1,"value":"15566D"}}},"wrapText":true,"horizontalAlignment":"center","styleId":25}} +{"kind":"computedStyle","sheet":"模型描述","for":"A6","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":38}} +{"kind":"computedStyle","sheet":"模型描述","for":"B6","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":41}} +{"kind":"computedStyle","sheet":"模型描述","for":"C6","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":32}} +{"kind":"computedStyle","sheet":"模型描述","for":"D6","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":32}} +{"kind":"computedStyle","sheet":"模型描述","for":"E6","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":44}} +{"kind":"computedStyle","sheet":"模型描述","for":"A7","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B7","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C7","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D7","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E7","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A8","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B8","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C8","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D8","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E8","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A9","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B9","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C9","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D9","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E9","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A10","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B10","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C10","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D10","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E10","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A11","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B11","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C11","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D11","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E11","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A12","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B12","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C12","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D12","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E12","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A13","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B13","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C13","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D13","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E13","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A14","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B14","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C14","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D14","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E14","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A15","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B15","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C15","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D15","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E15","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A16","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B16","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C16","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D16","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E16","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A17","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B17","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C17","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D17","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E17","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A18","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B18","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C18","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D18","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E18","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A19","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B19","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C19","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D19","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E19","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A20","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B20","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C20","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D20","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E20","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A21","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B21","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C21","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D21","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E21","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A22","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B22","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C22","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D22","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E22","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A23","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B23","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C23","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D23","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E23","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A24","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B24","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C24","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D24","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E24","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A25","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B25","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C25","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D25","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E25","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A26","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B26","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C26","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D26","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E26","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A27","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B27","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C27","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D27","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E27","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A28","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B28","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C28","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D28","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E28","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A29","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B29","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C29","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D29","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E29","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A30","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B30","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C30","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D30","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E30","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A31","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B31","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C31","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D31","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E31","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A32","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B32","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C32","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D32","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E32","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A33","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B33","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C33","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D33","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E33","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A34","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B34","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C34","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D34","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E34","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A35","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B35","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C35","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D35","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E35","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A36","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B36","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C36","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D36","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E36","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A37","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B37","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C37","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D37","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E37","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A38","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B38","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C38","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D38","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E38","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A39","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B39","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C39","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D39","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E39","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A40","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B40","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C40","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D40","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E40","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A41","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B41","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C41","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D41","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E41","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A42","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B42","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C42","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D42","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E42","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A43","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B43","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C43","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D43","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E43","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A44","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B44","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C44","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D44","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E44","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A45","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B45","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C45","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D45","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E45","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A46","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B46","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C46","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D46","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E46","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A47","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B47","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C47","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D47","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E47","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A48","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B48","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C48","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D48","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E48","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A49","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B49","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C49","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D49","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E49","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A50","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B50","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C50","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D50","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E50","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A51","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B51","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C51","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D51","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E51","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A52","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B52","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C52","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D52","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E52","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A53","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B53","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C53","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D53","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E53","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A54","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B54","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C54","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D54","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E54","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A55","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B55","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C55","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D55","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E55","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A56","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B56","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C56","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D56","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E56","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A57","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B57","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C57","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D57","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E57","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A58","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B58","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C58","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D58","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E58","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A59","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B59","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C59","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D59","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E59","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A60","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B60","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C60","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D60","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E60","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A61","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B61","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C61","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D61","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E61","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A62","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B62","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C62","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D62","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E62","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A63","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B63","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C63","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D63","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E63","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A64","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B64","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C64","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D64","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E64","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A65","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B65","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C65","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D65","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E65","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A66","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B66","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C66","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D66","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E66","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A67","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B67","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C67","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D67","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E67","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A68","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B68","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C68","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D68","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E68","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A69","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B69","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C69","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D69","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E69","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A70","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B70","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C70","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D70","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E70","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A71","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B71","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C71","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D71","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E71","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A72","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B72","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C72","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D72","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E72","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A73","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B73","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C73","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D73","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E73","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A74","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B74","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C74","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D74","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E74","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A75","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B75","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C75","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D75","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E75","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A76","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B76","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C76","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D76","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E76","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A77","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B77","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C77","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D77","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E77","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A78","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B78","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C78","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D78","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E78","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A79","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B79","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C79","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D79","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E79","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A80","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B80","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C80","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D80","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E80","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A81","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B81","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C81","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D81","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E81","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A82","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B82","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C82","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D82","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E82","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A83","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B83","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C83","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D83","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E83","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A84","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B84","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C84","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D84","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E84","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A85","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B85","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C85","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D85","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E85","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A86","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B86","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C86","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D86","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E86","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A87","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B87","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C87","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D87","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E87","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A88","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B88","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C88","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D88","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E88","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A89","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B89","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C89","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D89","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E89","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A90","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B90","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C90","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D90","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E90","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A91","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B91","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C91","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D91","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E91","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A92","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":39}} +{"kind":"computedStyle","sheet":"模型描述","for":"B92","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":42}} +{"kind":"computedStyle","sheet":"模型描述","for":"C92","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"D92","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":33}} +{"kind":"computedStyle","sheet":"模型描述","for":"E92","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":45}} +{"kind":"computedStyle","sheet":"模型描述","for":"A93","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"163A5F"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"center","styleId":40}} +{"kind":"computedStyle","sheet":"模型描述","for":"B93","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"245B75"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":43}} +{"kind":"computedStyle","sheet":"模型描述","for":"C93","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":34}} +{"kind":"computedStyle","sheet":"模型描述","for":"D93","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito"},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":34}} +{"kind":"computedStyle","sheet":"模型描述","for":"E93","style":{"fill":{"type":3,"gradientStops":[],"pattern":{"patternType":1},"pictureEffects":[]},"font":{"fontSize":9,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"687780"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D9E2E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":46}} \ No newline at end of file