没3D软件怎么查尺寸?我用Python干掉线束数模的"视觉盲区"
在离散制造和线束加工的车间里,核算下料长度(BOM)永远是个让人头疼的细活。
最传统的方法,是工艺工程师在电脑上面打开大体积的 3D 软件(如 SolidWorks、Fusion 360 等),顺着一条条空间折弯的线束中心线,用鼠标去点、去测、去拉拉直长度。
但如果出差在外,手头只有一台轻薄本,甚至根本没有装 3D 软件,刚好客户发来一个 .stp 格式的 3D 快调急件,要你半小时内出报价和下料总长,怎么办?
今天,我们就用 Python(基于 OpenCASCADE 几何内核),不依赖任何重型 3D 客户端,直接暴力拆解一个真实工业级大电流"双线并绕"引出线组件(STEP文件),看看一个高精度的线束长度自动精算脚本是如何在实际痛点中死磕迭代出来的。

🛑 乱象丛生:3D 数模底层隐藏的"虚胖伪装"
当我们用 Python 里的 OCC 库去遍历一个 STEP 文件的 SOLID(实体)时,理想状态下,模型有 6 根线,代码就应该吐出 6 个长度。
但现实往往会给你一记重锤。
第一版基础脚本跑完后,车间看板直接傻眼——明明肉眼看只有 6 根线,代码却吐出了 11 根,而且算出来的长度累计大得离谱。
🔍 破案现场 1:多层嵌套
在三维装配体(Assembly)中,为了表现真实物理结构,设计人员建模时往往采用多层嵌套。多芯线、带有屏蔽层的电缆,或者套了热缩管、绿色编织网管的引出线,在 STEP 底层其实是由内部导线芯、外层绝缘皮、外部保护套管等多个独立实体(Solid)重叠组合而成的。
如果只做简单的特征遍历,代码会把同一根线的"芯线"、"皮"、"套管"全部独立计算一次,导致物料总量严重偏大。
🛠️ 进化阶段 1:引入"空间重叠归一化"算法
为了干掉套管的重复统计,我们在 Python 中引入了物理质心(Center of Mass)空间算法:
def get_shape_center_of_mass(shape): props = GProp_GProps()
brepgprop.VolumeProperties(shape, props)
return props.CentreOfMass()
判定逻辑:如果两个实体组件在空间中的轴线方向平行,且它们的空间质心距离极近,说明它们是同一根导线的内部铜芯与外部套管关系。
合并策略:在空间重叠的组件中,只保留最长的特征,剔除虚胖面。
然而,当开启质心去重后,问题从 11 根骤降到了 4 根——这下过头了。

🔍 破案现场 2:大电流双线并绕的"视觉欺骗"
点开 3D 软件对齐数模后发现,这根本不是普通的单芯线,而是一个典型的大电流双线并绕组件。3 个接线端子,每个压接筒里两两成对,并排插着 2 根导线(3 × 2 = 6 根)。
因为并绕的两根导线靠得实在太紧了,它们的空间质心完全落入了算法的去重判定圈里,结果并走的两根线被代码粗暴地执行了"二合一"!
🚀 终极回归:表面积守恒定律 + 剥离外部拓扑
面对高度碎裂、空间紧密并绕的复杂扫描导线,我们彻底放弃模糊的空间距离判定,直接回归最纯粹的工业物料本质——圆柱体表面积守恒。
💡 核心原理
一根直径为 d、长度为 L 的圆柱体导线,无论在 CAD 软件里被切得多么碎、顺着折弯节点断开成多少个小面,它的圆周侧面积 A 是绝对不会凭空消失的。根据公式 A = π · d · L,我们反推出单实体净展开长度:
L = A_total / (π · d)同时,我们在代码中加入硬核的特征线径过滤机制:
- 识别到直径 ≥ 4.1mm 的实体:判定为绿色保护套/红色热缩管,移出线芯统计
- 识别到直径 ≈ 2.8mm 的实体:判定为真实实物线芯,全额累加面积

