@constructorfleet/sketchforge3d

中文文档
1.30.0发布于 2026/10/28AGPL-3.0-onlyTypeScriptES Module构建来源已验证近 30 天下载 5074

为 SketchForge REST API 键入客户端和宏跑取器.

中文使用文档
文档目录11 节
  1. MCP桥
  2. 连接中
  3. 客户
  4. 宏
  5. 直接指挥线业务
  6. 项目模式和为什么没有默认
  7. 参数
  8. 实例
  9. 发展
  10. 出版
  11. 本地 MCP 模式

@ builtctorfleet/ sketchforge3d

输入客户端 缩写Forge REST API,并有一个跑者 宏: 短参数化脚本来构建一个部分.

npm install @constructorfleet/sketchforge3d

API是无头的——它在服务器侧存储器中读取并写出工程,没有打开浏览器——所以一个宏是设计用的构建脚本,一个Makefile是二进制的.

MCP桥

此软件包还搭载了 SketchForge MCP 服务器. 有地方性的 默认的工程文件; LLM 可以通过 url 个人工具呼叫 瞄准运行中的 SketchForge 服务器 :

{
  "mcpServers": {
    "sketchforge": {
      "command": "npx",
      "args": ["-y", "-p", "@constructorfleet/sketchforge3d", "sketchforge-mcp"],
      "env": { "SKETCHFORGE_PROJECTS_DIR": "/path/to/SketchForgeProjects" }
    }
  }
}

它会说Stio JSON -RPC。 无 url,项目和几何工具使用 本地引擎和 SKETCHFORGE_PROJECTS_DIR,没有浏览器、 HTTP 服务器,或 API标志. 过 url: "http://127.0.0.1:3000" 到工具调用运行 服务器;然后编辑工具需要打开编辑器标签(/?editor=1) (中文(简体) ). 远程 REST业务也要求 SKETCHFORGE_API_TOKEN 在多边协商进程中 环境.

两个工具运行 宏 - 这个软件包的运行符执行的脚本 -- 并且只有文件已经坐在 SKETCHFORGE_MACRO_DIR。这是故意的 没有接受代码运行的工具 。 Python 宏需求 sketchforge3d 已安装 从 PyPI 中, 因为只有退货 带有 Python 源.

连接中

import { SketchForgeClient } from "@constructorfleet/sketchforge3d";

const client = new SketchForgeClient({
  baseUrl: "http://localhost:3000",       // with or without /api/v1
  token: process.env.SKETCHFORGE_API_TOKEN!,
});

const health = await client.check();
if (!health.ok) throw new Error(health.reason);

clientFromEnvironment() 执行同样从 SKETCHFORGE_API_TOKEN 财务报告和审定财务报表 SKETCHFORGE_URL (默认) http://127.0.0.1:3000) (中文(简体) ). 偏好:指使是服务器上每个项目的密码,不属于脚本或 shell 历史.

服务器需要两个 SKETCHFORGE_API_TOKEN 财务报告和审定财务报表 SKETCHFORGE_PROJECTS_DIR 设置,或 API 分别回答 404 和 503 . check() 告诉你.

客户

new SketchForgeClient({ baseUrl, token, fetch?, timeoutMs?, retryOnBusy?, headers?, expectedRevision? })

client.apiRoot: string
client.check(): Promise<{ ok: true; projects: number } | { ok: false; reason: string }>
client.project(projectId): ProjectHandle

client.projects.list(): Promise<ProjectSummary[]>
client.projects.listPage({ limit?, cursor? }): Promise<{ items: ProjectSummary[]; nextCursor: string | null }>
client.projects.get(projectId): Promise<ProjectDetail>
client.projects.create(name): Promise<ProjectSummary>
client.projects.delete(projectId): Promise<DeleteResult>
client.projects.findByName(name): Promise<ProjectSummary | null>

