跳到内容
CADCAMFUN
文档

文档

视口

Canvas 绘图工具和 Three.js 渲染共享核心几何体,同时保持编辑和播放状态明确。

从英文自动翻译。 查看英文原文

视口组件

apps/desktop/src/viewport/index.ts 出口 Editor2D, View3D,它们的道具类型,可用的工具标识符,以及 solidsFromProfiles。两个组件都填充其父组件:提供一个具有非零宽度和高度的容器。核心 CAD 实体和 CAM 程序是共享数据模型。

2D 编辑器渲染到 Canvas 2D 并维护自己的选择、相机和命令历史记录。 3D 查看器使用 Three.js、轨道和变换控件、异步网格作业和程序播放。渲染路径或实体与保存它或验证制造操作无关。

整合合同

Editor2DProps 需要 drawing, onChange, 和 selectedTool。将发出的图纸送回道具中。该组件识别它自己发出的文档对象;从外部替换文档会加载新绘图并重置编辑器状态。

受控的 2D 编辑器
import { createSignal } from "solid-js"
import { Editor2D, type ToolId } from "./viewport/index.ts"

const [drawing, setDrawing] = createSignal(initialDrawing)
const [tool, setTool] = createSignal<ToolId>("select")

<Editor2D drawing={drawing()} onChange={setDrawing} selectedTool={tool()} />

选择会排除锁定的实体和锁定图层上的实体。不可见实体和隐藏层被排除在渲染的空间索引之外。新实体接收文档唯一标识符,并在可用时将其分配给可见的未锁定层。

绘图工具和手势

工具注册表具有用于选择、直线、矩形、圆、椭圆、折线、圆弧、文本、尺寸、移动、旋转、缩放、镜像、偏移、修剪和圆角的工厂。 AVAILABLE_TOOLS 来自那些注册工厂。 startTool 另外认识到 chamfer 通过圆角工具工厂,但倒角未在该数组中公布。消费者应使用实际的注册表来决定启用哪些工具。

  • 滚轮或捏合缩放;按空格键拖动、按中键拖动和平移鼠标右键。
  • F 适合索引绘图。删除或退格键可删除解锁的选择。
  • Cmd/Ctrl+Z 撤销; Cmd/Ctrl+Shift+Z 或 Cmd/Ctrl+Y 重做。
  • Enter 提交多段线。 Escape 被传递到活动工具以取消其当前手势。
  • 在文本处于活动状态时,在放置前键入字符或退格键;初始文本是 Text.

在工具调度之前,指针位置会转换为世界坐标。捕捉结合了空间索引、几何候选和依赖于相机的网格;选择命中容差对应于八个屏幕像素。工具机生成命令和预览,而不是编写文件或调用 API。

3D 渲染和播放

View3D 接受图纸、实体、可选型材挤压、 cam.Program、库存尺寸和工具定义。它支持透视、顶、底、前、后、左、右、等距和四视图预设。显示模式有阴影、带边缘阴影、线框和X射线。照明、材质属性、裁剪部分、网格、轴和原点都可以通过道具进行配置。

控制播放 playing, playbackTime 在几秒钟内, playbackRate, 和 onPlaybackTimeChange。回放可扩展钻孔循环、细分圆形/螺旋运动、保留停留时间并清晰地绘制快速移动。刀具图和可选的默认刀具供应刀具、刀柄和刀柄几何形状;假定的几何形状在视觉上是可区分的。

选择回调报告可靠的索引。变换控件通过以下方式发出位置、旋转和缩放 onTransformCommit;调用者决定如何保持这种转换。刀具路径叠加可以显示点和标签、点类型过滤器、圆弧中心、圆弧半径和毛坯透明度。

回放是一种视觉表现

观看者对所提供的程序进行动画处理并绘制库存和工具。它不执行材料去除。碰撞和机器包络检查属于CAM验证模块;查看程序并不构成机器认可。

坐标和单位

