双视图重建3d solid

 

import { FaceNode } from "chili";
import {IDocument,IEdge,Logger,ShapeNode,XYZ
} from "chili-core";
import { Graph } from "graphlib";
function pointToString(point: XYZ): string {return `${point.x.toFixed(0)}-${point.y.toFixed(0)}-${point.z.toFixed(0)}`;}
function buildGraphFromEdges(edges: IEdge[]
): { graph: Graph; edgeIndexMap: Map<string, number[]> } {const graph = new Graph({ directed: false });const edgeIndexMap = new Map<string, number[]>();edges.forEach((edge, index) => {const start = pointToString(edge.curve().startPoint());const end = pointToString(edge.curve().endPoint());graph.setEdge(start, end);if (!edgeIndexMap.has(start)) edgeIndexMap.set(start, []);if (!edgeIndexMap.has(end)) edgeIndexMap.set(end, []);edgeIndexMap.get(start)!.push(index);edgeIndexMap.get(end)!.push(index);});return { graph, edgeIndexMap };
}
function findCyclesWithEdgeIndices(graph: Graph,edgeIndexMap: Map<string, number[]>
): { nodeCycles: string[][]; edgeCycles: number[][] } {const nodeCycles: string[][] = [];const edgeCycles: number[][] = [];const addedCycles = new Set<string>(); // 新增:用于记录已添加的环路径for (const start of graph.nodes()) {const stack: { current: string; path: string[]; visited: Set<string> }[] = [];stack.push({ current: start, path: [], visited: new Set() });while (stack.length > 0) {const { current, path, visited } = stack.pop()!;if (visited.has(current)) continue;const newPath = [...path, current];const newVisited = new Set(visited);newVisited.add(current);const neighbors = graph.neighbors(current) || [];for (const neighbor of neighbors) {if (newPath.length > 1 && newPath[newPath.length - 2] === neighbor) {continue;}if (neighbor === start && newPath.length >= 3) {// 构造唯一标识符并判断是否重复const cycleKey = [...newPath].sort().join('-');if (!addedCycles.has(cycleKey)) {nodeCycles.push(newPath);addedCycles.add(cycleKey); // 标记为已添加// 提取边索引逻辑const edgeIndices = new Set<number>();for (let i = 0; i < newPath.length; i++) {const a = newPath[i];const b = newPath[(i + 1) % newPath.length];const indicesA = edgeIndexMap.get(a) || [];const indicesB = edgeIndexMap.get(b) || [];const common = indicesA.filter((idx) => indicesB.includes(idx));common.forEach((idx) => edgeIndices.add(idx));}edgeCycles.push([...edgeIndices]);}} else if (!newVisited.has(neighbor)) {stack.push({current: neighbor,path: newPath,visited: newVisited,});}}}}return { nodeCycles, edgeCycles };
}
function isPointsCoplanar(points: XYZ[]): boolean {if (points.length <= 3) return true; // 3个点或更少一定共面const p0 = points[0];const p1 = points[1];const p2 = points[2];// 计算平面的法向量const v1 =new XYZ(p1.x - p0.x,p1.y - p0.y,p1.z - p0.z) ;const v2 =new XYZ(p2.x - p0.x,p2.y - p0.y,p2.z - p0.z ) ;const normal = crossProduct(v1, v2);// 平面方程 ax + by + cz + d = 0const a = normal.x;const b = normal.y;const c = normal.z;const d = -(a * p0.x + b * p0.y + c * p0.z);for (let i = 3; i < points.length; i++) {const p = points[i];const distance = Math.abs(a * p.x + b * p.y + c * p.z + d);if (distance > Number.EPSILON) {return false; // 点不共面}}return true;
}// 向量叉乘
function crossProduct(v1: XYZ, v2: XYZ): XYZ {const xyz=new XYZ(  v1.y * v2.z - v1.z * v2.y,v1.z * v2.x - v1.x * v2.z,v1.x * v2.y - v1.y * v2.x,)return xyz;
}
function addUniquePoint(point: XYZ, set: Set<string>, list: XYZ[]) {const key = `${point.x},${point.y},${point.z}`;if (!set.has(key)) {set.add(key);list.push(point);}
}
// 提取边中的点
function extractPointsFromEdges(edges: IEdge[]): XYZ[] {const pointsSet = new Set<string>();const pointsList: XYZ[] = [];edges.forEach(edge => {const start = edge.curve().startPoint();const end = edge.curve().endPoint();addUniquePoint(start, pointsSet, pointsList);addUniquePoint(end, pointsSet, pointsList);});return pointsList;
}
export function face_rebuild(document: IDocument): void {const models=document.selection.getSelectedNodes().map((x) => x as ShapeNode).filter((x) => {if (x === undefined) return false;let shape = x.shape.value;if (shape === undefined) return false;return true;});document.selection.clearSelection();const edges = models.map((x) => x.shape.value.copy()) as IEdge[];  const { graph, edgeIndexMap } = buildGraphFromEdges(edges);
const { nodeCycles, edgeCycles } = findCyclesWithEdgeIndices(graph, edgeIndexMap);
const addedCycles = new Set<string>(); // 用于跟踪已添加的环
const validEdgeCycles = edgeCycles.filter((cycle, i) => nodeCycles[i].length >= 4);
Logger.info("Edge Cycles:", validEdgeCycles);for (let i = 0; i < validEdgeCycles .length; i++) {const edgeCycle = validEdgeCycles [i];const nodeCycle = nodeCycles[i];// 提取该环涉及的所有边const cycleEdges = edgeCycle.map(index => edges[index]);// 判断这些边是否共面const points = extractPointsFromEdges(cycleEdges);const isCoplanar = isPointsCoplanar(points);if (isCoplanar) {document.addNode(new FaceNode(document, cycleEdges));} else {Logger.info(`Skipped non-coplanar cycle (edges: ${edgeCycle.join(",")}):`, nodeCycle.join(" → "));}
}}