💻 工业级 Python 线束精算脚本全案
以下是历经车间数据对账、多轮调校后的最终版 Python 脚本。它不需要任何 3D 图形界面,即可在后台秒级解析 STEP 装配体,剥离套管,精准输出 6 根线芯各自的拉直展开长。
import os import sys
import warnings
import math
try:
from OCC.Core.Message import Message, Message_PrinterOStream
Message.DefaultMessenger().RemovePrinters(Message_PrinterOStream.get_type_descriptor())
except Exception:
pass
from OCC.Core.STEPControl import STEPControl_Reader
from OCC.Core.IFSelect import IFSelect_RetDone
from OCC.Core.Bnd import Bnd_Box
from OCC.Core.BRepBndLib import brepbndlib
from OCC.Core.TopExp import TopExp_Explorer
from OCC.Core.TopAbs import TopAbs_SOLID, TopAbs_FACE
from OCC.Core.BRepAdaptor import BRepAdaptor_Surface
from OCC.Core.GeomAbs import GeomAbs_Cylinder, GeomAbs_Torus
from OCC.Core.GProp import GProp_GProps
from OCC.Core.BRepGProp import brepgprop
warnings.filterwarnings("ignore", category=DeprecationWarning)
==================== 🛠️ 工业级下料配置开关 ====================
已完美对齐 3D 软件实测尺寸:
两端各加约 55mm 的亮黄端头 Part 长度 + 20mm 剥线压接工艺补偿 = 130.0mm
WIRE_COMPENSATION = 130.0
========================================================
def read_step_file(file_path):
step_reader = STEPControl_Reader()
status = step_reader.ReadFile(file_path)
if status == IFSelect_RetDone:
step_reader.TransferRoots()
return step_reader.OneShape()
else:
raise Exception(f"无法读取 STEP 文件")
def get_shape_dimensions(shape):
bbox = Bnd_Box()
brepbndlib.Add(shape, bbox)
xmin, ymin, zmin, xmax, ymax, zmax = bbox.Get()
return xmax - xmin, ymax - ymin, zmax - zmin
def analyze_solid_by_surface_area(sub_shape):
"""基于表面积守恒定律反推真实长度,解决CAD碎面与并绕去重痛点"""
dx, dy, dz = get_shape_dimensions(sub_shape)
face_explorer = TopExp_Explorer(sub_shape, TopAbs_FACE)
total_cylinder_area = 0.0
detected_radii = {}
while face_explorer.More():
face = face_explorer.Current()
surf_adaptor = BRepAdaptor_Surface(face)
surf_type = surf_adaptor.GetType()
props = GProp_GProps()
brepgprop.SurfaceProperties(face, props)
face_area = props.Mass()
if face_area <= 0.5:
face_explorer.Next()
continue
if surf_type == GeomAbs_Cylinder:
cyl = surf_adaptor.Cylinder()
r = round(cyl.Radius(), 1)
if 0.5 < r < 10.0:
detected_radii[r] = detected_radii.get(r, 0.0) + face_area
total_cylinder_area += face_area
elif surf_type == GeomAbs_Torus:
torus = surf_adaptor.Torus()
r_minor = round(torus.MinorRadius(), 1)
if 0.5 < r_minor < 10.0:
detected_radii[r_minor] = detected_radii.get(r_minor, 0.0) + face_area
total_cylinder_area += face_area
face_explorer.Next()
if not detected_radii or total_cylinder_area < 10.0:
return "Solid", (dx, dy, dz)
main_r = max(detected_radii, key=detected_radii.get)
main_diameter = main_r * 2
calculated_length = total_cylinder_area / (2 math.pi main_r)
# 闸门拦截:过滤掉外层包裹的粗套管 (直径 >= 4.1mm)
if main_diameter >= 4.1:
return "Sleeve", calculated_length
if calculated_length < 15.0:
return "Solid", (dx, dy, dz)
return "Wire", (calculated_length, main_diameter)
if __name__ == "__main__":
files = [f for f in os.listdir('.') if f.lower().endswith(('.stp', '.step'))]
if not files:
print("❌ 未找到 .stp 文件")
sys.exit(1)
main_shape = read_step_file(files[0])
explorer = TopExp_Explorer(main_shape, TopAbs_SOLID)
final_wires = []
sleeve_count = 0
rigid_count = 0
while explorer.More():
sub_shape = explorer.Current()
part_type, data = analyze_solid_by_surface_area(sub_shape)
if part_type == "Wire":
final_wires.append(data)
elif part_type == "Sleeve":
sleeve_count += 1
else:
rigid_count += 1
explorer.Next()
final_wires.sort(key=lambda x: x[0], reverse=True)
print("\n================== 整体模型总外形 ==================")
gx, gy, gz = get_shape_dimensions(main_shape)
print(f"👉 整个装配体宏观边界: {gx:.2f} x {gy:.2f} x {gz:.2f} mm")
print("====================================================")
print(f"\n--- 🗺️ 面积守恒全额还原后:独立导线明细 ---")
total_net_length = 0.0
total_cut_length = 0.0
for idx, (length, diameter) in enumerate(final_wires, 1):
cut_length = length + WIRE_COMPENSATION
total_net_length += length
total_cut_length += cut_length
print(f"导线 #{idx:02d}: 📊 实物线径: ⌀{diameter:.1f} mm | 中段净轴长: {length:6.2f} mm | 🎁 建议下料长: {cut_length:.2f} mm")
print("\n================== 📏 车间生产物料看板 ==================")
print(f"实物导线(线芯)总计 : {len(final_wires)} 根")
print(f"外包绝缘套管/热缩管 : {sleeve_count} 段")
print(f"青铜接线端子/刚性结构: {rigid_count} 个")
print(f"🔥 车间铜线【下料总需求】: {total_cut_length:.2f} mm")
print("========================================================\n")
🏁 终审对账:完美闭环的生产数据
在终端里敲下 python get_stp_dimensions.py 后,终端吐出了最终的完美看板:
================== 整体模型总外形 ================== 👉 整个装配体宏观边界: 162.64 x 70.67 x 89.76 mm
====================================================
--- 🗺️ 面积守恒全额还原后:独立导线明细 ---
导线 #01: 📊 实物线径: ⌀2.8 mm | 中段净轴长: 167.30 mm | 🎁 建议下料长: 297.30 mm
导线 #02: 📊 实物线径: ⌀2.8 mm | 中段净轴长: 148.04 mm | 🎁 建议下料长: 278.04 mm
导线 #03: 📊 实物线径: ⌀2.8 mm | 中段净轴长: 137.71 mm | 🎁 建议下料长: 267.71 mm
导线 #04: 📊 实物线径: ⌀2.8 mm | 中段净轴长: 114.11 mm | 🎁 建议下料长: 244.11 mm
导线 #05: 📊 实物线径: ⌀2.8 mm | 中段净轴长: 91.10 mm | 🎁 建议下料长: 221.10 mm
导线 #06: 📊 实物线径: ⌀2.8 mm | 中段净轴长: 64.52 mm | 🎁 建议下料长: 194.52 mm
================== 📏 车间生产物料看板 ==================
实物导线(线芯)总计 : 6 根
外包绝缘套管/热缩管 : 8 段
青铜接线端子/刚性结构: 1 个
🔥 车间铜线【下料总需求】: 1502.79 mm
========================================================

