- 开发工具
- CLI
【免费下载链接】fq
fq - jq for binary formats. Tool, language and decoders for working with binary formats.
fq 是一个面向二进制格式的 jq 风格工具、脚本语言与解码器集合,其内置的stl解码器用于解析二进制 STL(Stereolithography / Standard Tessellation Language,立体光刻/标准曲面细分语言)文件,把原本不可读的三角形网格数据映射为结构化 JSON,从而可以直接用 jq 查询模型的面片数量、顶点坐标、法向量等几何信息。本文围绕 format/stl/stl.md 展开,结合 stl.go 的源码实现与 file.stl.fqtest 测试样例,完整讲解二进制 STL 的布局、fq 的字段映射方式、实际解码输出与当前已知限制,读完即可上手分析任意二进制 STL 文件。
STL 格式背景与二进制布局
STL 是 3D 打印与 CAD/CAM 领域最常用的三角网格交换格式之一,它把模型表面描述为一系列互不相交的三角形(facet)。格式分为 ASCII 与二进制两种变体,本文讨论的 fq 解码器只针对二进制变体。
一个标准二进制 STL 文件由三部分组成:
| 偏移 | 长度 | 内容 |
|---|---|---|
| 0 | 80 字节 | 文件头(header),通常为 ASCII 文本,例如Exported from Blender-3.6.23 |
| 80 | 4 字节 | 面片数量num_facets(uint32,小端) |
| 84 | 50 × N 字节 | N 个三角形面片,每个固定 50 字节 |
每个三角形面片固定为 50 字节,内部结构为:
| 偏移 | 长度 | 内容 |
|---|---|---|
| 0 | 12 字节 | 法向量normal,3 个 float32(x/y/z) |
| 12 | 36 字节 | 三个顶点vertices,每个顶点 3 个 float32(x/y/z) |
| 48 | 2 字节 | attribute_byte_count(uint16,通常为 0),表示其后跟随后续属性数据的字节数 |
fq 中 STL 解码器的注册与入口
在 fq 中,每个格式都以decode.Group的形式登记在 format/format.go:
STL = &decode.Group{Name: "stl"}随后在 format/stl/stl.go 中通过interp.RegisterFormat把stl组名、描述字符串Standard Tessellation Language与解码函数decodeSTL绑定,同时用interp.RegisterFS注册内嵌的stl.md文档,使得用户在交互式 REPL 中输入:help stl之类指令时能看到格式说明:
func init() { interp.RegisterFormat( format.STL, &decode.Format{ Description: "Standard Tessellation Language", DecodeFn: decodeSTL, }) interp.RegisterFS(stlFS) }入口函数decodeSTL首先把解码器端序切换为小端,然后调用decodeSTLModel:
func decodeSTL(d *decode.D) any { d.Endian = decode.LittleEndian decodeSTLModel(d) return nil }由于 STL 二进制格式全部使用 IEEE 754 小端 float32 与 uint32/uint16,这里强制LittleEndian是正确且必要的——它保证后续所有FieldF32/FieldU32读取都按二进制 STL 规范的字节序解释。
字段映射:从字节到结构化 JSON
decodeSTLModel是核心解码逻辑,对应 STL 文件的三段式布局:
func decodeSTLModel(d *decode.D) { d.FieldUTF8NullFixedLen("header", headerLength) numFacets := d.FieldU32("num_facets") d.FieldStructNArray("facets", "facet", int64(numFacets), decodeFacet) }header:调用 FieldUTF8NullFixedLen 读取固定 80 字节并以 NUL 结尾截断的 UTF-8 字符串(headerLength常量定义为 80,见 stl.go)。许多建模软件会把导出工具与版本号写入此处,例如测试样例中的Exported from Blender-3.6.23。num_facets:用FieldU32读取 4 字节小端无符号整数,作为后续面片数组的长度。facets:通过FieldStructNArray按num_facets重复解码decodeFacet,每个元素在输出中的结构名(structName)为facet,从而在 JSON 中呈现为facets[0]、facets[1]… 每个 facet 都带facet标签。
单个面片的解码由decodeFacet完成:
func decodeFacet(d *decode.D) { d.FieldStruct("normal", decodeVector) d.FieldStructNArray("vertices", "vertex", 3, decodeVector) attributeByteCount := d.FieldU16("attribute_byte_count") if attributeByteCount > 0 { d.FieldRawLen("attribute", int64(attributeByteCount)*8) } }normal:一个由decodeVector定义的结构体,包含 x/y/z 三个字段。vertices:固定 3 个vertex结构体的数组。attribute_byte_count:2 字节 uint16。多数文件为 0;若大于 0,解码器会用 FieldRawLen 以位为单位(字节数 × 8)把剩余属性原始数据整体读入attribute字段,而不是丢弃,从而保证解码后的字节范围与实际文件完全对齐。
decodeVector定义了三元组坐标的通用读取方式,三个字段均为FieldF32(pkg/decode/decode_gen.go#L18293,读取当前端序下的 32 位 IEEE 754 浮点数):
func decodeVector(d *decode.D) { d.FieldF32("x") d.FieldF32("y") d.FieldF32("z") }由此,任意二进制 STL 文件会被解码成如下结构的 JSON 树:
{ "header": "Exported from Blender-3.6.23", "num_facets": 4, "facets": [ { "normal": { "x": 0, "y": -1, "z": 0 }, "vertices": [ { "x": 1, "y": 0, "z": 0 }, { "x": 0, "y": 0, "z": 1 }, { "x": 0, "y": 0, "z": 0 } ], "attribute_byte_count": 0 } ] }实际解码输出验证
仓库在 format/stl/testdata/file.stl.fqtest 中提供了完整的解码期望输出,对应一个由 Blender 3.6 导出的、包含 4 个面片的 284 字节 STL 文件。其顶层摘要为:
$ fq -d stl dv file.stl .{}: file.stl (stl) 0x0-0x11c (284) header: "Exported from Blender-3.6.23" 0x0-0x50 (80) num_facets: 4 0x50-0x54 (4) facets[0:4]: 0x54-0x11c (200)可以看到 fq 的十六进制与反汇编视图会为每个字段标注精确的字节区间:header 占0x0-0x50(80 字节),num_facets占0x50-0x54,4 个面片恰好覆盖文件剩余0x54-0x11c(4 × 50 = 200 字节),与二进制 STL 布局完全一致。
面片内部也逐字段给出坐标值与偏移,例如第一个面片的法向量:
normal{}: 0x54-0x60 (12) x: 0 0x54-0x58 (4) y: -1 0x58-0x5c (4) z: 0 0x5c-0x60 (4) vertices[0:3]: 0x60-0x84 (36) [0]{}: vertex 0x60-0x6c (12) x: 1 0x60-0x64 (4) y: 0 0x64-0x68 (4) z: 0 0x68-0x6c (4) attribute_byte_count: 0 0x84-0x86 (2)字节区间的总和(12 + 36 + 2 = 50 字节)精确等于一个 facet 的标准长度。值得注意的是第 4 个面片展示了浮点坐标的原始精度:
x: 0.5773502588272095 0xea-0xee (4)0.5773502588272095是 1/√3 的 float32 近似值(STL 中常见于对角线方向的单位法向量),fq 保留完整的 IEEE 754 解析结果而非四舍五入,便于后续做精确的几何校验。
使用方式:命令行与 jq 查询
编译安装 fq 后,可直接用-d stl指定解码器解析 STL 文件。常用命令示例:
# 查看整体结构与字节偏移(d=display,v=verbose) fq -d stl dv model.stl # 输出为 JSON,配合 jq 查询几何信息 fq -d stl tojson model.stl # 统计面片数量 fq -d stl tojson model.stl | jq '.num_facets' # 查看所有面片的法向量 fq -d stl tojson model.stl | jq '.facets[].normal' # 提取第一个顶点的坐标 fq -d stl tojson model.stl | jq '.facets[0].vertices[0]' # 找出坐标最大/最小边界(包围盒估算) fq -d stl tojson model.stl | jq '[.facets[].vertices[] | .x] | {min: min, max: max}'由于 facets 是定长结构数组,jq 的管道、映射与聚合能力可以直接作用于网格数据,这让"统计三角形数量、检查法向量是否单位化、计算模型包围盒、验证网格水密性"等分析任务都可以在命令行一步完成,无需编写解析脚本。
当前限制与注意事项
根据 format/stl/stl.md 的说明,fq 的 STL 解码器目前存在以下限制,分析文件时需要留意:
- 不支持 ASCII STL 文件:
decodeSTL只按二进制布局(80 字节头 + uint32 面片数)解析,遇到以solid开头的 ASCII STL 会被当作二进制头处理,得到错误的num_facets。使用前需确认文件为二进制变体。 - 不支持 VisCAM 和 SolidView 颜色:这两款软件把颜色信息编码在
attribute_byte_count之后的 2 字节属性中。源码在 stl.go 以// TODO support color of VisCAM and SolidView注明尚未实现。 - 不支持 Materialise Magics 颜色:Materialise Magics 把整个模型以"一个 facet + 颜色数据"的变体格式存储,同样在 stl.go 与 stl.go 处以 TODO 标注,尚未支持。
好消息是:即使attribute_byte_count > 0,fq 仍会把属性字节原样读入attribute字段(FieldRawLen逻辑),因此带 VisCAM/Materialise 颜色扩展的文件虽无法解析出语义化的颜色,但结构依然能够完整解码、字节不会错位,用户仍可对几何数据部分进行 jq 分析。
结合源码进一步扩展
对于希望把 STL 能力接入其他格式或自定义脚本的读者,可以从以下几处继续深入当前仓库:
- format/stl/stl.go:完整的 STL 解码实现,约 60 行,是理解 fq 格式插件最精简的范例之一(注册、内嵌文档、小端切换、结构体数组递归解码一应俱全)。
- format/format.go:
format.STL组的定义位置,可参照其模式在 fq 中登记自定义格式。 - format/stl/testdata/file.stl.fqtest:fq 的回归测试样例,展示了逐字节的期望解码结果,也是理解
fq -d stl dv输出格式的最佳参考。 - pkg/decode/decode_gen.go、pkg/decode/decode_gen.go:
FieldUTF8NullFixedLen、FieldRawLen、FieldF32等基础读取原语的定义,可用于推断其他二进制格式解码器的工作原理。
从源码结构看,STL 解码器完全由通用的 decode 原语组合而成,没有依赖任何第三方库;若未来要支持 ASCII STL,最自然的做法是在decodeSTL入口先读取并判断头 5 个字节是否为solid关键字,再分流到两条解析路径,但这一能力在当前仓库中尚未实现。
- 开发工具
- CLI
【免费下载链接】fq
fq - jq for binary formats. Tool, language and decoders for working with binary formats.
相关推荐
用 fq 解析 TZX 磁带格式:ZX Spectrum 二进制格式的 jq 式结构化解码
用 fq 解析 TZX 磁带格式:ZX Spectrum 二进制格式的 jq 式结构化解码 TZX 是专为保存 ZX Spectrum 等 8 位家用计算机盒式
开发工具CLIfq 的 bits / bytes 格式:用 jq 对二进制位与字节进行切片、索引与解码
fq 的 bits / bytes 格式:用 jq 对二进制位与字节进行切片、索引与解码 本文讲解 fq 内置的 bits (原始位)与 bytes (原始字节
开发工具CLINginx流量监控终极指南:10分钟搭建专业级监控系统
Nginx流量监控终极指南:10分钟搭建专业级监控系统 Nginx module vts是一款功能强大的Nginx虚拟主机流量状态监控模块,能够帮助用户实时监控
后端可观测性指标监控
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考