多了一个面少了一个面

问题应该是出在取点没有按edge顺序导致面不准确



8条边输出了10条

完美

import { FaceNode } from "chili";
import {IDocument,IEdge,Logger,ShapeNode,XYZ
} from "chili-core";
import { Graph } from "graphlib";
function pointToString(point: XYZ): string {return `${point.x.toFixed(1)}-${point.y.toFixed(1)}-${point.z.toFixed(1)}`;}
function buildGraphFromEdges(edges: IEdge[]
): { graph: Graph; edgeIndexMap: Map<string, number[]> } {const graph = new Graph({ directed: false });const edgeIndexMap = new Map<string, number[]>();const edgePairSet = new Map<string, number>(); // 新增:记录已添加的边对edges.forEach((edge, index) => {const start = pointToString(edge.curve().startPoint());const end = pointToString(edge.curve().endPoint());// 构建唯一键,保证无向边的唯一性(无论起点终点顺序)const key = [start, end].sort().join('|');// 如果该边对已经存在,则不再重复添加图边if (!edgePairSet.has(key)) {graph.setEdge(start, end);edgePairSet.set(key, index);}// 维护每个点对应的边索引if (!edgeIndexMap.has(start)) edgeIndexMap.set(start, []);if (!edgeIndexMap.has(end)) edgeIndexMap.set(end, []);edgeIndexMap.get(start)!.push(index);edgeIndexMap.get(end)!.push(index);});return { graph, edgeIndexMap };
}
function findCyclesWithEdgeIndices(graph: Graph,edgeIndexMap: Map<string, number[]>
): { nodeCycles: string[][]; edgeCycles: number[][] } {const nodeCycles: string[][] = [];const edgeCycles: number[][] = [];const addedCycles = new Set<string>(); // 记录已添加的环路径const usedEdgesInCycles = new Set<string>(); // 记录所有已被使用的边组合for (const start of graph.nodes()) {const stack: { current: string; path: string[]; visited: Set<string> }[] = [];stack.push({ current: start, path: [], visited: new Set() });while (stack.length > 0) {const { current, path, visited } = stack.pop()!;if (visited.has(current)) continue;const newPath = [...path, current];const newVisited = new Set(visited);newVisited.add(current);const neighbors = graph.neighbors(current) || [];for (const neighbor of neighbors) {// 跳过回头路(避免 A → B → A)if (newPath.length > 1 && newPath[newPath.length - 2] === neighbor) {continue;}// 检查是否回到起点并形成环if (neighbor === start && newPath.length >= 3) {// 构造唯一标识符(排序节点)用于去重const cycleKey = [...newPath].sort().join('-');if (!addedCycles.has(cycleKey)) {nodeCycles.push(newPath);addedCycles.add(cycleKey); // 标记为已添加// 提取边索引,并确保每条边只用一次const edgeIndices = new Set<number>();const usedEdgesInThisCycle = new Set<string>(); // 当前环中已使用的边for (let i = 0; i < newPath.length; i++) {const a = newPath[i];const b = newPath[(i + 1) % newPath.length];// 边的唯一标识(无向)const edgeKey = [a, b].sort().join('|');// 如果这条边已经被这个环使用过,则跳过if (usedEdgesInThisCycle.has(edgeKey)) continue;// 获取共享该边的索引const indicesA = edgeIndexMap.get(a) || [];const indicesB = edgeIndexMap.get(b) || [];const common = indicesA.filter((idx) => indicesB.includes(idx));if (common.length > 0) {edgeIndices.add(common[0]); // 使用第一个匹配的边索引usedEdgesInThisCycle.add(edgeKey); // 当前环中标记为已使用usedEdgesInCycles.add(edgeKey);   // 全局标记为已使用}}edgeCycles.push([...edgeIndices]);}} else if (!newVisited.has(neighbor)) {// 继续深度优先搜索stack.push({current: neighbor,path: newPath,visited: newVisited,});}}}}return { nodeCycles, edgeCycles };
}
function isPointsCoplanar(points: XYZ[]): boolean {if (points.length <= 3) return true; // 3个点或更少一定共面const p0 = points[0];const p1 = points[1];const p2 = points[2];// 计算平面的法向量const v1 =new XYZ(p1.x - p0.x,p1.y - p0.y,p1.z - p0.z) ;const v2 =new XYZ(p2.x - p0.x,p2.y - p0.y,p2.z - p0.z ) ;const normal = crossProduct(v1, v2);// 平面方程 ax + by + cz + d = 0const a = normal.x;const b = normal.y;const c = normal.z;const d = -(a * p0.x + b * p0.y + c * p0.z);for (let i = 3; i < points.length; i++) {const p = points[i];const distance = Math.abs(a * p.x + b * p.y + c * p.z + d);if (distance > Number.EPSILON) {return false; // 点不共面}}return true;
}// 向量叉乘
function crossProduct(v1: XYZ, v2: XYZ): XYZ {const xyz=new XYZ(  v1.y * v2.z - v1.z * v2.y,v1.z * v2.x - v1.x * v2.z,v1.x * v2.y - v1.y * v2.x,)return xyz;
}
function addUniquePoint(point: XYZ, set: Set<string>, list: XYZ[]) {const key = `${point.x},${point.y},${point.z}`;if (!set.has(key)) {set.add(key);list.push(point);}
}
// 提取边中的点
function extractPointsFromEdges(edges: IEdge[]): XYZ[] {const pointsSet = new Set<string>();const pointsList: XYZ[] = [];edges.forEach(edge => {const start = edge.curve().startPoint();const end = edge.curve().endPoint();addUniquePoint(start, pointsSet, pointsList);addUniquePoint(end, pointsSet, pointsList);});return pointsList;
}
export function face_rebuild(document: IDocument): void {const models=document.selection.getSelectedNodes().map((x) => x as ShapeNode).filter((x) => {if (x === undefined) return false;let shape = x.shape.value;if (shape === undefined) return false;return true;});document.selection.clearSelection();const edges = models.map((x) => x.shape.value.copy()) as IEdge[];  const { graph, edgeIndexMap } = buildGraphFromEdges(edges);
const { nodeCycles, edgeCycles } = findCyclesWithEdgeIndices(graph, edgeIndexMap);
const addedCycles = new Set<string>(); // 用于跟踪已添加的环
const validEdgeCycles = edgeCycles.filter((cycle, i) => nodeCycles[i].length >= 4);
//Logger.info("Edge Cycles:", validEdgeCycles);for (let i = 0; i < validEdgeCycles .length; i++) {const edgeCycle = validEdgeCycles [i];const nodeCycle = nodeCycles[i];// 提取该环涉及的所有边const cycleEdges = edgeCycle.map(index => edges[index]);// 判断这些边是否共面const points = extractPointsFromEdges(cycleEdges);const isCoplanar = isPointsCoplanar(points);if (isCoplanar) {Logger.info(`Rebuilding face (edges: ${edgeCycle.join(",")}):`, nodeCycle.join(" → "));cycleEdges.map(edge => (Logger.info(`  ${pointToString(edge.curve().startPoint())} → ${pointToString(edge.curve().endPoint())}`)));document.addNode(new FaceNode(document, cycleEdges));} else {//  Logger.info(`Skipped non-coplanar cycle (edges: ${edgeCycle.join(",")}):`, nodeCycle.join(" → "));}
}}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/86919.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/86919.shtml
英文地址,请注明出处:http://en.pswp.cn/web/86919.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Kotlin 协程使用与通信