💡 为什么这组数据无懈可击?
长短两解的内外圈差值:导线 #01(167.30mm)和 #02(148.04mm)处于同一条并绕通道,由于 3D 折弯产生的内外圈弧度差,长短自然相差约 19mm,这极其符合真实车间走线工艺。
完美对齐空间直觉:由于建模时每根彩线两端都有独立的亮黄色压接 Part(拆分零件机制),在代码中通过统一追加 130mm 的合并长补偿后,最长分支的单根下料长度来到了 297.30 mm,精准咬合了资深工程师肉眼评估的"接近 300 毫米"的空间概念。
📝 关于精度的一点说明
有人问:用 OpenCASCADE 这种通用几何内核算出来的精度,能信吗?
实验室环境里,你可以打开 SolidWorks,选中 6 根线的中心线,一条一条拉直,5 分钟出数。但产线的现实是——出差半夜在酒店,客户追着要报价,轻薄本装不了 CAD,压缩包里的 .stp 文件在等你。
这套脚本的精度,核心不取决于 OpenCASCADE,而取决于建模端:如果原始数模的线径、折弯半径、端头Part分界完全对齐工艺要求,用面积守恒反推出来的长度可以做到 0.1mm 级偏差。反之,如果数模本身就是"意思到了就行"的快模,那什么算法都救不了。
数字化工具的上限,永远是你的数据质量。但反过来说,如果你的数据本身是干净的,就不需要花几十万买软件才能读到它。写在最后
代码写了三版。
第一版跑完,11 根线,车间说不对。第二版加空间质心去重,4 根,过头了。第三版回到表面积守恒,6 根,严丝合缝。
我们花在调试上的时间,不是在跟代码较劲,而是在跟建模习惯对话。 每一根误判的导线背后,都藏着一个设计人员没写在图纸上的直觉。而当你终于写出一段能听懂这些直觉的脚本时,你得到的不仅是一组准确的下料数据——
你得到了一台能读懂 3D 数模的"工艺翻译机",它不吃设备、不挑环境、随时等你敲回车键。#python #step #opencascade #线束 #线束长度 #cad解析 #离散制造 #数字化 #本地回路
本文首发于微信公众号「本地回路」,转发请注明出处。

夜雨聆风