client.shapes.list(projectId): Promise<ShapeSummary[]>
client.shapes.listPage(projectId, { limit?, cursor? }): Promise<{ items: ShapeSummary[]; nextCursor: string | null }>
client.shapes.create(projectId, shape): Promise<ShapeSummary>
client.shapes.createMany(projectId, shapes): Promise<ShapeWriteResult>
client.shapes.update(projectId, shapeId, changes): Promise<ShapePatchResult>
client.shapes.delete(projectId, shapeId): Promise<DeleteResult>
client.shapes.deleteAll(projectId): Promise<number>

client.edges.list(projectId, shapeId): Promise<EdgeSummary[]>
client.edges.filletableIndexes(projectId, shapeId): Promise<number[]>

client.treatments.apply(projectId, shapeId, { kind, amount, angle?, edges? }): Promise<TreatmentResult>
client.treatments.fillet(projectId, shapeId, radius, edges?): Promise<TreatmentResult>
client.treatments.chamfer(projectId, shapeId, distance, { angle?, edges? }): Promise<TreatmentResult>

client.faces.list(projectId, shapeId, { topology? }?): Promise<FaceSummary[]>
client.faces.facing(projectId, shapeId, direction): Promise<number[]>

client.measurements.list(projectId, shapeId): Promise<MeasurementSummary[]>
client.measurements.withRadius(projectId, shapeId): Promise<MeasurementSummary[]>
client.measurements.between(projectId, from, to): Promise<MeasurementPair>
client.properties.get(projectId, shapeId): Promise<ShapeProperties>
client.interference.check(projectId, { ids?, clearance? }): Promise<InterferenceReport>
client.mirrors.bake(projectId, ids, axis): Promise<BakedMirrorResult>
client.explosions.explode(projectId, shapeId): Promise<ShapeWriteResult>
client.thickness.estimate(projectId, shapeId): Promise<WallThicknessEstimate>
client.drawings.dxf(projectId, { plane, offset, shapeIds?, name? }): Promise<SectionDxfArtifact>
client.exports.export(projectId, { format, shapeIds?, name?, view?, featureAngle?, section? }): Promise<ProjectExportArtifact>

client.booleans.cut(projectId, { solidIds, holeIds }): Promise<BooleanCutResult>
client.groups.group(projectId, ids): Promise<GroupResult>
client.groups.ungroup(projectId, shapeId): Promise<ShapeWriteResult>
client.parts.separate(projectId, shapeId): Promise<ShapeWriteResult>

client.shell.apply(projectId, shapeId, { thickness, openFaces? }): Promise<ShellResult>
client.shell.hollow(projectId, shapeId, thickness): Promise<ShellResult>
client.shell.tray(projectId, shapeId, thickness, openFaces?): Promise<ShellResult>
client.draftPrism.apply(projectId, shapeId, angle): Promise<TreatmentResult>

client.align.apply(projectId, { ids, axis, target, anchorId? }): Promise<AlignResult>
client.align.tops(projectId, ids, anchorId?) | bottoms(...) | centers(projectId, ids, axis, anchorId?)

client.distribute.apply(projectId, { ids, axis?, spacing? }): Promise<DistributeResult>
client.distribute.gaps(projectId, ids, axis?) | pitch(projectId, ids, axis?)

client.patterns.apply(projectId, { ids, kind?, count?, stepX?, stepZ?, centerX?, centerZ?, sweepAngle?, rotateCopies? }): Promise<PatternResult>
client.patterns.row(projectId, ids, count, stepX, stepZ?): Promise<PatternResult>
client.patterns.ring(projectId, ids, count, { x, z }, { sweepAngle?, rotateCopies? }?): Promise<PatternResult>

client.imports.apply(projectId, { fileName, bytes, mediaType? }): Promise<MeshImportResult>
client.imports.readable(fileName): boolean