核心绘图坐标和毛坯尺寸使用毫米。绘制角度和Three.js旋转组件使用弧度;请勿将该约定应用于明确使用度数的 CAM 旋转轴值或特征输入。 viewport/units.ts 包含显示毫米和英寸的转换助手。在输入/输出边界处进行转换,以便捕捉、几何图形和刀具路径运动保持一致的单位系统。

2D 相机将世界坐标映射到 CSS 像素;背景画布尺寸包含设备像素比。调整大小通过更新相机和后备缓冲区 ResizeObserver。相机比例决定网格间距和屏幕空间命中容差。

英寸显示精确地转换为每英寸 25.4 毫米,长度默认为 3 小数位(apps/desktop/src/viewport/units.ts)。在 2D 编辑器中,每个滚轮步长都会缩放 1.15 倍,比例被限制在 0.001 和 100、000 之间,并且适合视图留下 10% 的边距 (editor2d/camera.ts).

资源和生命周期

2D 组件取消排队的动画帧并在卸载时断开其调整大小观察器的连接。 3D 组件发布调整大小和可见性侦听器、轨道和变换控件、网格工作器、动画调度、几何图形、材质、精灵、工具网格和 WebGL 渲染器。

帧调度程序会合并无效并在相机阻尼或回放需要时请求更多帧。能见度变化导致工作暂停。这是一个调度策略,而不是每个型号和设备的有保证的帧速率预算。

模块图

  • editor2d/editor2d.tsx:受控组件、指针调度、相机、选择、历史记录和画布绘画。
  • editor2d/tools/:手势状态机和命令构建。
  • editor2d/camera.ts, render.ts, 和 snap-pipeline.ts:坐标变换、实体渲染、拾取和捕捉编排。
  • spatial/grid.ts:绘图空间索引。
  • view3d/view3d.tsx:场景合成、摄像机控制、选择、变换、库存和工具。
  • view3d/playback.ts, toolpath-overlays.ts, 和 modeling.ts:运动时间线、叠加数据和轮廓到实体的转换。
  • view3d/mesh-jobs.ts 和 mesh-worker.ts:背景几何构造和工作所有权。

工具可用性

ToolId 描述更大的工具词汇; AVAILABLE_TOOLS 描述已注册的子集。对于交互式工具,请使用 startTool(id, lookup, context, text)。选择查找查询候选实体;上下文提供当前选择和实体访问; text 提供注释内容。

失踪的工厂返回 undefined。明确地处理该问题,而不是将禁用或未实现的工具呈现为活动编辑模式。修改现有几何图形的工具需要当前选择和实体上下文;调用没有这些依赖项的工厂不足以进行有用的编辑。

网格划分和验证

对于估计顶点数高于 50、000 且浏览器 Worker 支持的实体,查看器会在模块 Worker 中对网格构建进行排队。较小的实体同步网格化。网格队列处理完成、陈旧作业、工作错误和清理。阈值是用于调度的估计值,而不是记录的模型大小限制。

视口逻辑检查,来自存储库根
bun test apps/desktop/src/viewport
bun run --filter @cadcamfun/desktop typecheck

逻辑测试涵盖渲染助手、工具手势、空间索引、播放和叠加。浏览器/WebGL 行为还取决于图形环境;在更改相机控制、场景生命周期或渲染时检查实际视口交互。

受控播放示例

家长可以拥有传输控制,而无需复制观看者的运动时间线。该组件接受已构建的 CAM 程序并控制时间、播放状态、速度和视口预设。它的导入路径假设有一个文件直接位于 apps/desktop/src.

使用外部传输控件进行节目预览
import { createSignal } from "solid-js"
import { cam } from "@cadcamfun/core"
import { View3D } from "./viewport/index.ts"

