3d打印自动生成连接件
2026/8/8 9:54:19 网站建设 项目流程

目录

思路和步骤

确定凸、凹部件

2. 计算部件质心

3. 计算方向向量 dir_vec

4. 构建旋转矩阵,将默认 Z 轴旋转到 dir_vec

5. 应用旋转与平移,生成凸起圆柱

6. 应用旋转与平移,生成孔洞圆柱(用于布尔差集)

7. 布尔操作

源代码:


思路和步骤

确定凸、凹部件

  • 对于每一对相交的核心部件(i, j),比较它们的体积:

    python

    vol_i = abs(world_geoms[i].volume) vol_j = abs(world_geoms[j].volume) if vol_i >= vol_j: convex_idx, concave_idx = i, j else: convex_idx, concave_idx = j, i

    体积较大的部件作为凸部件(在其上加凸起),体积较小的作为凹部件(在其上挖孔)。
    (注:此处“凸/凹”仅用于区分角色,方向计算依赖于质心连线。)


2. 计算部件质心

  • 预先为所有核心部件计算质心(若无法获取则用顶点均值):

    python

    centroids[idx] = geom.centroid if hasattr(geom, 'centroid') else np.mean(geom.vertices, axis=0)

3. 计算方向向量dir_vec

  • 取凸部件质心指向凹部件质心的向量:

    python

    dir_vec = centroids[concave_idx] - centroids[convex_idx]
  • 归一化:

    python

    norm = np.linalg.norm(dir_vec) if norm < 1e-8: continue # 避免零向量 dir_vec = dir_vec / norm

    该向量即为圆柱的目标轴线方向(Z轴最终对齐的方向)。


4. 构建旋转矩阵,将默认 Z 轴旋转到dir_vec

  • 默认圆柱体(trimesh.creation.cylinder)的轴线沿世界坐标系的 Z 轴z_axis = [0,0,1]

  • 使用Rodrigues 旋转公式(轴角法)构造旋转矩阵:

    • dir_vecz_axis近似平行(同向或反向),则旋转矩阵取单位阵(反向时不需要特殊处理,因为后续位置偏移会补偿)。

    • 否则:

      python

      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))
    • 得到的rot满足rot @ z_axis = dir_vec,即圆柱的轴线将被旋转到dir_vec方向。


5. 应用旋转与平移,生成凸起圆柱

  • 创建默认圆柱(中心在原点,高度peg_extend,沿 Z 轴):

    python

    peg_cyl = trimesh.creation.cylinder(radius=peg_radius, height=peg_extend, segments=24)
  • 计算圆柱的新中心位置:让圆柱的底面恰好位于交面中心center,并向dir_vec方向伸出(即朝凹部件方向):

    python

    peg_mid = center + dir_vec * (peg_extend / 2.0)

    这样,圆柱的底部(Z负端)落在center,顶部在center + dir_vec * peg_extend,恰好从接触面向外伸出。

  • 组合旋转和平移变换:

    python

    T_peg = np.eye(4) T_peg[:3, :3] = rot T_peg[:3, 3] = peg_mid peg_cyl.apply_transform(T_peg)

    此时圆柱轴线沿dir_vec,并从接触面伸出。


6. 应用旋转与平移,生成孔洞圆柱(用于布尔差集)

  • 创建默认圆柱(半径稍大,带公差):

    python

    hole_cyl = trimesh.creation.cylinder(radius=hole_radius, height=hole_depth, segments=24)
  • 计算孔洞圆柱的中心位置:为了让圆柱穿过接触面进入凹部件内部,将中心向dir_vec方向偏移一个较小的量(代码中为hole_depth / 6.0,这仅是一个粗略定位,确保圆柱体大部分在凹部件内部,布尔差集后即可挖出通孔):

    python

    hole_mid = center + dir_vec * (hole_depth / 6.0)
  • 同样应用旋转和平移:

    python

    T_hole = np.eye(4) T_hole[:3, :3] = rot T_hole[:3, 3] = hole_mid hole_cyl.apply_transform(T_hole)

    圆柱轴线同样沿dir_vec,从接触面伸入凹部件内部。


7. 布尔操作

  • 将旋转后的凸起圆柱与凸部件做并集union),得到带凸起的部件。

  • 将旋转后的孔洞圆柱与凹部件做差集difference),得到带孔的部件。