设定 expectedRevision 从项目上一个回复发送的修改 If-Match: "<revision>" 每一个写请求。 呆滞价值上升 SketchForgeApiError 有状态 409 代码 revision_mismatch; 错误 revision 是当前项目的修订。 使用一个客户端配置用于一次一个有条件的写入,然后为下一次写入创建一个带有返回版本的新版本.

shapes.create 接受 SketchSweepShapeRequest 用于服务器所构建的草图 扫地取道. 内容 profile 包含封闭的路由,一个开口的脊椎,以及 选项 sweepFrames;该答复保留该数据为 sketchProfile。 。 。 . 也让马厩回归 sketchSourceId 用于项目特性元数据. REST API 接受重建持续草图扫描的配置更新 。 发送 替换 sketchProfile 改为 PATCH /api/v1/shapes,针对任一 输出形状 ID 或其 sketchSourceId; 重建保留同样的形状 还有源码 见 REST 扫描更新合同。 。 。 .

A 类 ProjectHandle 与工程 id 绑定是同一个表面,这就是宏通过:

project.get() | delete() | listShapes() | add(shape) | addMany(shapes) | update(id, changes)
project.remove(id) | clear() | edges(id) | filletableEdges(id)
project.treat(id, treatment) | fillet(id, radius, edges?) | chamfer(id, distance, options?)
project.faces(id, { topology? }?) | facesFacing(id, direction)
project.measurements(id) | measurementsWithRadius(id) | measureBetween(from, to) | propertiesOf(id) | interferenceReport({ ids?, clearance? })
project.sectionDrawing({ plane: "xy", offset: 3, shapeIds? })
project.sectionDxf({ plane: "xy", offset: 3, shapeIds? }) | estimateWallThickness(id) | explodeStepAssembly(id)
project.export({ format, shapeIds?, name?, view?, section? })
project.cut({ solidIds, holeIds }) | group(ids) | ungroup(id) | separate(id)
project.mirrorBaked(ids, axis)
project.shell(id, { thickness, openFaces? }) | hollow(id, thickness) | tray(id, thickness, openFaces?)
project.draftPrism(id, angle)
project.align({ ids, axis, target }) | alignTops(ids, anchorId?) | distribute({ ids, axis?, spacing? })
project.pattern(request) | row(ids, count, stepX, stepZ?) | ring(ids, count, { x, z }, options?)
project.importMesh({ fileName, bytes })

形状被描述为参数,每个类型有一个结合,所以属于另一个类型的一个字段是一个编译错误:

await project.addMany([
  { kind: "box", name: "Plate", width: 120, depth: 40, height: 6 },
  { kind: "cylinder", width: 8, height: 12, sides: 6, x: -40, elevation: -1, hole: true },
  { kind: "thread", size: "M8", length: 10, hole: true, x: 40 },
  { kind: "revolve", profile: [[8, 0], [10, 0], [10, 2], [8, 2]], pitch: 6, turns: 6 },
]);

addMany 是每个请求的项目包的一则写出,拆分列表比服务器的200形状限制更长——这也意味着更长的列表不再是原子了.

保留草图扫描,替换 sketchProfile 用于重建固件,同时保持相同的形状和 source-sketch ID:

const sweep = await client.shapes.create(projectId, { kind: "sketchSweep", profile });
const widerProfile = {
  ...sweep.sketchProfile!,
  points: sweep.sketchProfile!.points.map((point) =>
    point.sweepRole === "section" ? { ...point, x: point.x * 1.1 } : point,
  ),
};
await client.shapes.update(projectId, sweep.id, { sketchProfile: widerProfile });

