trimesh 3d 切割 2026
fenge2.pyimport 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() mesh.remove_unreferenced_vertices() mesh.remove_infinite_values() return mesh def bounds_intersect(a_bounds, b_bounds): 检查两个AABB是否相交 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 try: used_vertices np.unique(geom.faces.flatten()) if len(used_vertices) geom.vertices.shape[0] * 0.5: pass except: pass return True def cut_scene_geometries(scene, enginemanifold): 对场景中的所有部件进行两两切割后面的被前面的切并记录交面中心。 返回 (新场景, 交面信息列表) 交面信息: [(name_i, name_j, center_global, area), ...] # area 为交面总面积 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) # 2. 两两切割并记录交面 intersections [] # 存储 (name_i, name_j, center_global, area) for i in range(len(world_geoms)): for j in range(i1, len(world_geoms)): A world_geoms[i] B world_geoms[j] if not bounds_intersect(A.bounds, B.bounds): continue print(f处理: {geom_names[i]} vs {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 intersections.append((geom_names[i], geom_names[j], center, area)) print(f 记录交面中心: {center}, 面积: {area:.6f}) else: print( 交集为空或无效) else: print( 交集返回 None) except Exception as e: print(f 计算交集失败: {e}) # --- 用 A 切割 B: B B - A --- 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: volumes [r.volume for r in result] result result[np.argmax(volumes)] else: result result[0] if isinstance(result, trimesh.Trimesh): world_geoms[j] result print(f 成功切割 {geom_names[j]}) else: print(f 切割结果类型异常: {type(result)}) except Exception as e: print(f 切割失败: {e}) # 3. 构建新场景所有部件已是世界坐标变换设为单位矩阵 new_scene trimesh.Scene() for name, geom in zip(geom_names, world_geoms): new_scene.add_geometry(geom, geom_namename, transformnp.eye(4)) return new_scene, intersections def cut_glb(input_path, output_path, enginemanifold): 加载 GLB切割所有部件返回 (切割后的场景, 交面信息) 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) cut_scene.export(output_path) print(f切割后的场景已保存至: {output_path}) return cut_scene, intersections def explode_mesh(mesh, intersectionsNone, explosion_scale0.4, area_threshold_ratio0.06): 生成爆炸图并在原本接触的部件之间绘制连接线。 intersections: 从 cut_glb 返回的交面信息列表 [(name_i, name_j, center_global, area), ...] area_threshold_ratio: 面积小于最大面积*ratio 的交面将被忽略不绘制连接线 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): 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): 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: # 1. 计算最大面积仅考虑有面积的项 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}) 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 if __name__ __main__: input_file rbaozha.glb output_cut output_cut.glb output_explode output_explode.glb cut_scene, intersections cut_glb(input_file, output_cut, enginemanifold) # 打印所有切割面的面积汇总 if intersections: total_area 0.0 print(\n 切割面面积统计 ) for item in intersections: if len(item) 4: name_i, name_j, center, area item print(f {name_i} ∩ {name_j}: 面积 {area:.6f}) 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.06) explode_scene.export(output_explode) print(f爆炸图已保存至: {output_explode})

相关新闻

产品经理必备的可导出源码AI生成原型工具排行榜推荐

产品经理必备的可导出源码AI生成原型工具排行榜推荐

产品经理这个岗位,我觉得最痛苦的事情之一就是——好不容易想清楚一个需求,画了原型,跟开发对完,开发说“这个做不了”或者“这个要排到三个月后”。所以我一直很关注能“直接产出可运行源码”的原型工具,这样至少在需…

2026/8/3 0:05:50 阅读更多 →
如何在VMware ESXi上解锁macOS虚拟化:终极ESXi Unlocker完整指南

如何在VMware ESXi上解锁macOS虚拟化:终极ESXi Unlocker完整指南

