feat: add conservative topology history fallback
This commit is contained in:
@@ -18,7 +18,7 @@
|
|||||||
},
|
},
|
||||||
"facadeCapabilities": {
|
"facadeCapabilities": {
|
||||||
"geometry": { "level": "experimental", "provider": "BitBybit OCCT 1.1.1", "operations": ["box", "cylinder", "sphere", "cone", "placement", "union", "cut", "intersection", "part-primitive-recompute", "part-boolean-recompute", "shape-check", "pad", "pocket", "pocket-through-all", "revolution", "fillet", "chamfer", "linear-pattern-whole-shape", "polar-pattern-whole-shape", "hole-basic-origin-normal", "feature-shape-cache", "face-edge-vertex-topology", "step-export", "stl-export"] },
|
"geometry": { "level": "experimental", "provider": "BitBybit OCCT 1.1.1", "operations": ["box", "cylinder", "sphere", "cone", "placement", "union", "cut", "intersection", "part-primitive-recompute", "part-boolean-recompute", "shape-check", "pad", "pocket", "pocket-through-all", "revolution", "fillet", "chamfer", "linear-pattern-whole-shape", "polar-pattern-whole-shape", "hole-basic-origin-normal", "feature-shape-cache", "face-edge-vertex-topology", "step-export", "stl-export"] },
|
||||||
"document": { "level": "experimental", "operations": ["typed-properties", "property-link-sub", "expressions", "units", "dependency-dag", "parallel-level-recompute", "sync-recompute", "async-generation-recompute", "root-cause-diagnostics", "repair-branch-recompute", "versioned-toporef", "toporef-migration", "undo-redo"] },
|
"document": { "level": "experimental", "operations": ["typed-properties", "property-link-sub", "expressions", "units", "dependency-dag", "parallel-level-recompute", "sync-recompute", "async-generation-recompute", "root-cause-diagnostics", "repair-branch-recompute", "versioned-toporef", "toporef-migration", "signature-topology-history-fallback", "undo-redo"] },
|
||||||
"sketcher": { "level": "experimental", "operations": ["point-line-circle-arc-model", "basic-constraints", "diameter-symmetric-tangent", "basic-solver", "persistence"] },
|
"sketcher": { "level": "experimental", "operations": ["point-line-circle-arc-model", "basic-constraints", "diameter-symmetric-tangent", "basic-solver", "persistence"] },
|
||||||
"fcstd": { "level": "read-only", "operations": ["zip-preflight", "document-xml-metadata", "proxy-report", "script-isolation"] }
|
"fcstd": { "level": "read-only", "operations": ["zip-preflight", "document-xml-metadata", "proxy-report", "script-isolation"] }
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -338,7 +338,8 @@ Geometry Facade 还接入了 Bitbybit IO 的 STEP 和 ASCII STL 导出,作为
|
|||||||
| P3-05 Pattern 实验切片 | `linear-pattern` 和 `polar-pattern` 通过 Part Design 选择谓词、Task 和类型化属性创建对应对象;Linear 按总 Length 等距平移,Polar 在小于 360° 时覆盖首末角、360° 时不复制重合端点;两者均由 OCCT union 合并并释放临时副本 | 线性间距/方向、圆周完整/部分角度分布、联合输入、句柄释放、非法参数诊断、Base 链接、Body.Tip、属性校验和任务提交回归测试通过 | 当前为 FreeCAD 1.0+ 的 Transform body 近似,只复制整个 Base Shape;未实现 Transform tool shapes、连接单实体过滤、稳定 TopoRef、历史映射、反向轴和引用轴,因此保持 `experimental` |
|
| P3-05 Pattern 实验切片 | `linear-pattern` 和 `polar-pattern` 通过 Part Design 选择谓词、Task 和类型化属性创建对应对象;Linear 按总 Length 等距平移,Polar 在小于 360° 时覆盖首末角、360° 时不复制重合端点;两者均由 OCCT union 合并并释放临时副本 | 线性间距/方向、圆周完整/部分角度分布、联合输入、句柄释放、非法参数诊断、Base 链接、Body.Tip、属性校验和任务提交回归测试通过 | 当前为 FreeCAD 1.0+ 的 Transform body 近似,只复制整个 Base Shape;未实现 Transform tool shapes、连接单实体过滤、稳定 TopoRef、历史映射、反向轴和引用轴,因此保持 `experimental` |
|
||||||
| P3-05 Hole 基础切片 | `hole` 创建 `PartDesign::Hole`,用 32 边圆形 Profile 调用现有 OCCT Pocket;支持 Diameter、Depth 以及 Dimension/Through all,校验正尺寸并纳入 Base 链接和 Body.Tip | 圆孔 Profile、有限深度调用、非法直径诊断、Task 属性提交和 Tip 更新测试通过 | 仅为基体原点 XY 面、+Z 法向的直孔;草图圆/面定位、沉孔、锪孔、钻尖、螺纹、尺寸标准和稳定支撑面引用仍待实现,保持 `experimental` |
|
| P3-05 Hole 基础切片 | `hole` 创建 `PartDesign::Hole`,用 32 边圆形 Profile 调用现有 OCCT Pocket;支持 Diameter、Depth 以及 Dimension/Through all,校验正尺寸并纳入 Base 链接和 Body.Tip | 圆孔 Profile、有限深度调用、非法直径诊断、Task 属性提交和 Tip 更新测试通过 | 仅为基体原点 XY 面、+Z 法向的直孔;草图圆/面定位、沉孔、锪孔、钻尖、螺纹、尺寸标准和稳定支撑面引用仍待实现,保持 `experimental` |
|
||||||
| TSN-02/05/06/08 TopoRef 基础迁移 | `topologyReferences` 提供 schema v1 创建、序列化、安全解析、解析和跨版本迁移;重复几何候选 ID 唯一,一旧多新不会错误继承;匹配按 kind 隔离并用形状尺度归一化几何评分支持纯平移 | round-trip、瞬时索引拒绝、resolved/ambiguous/deleted、重复拆分和纯平移回归测试通过 | 仍是网格签名启发式;没有 OCCT Generated/Modified/Deleted、邻接/曲率签名、布尔历史和黄金模型规模验证,不得提升为 compatible |
|
| TSN-02/05/06/08 TopoRef 基础迁移 | `topologyReferences` 提供 schema v1 创建、序列化、安全解析、解析和跨版本迁移;重复几何候选 ID 唯一,一旧多新不会错误继承;匹配按 kind 隔离并用形状尺度归一化几何评分支持纯平移 | round-trip、瞬时索引拒绝、resolved/ambiguous/deleted、重复拆分和纯平移回归测试通过 | 仍是网格签名启发式;没有 OCCT Generated/Modified/Deleted、邻接/曲率签名、布尔历史和黄金模型规模验证,不得提升为 compatible |
|
||||||
| TSN-08/09 LinkSub 文档接线 | `PropertyValue` 支持 schema v1 TopoRef,`App::PropertyLinkSub` 经 Facade 解析/校验和深克隆;依赖图产生带 persistentId 的 `topo-ref` 边;SQLite/内存 Project 以结构化 JSON 保存;Pocket 暴露 `UpToFace` 槽,Property 面板显示状态并可清除 | 非法瞬时索引/缺失对象拒绝、DAG 边、保存加载 round-trip、49 项 Facade 测试、桌面属性面板无横向溢出 | 尚未从 3D 选择创建引用,也未在重算后自动写回迁移结果;Pocket Up to face executor 仍保持结构化 unsupported,等待 OCCT 面历史/解析接线 |
|
| TSN-08/09 LinkSub 文档接线 | `PropertyValue` 支持 schema v1 TopoRef,`App::PropertyLinkSub` 经 Facade 解析/校验和深克隆;依赖图产生带 persistentId 的 `topo-ref` 边;SQLite/内存 Project 以结构化 JSON 保存;Pocket 暴露 `UpToFace` 槽,Property 面板显示状态并可清除 | 非法瞬时索引/缺失对象拒绝、DAG 边、保存加载 round-trip、LinkSub 专项测试、桌面属性面板无横向溢出 | 尚未从 3D 选择创建引用,也未在重算后自动写回迁移结果;Pocket Up to face executor 仍保持结构化 unsupported,等待 OCCT 面历史/解析接线 |
|
||||||
|
| TSN-03/07 历史适配器回退 | Bitbybit 1.1.1 绑定仅有 `HasGenerated()`,没有逐 Shape 的 Generated/Modified/IsDeleted;新增显式 `signature-fallback` provider,把多输入到输出保守分类为 preserved/modified/generated/deleted/ambiguous,跨对象相同签名不抢占继承 | 平移修改、新增面、删除面和两个输入竞争同一输出的歧义测试通过;全套 50 项测试通过 | 此 provider 不是 OCCT history;只有自构建绑定暴露逐子形状历史并通过布尔黄金模型后,才能将 TSN-03/07 标为完成 |
|
||||||
|
|
||||||
后续按以下顺序连续开发:
|
后续按以下顺序连续开发:
|
||||||
|
|
||||||
|
|||||||
@@ -1605,7 +1605,7 @@ P0 基线/治理
|
|||||||
| P3-04 Boolean/Pad/Pocket/Revolution | `IN PROGRESS` | 文档作用域 Union/Cut/Intersection 与 PlanarProfile 驱动的 Pad/Pocket/Revolution 已通过真实 OCCT 浏览器矩阵;重算 executor 已在 Worker ready 时执行 Pad/Pocket/Revolution,Revolution Angle/Reversed 已接入,Fillet/Chamfer 也有 Shape 缓存回写;Up to face、FCStd Shape 持久化仍待完成 |
|
| P3-04 Boolean/Pad/Pocket/Revolution | `IN PROGRESS` | 文档作用域 Union/Cut/Intersection 与 PlanarProfile 驱动的 Pad/Pocket/Revolution 已通过真实 OCCT 浏览器矩阵;重算 executor 已在 Worker ready 时执行 Pad/Pocket/Revolution,Revolution Angle/Reversed 已接入,Fillet/Chamfer 也有 Shape 缓存回写;Up to face、FCStd Shape 持久化仍待完成 |
|
||||||
| P3-05 Fillet/Chamfer/Pattern/Hole | `IN PROGRESS` | Fillet/Chamfer 已接入 Shape 缓存;Linear/Polar Pattern 已贯通选择谓词、Task、Placement/union 和清理;基础 Hole 用圆形 Profile 复用 Pocket,支持 Diameter、Depth、Dimension/Through all。Pattern 当前为整 Shape,Hole 当前固定原点法向;局部特征历史、连接单实体过滤、稳定 TopoRef、草图/面定位及沉孔/锪孔/螺纹仍待实现 |
|
| P3-05 Fillet/Chamfer/Pattern/Hole | `IN PROGRESS` | Fillet/Chamfer 已接入 Shape 缓存;Linear/Polar Pattern 已贯通选择谓词、Task、Placement/union 和清理;基础 Hole 用圆形 Profile 复用 Pocket,支持 Diameter、Depth、Dimension/Through all。Pattern 当前为整 Shape,Hole 当前固定原点法向;局部特征历史、连接单实体过滤、稳定 TopoRef、草图/面定位及沉孔/锪孔/螺纹仍待实现 |
|
||||||
| P5-02 网格增量接入 | `IN PROGRESS` | Three Adapter 可用 `BufferGeometry` 接收 Facade `MeshAsset`,替换时释放旧 GPU geometry;视口优先使用重计算缓存的对象 Shape,失败时保留最近有效结果,无缓存时才创建并释放 Pad/Pocket 临时预览链;对象级增量缓存与选择映射尚未实现 |
|
| P5-02 网格增量接入 | `IN PROGRESS` | Three Adapter 可用 `BufferGeometry` 接收 Facade `MeshAsset`,替换时释放旧 GPU geometry;视口优先使用重计算缓存的对象 Shape,失败时保留最近有效结果,无缓存时才创建并释放 Pad/Pocket 临时预览链;对象级增量缓存与选择映射尚未实现 |
|
||||||
| P8-01 第一批自动化场景 | `IN PROGRESS` | `tests/facade.test.ts` 已覆盖 49 个场景,包括 Part/PartDesign、Pattern/Hole、TopoRef round-trip/迁移/歧义、LinkSub DAG/持久化、几何重算、async generation、诊断修复、FCStd 安全、资源、任务、历史和表达式;E2E/黄金几何待补齐 |
|
| P8-01 第一批自动化场景 | `IN PROGRESS` | `tests/facade.test.ts` 已覆盖 50 个场景,包括 Part/PartDesign、Pattern/Hole、TopoRef/LinkSub、signature topology history fallback、几何重算、async generation、诊断修复、FCStd 安全、持久化、资源、任务、历史和表达式;E2E/黄金几何待补齐 |
|
||||||
|
|
||||||
本迭代验证命令:`npm run check:facade-boundary`、`npm run test:facade`、`npm run build`。构建产物将 Three.js 拆为独立 chunk,避免把全部渲染库重复打入应用主 chunk。当前 npm registry 的 `three` 最新版本为 `0.185.1`,已在 `package.json` 和运行时基线中锁定。下一迭代继续完成 P0-01/P0-02 的精确锁定、P2-01/P2-02 的 SQLite/OPFS schema 与 Worker 单写者实验,以及 P3-01 的 FreeCAD/OCCT WASM 构建验证。
|
本迭代验证命令:`npm run check:facade-boundary`、`npm run test:facade`、`npm run build`。构建产物将 Three.js 拆为独立 chunk,避免把全部渲染库重复打入应用主 chunk。当前 npm registry 的 `three` 最新版本为 `0.185.1`,已在 `package.json` 和运行时基线中锁定。下一迭代继续完成 P0-01/P0-02 的精确锁定、P2-01/P2-02 的 SQLite/OPFS schema 与 Worker 单写者实验,以及 P3-01 的 FreeCAD/OCCT WASM 构建验证。
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ export type { ApplyPlacementInput, BitBybitViewportAdapter, BitBybitWebCadFacade
|
|||||||
export { createEdgeSubshapeRefs, createSubshapeRefs, createVertexSubshapeRefs, matchSubshapes, signatureForEdge, signatureForFace, signatureForVertex } from './topologyNaming'
|
export { createEdgeSubshapeRefs, createSubshapeRefs, createVertexSubshapeRefs, matchSubshapes, signatureForEdge, signatureForFace, signatureForVertex } from './topologyNaming'
|
||||||
export { createPersistedTopoRef, migrateTopoRefs, parseTopoRef, resolveTopoRef, serializeTopoRef } from './topologyReferences'
|
export { createPersistedTopoRef, migrateTopoRefs, parseTopoRef, resolveTopoRef, serializeTopoRef } from './topologyReferences'
|
||||||
export type { PersistedTopoRef, TopoRefResolution, TopologyMigration } from './topologyReferences'
|
export type { PersistedTopoRef, TopoRefResolution, TopologyMigration } from './topologyReferences'
|
||||||
|
export { captureSignatureTopologyHistory } from './topologyHistory'
|
||||||
|
export type { TopologyHistoryEntry, TopologyHistoryRelation, TopologyHistoryResult } from './topologyHistory'
|
||||||
export { BasicSketchSolverAdapter, cloneSketch, createSketch, solveSketch } from './sketcher'
|
export { BasicSketchSolverAdapter, cloneSketch, createSketch, solveSketch } from './sketcher'
|
||||||
export type { SketchConstraint, SketchDiagnostic, SketchGeometry, SketchPoint, SketchPointRef, SketchSnapshot, SketchSolveOptions, SketchSolveResult, SketchSolverAdapter, SketchSolverStatus } from './sketcher'
|
export type { SketchConstraint, SketchDiagnostic, SketchGeometry, SketchPoint, SketchPointRef, SketchSnapshot, SketchSolveOptions, SketchSolveResult, SketchSolverAdapter, SketchSolverStatus } from './sketcher'
|
||||||
export { createFacadeGeometryRecomputeExecutor, executeFacadeRecomputeNode, RecomputeCoordinator } from './recomputeEngine'
|
export { createFacadeGeometryRecomputeExecutor, executeFacadeRecomputeNode, RecomputeCoordinator } from './recomputeEngine'
|
||||||
|
|||||||
55
src/facade/topologyHistory.ts
Normal file
55
src/facade/topologyHistory.ts
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import type { SubshapeRef } from './types'
|
||||||
|
import { matchSubshapes, type SubshapeSignature } from './topologyNaming'
|
||||||
|
|
||||||
|
export type TopologyHistoryEntry = { ref: SubshapeRef; signature: SubshapeSignature }
|
||||||
|
|
||||||
|
export type TopologyHistoryRelation = {
|
||||||
|
relation: 'preserved' | 'modified' | 'generated' | 'deleted' | 'ambiguous'
|
||||||
|
sourceObjectId?: string
|
||||||
|
sourcePersistentId?: string
|
||||||
|
resultPersistentId?: string
|
||||||
|
candidates?: Array<{ sourceObjectId: string; persistentId: string }>
|
||||||
|
score: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TopologyHistoryResult = {
|
||||||
|
operationId: string
|
||||||
|
provider: 'signature-fallback'
|
||||||
|
relations: TopologyHistoryRelation[]
|
||||||
|
counts: Record<TopologyHistoryRelation['relation'], number>
|
||||||
|
}
|
||||||
|
|
||||||
|
const sourceKey = (objectId: string, persistentId: string) => `${encodeURIComponent(objectId)}::${persistentId}`
|
||||||
|
|
||||||
|
export const captureSignatureTopologyHistory = (
|
||||||
|
operationId: string,
|
||||||
|
inputs: Array<{ objectId: string; entries: TopologyHistoryEntry[] }>,
|
||||||
|
output: TopologyHistoryEntry[],
|
||||||
|
): TopologyHistoryResult => {
|
||||||
|
if (!operationId.trim()) throw new RangeError('Topology history operationId is required.')
|
||||||
|
const sources = new Map<string, { objectId: string; persistentId: string; signature: SubshapeSignature }>()
|
||||||
|
const previous = inputs.flatMap(({ objectId, entries }) => entries.map((entry) => {
|
||||||
|
const key = sourceKey(objectId, entry.ref.persistentId)
|
||||||
|
sources.set(key, { objectId, persistentId: entry.ref.persistentId, signature: entry.signature })
|
||||||
|
return { ref: { ...entry.ref, persistentId: key }, signature: entry.signature }
|
||||||
|
}))
|
||||||
|
const matches = matchSubshapes(previous, output)
|
||||||
|
const counts: TopologyHistoryResult['counts'] = { preserved: 0, modified: 0, generated: 0, deleted: 0, ambiguous: 0 }
|
||||||
|
const relations = matches.map((match): TopologyHistoryRelation => {
|
||||||
|
if (match.status === 'new') return { relation: 'generated', resultPersistentId: match.current.persistentId, score: match.score }
|
||||||
|
if (match.status === 'deleted') {
|
||||||
|
const source = match.previousId ? sources.get(match.previousId) : undefined
|
||||||
|
return { relation: 'deleted', sourceObjectId: source?.objectId, sourcePersistentId: source?.persistentId, score: 0 }
|
||||||
|
}
|
||||||
|
if (match.status === 'ambiguous') {
|
||||||
|
const candidates = (match.current.candidates ?? []).map((id) => sources.get(id)).filter((candidate): candidate is NonNullable<typeof candidate> => Boolean(candidate)).map((candidate) => ({ sourceObjectId: candidate.objectId, persistentId: candidate.persistentId }))
|
||||||
|
return { relation: 'ambiguous', resultPersistentId: match.current.persistentId, candidates, score: match.score }
|
||||||
|
}
|
||||||
|
const source = match.previousId ? sources.get(match.previousId) : undefined
|
||||||
|
const outputEntry = output.find((entry) => entry.ref.persistentId === match.current.persistentId || entry.ref.signature === match.current.signature)
|
||||||
|
const relation = source && outputEntry && source.signature.hash === outputEntry.signature.hash ? 'preserved' : 'modified'
|
||||||
|
return { relation, sourceObjectId: source?.objectId, sourcePersistentId: source?.persistentId, resultPersistentId: match.current.persistentId, score: match.score }
|
||||||
|
})
|
||||||
|
for (const relation of relations) counts[relation.relation] += 1
|
||||||
|
return { operationId, provider: 'signature-fallback', relations, counts }
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ import { DependencyGraph } from '../src/facade/dependencyGraph'
|
|||||||
import { evaluateQuantityExpression, quantityFromNumber } from '../src/facade/units'
|
import { evaluateQuantityExpression, quantityFromNumber } from '../src/facade/units'
|
||||||
import { createEdgeSubshapeRefs, createSubshapeRefs, createVertexSubshapeRefs, matchSubshapes, signatureForEdge, signatureForFace, signatureForVertex } from '../src/facade/topologyNaming'
|
import { createEdgeSubshapeRefs, createSubshapeRefs, createVertexSubshapeRefs, matchSubshapes, signatureForEdge, signatureForFace, signatureForVertex } from '../src/facade/topologyNaming'
|
||||||
import { createPersistedTopoRef, migrateTopoRefs, parseTopoRef, resolveTopoRef, serializeTopoRef } from '../src/facade/topologyReferences'
|
import { createPersistedTopoRef, migrateTopoRefs, parseTopoRef, resolveTopoRef, serializeTopoRef } from '../src/facade/topologyReferences'
|
||||||
|
import { captureSignatureTopologyHistory } from '../src/facade/topologyHistory'
|
||||||
import { createSketch, solveSketch } from '../src/facade/sketcher'
|
import { createSketch, solveSketch } from '../src/facade/sketcher'
|
||||||
import { createFacadeGeometryRecomputeExecutor, executeFacadeRecomputeNode, RecomputeCoordinator, type RecomputeGeometryRuntime } from '../src/facade/recomputeEngine'
|
import { createFacadeGeometryRecomputeExecutor, executeFacadeRecomputeNode, RecomputeCoordinator, type RecomputeGeometryRuntime } from '../src/facade/recomputeEngine'
|
||||||
import { inspectFcstdArchive } from '../src/facade/fcstd'
|
import { inspectFcstdArchive } from '../src/facade/fcstd'
|
||||||
@@ -182,6 +183,27 @@ test('versioned TopoRefs serialize without transient indexes and resolve migrati
|
|||||||
assert.equal(resolveTopoRef(restored, split.refs).status, 'ambiguous')
|
assert.equal(resolveTopoRef(restored, split.refs).status, 'ambiguous')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('signature topology history conservatively classifies boolean fallback relations', () => {
|
||||||
|
const square = { vertexCoord: [0, 0, 0, 2, 0, 0, 2, 2, 0, 0, 2, 0], normalCoord: [], triIndexes: [0, 1, 2, 0, 2, 3] }
|
||||||
|
const translated = { ...square, vertexCoord: square.vertexCoord.map((value, index) => index % 3 === 0 ? value + 5 : value) }
|
||||||
|
const triangle = { vertexCoord: [0, 0, 1, 1, 0, 1, 0, 1, 1], normalCoord: [], triIndexes: [0, 1, 2] }
|
||||||
|
const before = createSubshapeRefs('base-shape', 1, [square])
|
||||||
|
const after = createSubshapeRefs('result-shape', 2, [translated, triangle])
|
||||||
|
const history = captureSignatureTopologyHistory('boolean-1', [{ objectId: 'base', entries: before.refs.map((ref, index) => ({ ref, signature: before.signatures[index] })) }], after.refs.map((ref, index) => ({ ref, signature: after.signatures[index] })))
|
||||||
|
assert.equal(history.provider, 'signature-fallback')
|
||||||
|
assert.equal(history.counts.modified, 1)
|
||||||
|
assert.equal(history.counts.generated, 1)
|
||||||
|
|
||||||
|
const deleted = captureSignatureTopologyHistory('boolean-2', [{ objectId: 'base', entries: before.refs.map((ref, index) => ({ ref, signature: before.signatures[index] })) }], [])
|
||||||
|
assert.equal(deleted.counts.deleted, 1)
|
||||||
|
const ambiguous = captureSignatureTopologyHistory('boolean-3', [
|
||||||
|
{ objectId: 'base', entries: before.refs.map((ref, index) => ({ ref, signature: before.signatures[index] })) },
|
||||||
|
{ objectId: 'tool', entries: before.refs.map((ref, index) => ({ ref, signature: before.signatures[index] })) },
|
||||||
|
], before.refs.map((ref, index) => ({ ref, signature: before.signatures[index] })))
|
||||||
|
assert.equal(ambiguous.counts.ambiguous, 1)
|
||||||
|
assert.deepEqual(new Set(ambiguous.relations[0].candidates?.map((candidate) => candidate.sourceObjectId)), new Set(['base', 'tool']))
|
||||||
|
})
|
||||||
|
|
||||||
test('edge and vertex topology signatures are orientation/index independent', () => {
|
test('edge and vertex topology signatures are orientation/index independent', () => {
|
||||||
const edge = signatureForEdge([0, 0, 0], [2, 0, 0])
|
const edge = signatureForEdge([0, 0, 0], [2, 0, 0])
|
||||||
assert.equal(edge.hash, signatureForEdge([2, 0, 0], [0, 0, 0]).hash)
|
assert.equal(edge.hash, signatureForEdge([2, 0, 0], [0, 0, 0]).hash)
|
||||||
|
|||||||
Reference in New Issue
Block a user