exports.export 涵盖格式 PROJECT_EXPORT_FORMATS - 说吧 stl, (中文(简体) ), obj, (中文(简体) ), 3mf, (中文(简体) ), step, (中文(简体) ), skf, (中文(简体) ), zip, (中文(简体) ), svg 财务报告和审定财务报表 dxf -- 并返回整个文件 data: Uint8Array,与服务器的 filename, (中文(简体) ), mimeType, (中文(简体) ), bytes 财务报告和审定财务报表 shapeCount。STEP出口也报告 exportedCount 编辑 skipped 形状不能写成精确的固体. view (单位:千美元) EXPORT_VIEWS)仅适用于SVG, featureAngle 在 SVG 预测( 默认 30; 精确的固体忽略它) 上设置网格折叠角从 0 到 180 度 ,以及 section (单位:千美元){ plane, offset })适用于SVG和DXF. 客户端在发送任何信息之前拒绝不支持的选项组合, 并带有服务器自己的消息 :

import { writeFile } from "node:fs/promises";

const stl = await project.export({ format: "stl", name: "bracket" });
await writeFile(stl.filename, stl.data);
const top = await project.export({ format: "svg", view: "top" });

client.faces.list(projectId, shapeId, { topology: "exact" }) 财务报告和审定财务报表 project.faces(shapeId, { topology: "exact" }) 选择分析面孔,包括 平地转动。 选择选项保留默认网格列表; topology: "mesh" 明确选择它.

ShapePatch 还接受 faceDraft 在草图上保留一张面部草稿 源头脸是用它的稳定身份命名的 GET /faces;草案存储了已签名的本地拉轴和角度。 配置文件只当一个平面符合其原常态时才会编辑重放. 发送 faceDraft: null 以清除行动. .skf 保留了操作和源框;棱镜草稿和边缘处理尚不能与之结合.

失败的是 SketchForgeApiError (单位:千美元)status, (中文(简体) ), detail附加时 looksDisabled, (中文(简体) ), needsStorage, (中文(简体) ), isProjectBusy,以及 isLimitExceeded)或(或) SketchForgeTransportError。 。 。 . isLimitExceeded 承认 413 limit_exceeded 遗产 too_many_shapes 代码。 丢失项目锁的文字经过重审; a 锁定形状答案为409, 原因无关, 而不是.

宏

宏是一个文件, 它会输出一个定义, 当它被传递到一个连接的工程时会构建某个东西 :

// macros/bolt-plate.macro.mjs
export default {
  name: "Bolt plate",
  project: { mode: "replace", name: "Bolt plate" },
  params: { holes: 5, spacing: 25, size: "M8", thickness: 8 },

  async run({ project, params, layout, log }) {
    log(`${params.holes} ${params.size} holes`);
    await project.addMany([
      { kind: "box", name: "Plate", width: 200, depth: 40, height: params.thickness },
      ...layout.row(params.holes, params.spacing).map((at, index) => ({
        kind: "thread",
        name: `hole ${index + 1}`,
        size: params.size,
        length: params.thickness + 2,
        hole: true,
        x: at.x,
        z: at.z,
        elevation: -1,
      })),
    ]);
  },
};
SKETCHFORGE_API_TOKEN=... npx sketchforge-macro macros/bolt-plate.macro.mjs --holes=7 --spacing=30

环境包含着宏观需要的一切 -- client, (中文(简体) ), project, (中文(简体) ), params, (中文(简体) ), layout, (中文(简体) ), log - 因此,宏文件不导入任何内容,并且可以在任何文件夹中存在.

直接指挥线业务

用同样的 sketchforge-macro 用于项目管理和文件传输而无需写入宏的可执行文件 :

npx sketchforge-macro projects list
npx sketchforge-macro projects create "Bracket"
npx sketchforge-macro projects delete <project-id>
npx sketchforge-macro export --format stl --out bracket.stl --project <project-id>
npx sketchforge-macro import bracket.stl

出口用途 --project 或者说 SKETCHFORGE_PROJECT_ID。当给定时,导入到选定的项目中;如果没有一个,它就会创建以文件命名的项目。 直接文件导入支持STL,OBJ,和SVG;导出支持STL,OBJ,3MF,STEP,SKF,ZIP,SVG,和DXF.

项目模式和为什么没有默认