export function ProgramPreview(props: { program: cam.Program }) {
  const [seconds, setSeconds] = createSignal(0)
  const [playing, setPlaying] = createSignal(false)
  return <div style={{ height: "600px" }}>
    <button onClick={() => setPlaying(value => !value)}>Play / pause</button>
    <button onClick={() => { setSeconds(0); setPlaying(true) }}>Restart</button>
    <View3D
      program={props.program}
      preset="isometric"
      playing={playing()}
      playbackTime={seconds()}
      playbackRate={1}
      onPlaybackTimeChange={setSeconds}
      stock={{ origin: { x: 0, y: 0, z: -10 }, size: { x: 100, y: 80, z: 10 } }}
    />
  </div>
}

播放构建器从原点开始,将循环扩展为移动,并根据行进长度和进给计算持续时间。其默认快速速度为 5,000 mm/min,默认圆弧细分公差为 0.1 mm。这些是预览假设,而不是测量的机器加速度或控制器时序。停留命令保留其规定的秒数;评论和仅限定向的条目不会增加线性行程。

工具颜色由每个工具标识符第一次出现时分配,并在其路径中保留。为装配几何体提供工具图/默认工具;单独的彩色路径并不能确定验证所使用的刀具或支架尺寸。

编辑配方和当前工具限制

手术手势和前提条件目前的行为
抵消选择一个支持的实体,然后拖动; Shift 键会选择光标下的实体。直线/折线/矩形产生折线结果。圆和圆弧实现平移中心,同时保留半径;该工具不是一般的曲线同心偏移操作。
修剪/延伸首先选择源实体和边界实体,激活“修剪”,然后单击目标。 Alt 选择扩展模式。该实现搜索其他选定实体的交集。它改变直线和折线;不支持的目标类型或缺失的交集不会产生任何编辑。
圆角/倒角选择两条相交线。 Shift 切换到倒角。共享工具使用 5 mm 的初始半径,并用修剪线和连接折线替换该对。它不是任意的实边圆角。
变换实体提供 onTransformCommit,选择一个网格,然后激活平移/旋转/缩放。回调报告实体索引和转换值。在四视图模式下禁用拾取和变换小控件。

这些细节来自手势实现而不是调色板标签。在提交之前检查预览,如果结果与预期几何图形不同,请使用“撤消”。不返回命令的工具可能意味着其几何先决条件未得到满足,而不是编辑器丢失了指针事件。

渲染和网格划分失败

默认情况下,后台网格队列最多接受 128 个待处理作业,一次运行一个作业,并且每个作业有一个默认的 15 秒超时。重置场景会终止工作线程并清除排队的工作;过时的响应将被忽略。无效的工作响应、消息解码错误、超时和队列容量故障通过队列回调报告错误。

症状行动
没有几何图形的空画布为父级指定非零大小,检查实体/图层可见性并适合视图。
没有什么可以选择的检查 2D 中的实体/层锁;检查 3D 中的四视图模式和变换状态。
网格超时或队列已满降低场景复杂性或同时提交的实体数量并检查工作人员错误。失败的批次将被清除,而不是作为已完成的作业部分安装。
播放时间好像不对检查程序进给、停留移动、快速假设以及更改的程序是否重置父时间。
WebGL 不可用使用支持的图形环境并检查组件的不可用渲染器状态;逻辑测试无法证明 GPU 可用性。

相关测试包括 editor2d/tools/offset.test.ts, annotation-transform.test.ts, spatial/grid.test.ts, view3d/playback.test.ts, 和 toolpath-overlays.test.ts。网格队列的实现是 view3d/mesh-jobs.ts.

详细显示单位

编辑器总是以毫米为单位进行工作。仅使用以下确切因素转换用户读取和键入的内容。存储的图形自己的单位保留 mm 或 inch;额外的单位仅供显示选择。

单元标签每单位毫米默认小数位G代码程序单元
mm毫米13毫米
umµm0.0011毫米
cm厘米104毫米
mm10006毫米
inch英寸25.44英寸
thou你0.02542英寸
转换和解析长度(apps/desktop/src/viewport/units.ts)
import { formatLength, parseLength, formatAngle, programUnitsOf } from "./units"