源代码:

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_volume=True, min_vertices=3, min_faces=1): """检查网格是否有效""" 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, axis=1) if np.mean(areas) < 1e-10: return False except: pass return True # -------------------- 核心切割与合并 -------------------- def cut_scene_geometries(scene, engine='manifold', top_k=6, peg_radius=None, peg_length=None, add_visual_connectors=False): """ 按体积选择 top_k 个核心部件,对其他部件执行切割(核心部件被切割), 然后将每个未选中的部件合并到与之接触面积最大的核心部件中。 之后在核心部件的接触面上生成凸起和孔洞(用于3D打印插接)。 返回 (新场景, 交面信息列表) ,交面信息为 (name_i, name_j, center, area, normal) """ 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(vertices=vertices, faces=geom.faces, process=False) 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) 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], engine=engine) 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(axis=1) center = np.mean(face_centers, axis=0) 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, axis=0, weights=face_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]) all_intersections.append((i, j, center, area, normal)) print(f" 记录交面中心: {center}, 面积: {area:.6f}, 法线: {normal}") 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], engine=engine) 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. 合并未选中部件到与之接触面积最大的核心部件 unselected_indices = [idx for idx in range(n) if idx not in keep_indices] print(f"\n未选中的部件索引: {unselected_indices},共 {len(unselected_indices)} 个") contact_map = {idx: {} for idx in unselected_indices} for i, j, _, area, _ in all_intersections: if i in keep_indices and j in unselected_indices: contact_map[j][i] = contact_map[j].get(i, 0.0) + area elif j in keep_indices and i in unselected_indices: contact_map[i][j] = contact_map[i].get(j, 0.0) + area for un_idx in unselected_indices: if not contact_map[un_idx]: print(f"警告: 部件 {geom_names[un_idx]} 与任何核心部件均无接触,将保持独立") continue best_core = max(contact_map[un_idx], key=contact_map[un_idx].get) best_area = contact_map[un_idx][best_core] print(f"将 {geom_names[un_idx]} 合并到核心 {geom_names[best_core]} (交面面积 {best_area:.6f})") try: merged = trimesh.util.concatenate([world_geoms[best_core], world_geoms[un_idx]]) merged = prepare_mesh(merged) if merged.vertices.shape[0] > 0 and merged.faces.shape[0] > 0: world_geoms[best_core] = merged else: print(f" 合并结果无效,保留独立") except Exception as e: print(f" 合并失败: {e},保留独立") # 5.5 在核心部件上生成凸起和孔洞(用于3D打印插接) print("\n===== 开始生成凸起和孔洞 =====") core_indices = sorted(keep_indices) world_geoms = add_peg_and_hole_to_parts( world_geoms, geom_names, all_intersections, core_indices, radius=peg_radius, length=peg_length, engine=engine ) # 6. 构建新场景:只保留核心部件 new_scene = trimesh.Scene() for idx in core_indices: name = geom_names[idx] geom = world_geoms[idx] if geom.vertices.shape[0] > 0 and geom.faces.shape[0] > 0: new_scene.add_geometry(geom, geom_name=name, transform=np.eye(4)) print(f"添加核心部件: {name} (已合并相邻未选中部件,并已加工凸起/孔洞)") else: print(f"警告: 核心部件 {name} 无效,跳过添加") # 可选:添加可视化的连接件(独立圆柱) if add_visual_connectors: new_scene = add_connectors_as_visual(new_scene, all_intersections, core_names=geom_names) print(f"最终场景包含 {len(new_scene.geometry)} 个几何体") print(f"几何体名称列表: {list(new_scene.geometry.keys())}") # 7. 返回交面信息(含法线) intersections_return = [] for i, j, center, area, normal in all_intersections: intersections_return.append((geom_names[i], geom_names[j], center, area, normal)) return new_scene, intersections_return import trimesh import numpy as np def add_peg_and_hole_to_parts(world_geoms, geom_names, intersections, core_indices, radius=None, length=None, tolerance=0.002, engine='manifold'): """ 微调版: 1. 凸起底面严格贴相交面中心,只向外侧凸出去,不埋入凸零件 2. 孔洞开口严格贴相交面中心,向凹零件内部挖;深度按【凹部件包围盒厚度】比例计算 3. peg伸出 < hole深度,装配不会顶死;沿用原有质心dir_vec方向逻辑 """ 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, axis=0)) if radius is None: peg_radius = max(0.01 * scene_diag, 0.001) else: peg_radius = radius hole_radius = peg_radius + tolerance centroids = {} for idx in core_indices: g = world_geoms[idx] centroids[idx] = g.centroid if hasattr(g, 'centroid') else np.mean(g.vertices, axis=0) processed_pairs = set() for i, j, center, area, normal in intersections: 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) vol_i = abs(getattr(world_geoms[i], 'volume', 0)) vol_j = abs(getattr(world_geoms[j], 'volume', 0)) if vol_i >= vol_j: convex_idx, concave_idx = i, j else: convex_idx, concave_idx = j, i geom_convex = world_geoms[convex_idx] geom_concave = world_geoms[concave_idx] # dir_vec:凸部件质心 → 凹部件质心(向外就是 -dir_vec) dir_vec = centroids[concave_idx] - centroids[convex_idx] norm = np.linalg.norm(dir_vec) if norm < 1e-8: continue dir_vec = dir_vec / norm # ========= 按凹部件包围盒,计算孔洞深度 ========= bbox_concave = geom_concave.bounds bbox_extent = bbox_concave[1] - bbox_concave[0] # 凹零件沿dir_vec方向的物理厚度 concave_thickness = np.dot(bbox_extent, np.abs(dir_vec)) # 取凹零件厚度的0.25作为孔洞深度,下限不小于销直径,避免孔过浅 hole_depth = max(concave_thickness * 0.15, peg_radius * 1.2) # 凸起伸出长度,比孔洞短一点,装配留余量 peg_extend = hole_depth * 0.85 # 旋转矩阵,圆柱Z对齐dir_vec 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:底面正好在center,朝 -dir_vec(凸零件外侧)伸出去 ========= # cylinder高度=peg_extend;圆柱中点 = center + 向外偏移半高,底面落在center peg_cyl = trimesh.creation.cylinder(radius=peg_radius, height=peg_extend, segments=24) # peg_mid = center - dir_vec * (peg_extend / 2.0) 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:开口正好在center,朝 +dir_vec(凹零件内部)挖入 ========= hole_cyl = trimesh.creation.cylinder(radius=hole_radius, height=hole_depth, segments=24) # hole_mid = center + dir_vec * (hole_depth / 2.0) hole_mid = center + dir_vec * (hole_depth / 6.0) T_hole = np.eye(4) T_hole[:3, :3] = rot T_hole[:3, 3] = hole_mid hole_cyl.apply_transform(T_hole) # 布尔合并凸起 try: new_convex = trimesh.boolean.union([geom_convex, peg_cyl], engine=engine) if isinstance(new_convex, trimesh.Trimesh) and new_convex.is_volume: world_geoms[convex_idx] = new_convex print(f"✅ 凸起 {geom_names[convex_idx]} 伸出:{peg_extend:.4f}") else: print(f"⚠️ 凸起合并失败 {geom_names[convex_idx]}") except Exception as e: print(f"❌ 凸起异常 {geom_names[convex_idx]}: {e}") # 布尔挖孔洞 try: new_concave = trimesh.boolean.difference([geom_concave, hole_cyl], engine=engine) if isinstance(new_concave, trimesh.Trimesh) and new_concave.is_volume: world_geoms[concave_idx] = new_concave print(f"✅ 孔洞 {geom_names[concave_idx]} 深度:{hole_depth:.4f}") else: print(f"⚠️ 孔洞挖除失败 {geom_names[concave_idx]}") except Exception as e: print(f"❌ 孔洞异常 {geom_names[concave_idx]}: {e}") return world_geoms def add_connectors_as_visual(scene, intersections, core_names=None, radius=None, length=None): """添加独立的小圆柱作为连接件指示,两端指向两个部件的质心""" 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, axis=0) 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(radius=radius, height=length, segments=16) 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 = f"visual_connector_{name_i}_{name_j}" scene.add_geometry(cyl, geom_name=conn_name, transform=np.eye(4)) added += 1 print(f"添加可视化连接件: {conn_name} 于 {center}") print(f"共添加 {added} 个可视化连接件") return scene # -------------------- 爆炸视图生成 -------------------- def explode_mesh(mesh, intersections=None, explosion_scale=0.4, area_threshold_ratio=0.06): """ 生成爆炸视图,并在部件之间绘制连接线(基于交面中心)。 若提供了 intersections(含法线),仍仅使用中心和面积过滤。 """ if isinstance(mesh, trimesh.Scene): scene = mesh elif isinstance(mesh, trimesh.Trimesh): print("Warning: Single mesh provided, can't create exploded view") scene = trimesh.Scene(mesh) return scene else: print(f"Warning: 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, axis=0) 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, axis=0) 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, transform=transform, geom_name=geometry_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(points=np.array(idx_pair))) path = trimesh.path.Path3D(entities=entities, vertices=vertices) exploded_scene.add_geometry(path, geom_name='connection_lines', transform=np.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, engine='manifold', top_k=6, peg_radius=None, peg_length=None, add_visual_connectors=False): """ 加载 GLB 场景,执行切割合并,生成凸起/孔洞,并可选添加可视化连接件。 返回 (cut_scene, intersections) """ scene = trimesh.load(input_path, force='scene') 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_volume=False): 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, engine=engine, top_k=top_k, peg_radius=peg_radius, peg_length=peg_length, add_visual_connectors=add_visual_connectors ) cut_scene.export(output_path) print(f"切割后的场景(含凸起/孔洞)已保存至: {output_path}") return cut_scene, intersections if __name__ == "__main__": input_file = r"baozha.glb" # 输入文件 top_k = 4 # 保留的核心部件数 output_cut = f"ka2_{top_k}.glb" output_explode = f"ka2_explode_{top_k}.glb" start=time.time() # 执行切割并生成凸起/孔洞(不添加可视化连接件) cut_scene, intersections = cut_glb( input_file, output_cut, engine='manifold', top_k=top_k, peg_radius=None, # 自动计算 peg_length=None, # 自动计算 add_visual_connectors=False # 设为 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, intersections=intersections, explosion_scale=0.1, area_threshold_ratio=0.0 ) explode_scene.export(output_explode) print(f"爆炸图已保存至: {output_explode} time: {time.time() - start}")

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询