第二次运行是使宏观有用或危险的. 一个附加在它每次发现的一个项目上的脚本将部分翻了一倍,在打开项目之前没有人会注意到. 因此模式被宣布,而跑者,而不是宏体,则对它采取行动:

project跑者会先做什么用它来
"fresh"创建新工程, 名称随时间而印出由您生成若干比较
{ mode: "replace", name }发现项目和 虚空 或创造它脚踏实地的部分; 重跑是一能的
{ mode: "reuse", name }发现或创造它,而留下它的内容将一个特性添加到任何事物中
"target"要求数 SKETCHFORGE_PROJECT_ID 或者说 --project,不要碰别的东西在其它命名的项目上运行

一个输出一个空函数而不是定义的模块运行在 fresh project,因为这是唯一可以安全假设的模式.

参数

params 既是默认,也是计划。 命令行覆盖被强制用于其默认类型,宏不声明的名称被拒绝 -- -- 所以 --hoels=7 是一个错误,而不是一个默默被忽略的字典。 参数在项目创建前就解决了,所以一个坏的不费钱.

实例

examples/ 持有四个有效的宏,作为整合测试的双倍:

宏构建
bolt-plate.macro.mjs一个盘子大小 绕一排计算出来的被窃听的孔
bolt-circle.macro.mjs半径R的一圈有被打孔的花瓶
filleted-box.macro.mjs一个来自三个数字的盒子,每个可圆形边缘都填入服务器
tap-drill-plate.macro.mjs用M8线穿过的盘子切 project.cut,导出为 STL

纹孔是 kind: "thread" 与 hole: true:表型被清除后切得更宽,外形被标记为出洞,因此在编辑器中或经过中与板块相组合 project.group 敲打盘子. project.cut 也可以直接去掉线网. tap-drill-plate.macro.mjs 通过 API 运行整个剪接和输出工作流程,并配有 M8 线程剪接器.

发展

npm run client:build       # compile to dist/, from the repository root
npm run client:typecheck   # including the tests
npm test                   # unit tests, in the repository's vitest run
npm run test:integration   # the examples, against a live server

集成测试需要一个真正的服务器, 而不是其中的一部分 npm test数字 :

SKETCHFORGE_PROJECTS_DIR=$(mktemp -d) SKETCHFORGE_API_TOKEN=test-token \
  npx next dev apps/web -p 3100 &
SKETCHFORGE_API_TOKEN=test-token SKETCHFORGE_URL=http://127.0.0.1:3100 npm run test:integration

出版

见 文件/资料 在发布工作流程发布此软件包之前, npm 上必须配置的内容.

本地 MCP 模式

MCP直接对项目文件使用被提取的引擎包,没有浏览器,HTTP服务器或API令牌. 设定 SKETCHFORGE_PROJECTS_DIR 中包含 <projectId>.json 财务报告和审定财务报表 <projectId>.skf 文件 :

SKETCHFORGE_PROJECTS_DIR="$HOME/SketchForgeProjects" npm run mcp:sketchforge