formatLength(25.4, "inch")     // "1.0000 inch"
formatLength(12.5)             // "12.500 mm"
formatLength(0.0254, "thou")   // "1.00 thou"
parseLength("2 in", "inch")    // 50.8  (a trailing unit word is ignored)
parseLength("abc")             // undefined
formatAngle(Math.PI / 2)       // "90.0°"
programUnitsOf("thou")         // "inch"
parseLength 删除尾随字母后缀(包括 µ 和 °)并解释当前显示单位中的数字。后缀不用于选择单位,因此输入 2 mm 而显示单位为英寸则为 50.8 毫米。空、非数字和无限输入返回 undefined,在这种情况下编辑者应该保留以前的值。

2D 相机和网格

世界(毫米,+Y 向上)屏幕(px,+Y 向下) 中心+X+Y (宽度/2,高度/2)+x+y →
2D 编辑器的相机存储视口中心的世界点和每毫米像素的比例。 worldToScreen 翻转 Y,使世界 +Y 在屏幕上指向上方。
常数/函数价值行为
ZOOM_STEP1.15每轮步距系数; zoomAt 保持光标下的世界点固定。
MIN_SCALE / MAX_SCALE0.001 / 100,000 像素/毫米比例被限制在这个范围内。
FIT_MARGIN0.1视口的一部分保留为边距 cameraForBounds.
createCamera()中心 (0, 0),比例 1,800 × 600第一次调整大小之前的初始相机。
gridStep(scale, major = 10, minPixels = 8)1-2-5 梯子使网格点间隔至少 8 像素的最小步长。
世界和屏幕之间的映射
const camera = createCamera({ x: 0, y: 0 }, 2, 800, 600)   // 2 px per mm
worldToScreen(camera, { x: 10, y: 10 })   // { x: 420, y: 280 }
screenToWorld(camera, { x: 420, y: 280 }) // { x: 10, y: 10 }
gridStep(2)    // 5   -> 5 mm apart = 10 px
gridStep(0.5)  // 20  -> 20 mm apart = 10 px

渲染剔除实体 visibleBounds(camera)。捕捉还避免了扫描整个绘图:统一的空间网格返回边界位于捕捉容差加上 2 mm 候选边距内的实体,并且只有这些实体才会传递到核心 findSnap。这使得指针在具有许多实体的绘图中快速移动。

捕捉参考

捕捉类型按优先顺序进行尝试。默认顺序与旧版编辑器匹配: endpoint, midpoint, center, intersection, perpendicular, tangent, nearest, 和 quadrant。端点是源实体的顶点,而不是其细分的顶点。

环境意义
enabled主开关; false 禁用所有捕捉。
types启用的捕捉类型,优先级最高。
tolerance捕捉半径(以毫米为单位);它不依赖于变焦。
gridEnabled, gridSpacing当没有对象捕捉应用时捕捉到网格。
referencePoint从中绘制的点。 perpendicular 和 tangent 没有它什么也找不到。

管道返回快照、光标下实体的 ID(最接近的第一个,选择工具使用的实体)以及考虑的候选者数量。

刀具路径回放如何计时

buildPlayback(program, rapidRate = 5000, tolerance = 0.1) 将 CAM 程序转换为定时段。每个移动的持续时间是其长度除以进给量(或急流的速度),乘以 60,即秒数。居住者无需移动即可添加自己的时间。注释、方向移动和固定循环不会绘制为段。弧线被分成和弦,其矢矢留在其中 tolerance (在 2 和 65 之间,每弧 536 个弦),并且 Z 沿着螺旋弧进行插值。
寻找预览并为其着色
const playback = buildPlayback(program)          // rapids at 5000 mm/min
playback.duration                                // total seconds
positionAt(playback, 12.5)                       // tool position at t = 12.5 s
toolAt(playback, 12.5)                           // toolId cutting at that time
toolColors(program)                              // Map toolId -> colour, first use order