如何在VMware ESXi上解锁macOS虚拟化:终极ESXi Unlocker完整指南 【免费下载链接】esxi-unlocker VMware ESXi macOS 项目地址: https://gitcode.com/gh_mirrors/es/esxi-unlocker 想要在VMware ESXi企业级虚拟化平台上运行macOS虚拟机吗?ESXi Un…

2026/8/3 0:05:50 阅读更多 →
QModMaster:5分钟掌握工业自动化调试的终极免费Modbus工具

QModMaster:5分钟掌握工业自动化调试的终极免费Modbus工具

QModMaster:5分钟掌握工业自动化调试的终极免费Modbus工具 【免费下载链接】qModbusMaster Fork of QModMaster (https://sourceforge.net/p/qmodmaster/code/ci/default/tree/) 项目地址: https://gitcode.com/gh_mirrors/qm/qModbusMaster 在工业自动化领域…

2026/8/3 0:05:49 阅读更多 →

最新新闻

如何用SPT-AKI存档编辑器快速打造你的完美塔科夫角色:终极免费指南

如何用SPT-AKI存档编辑器快速打造你的完美塔科夫角色:终极免费指南

如何用SPT-AKI存档编辑器快速打造你的完美塔科夫角色:终极免费指南 【免费下载链接】SPT-AKI-Profile-Editor Программа для редактирования профиля игрока на сервере SPT-AKI 项目地址: https://gitcode.com/…

2026/8/3 0:31:04 阅读更多 →
大麦网自动抢票脚本:告别秒光烦恼的终极武器

大麦网自动抢票脚本:告别秒光烦恼的终极武器

大麦网自动抢票脚本:告别秒光烦恼的终极武器 【免费下载链接】Automatic_ticket_purchase 大麦网抢票脚本 项目地址: https://gitcode.com/GitHub_Trending/au/Automatic_ticket_purchase 还在为周杰伦、五月天演唱会门票秒光而抓狂吗?当热门演出…

2026/8/3 0:31:04 阅读更多 →
5分钟搭建原神私服:从零开始的完整免费终极指南

5分钟搭建原神私服:从零开始的完整免费终极指南

5分钟搭建原神私服:从零开始的完整免费终极指南 【免费下载链接】KCN-GenshinServer 基于GC制作的原神一键GUI多功能服务端。 项目地址: https://gitcode.com/gh_mirrors/kc/KCN-GenshinServer 你是否曾想过在本地电脑上搭建一个完全由自己掌控的原神游戏服务…

2026/8/3 0:31:04 阅读更多 →
RedisDesktopManager Windows版:免费Redis可视化工具的终极指南

RedisDesktopManager Windows版:免费Redis可视化工具的终极指南

RedisDesktopManager Windows版:免费Redis可视化工具的终极指南 【免费下载链接】RedisDesktopManager-Windows RedisDesktopManager Windows版本 项目地址: https://gitcode.com/gh_mirrors/re/RedisDesktopManager-Windows RedisDesktopManager Windows版是…

2026/8/3 0:31:04 阅读更多 →
终极指南:如何用AKShare免费获取全球金融数据

终极指南:如何用AKShare免费获取全球金融数据

终极指南:如何用AKShare免费获取全球金融数据 【免费下载链接】akshare AKShare is an elegant and simple financial data interface library for Python, built for human beings! 开源财经数据接口库 项目地址: https://gitcode.com/gh_mirrors/aks/akshare …

2026/8/3 0:31:04 阅读更多 →
BetterGI如何通过计算机视觉技术实现原神自动化:从图像识别到智能决策

BetterGI如何通过计算机视觉技术实现原神自动化:从图像识别到智能决策

BetterGI如何通过计算机视觉技术实现原神自动化:从图像识别到智能决策 【免费下载链接】better-genshin-impact 📦BetterGI 更好的原神 - 自动拾取 | 自动剧情 | 全自动钓鱼(AI) | 全自动七圣召唤 | 自动伐木 | 自动刷本 | 自动采集/挖矿/锄地 | 一条龙…

2026/8/3 0:30:04 阅读更多 →

日新闻

3个让你工作效率翻倍的Umi-OCR实战技巧:免费离线文字识别完全指南

3个让你工作效率翻倍的Umi-OCR实战技巧:免费离线文字识别完全指南

3个让你工作效率翻倍的Umi-OCR实战技巧:免费离线文字识别完全指南 【免费下载链接】Umi-OCR OCR software, free and offline. 开源、免费的离线OCR软件。支持截屏/批量导入图片,PDF文档识别,排除水印/页眉页脚,扫描/生成二维码。…

2026/8/3 0:00:47 阅读更多 →
[具身智能-181]:PC+服务器+具身机器人:构建具身智能从仿真到量产的闭环迭代混合架构

[具身智能-181]:PC+服务器+具身机器人:构建具身智能从仿真到量产的闭环迭代混合架构

PC服务器具身机器人:构建具身智能从仿真到量产的闭环迭代混合架构一、前言:具身智能需要“混合算力闭环系统”传统人工智能依赖云端静态数据集训练,不具备物理交互能力,无法适应真实世界的不确定性。具身智能(Embodied…

2026/8/3 0:00:47 阅读更多 →
[具身智能-181]:大分布式通信模型对比:看懂为什么 DDS 是 ROS2 底层通信最优解

[具身智能-181]:大分布式通信模型对比:看懂为什么 DDS 是 ROS2 底层通信最优解

前言构建机器人、具身智能这类分布式实时系统,通信底座直接决定整套系统的实时性、容错性、组网能力。分布式领域长期存在 4 类经典通信架构:点对点模式、Broker 中间代理模式、广播模式、以数据为中心(DDS)模式。很多开发者疑惑&…

2026/8/3 0:00:47 阅读更多 →

周新闻

最大流算法详解:从水管网络到Ford-Fulkerson与Dinic实战

最大流算法详解:从水管网络到Ford-Fulkerson与Dinic实战

1. 从水管网络到最大流:一个核心问题的诞生想象一下,你是一个城市供水系统的总工程师。你的城市有多个水源(水库),需要通过一个复杂的地下管道网络,将水输送到各个居民区。每条管道都有其最大通水能力&…

2026/8/2 0:00:38 阅读更多 →
基于Springboot的企业门户网站(源码+LW+调试文档+讲解)

基于Springboot的企业门户网站(源码+LW+调试文档+讲解)

温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台…

2026/8/2 0:00:38 阅读更多 →
MATLAB xcorr函数详解:从互相关原理到四大实战应用

MATLAB xcorr函数详解:从互相关原理到四大实战应用

1. 从一次信号“找茬”说起:为什么我们需要互相关几年前,我在处理一组声学传感器数据时遇到了一个棘手的问题。我有两个麦克风记录了一段相同的音频信号,理论上它们接收到的声音波形应该非常相似,只是由于麦克风位置不同&#xff…

2026/8/2 0:00:38 阅读更多 →

月新闻

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南 【免费下载链接】BaiduNetdiskPlugin-macOS For macOS.百度网盘 破解SVIP、下载速度限制~ 项目地址: https://gitcode.com/gh_mirrors/ba/BaiduNetdiskPlugin-macOS 还在为百度网盘macOS版的龟速下…

2026/8/2 6:34:16 阅读更多 →
终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换 【免费下载链接】ncmdump 项目地址: https://gitcode.com/gh_mirrors/ncmd/ncmdump 还在为网易云音乐下载的NCM格式文件无法在其他播放器播放而烦恼吗?ncmdump解密工具帮你轻松解决这个困…

2026/8/2 2:47:48 阅读更多 →
HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

AgentCard 智能体卡片:为英语学习 App 打造桌面级学习助手适用平台:HarmonyOS 7.0 (API 26 Beta)一、引言 HarmonyOS 7.0(API 26 Beta)新增了 AgentCard 智能体卡片能力,这是继 HMAF(鸿蒙智能体框架&#x…

2026/8/2 0:23:22 阅读更多 →