目录使用相同的 <projectId>.json 财务报告和审定财务报表 <projectId>.skf 文件作为服务器侧项目存储。 在本地模式下, 使用 sketchforge_list_projects 发现项目, sketchforge_create_project 开始空的,或 sketchforge_import_project 带有本地 .skf filePath 以添加已有的软件包. sketchforge_import_file 进口 .stl, (中文(简体) ), .obj,或 .svg 文件输入选定的工程;其选项是 projectId, (中文(简体) ), filePath选择 url。源文件名提供导入的形状名称,颜色和位置使用输入器默认值。 与一个 url,将文件从 MCP 主机读取并作为基数64 发送到 POST /api/v1/imports 使用 SKETCHFORGE_API_TOKEN。 。 。 . sketchforge_import_stl 仍然是本地唯一兼容的化名, 也可以接受 name, (中文(简体) ), color, (中文(简体) ), x, (中文(简体) ), z,以及 elevation它拒绝 url。当地进出口路径仅限于: SKETCHFORGE_MCP_FILES_DIR,或改为 SKETCHFORGE_PROJECTS_DIR 当新设置未设置时。 设定 overwrite: true 以替换已存在的导出;否则将保留已存在的文件。 对于相容性,当两个根变量都未被设置时,路径不受限制. 在检查文件根之前解决了共通性 。 需要时会创建配置的目录 。 这些工具返回新的 projectId 其他项目工具. sketchforge_read_scene 接受 shapeIds, (中文(简体) ), excludeShapeIds, (中文(简体) ), includeRawShapes,以及 includeRawShapeIds; 原网格几何默认被省略,可以请求用于所选形状. 变异工具返回压缩形状元数据,同时 sketchforge_list_objects 总是返回对象摘要。 STL, OBJ, 3MF, ZIP- kit, SKF, STEP, SVG 出口可以写到 outputPath;工程名称提供默认文件名. sketchforge_project_view 返回 SVG 绘图本身 – 内核对所存储几何的隐藏行投影,作为四视图网格或任何一种命名的视图 – 这是一个没有视觉的代理的可读部分的图片 . sketchforge_section_view 返回区域轮廓为 SVG xy, (中文(简体) ), xz,或 yz 平面和平面;平面必须跨越选定的几何。 精确的外形被内核切出,网状外形被直接切出,在整个选择中可高达2,000,000个网状三角.

本地模式涵盖项目读取和编辑、形状创建/进口、安排、镜像、分组、布林斯、边缘和面部操作、测量和每个形状的质量属性、空心、网格和STEP导出、场景地貌、清理和交叉检查以及JavaScript宏。 图像捕捉光线将相同的图画画给PNG(一个命名的面是一景; home 财务报告和审定财务报表 current 是网格;它不捕捉编辑器铬或活式取景相机。 npm MCP客户端和 @constructorfleet/sketchforge-engine 由发布工作流程一起发布。 编辑器发现和编辑器错误检查需要URL和开放编辑器. Python宏需要URL和HTTP适配器.

版本与发布记录

@constructorfleet/sketchforge3d 共发布 32 个版本,首次发布于 2026/10/20;过去 12 个月发布了 32 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
1.30.0latest2026/10/2813 天前
1.29.12026/10/2714 天前
1.29.02026/10/2615 天前
1.28.02026/10/2615 天前
1.27.02026/10/2516 天前
1.26.02026/10/2417 天前

关于 @constructorfleet/sketchforge3d 的常见问题

@constructorfleet/sketchforge3d 是什么?

为 SketchForge REST API 键入客户端和宏跑取器.

如何安装 @constructorfleet/sketchforge3d?

在项目目录运行 npm install @constructorfleet/sketchforge3d,或使用 pnpm add / yarn add / bun add。它还提供命令行工具 sketchforge-mcp、sketchforge-macro,可以用 npx sketchforge-mcp 直接运行。

@constructorfleet/sketchforge3d 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@constructorfleet/sketchforge3d 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@constructorfleet/sketchforge3d 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=20。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@constructorfleet/sketchforge3d 可以商用吗?

@constructorfleet/sketchforge3d 使用 AGPL-3.0-only 许可。最严格的 Copyleft:除分发外,通过网络向用户提供基于它的服务也需要开源对应源码。闭源 SaaS 使用前务必评估。以上为概括,不构成法律意见,请以许可原文为准。

@constructorfleet/sketchforge3d 的下载量有多少?

根据 npm 官方下载统计,@constructorfleet/sketchforge3d 在 2026/10/11 – 2026/11/09 这 30 天内被下载 5,074 次,日均约 169 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@constructorfleet/sketchforge3d 还在维护吗?

最新版本 1.30.0 发布于 2026/10/28(13 天前);至今共发布 32 个版本,其中过去 12 个月发布了 32 个;首次发布于 2026/10/20。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。