一、协程基础使用 1. 协程的三种创建方式 (1) launch - 启动后台作业 val job CoroutineScope(Dispatchers.IO).launch {// 后台操作delay(1000)println("任务完成 ${Thread.currentThread().name}")// 输出&#xff1a;任务完成 DefaultDispatcher-worker-1 } j…

Ubuntu服务器(公网)- Ubuntu客户端(内网)的FRP内网穿透配置教程

以下是为Ubuntu服务器&#xff08;公网&#xff09;- Ubuntu客户端&#xff08;内网&#xff09;的FRP内网穿透配置教程&#xff0c;基于最新版本&#xff08;2025年6月&#xff0c;使用frp_0.61.1_linux_amd64&#xff09;整理&#xff1a; 一、服务端配置&#xff08;公网Ubu…

什么是哈希函数(SHA-256)

SHA-256 是区块链系统中最核心的加密基础之一&#xff0c;尤其是在比特币、以太坊、文件存证等场景中扮演“指纹识别器”的角色。下面是对它的详细讲解&#xff0c;包括原理、特点、用途和代码示例。 &#x1f4cc; 一、什么是 SHA-256&#xff1f; SHA-256 是一种密码学哈希函…

大模型的“Tomcat”:一文读懂AI推理引擎(Inference Engine)

点击下方“JavaEdge”&#xff0c;选择“设为星标” 第一时间关注技术干货&#xff01; 免责声明~ 任何文章不要过度深思&#xff01; 万事万物都经不起审视&#xff0c;因为世上没有同样的成长环境&#xff0c;也没有同样的认知水平&#xff0c;更「没有适用于所有人的解决方案…

《从0到1:C/C++音视频开发自学完全指南》

从0到1&#xff1a;C/C音视频开发自学完全指南 一、开篇&#xff1a;为什么选择C/C切入音视频开发&#xff1f; 当你刷着抖音短视频、参加腾讯会议、观看B站直播时&#xff0c;背后都是音视频技术在支撑。根据艾瑞咨询数据&#xff0c;2024年中国音视频相关产业规模已突破5000…

微信小程序之单行溢出隐藏和双行溢出隐藏

首先&#xff0c;我们做个text&#xff0c;加入了一个长文本&#xff0c;就像下面那样&#xff1a; wxml : <view class"container"><text>刘德华&#xff08;Andy Lau&#xff09;&#xff0c;1961年9月27日出生于中国香港&#xff0c;华语影视男演员、…

PHP安装使用教程

一、PHP 简介 PHP&#xff08;Hypertext Preprocessor&#xff09;是一种广泛应用的开源服务器端脚本语言&#xff0c;尤其适用于 Web 开发&#xff0c;可嵌入 HTML 中使用。其运行速度快、易学易用&#xff0c;支持多种数据库和平台。 二、PHP 安装教程 2.1 支持平台 PHP 支…

ThreadLocal、InheritableThreadLocal与TransmittableThreadLocal深度解析

文章目录 一、概念说明1、ThreadLocal2、InheritableThreadLocal3、TransmittableThreadLocal 二、使用场景1、ThreadLocal2、InheritableThreadLocal3、TransmittableThreadLocal 三、存在的问题1、ThreadLocal2、InheritableThreadLocal3、TransmittableThreadLocal 四、示例…

ERP系统Bug记录

2025.06.30 2025/06/30-10:51:02 [http-nio-9999-exec-3] com.yxx.jsh.erp.service.LogService - 异常码[300],异常提示[数据查询异常],异常[{}] java.lang.NullPointerException: nullat com.yxx.jsh.erp.base.TableSupport.getBuildPageRequest(TableSupport.java:46)at com…

C# Avalonia 的 Source Generators 用处

C# Avalonia 的 Source Generators 用处 文章目录 **1. 自动生成 MVVM 绑定代码****2. 强类型 XAML 数据绑定****3. 自动注册视图&#xff08;View&#xff09;与视图模型&#xff08;ViewModel&#xff09;****4. 资源文件与本地化的强类型访问****5. 路由事件与命令的自动化处…

stm32之测量占空比

#include "tim4.h"void TIM4_Init(void) {// 开启时钟RCC->APB1ENR | RCC_APB1ENR_TIM4EN;RCC->APB2ENR | RCC_APB2ENR_IOPBEN; // 使用 TIM4 的 GPIOB 时钟// 配置 PB6 为浮空输入 CNF 01 MODE 00GPIOB->CRL & ~GPIO_CRL_MODE6;GPIOB->CRL & ~G…

工厂模式 - Flutter中的UI组件工厂,按需生产各种“产品

想要动态创建不同风格的按钮&#xff1f;想一键切换整个主题&#xff1f;工厂模式就是你的"生产流水线"&#xff01; 想象一下这个场景&#xff1a; 你决定扩大奶茶店业务&#xff0c;推出两个品牌系列&#xff1a; 经典系列&#xff1a;传统珍珠奶茶&#xff0c;红…

基于 SpringBoot+Vue.js+ElementUI 的 Cosplay 论坛设计与实现7000字论文

基于 SpringBootVue.jsElementUI 的 Cosplay 论坛设计与实现 摘要 本论文设计并实现了一个基于 SpringBoot、Vue.js 和 ElementUI 的 Cosplay 论坛平台。该平台旨在为 Cosplay 爱好者提供一个集作品展示、交流互动、活动组织于一体的综合性社区。论文首先分析了 Cosplay 论坛…

超标量处理器11-Alpha21264 处理器

1. 简介 21264处理器是一款4-way&#xff0c;乱序执行的超标量处理器&#xff0c;采用0.35um的CMOS工艺&#xff0c;工作电压是2.2V, 工作频率是466-667MHz; 处理器能支持60条指令&#xff0c;也即ROB的深度是60; Load/Store指令也采取乱序执行, 总共7级流水。I-CACHE和D-CACH…

Spring 中 Bean 的生命周期

一、什么是 Bean 生命周期&#xff1f; Spring 中的 Bean 生命周期是指一个 Bean 从 被容器创建到 最终销毁 所经历的一系列过程。 它体现了 Spring IOC 容器在管理 Bean 实例时所执行的各个钩子流程&#xff0c;包括初始化、依赖注入、增强处理、销毁等多个环节。 二、Bean 生…

C++ 中 std::string 与 QString 的深度剖析

在 C 编程领域&#xff0c;std::string 和 QString 是两种广泛应用的字符串类型&#xff0c;它们在设计理念、功能特性以及适用场景上都呈现出鲜明的特点。本文将从多个维度对这两种字符串类型进行深度剖析&#xff0c;并详细阐述它们之间的相互转化方法。 std::string 是 C 标…

不止于“修补”:我如何用Adobe AI重塑设计与视频叙事流程

最近我深度体验了一把来自英国Parvis School of Economics and Music的Adobe正版教育订阅&#xff0c;在把玩PhotoShop、Premiere Pro这些“老伙计”的新功能时&#xff0c;的确挖到了一些宝藏&#xff0c;觉得非常有必要与大家说道说道。首先得聊聊这个订阅给我的直观感受&…

重头开始学ROS(5)---阿克曼底盘的URDF建模与Gazebo控制(使用Xacro优化)

阿克曼底盘的URDF建模与Gazebo控制&#xff08;使用Xacro优化&#xff09; 阿克曼底盘建模 建模 我们使用后轮驱动&#xff0c;前轮转向的阿克曼底盘模型。 那么对于后轮只需进行正常的continous joint连接即可 对于前轮&#xff0c;有两个自由度&#xff0c;旋转和转向&…

RabbitMq中启用NIO

✅ 所属类 com.rabbitmq.client.ConnectionFactory&#x1f9e0; 使用背景 RabbitMQ Java 客户端默认使用传统的 阻塞 I/O (java.net.Socket) 实现。如果你希望&#xff1a; 更好地控制 线程数获得更好的 并发性能降低 每个连接的线程占用在高并发连接或消费者数量较多的系统…

[Dify]-基础篇2- 如何注册并快速上手 Dify 平台

在生成式 AI 应用开发新时代,如何快速搭建一个高效、可维护、易上线的 AI 工具,是每位开发者关注的核心。Dify,正是为此而生的一站式平台。本篇将以新手视角,带你从注册账号、配置环境,到构建应用、部署上线,手把手完成你的第一个 AI 项目。 注册并设置工作环境 1. 账号…