
子部件挖孔带连接件import time import trimesh import numpy as np import time import trimesh import numpy as np # -------------------- 辅助函数 -------------------- def prepare_mesh(mesh): 修复网格常见问题返回副本 mesh mesh.copy() mesh.merge_vertices() mesh.fix_normals() if not mesh.is_watertight: mesh mesh.fill_holes() if hasattr(mesh, remove_degenerate_faces): mesh.remove_degenerate_faces() try: mesh.remove_unreferenced_vertices() mesh.remove_infinite_values() except Exception as e: print(e) return mesh def bounds_intersect(a_bounds, b_bounds): 检查两个包围盒是否相交 return not (a_bounds[0][0] b_bounds[1][0] or a_bounds[1][0] b_bounds[0][0] or a_bounds[0][1] b_bounds[1][1] or a_bounds[1][1] b_bounds[0][1] or a_bounds[0][2] b_bounds[1][2] or a_bounds[1][2] b_bounds[0][2]) def is_valid_mesh(geom, check_volumeTrue, min_vertices3, min_faces1): 检查网格是否有效 if not isinstance(geom, trimesh.Trimesh): return False if geom.vertices is None or geom.faces is None: return False if geom.vertices.shape[0] min_vertices: return False if geom.faces.shape[0] min_faces: return False if not np.all(np.isfinite(geom.vertices)): return False if not np.all(np.isfinite(geom.faces)): return False if geom.faces.max() geom.vertices.shape[0]: return False if geom.faces.min() 0: return False try: bounds geom.bounds if not np.all(np.isfinite(bounds)): return False size bounds[1] - bounds[0] if np.any(size 0): return False except: return False if check_volume: try: volume geom.volume if abs(volume) 1e-8: size geom.bounds[1] - geom.bounds[0] if np.all(size 1e-6): pass except: return False try: triangles geom.vertices[geom.faces] v0 triangles[:, 1] - triangles[:, 0] v1 triangles[:, 2] - triangles[:, 0] cross np.cross(v0, v1) areas 0.5 * np.linalg.norm(cross, axis1) if np.mean(areas) 1e-10: return False except: pass return True def add_connectors_as_visual(scene, intersections, core_namesNone, radiusNone, lengthNone): 添加独立的小圆柱作为连接件指示未修改 if core_names is None: core_names list(scene.geometry.keys()) centroids {} for name in core_names: geom scene.geometry.get(name) if geom and hasattr(geom, centroid): centroids[name] geom.centroid elif geom and hasattr(geom, vertices) and geom.vertices.shape[0] 0: centroids[name] np.mean(geom.vertices, axis0) else: centroids[name] np.array([0, 0, 0]) bounds scene.bounds if bounds is not None and np.all(np.isfinite(bounds)): scene_size np.linalg.norm(bounds[1] - bounds[0]) else: scene_size 1.0 if radius is None: radius max(0.005 * scene_size, 0.001) if length is None: length max(0.015 * scene_size, 0.005) added 0 for item in intersections: if len(item) 5: continue name_i, name_j, center, area, normal item[:5] if name_i not in core_names or name_j not in core_names: continue c_i centroids.get(name_i) c_j centroids.get(name_j) if c_i is None or c_j is None: continue direction c_j - c_i norm_dir np.linalg.norm(direction) if norm_dir 1e-8: continue direction direction / norm_dir cyl trimesh.creation.cylinder(radiusradius, heightlength, segments16) z_axis np.array([0, 0, 1]) if np.allclose(direction, z_axis) or np.allclose(direction, -z_axis): rot np.eye(3) else: v np.cross(z_axis, direction) s np.linalg.norm(v) c np.dot(z_axis, direction) vx np.array([[0, -v[2], v[1]], [v[2], 0, -v[0]], [-v[1], v[0], 0]]) rot np.eye(3) vx np.dot(vx, vx) * ((1 - c) / (s ** 2)) transform np.eye(4) transform[:3, :3] rot transform[:3, 3] center cyl.apply_transform(transform) conn_name fvisual_connector_{name_i}_{name_j} scene.add_geometry(cyl, geom_nameconn_name, transformnp.eye(4)) added 1 print(f添加可视化连接件: {conn_name} 于 {center}) print(f共添加 {added} 个可视化连接件) return scene # -------------------- 核心切割不合并预计算尖尖 -------------------- def cut_scene_geometries(scene, enginemanifold, top_k6, peg_radiusNone, peg_lengthNone, add_visual_connectorsFalse): 按体积选择 top_k 个核心部件对其他部件执行切割核心部件被切割。 未选中的部件保持独立不合并。 在切割之前预计算尖尖体积和质心距离正确使用平面切割。 返回 (新场景, 交面信息列表) 交面信息为 (name_i, name_j, center, area, normal, vol_tip_A, vol_tip_B, dist_A, dist_B) if not isinstance(scene, trimesh.Scene): raise ValueError(输入必须是 trimesh.Scene) # 1. 变换到世界坐标系 geom_names [] world_geoms [] for name, geom in scene.geometry.items(): if not isinstance(geom, trimesh.Trimesh): continue if name in scene.graph: transform scene.graph[name][0] else: transform np.eye(4) vertices trimesh.transformations.transform_points(geom.vertices, transform) world_geom trimesh.Trimesh(verticesvertices, facesgeom.faces, processFalse) try: world_geom prepare_mesh(world_geom) print(f预处理 {name} 成功) except Exception as e: print(f预处理几何体 {name} 失败: {e}) geom_names.append(name) world_geoms.append(world_geom) n len(world_geoms) # 2. 计算体积 volumes [] for i, geom in enumerate(world_geoms): try: vol geom.volume if vol 0: vol -vol except: size geom.bounds[1] - geom.bounds[0] vol np.prod(size) volumes.append(vol) print(f部件 {geom_names[i]} 体积: {vol:.6f}) sorted_indices np.argsort(volumes)[::-1] keep_indices set(sorted_indices[:top_k].tolist()) print(f\n选择体积最大的 {top_k} 个部件作为核心: {[geom_names[i] for i in keep_indices]}) # 3. 计算所有交面含法线并正确预计算尖尖体积和质心距离 all_intersections [] # (i, j, center, area, normal, vol_tip_A, vol_tip_B, dist_A, dist_B) for i in range(n): for j in range(i 1, n): A world_geoms[i] B world_geoms[j] if not bounds_intersect(A.bounds, B.bounds): continue print(f计算交集: {geom_names[i]} ∩ {geom_names[j]}) try: inter trimesh.boolean.intersection([A, B], engineengine) if inter is not None: if isinstance(inter, list) and len(inter) 0: combined trimesh.util.concatenate(inter) else: combined inter if isinstance(combined, trimesh.Trimesh) and combined.vertices.shape[0] 0 and combined.faces.shape[0] 0: # 交面中心与法线 face_centers combined.vertices[combined.faces].mean(axis1) center np.mean(face_centers, axis0) area combined.area face_normals combined.face_normals face_areas combined.area_faces if face_normals.shape[0] 0 and face_areas.sum() 1e-12: weighted_normal np.average(face_normals, axis0, weightsface_areas) norm np.linalg.norm(weighted_normal) normal weighted_normal / norm if norm 1e-12 else np.array([0, 0, 1]) else: normal np.array([0, 0, 1]) # 正确预计算尖尖体积使用平面切割 try: tip_A trimesh.intersections.slice_mesh_plane( A, plane_origincenter, plane_normalnormal, capTrue ) vol_tip_A tip_A.volume if (isinstance(tip_A, trimesh.Trimesh) and tip_A.is_volume) else 0 except: vol_tip_A 0 try: tip_B trimesh.intersections.slice_mesh_plane( B, plane_origincenter, plane_normal-normal, capTrue ) vol_tip_B tip_B.volume if (isinstance(tip_B, trimesh.Trimesh) and tip_B.is_volume) else 0 except: vol_tip_B 0 # 计算质心到交面中心的距离 cent_A A.centroid if hasattr(A, centroid) else np.mean(A.vertices, axis0) cent_B B.centroid if hasattr(B, centroid) else np.mean(B.vertices, axis0) dist_A np.linalg.norm(cent_A - center) dist_B np.linalg.norm(cent_B - center) all_intersections.append((i, j, center, area, normal, vol_tip_A, vol_tip_B, dist_A, dist_B)) print(f 记录交面中心: {center}, 面积: {area:.6f}, 法线: {normal}, f尖尖A: {vol_tip_A:.6f}, 尖尖B: {vol_tip_B:.6f}, 距离A: {dist_A:.4f}, 距离B: {dist_B:.4f}) else: print( 交集为空或无效) else: print( 交集返回 None) except Exception as e: print(f 计算交集失败: {e}) # 4. 执行切割核心部件作为被减数j被其他所有部件切割 for i, j, _, _, _, _, _, _, _ in all_intersections: if j not in keep_indices: continue A world_geoms[i] B world_geoms[j] print(f执行切割: {geom_names[j]} {geom_names[j]} - {geom_names[i]}) try: result trimesh.boolean.difference([B, A], engineengine) if result is not None: if isinstance(result, list) and len(result) 0: if len(result) 1: vols [r.volume for r in result] result result[np.argmax(vols)] else: result result[0] if isinstance(result, trimesh.Trimesh) and result.vertices.shape[0] 0 and result.faces.shape[0] 0: world_geoms[j] result print(f 成功切割 {geom_names[j]}) else: print(f 切割结果无效保留原始部件) else: print( 切割返回 None保留原始部件) except Exception as e: print(f 切割失败: {e}保留原始部件) # 5. 在核心部件上生成凸起和孔洞使用预计算的尖尖体积和距离 print(\n 开始生成凸起和孔洞 ) core_indices sorted(keep_indices) world_geoms add_peg_and_hole_to_parts( world_geoms, geom_names, all_intersections, core_indices, radiuspeg_radius, lengthpeg_length, engineengine ) # 6. 构建新场景包含所有部件 new_scene trimesh.Scene() for idx in range(n): name geom_names[idx] geom world_geoms[idx] if geom.vertices.shape[0] 0 and geom.faces.shape[0] 0: geom.metadata[type] core if idx in keep_indices else non_core new_scene.add_geometry(geom, geom_namename, transformnp.eye(4)) print(f添加部件: {name} (类型: {geom.metadata[type]})) else: print(f警告: 部件 {name} 无效跳过添加) if add_visual_connectors: new_scene add_connectors_as_visual(new_scene, all_intersections, core_namesgeom_names) print(f最终场景包含 {len(new_scene.geometry)} 个几何体) print(f几何体名称列表: {list(new_scene.geometry.keys())}) # 7. 返回交面信息含尖尖体积和距离 intersections_return [] for i, j, center, area, normal, vol_tip_A, vol_tip_B, dist_A, dist_B in all_intersections: intersections_return.append((geom_names[i], geom_names[j], center, area, normal, vol_tip_A, vol_tip_B, dist_A, dist_B)) return new_scene, intersections_return def add_peg_and_hole_to_parts(world_geoms, geom_names, intersections, core_indices, radiusNone, lengthNone, tolerance0.002, enginemanifold): 基于预计算的尖尖体积和距离决定凸起/孔洞方向。 intersections 中每个元素必须包含 (i, j, center, area, normal, vol_tip_A, vol_tip_B, dist_A, dist_B) # 1. 计算场景尺寸 all_verts np.vstack([g.vertices for g in world_geoms if g.vertices.shape[0] 0]) scene_diag np.linalg.norm(np.ptp(all_verts, axis0)) if radius is None: peg_radius max(0.01 * scene_diag, 0.001) else: peg_radius radius if length is None: peg_length max(0.03 * scene_diag, 0.002) else: peg_length length hole_radius peg_radius tolerance print(f 凸起半径: {peg_radius:.4f}, 凸起长度: {peg_length:.4f}) print(f 孔洞半径: {hole_radius:.4f}, 间隙: {tolerance:.4f}) # 2. 计算所有核心部件的质心用于方向 centroids {} for idx in core_indices: g world_geoms[idx] if g.vertices.shape[0] 0: centroids[idx] g.centroid if hasattr(g, centroid) else np.mean(g.vertices, axis0) # 3. 处理每个交面对 processed_pairs set() peg_count 0 hole_count 0 for item in intersections: # 解包全部9个字段 if len(item) 9: print(f警告交面信息不足长度{len(item)}跳过) continue i, j, center, area, normal, vol_tip_A, vol_tip_B, dist_A, dist_B item if i not in core_indices or j not in core_indices: continue pair tuple(sorted((i, j))) if pair in processed_pairs: continue processed_pairs.add(pair) print(f\n 处理交面: {geom_names[i]} ↔ {geom_names[j]}) print(f 交面中心: {center}) print(f 交面面积: {area:.6f}) print(f 交面法线: {normal}) print(f 预计算尖尖A: {vol_tip_A:.6f}, 尖尖B: {vol_tip_B:.6f}) print(f 预计算距离A: {dist_A:.4f}, 距离B: {dist_B:.4f}) # 基于预计算值判断凸起方向 if vol_tip_A vol_tip_B: convex_idx, concave_idx i, j print(fa {i} {geom_names[i]} 的尖尖更小 ({vol_tip_A:.6f} {vol_tip_B:.6f})获得凸起) elif vol_tip_B vol_tip_A: convex_idx, concave_idx j, i print(fb {j} {geom_names[j]} 的尖尖更小 ({vol_tip_B:.6f} {vol_tip_A:.6f})获得凸起) else: # 回退到质心距离距离更小者作为凸起 print(f 尖尖体积相等或无效回退到质心距离) if dist_A dist_B: convex_idx, concave_idx i, j else: convex_idx, concave_idx j, i print(f 回退选择凸起部件 {geom_names[convex_idx]}孔洞部件 {geom_names[concave_idx]}) geom_convex world_geoms[convex_idx] geom_concave world_geoms[concave_idx] name_convex geom_names[convex_idx] name_concave geom_names[concave_idx] print(f 凸部件被切割:{convex_idx} {name_convex}) print(f 凹部件保留: {concave_idx} {name_concave}) # 确定凸起方向 dir_vec centroids[concave_idx] - centroids[convex_idx] norm np.linalg.norm(dir_vec) if norm 1e-8: dir_vec normal norm np.linalg.norm(dir_vec) if norm 1e-8: print(f 无法确定方向跳过) continue dir_vec dir_vec / norm # 计算凸起和孔洞尺寸 area_factor min(max(area / (scene_diag ** 2), 0.3), 1.0) peg_length_actual peg_length * area_factor peg_length_actual max(peg_length_actual, peg_radius * 1.5) bbox_concave geom_concave.bounds bbox_extent bbox_concave[1] - bbox_concave[0] concave_thickness np.dot(bbox_extent, np.abs(dir_vec)) hole_depth_ratio min(max(0.25 * area_factor, 0.15), 0.4) hole_depth max(concave_thickness * hole_depth_ratio, peg_radius * 1.2) peg_extend min(hole_depth * 0.85, peg_length_actual) print(f 交面面积因子: {area_factor:.3f}) print(f 凸起伸出长度: {peg_extend:.4f}) print(f 孔洞深度: {hole_depth:.4f}) # 创建旋转矩阵 z_axis np.array([0, 0, 1]) if np.allclose(dir_vec, z_axis) or np.allclose(dir_vec, -z_axis): rot np.eye(3) else: v np.cross(z_axis, dir_vec) s np.linalg.norm(v) c np.dot(z_axis, dir_vec) vx np.array([[0, -v[2], v[1]], [v[2], 0, -v[0]], [-v[1], v[0], 0]]) rot np.eye(3) vx np.dot(vx, vx) * ((1 - c) / (s ** 2)) # 生成凸起 peg_cyl trimesh.creation.cylinder(radiuspeg_radius, heightpeg_extend, segments24) peg_mid center dir_vec * (peg_extend / 2.0) T_peg np.eye(4) T_peg[:3, :3] rot T_peg[:3, 3] peg_mid peg_cyl.apply_transform(T_peg) # 生成孔洞 hole_cyl trimesh.creation.cylinder(radiushole_radius, heighthole_depth, segments24) hole_mid center - dir_vec * (hole_depth / 2.0) T_hole np.eye(4) T_hole[:3, :3] rot T_hole[:3, 3] hole_mid hole_cyl.apply_transform(T_hole) # 执行布尔运算 # 凸部件添加凸起 try: if bounds_intersect(geom_convex.bounds, peg_cyl.bounds): new_convex trimesh.boolean.union([geom_convex, peg_cyl], engineengine) if isinstance(new_convex, list) and len(new_convex) 0: new_convex new_convex[0] if isinstance(new_convex, trimesh.Trimesh) and new_convex.vertices.shape[0] 0: if new_convex.is_volume or new_convex.volume 1e-10: world_geoms[convex_idx] new_convex peg_count 1 print(f ✅ 凸起添加成功: {name_convex}) else: print(f ⚠️ 凸起结果无效保留原始) else: print(f ⚠️ 凸起合并失败) else: print(f 凸起与{name_convex}无接触跳过) except Exception as e: print(f ❌ 凸起异常: {e}) # 凹部件挖孔洞 try: if bounds_intersect(geom_concave.bounds, hole_cyl.bounds): new_concave trimesh.boolean.difference([geom_concave, hole_cyl], engineengine) if isinstance(new_concave, list) and len(new_concave) 0: vols [g.volume if hasattr(g, volume) else 0 for g in new_concave] new_concave new_concave[np.argmax(vols)] if isinstance(new_concave, trimesh.Trimesh) and new_concave.vertices.shape[0] 0: if new_concave.is_volume or new_concave.volume 1e-10: world_geoms[concave_idx] new_concave hole_count 1 print(f ✅ 孔洞挖除成功: {name_concave}) else: print(f ⚠️ 孔洞结果无效保留原始) else: print(f ⚠️ 孔洞挖除失败) else: print(f 孔洞与{name_concave}无接触跳过) except Exception as e: print(f ❌ 孔洞异常: {e}) print(f\n 凸起添加成功: {peg_count} 个) print(f 孔洞挖除成功: {hole_count} 个) return world_geoms # -------------------- 爆炸视图生成 -------------------- def explode_mesh(mesh, intersectionsNone, explosion_scale0.4, area_threshold_ratio0.06): 生成爆炸视图并在部件之间绘制连接线基于交面中心。 若提供了 intersections含法线仍仅使用中心和面积过滤。 if isinstance(mesh, trimesh.Scene): scene mesh elif isinstance(mesh, trimesh.Trimesh): print(Warning: Single mesh provided, cant create exploded view) scene trimesh.Scene(mesh) return scene else: print(fWarning: Unexpected mesh type: {type(mesh)}) scene mesh if len(scene.geometry) 1: print(Only one geometry found - nothing to explode) return scene print(f[EXPLODE_MESH] Starting mesh explosion with scale {explosion_scale}) print(f[EXPLODE_MESH] Processing {len(scene.geometry)} parts) exploded_scene trimesh.Scene() part_centers [] geometry_names [] for geometry_name, geometry in scene.geometry.items(): if hasattr(geometry, vertices) and geometry.vertices.shape[0] 0: center np.mean(geometry.vertices, axis0) part_centers.append(center) geometry_names.append(geometry_name) print(f[EXPLODE_MESH] Part {geometry_name}: center {center}) if not part_centers: print(No valid geometries with vertices found) return scene part_centers np.array(part_centers) global_center np.mean(part_centers, axis0) print(f[EXPLODE_MESH] Global center: {global_center}) offsets {} for i, (geometry_name, geometry) in enumerate(scene.geometry.items()): if hasattr(geometry, vertices) and geometry.vertices.shape[0] 0: if i len(part_centers): part_center part_centers[i] direction part_center - global_center direction_norm np.linalg.norm(direction) if direction_norm 1e-6: direction direction / direction_norm else: direction np.random.randn(3) direction direction / np.linalg.norm(direction) offset direction * explosion_scale offsets[geometry_name] offset else: offset np.zeros(3) offsets[geometry_name] offset transform np.eye(4) transform[:3, 3] offset exploded_scene.add_geometry(geometry, transformtransform, geom_namegeometry_name) print(f[EXPLODE_MESH] Part {geometry_name}: moved by {np.linalg.norm(offset):.4f}) # 添加连接线基于交面中心 if intersections is not None and len(intersections) 0: areas [item[3] for item in intersections if len(item) 4] if areas: max_area max(areas) threshold max_area * area_threshold_ratio print(f[EXPLODE_MESH] 最大交面面积: {max_area:.6f}, 阈值({threshold:.6f})将保留连接线) else: max_area None threshold None all_points [] line_indices [] filtered_count 0 for item in intersections: if len(item) 3: name_i, name_j, center item[0], item[1], item[2] else: continue if max_area is not None and len(item) 4: area item[3] if area threshold: filtered_count 1 print(f[EXPLODE_MESH] 忽略小面积交面: {name_i} ∩ {name_j} (面积{area:.6f})) continue if name_i in offsets and name_j in offsets: p1 center offsets[name_i] p2 center offsets[name_j] idx1 len(all_points) all_points.append(p1) idx2 len(all_points) all_points.append(p2) line_indices.append([idx1, idx2]) print(f[EXPLODE_MESH] Line between {name_i} and {name_j}) else: print(f[EXPLODE_MESH] 跳过连线 {name_i} ↔ {name_j}部件不存在或已合并) if filtered_count 0: print(f[EXPLODE_MESH] 共过滤掉 {filtered_count} 个小面积交面) if line_indices: vertices np.array(all_points) entities [] for idx_pair in line_indices: entities.append(trimesh.path.entities.Line(pointsnp.array(idx_pair))) path trimesh.path.Path3D(entitiesentities, verticesvertices) exploded_scene.add_geometry(path, geom_nameconnection_lines, transformnp.eye(4)) print(f[EXPLODE_MESH] Added {len(line_indices)} connection lines) else: print([EXPLODE_MESH] No connection lines to add (all filtered out or none)) print([EXPLODE_MESH] Mesh explosion complete) return exploded_scene # -------------------- 主程序入口 -------------------- def cut_glb(input_path, output_path, enginemanifold, top_k6, peg_radiusNone, peg_lengthNone, add_visual_connectorsFalse): 加载 GLB 场景执行切割生成凸起/孔洞并可选添加可视化连接件。 未选中的部件保持独立不合并。 返回 (cut_scene, intersections) scene trimesh.load(input_path, forcescene) if not isinstance(scene, trimesh.Scene): mesh trimesh.load(input_path) if isinstance(mesh, trimesh.Trimesh): scene trimesh.Scene(mesh) else: raise ValueError(无法加载为场景或网格) # 过滤有效几何体 valid_geoms [] invalid_geoms [] empty_geoms [] for name, geom in scene.geometry.items(): if not isinstance(geom, trimesh.Trimesh): invalid_geoms.append((name, f类型错误: {type(geom)})) elif geom.vertices.shape[0] 0 or geom.faces.shape[0] 0: empty_geoms.append((name, f顶点:{geom.vertices.shape[0]}, 面:{geom.faces.shape[0]})) elif not is_valid_mesh(geom, check_volumeFalse): invalid_geoms.append((name, 几何结构无效)) else: valid_geoms.append(name) if not valid_geoms: raise ValueError(警告场景中没有有效的几何体) print(f有效几何体: {len(valid_geoms)} 个) if invalid_geoms: print(f⚠ 无效几何体: {len(invalid_geoms)} 个) for name, reason in invalid_geoms[:5]: print(f - {name}: {reason}) if len(invalid_geoms) 5: print(f ... 还有 {len(invalid_geoms) - 5} 个无效几何体) if empty_geoms: print(f⚠ 空几何体: {len(empty_geoms)} 个) print(f加载场景包含 {len(scene.geometry)} 个子部件) cut_scene, intersections cut_scene_geometries(scene, engineengine, top_ktop_k, peg_radiuspeg_radius, peg_lengthpeg_length, add_visual_connectorsadd_visual_connectors) cut_scene.export(output_path) print(f切割后的场景含凸起/孔洞已保存至: {output_path}) return cut_scene, intersections if __name__ __main__: input_file rbaozha.glb # 输入文件 input_file rbaozha_2.glb # 输入文件 # input_file rC:\Users\ChanJing-01\Documents\niu_2.glb top_k 2 # 保留的核心部件数 output_cut fjian_{top_k}.glb output_explode fjian_explode_{top_k}.glb start time.time() # 执行切割并生成凸起/孔洞不添加可视化连接件 cut_scene, intersections cut_glb(input_file, output_cut, enginemanifold, top_ktop_k, peg_radiusNone, # 自动计算 peg_lengthNone, # 自动计算 add_visual_connectorsFalse # 设为 True 可额外添加独立指示圆柱 ) # 打印交面信息 if intersections: total_area 0.0 print(\n 切割面面积统计全部 ) for item in intersections: if len(item) 4: name_i, name_j, center, area, normal item[:5] print(f {name_i} ∩ {name_j}: 面积 {area:.6f}, 法线 {normal}) total_area area else: print(f {item[0]} ∩ {item[1]}: 面积 (未记录)) print(f总切割面积: {total_area:.6f}) print(\n) else: print(没有检测到切割面。) # 生成爆炸视图基于切割后的场景 explode_scene explode_mesh(cut_scene, intersectionsintersections, explosion_scale0.1, area_threshold_ratio0.0) explode_scene.export(output_explode) print(f爆炸图已保存至: {output_explode} time: {time.time() - start})