// A 10 mm feed move at 600 mm/min lasts 10 / 600 × 60 = 1 s.
// The same 10 mm as a rapid lasts 10 / 5000 × 60 = 0.12 s.
回放预览 拖动进行轨道旋转·滚动进行缩放·右键拖动进行平移
与 CAM 页面上相同的口袋程序,以第一种工具颜色 (388bdf22) 重播。在桌面中,每个附加工具都会获取调色板中的下一种颜色:5597316c879e、9f15、#e#f#、#facc# 等。
segmentIndexAt 通过二分查找找到播放段,因此即使对于长程序,查找也很便宜。寻找越过终点返回最终位置,并且 toolAt 然后报告最后一个工具。播放时间是基于编程提要的视觉估计;它忽略了机器加速度,因此真实机器需要更长的时间。

视口实践

  • 在边缘转换:以毫米和弧度存储和计算,并且格式仅用于显示 formatLength 和 formatAngle.
  • 切勿在之后重复使用已解析的长度 parseLength 回报 undefined;保留之前的值并显示验证提示。
  • 添加绘图工具时,将其注册到工具工厂表中,以便工具栏、快捷方式和测试通过相同的 ID 对其进行寻址。
  • 保持快照候选查询的空间性。将每个实体传递给 findSnap 每次指针移动都会破坏索引。
  • 将播放持续时间视为预览,而不是周期时间报价。

大规模命中测试:空间网格

2D 编辑器以 60、10 实体的 000 fps 为目标,因此命中测试和剔除不会扫描每个指针事件的每个实体。 viewport/spatial/grid.ts 保持统一的网格:每个项目都在其边界框接触的每个单元格中注册,并且查询按插入顺序返回边界与查询框相交的所有项目。结果是准确的,不是样品。

环境价值为什么
默认单元格大小50 世界单位(毫米)适合宽约 1 米的绘图
最小细胞尺寸1e-6防止零或负大小,这将永远循环
每个项目的单元格限制4,096较大的项目(整个工作表上的一条长线)将被放在一边而不是每个单元格中进行跟踪
方法行为
build(items)从头开始重建
upsert(id, item)按 id 插入或替换一项
remove(id)消除;未知的 id 会被忽略
query(bounds)边界与框相交的 ID
查询框实体AB长线

访问虚线查询框触及的单元格; A 和长线被返回,因为它们的边界与其相交,而 B 则不然。

3D 网格划分队列

实体在 Web Worker 中进行网格划分,因此 UI 线程永远不会阻塞。 MeshWorkerQueue 中 view3d/mesh-jobs.ts 一次运行一项作业:

规则价值失败消息
容量(排队+运行)1283D 网格队列已满。降低场景复杂度。
每个作业的超时时间15 s3D 网格划分超时。降低场景复杂度。
工人错误—工作人员的消息,或“网格工作人员意外停止”。
无法解码的回复—网格工作人员无法解码其响应。
几何形状不好职位必须是 Float32Array, 指数 Uint32Array网格工作器返回无效的几何体。

任何失败都会终止工作程序并清除整个队列,因此过时的场景工作在新的编辑后永远不会落地;下一次提交会启动一个新的工人。回复与职位 ID 相匹配,因此已终止工作人员的迟到回复将被忽略。

2D 工具实现

文件工具
tools/two-point.ts直线、矩形、直径圆、椭圆
tools/chained.ts折线、三点圆弧
tools/fillet-chamfer.ts将两条线之间的角倒圆或倒角
tools/offset.ts将折线、矩形、圆形或圆弧偏移一定距离
tools/trim.ts修剪和延伸
tools/select.ts单击、框选和添加/删除选择
tools/annotation-transform.ts移动和变换注释

每个工具都是一个小型状态机 onPointerDown, onPointerMove 和 onPointerUp,由一家工厂创建 tools/index.ts,每个调色板条目一个。指针位置经过 snap-pipeline.ts 在工具看到它们之前。