// 3MF 解析。
//
// 3MF(3D Manufacturing Format)是现代切片软件实际使用的格式——PrusaSlicer、
// Bambu Studio、Cura 导出的都是它,定位上就是 STL 的正式替代品。
//
// 它和 STL 有两处根本不同:
//
//   1. **它是个 ZIP 包**(OPC 容器),模型数据装在里面一个 XML 部件里,
//      通常叫 3D/3dmodel.model。
//   2. **顶点是共享的**。三角形存的是顶点表下标,和 OBJ 一样,
//      而不是像 STL 那样每个三角形把自己的三个顶点重写一遍。
//
// 第二点对「能不能打印」这个问题特别重要:**拓扑关系是现成的**。
// STL 里「哪些三角形共用同一个顶点」得靠容差焊接去猜(差 1e-9 的算不算同一个点?),
// 输入文件可明确共享身份,但本库统一展开到 Triangle 列表;
// 下游仍按坐标容差重建拓扑,不保留原始顶点身份。
//
// 代价是格式复杂得多:要解 ZIP、要解 XML,对象还能互相引用拼成装配体。

///|
/// 一个 3MF 对象。
///
/// 两种形态二选一:要么自带网格(顶点表 + 三角形索引),要么是一组组件
/// (引用别的对象,各带一个变换矩阵)。用两个数组共存来表示,
/// 比套一层枚举少绕几下——解析时本来就是一个一个往里塞。
priv struct MfObject {
  /// 共享顶点表
  vertices : Array[Vec3]
  /// 三角形,存的是顶点表下标
  triangles : Array[(Int, Int, Int)]
  /// 组件:引用的对象 id + 变换矩阵
  components : Array[(Int, FixedArray[Double]?)]
}

///|
/// 3MF 是个 ZIP 包。只看魔数,真正的校验在 `parse_3mf` 里做——
/// 「打开才知道」的检查不该塞进一个只看开头的判断函数。
pub fn is_3mf(data : Bytes) -> Bool {
  data.length() >= 4 &&
  data[0] == b'P' &&
  data[1] == b'K' &&
  // 空 ZIP 的开头是 PK\x05\x06,这里要的是有内容的那种
  data[2] == b'\x03' &&
  data[3] == b'\x04'
}

///|
/// 默认严格校验 OPC 关系和内容类型,再按 Core 几何子集读取。
/// compatibility=true 显式启用旧包定位回退;不放宽 XML/几何检查。
pub fn parse_3mf(
  data : Bytes,
  compatibility? : Bool = false,
) -> Result[Mesh, String] {
  // 解 ZIP。fzip 自带 zip bomb 防护(解压比超过 1000 倍直接拒绝)
  // 和不安全路径拒绝(绝对路径、`..` 一律不收)——解析用户传上来的文件,
  // 这两道防线是必须的,自己写很容易漏。
  let entries = @fzip.unzip_sync(data.to_fixedarray()) catch {
    @fzip.FzipError(message~, ..) =>
      return Err("这不是一个合法的 3MF:ZIP 解不开——\{message}")
  }
  let located = if compatibility {
    find_model_part(entries)
  } else {
    mf_strict_model_part(entries)
  }
  let xml = match located {
    Ok(text) => text
    Err(msg) => return Err(msg)
  }
  parse_model_xml(xml)
}

///|
/// 从压缩包里找出模型部件。
///
/// 正规做法是读 `_rels/.rels`,里面记着哪个部件是模型。绝大多数文件确实就叫
/// `3D/3dmodel.model`。两条路都走:先按规范查关系表,查不到再退化成
/// 「找唯一一个 .model 文件」——有些导出器写的关系表不规范,但文件名是对的。
fn find_model_part(
  entries : Array[(String, FixedArray[Byte])],
) -> Result[String, String] {
  let target = match find_entry(entries, "_rels/.rels") {
    Some(raw) => rels_model_target(@utf8.decode_lossy(Bytes::from_array(raw)))
    None => None
  }
  match target {
    Some(path) =>
      match find_entry(entries, path) {
        Some(raw) => mf_xml_text(raw)
        None =>
          Err(
            "3MF 的关系表指向「\{path}」,但包里没有这个部件",
          )
      }
    None => {
      // 退路:找 .model 文件
      let models : Array[String] = []
      for entry in entries {
        let (name, _) = entry
        if name.to_lower().has_suffix(".model") {
          models.push(name)
        }
      }
      if models.length() == 0 {
        Err(
          "这个 ZIP 里没有 3D 模型部件(找不到 .model 文件),不是 3MF 文件",
        )
      } else if models.length() > 1 {
        Err(
          "这个 3MF 里有 \{models.length()} 个 .model 部件,关系表也没说是哪个,认不出来",
        )
      } else {
        match find_entry(entries, models[0]) {
          Some(raw) => mf_xml_text(raw)
          None => Err("读不到模型部件「\{models[0]}」")
        }
      }
    }
  }
}

///|
/// 从 `_rels/.rels` 里找出模型部件路径。
///
/// 关系表长这样:
///
/// ```xml
/// 
/// ```
///
/// 认的是 Type 后缀而不是 Id——Id 是随便起的。
fn rels_model_target(xml : String) -> String? {
  let reader = @xml.Reader::from_string(xml)
  let mut found : String? = None
  while found is None {
    let event = reader.read_event() catch {
      // 关系表坏了不该让整个解析失败,退回去按文件名找就行
      _ => break
    }
    let element = match event.kind {
      @xml.EventKind::Start(e) | @xml.EventKind::Empty(e) => e
      // **必须在这里停下来。**
      //
      // 读到文件末尾之后,`read_event` 会一直返回 `Eof` 而不会抛错。
      // 少了这个分支,一个「关系表是合法 XML、但里面没有 3dmodel 关系」的包
      // 就会在这里无限空转——CPU 跑满,命令行和网页都卡死,而且永远等不到报错。
      // 现实中把 .docx 改名成 .3mf 就是这个样子。
      @xml.EventKind::Eof => break
      _ => continue
    }
    if local_name(element.name) != "Relationship" {
      continue
    }
    let kind = match element.get("Type") {
      Some(t) => t
      None => continue
    }
    if kind != "http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel" {
      continue
    }
    // 关系对上了但没有 Target——文件不规范,不算数,继续往后找
    match element.get("Target") {
      Some(t) => found = Some(normalize_part_path(t))
      None => ()
    }
  }
  found
}

///|
/// OPC 里的路径可能写成 `/3D/3dmodel.model` 这种带前导斜杠的,
/// 而压缩包里存的是 `3D/3dmodel.model`。统一去掉前导斜杠。
fn normalize_part_path(path : String) -> String {
  if path.has_prefix("/") {
    path[1:].to_owned()
  } else {
    path
  }
}

///|
/// 按名字找一个部件。
///
/// 大小写不敏感——OPC 的部件名在规范里不区分大小写,
/// 实际文件里 `3D/3dmodel.model` 和 `3d/3DModel.model` 都出现过。
fn find_entry(
  entries : Array[(String, FixedArray[Byte])],
  name : String,
) -> FixedArray[Byte]? {
  let want = normalize_entry_name(name)
  for entry in entries {
    let (entry_name, bytes) = entry
    if normalize_entry_name(entry_name) == want {
      return Some(bytes)
    }
  }
  None
}

///|
/// 把压缩包里的条目名规范化,好用来比对。
///
/// 两件事:
///
/// 1. **转小写**——OPC 的部件名不区分大小写。
/// 2. **反斜杠换成正斜杠**——ZIP 规范要求条目名用正斜杠,但 Windows 上一些
///    打包工具(比如 PowerShell 的 `Compress-Archive`)会写成反斜杠。
///    不归一化的话,一个结构完好的包会因为 `3D\3dmodel.model` 匹配不上
///    `3D/3dmodel.model` 而被判成「没有模型部件」——报错完全指错了方向。
fn normalize_entry_name(name : String) -> String {
  let lower = name.to_lower()
  if lower.contains("\\") {
    lower.replace_all(old="\\", new="/")
  } else {
    lower
  }
}

///|
/// 去掉命名空间前缀,只留本地名。
///
/// 3MF 的 XML 用默认命名空间,正常情况下标签就是 ``。
/// 但规范允许写前缀形式 ``,
/// 那就得把 `m:` 切掉才能按名字匹配。
fn local_name(name : String) -> String {
  let mut last : StringView = name[:]
  for part in name.split(":") {
    last = part
  }
  last.to_owned()
}

///|
/// 取命名空间前缀。没有冒号就是空串(默认命名空间)。
fn prefix_of(name : String) -> String {
  match name.find(":") {
    Some(i) => name[:i].to_owned()
    None => ""
  }
}

///|
const MF_CORE_NS : String = "http://schemas.microsoft.com/3dmanufacturing/core/2015/02"

///|
priv struct MfXmlScope {
  name : String
  tag : String
  bindings : Map[String, String]
}

///|
fn mf_bindings(
  element : @xml.XmlElement,
  inherited : Map[String, String],
) -> Map[String, String] {
  if !element.attributes.any(a => {
      a.name == "xmlns" || a.name.has_prefix("xmlns:")
    }) {
    return inherited
  }
  let bindings : Map[String, String] = Map([])
  for key, value in inherited {
    bindings.set(key, value)
  }
  for attr in element.attributes {
    if attr.name == "xmlns" {
      bindings.set("", attr.value)
    } else if attr.name.has_prefix("xmlns:") {
      bindings.set(attr.name[6:].to_owned(), attr.value)
    }
  }
  bindings
}

///|
fn mf_parent_of(tag : String) -> String? {
  match tag {
    "resources" | "build" => Some("model")
    "object" => Some("resources")
    "mesh" | "components" => Some("object")
    "vertices" | "triangles" => Some("mesh")
    "vertex" => Some("vertices")
    "triangle" => Some("triangles")
    "component" => Some("components")
    "item" => Some("build")
    _ => None
  }
}

///|
/// 解析 3D/3dmodel.model。
///
/// 关心的标签只有几个:`model`(拿单位)、`object`(攒顶点和三角形)、
/// `component`(装配体引用)、`item`(build 里列出要打印的对象)。
/// 其余(metadata、basematerials、切片信息……)全部跳过——
/// 这些语义不进入 Mesh;忽略不代表已验证制造语义或可打印。
fn parse_model_xml(xml : String) -> Result[Mesh, String] {
  let reader = @xml.Reader::from_string(xml)
  let objects : Map[Int, MfObject] = Map([])
  // build 里列出的 (对象 id, 变换)
  let build : Array[(Int, FixedArray[Double]?)] = []

  // 单位换算到毫米。STL 没有单位概念,3MF 有,不换算的话
  // 一个以英寸建模的模型体积会差 25.4³ ≈ 16387 倍
  let mut unit_scale = 1.0

  // 按 XML 作用域解析完整 URI;不把前缀名称当作命名空间身份。
  let scopes : Array[MfXmlScope] = []
  let empty_bindings : Map[String, String] = Map([])
  let mut model_seen = false
  let mut resources_seen = false
  let mut build_seen = false
  let mut geometry_seen = false

  // 当前正在攒的对象
  let mut cur_id : Int? = None
  let mut cur_vertices : Array[Vec3] = []
  let mut cur_triangles : Array[(Int, Int, Int)] = []
  let mut cur_components : Array[(Int, FixedArray[Double]?)] = []

  while !reader.is_eof() {
    let event = reader.read_event() catch {
      err =>
        return Err(
          "3MF 的模型 XML 解析失败(第 \{reader.line()} 行):\{err}",
        )
    }
    let element = match event.kind {
      @xml.EventKind::Start(e) | @xml.EventKind::Empty(e) => e
      @xml.EventKind::End(name) => {
        if scopes.length() == 0 {
          return Err("3MF XML 出现无对应开始标签的结束标签")
        }
        let scope = scopes.pop().unwrap()
        if scope.name != name {
          return Err("3MF XML 标签不匹配")
        }
        if scope.tag == "object" {
          flush_object(
            objects, cur_id, cur_vertices, cur_triangles, cur_components,
          )
          cur_id = None
          cur_vertices = []
          cur_triangles = []
          cur_components = []
        }
        continue
      }
      @xml.EventKind::Decl(encoding~, ..) => {
        match encoding {
          Some(e) =>
            if e.to_lower() != "utf-8" {
              return Err("3MF XML 声明必须使用 UTF-8")
            }
          None => ()
        }
        continue
      }
      @xml.EventKind::DocType(_) =>
        return Err("当前 3MF 几何读取子集不接受 DTD")
      @xml.EventKind::Text(text) | @xml.EventKind::CData(text) => {
        if scopes.length() == 0 && obj_words(text).length() > 0 {
          return Err("3MF XML 根元素外存在文本")
        }
        continue
      }
      _ => continue
    }
    let parent = if scopes.length() > 0 {
      scopes[scopes.length() - 1].tag
    } else {
      ""
    }
    let inherited = if scopes.length() > 0 {
      scopes[scopes.length() - 1].bindings
    } else {
      empty_bindings
    }
    let bindings = mf_bindings(element, inherited)
    let prefix = prefix_of(element.name)
    let uri = match bindings.get(prefix) {
      Some(value) => value
      None => ""
    }
    if prefix != "" && uri == "" {
      return Err("3MF XML 使用了未声明的命名空间前缀")
    }
    let tag_name = local_name(element.name)
    let mut tag = if uri == MF_CORE_NS { tag_name } else { "" }
    if scopes.length() == 0 {
      if model_seen || tag != "model" {
        return Err(
          "3MF XML 必须恰有一个使用 Core 2015/02 命名空间的 model 根元素",
        )
      }
      model_seen = true
    } else if parent == "" {
      // Optional foreign/unknown subtrees cannot inject Core lookalikes.
      tag = ""
    } else if tag == "model" {
      return Err("3MF model 不能嵌套")
    } else {
      match mf_parent_of(tag) {
        Some(required_parent) =>
          if parent != required_parent {
            return Err("3MF <\{tag}> 必须位于 <\{required_parent}> 中")
          }
        None => tag = ""
      }
    }
    if tag == "resources" {
      if resources_seen || build_seen {
        return Err("3MF resources 必须唯一且位于 build 之前")
      }
      resources_seen = true
    }
    if tag == "build" {
      if build_seen || !resources_seen {
        return Err("3MF build 必须唯一且位于 resources 之后")
      }
      build_seen = true
    }
    if tag == "mesh" || tag == "components" {
      if geometry_seen {
        return Err("3MF object 只能包含一个 mesh 或一个 components")
      }
      geometry_seen = true
    }
    if event.kind is @xml.EventKind::Start(_) {
      scopes.push({ name: element.name, tag, bindings, })
    }
    if tag == "" {
      continue
    }

    if tag == "model" {
      // 单位:micron / millimeter / centimeter / inch / foot / meter
      match element.get("requiredextensions") {
        Some(required) =>
          if obj_words(required).length() > 0 {
            return Err(
              "3MF 声明了本工具未实现的必需扩展 requiredextensions=\"\{required}\";不能仅按核心三角网格解释",
            )
          }
        None => ()
      }
      match element.get("unit") {
        Some(u) =>
          unit_scale = match unit_scale_of(u) {
            Ok(v) => v
            Err(msg) => return Err(msg)
          }
        None => ()
      }
    } else if tag == "object" {
      // 自闭合的未使用空对象在下一对象处收尾;不接受坏 XML。
      flush_object(objects, cur_id, cur_vertices, cur_triangles, cur_components)
      let id = match attribute_int(element, "id", "object") {
        Ok(v) => v
        Err(msg) => return Err(msg)
      }
      if objects.contains(id) {
        return Err("3MF object id 重复:\{id}")
      }
      geometry_seen = false
      cur_id = Some(id)
      cur_vertices = []
      cur_triangles = []
      cur_components = []
    } else if tag == "vertex" {
      let v = match attribute_vec3(element) {
        Ok(v) => v
        Err(msg) => return Err(msg)
      }
      cur_vertices.push(v)
    } else if tag == "triangle" {
      // 三角形存的是顶点表下标,不是坐标
      let a = match attribute_int(element, "v1", "triangle") {
        Ok(v) => v
        Err(msg) => return Err(msg)
      }
      let b = match attribute_int(element, "v2", "triangle") {
        Ok(v) => v
        Err(msg) => return Err(msg)
      }
      let c = match attribute_int(element, "v3", "triangle") {
        Ok(v) => v
        Err(msg) => return Err(msg)
      }
      cur_triangles.push((a, b, c))
    } else if tag == "component" {
      let id = match attribute_int(element, "objectid", "component") {
        Ok(v) => v
        Err(msg) => return Err(msg)
      }
      let tf = match attribute_transform(element) {
        Ok(t) => t
        Err(msg) => return Err(msg)
      }
      cur_components.push((id, tf))
    } else if tag == "item" {
      let id = match attribute_int(element, "objectid", "item") {
        Ok(v) => v
        Err(msg) => return Err(msg)
      }
      let tf = match attribute_transform(element) {
        Ok(t) => t
        Err(msg) => return Err(msg)
      }
      build.push((id, tf))
    }
  }
  if !model_seen || scopes.length() != 0 {
    return Err("3MF XML 缺少完整 model 根元素")
  }
  if !resources_seen || !build_seen {
    return Err("3MF 缺少 resources 或 build")
  }
  // 最后一个对象
  flush_object(objects, cur_id, cur_vertices, cur_triangles, cur_components)

  if build.length() == 0 {
    return Err(
      "这个 3MF 的 build 里没有列任何对象,没有东西可以打印",
    )
  }

  // 按 build 列出的对象拼出最终网格
  let triangles : Array[Triangle] = []
  for (id, tf) in build {
    // item 不带 transform 就是原样放进去
    let transform = match tf {
      Some(t) => t
      None => identity_transform()
    }
    let body = match build_object(objects, id, transform, 0) {
      Ok(v) => v
      Err(msg) => return Err(msg)
    }
    for tri in body {
      triangles.push(tri)
    }
  }

  if triangles.length() == 0 {
    // 措辞要说清楚两种可能,别让人以为文件坏了。
    // 3MF 的 Beam Lattice 扩展明确规定:挂了 beamlattice 的 mesh
    // **可以一个三角形都没有**(规范原话:MAY consist of no triangles),
    // 几何全写在扩展元素里。这种文件是合法的,只是本工具处理不了
    return Err(
      "这个 3MF 的 build 引用的对象里没有三角形——可能是空对象," +
      "也可能是几何写在扩展元素里的对象(比如 beam lattice,本工具只处理三角网格)",
    )
  }
  let scaled = scale_mesh(triangles, unit_scale)
  for tri in scaled {
    if !finite_vertex(tri.a) || !finite_vertex(tri.b) || !finite_vertex(tri.c) {
      return Err("3MF 单位换算后坐标超出有限范围")
    }
  }
  Ok(Mesh::new(scaled))
}

///|
/// 把一个对象的当前状态收进对象表。
///
/// 抽成函数是因为「收尾」要在两个地方发生:遇到结束标签时,
/// 以及开始下一个对象之前。后一处是给自闭合或者结构不完整的文件兜底的。
fn flush_object(
  objects : Map[Int, MfObject],
  id : Int?,
  vertices : Array[Vec3],
  triangles : Array[(Int, Int, Int)],
  components : Array[(Int, FixedArray[Double]?)],
) -> Unit {
  match id {
    Some(i) => objects.set(i, MfObject::{ vertices, triangles, components, })
    None => ()
  }
}

///|
/// 把所有顶点乘一个系数。
///
/// 单位换算单独走一遍,而不是在解析顶点时乘——因为变换矩阵里的平移量
/// 也是同一个单位的长度,两处都得乘。统一到最后做,不会漏。
fn scale_mesh(triangles : Array[Triangle], scale : Double) -> Array[Triangle] {
  if scale == 1.0 {
    return triangles
  }
  let out : Array[Triangle] = []
  for t in triangles {
    out.push({
      normal: t.normal,
      a: t.a.scale(scale),
      b: t.b.scale(scale),
      c: t.c.scale(scale),
    })
  }
  out
}

///|
/// 把一个对象(可能是装配体)展开成三角形,顺便套上变换。
///
/// `depth` 是递归深度。3MF 允许对象互相引用,**引用成环是坏文件,
/// 但真会出现**,不设上限就会栈溢出。
fn build_object(
  objects : Map[Int, MfObject],
  id : Int,
  transform : FixedArray[Double],
  depth : Int,
) -> Result[Array[Triangle], String] {
  if depth > 32 {
    return Err(
      "3MF 的对象引用套得太深(超过 32 层),可能是循环引用",
    )
  }
  let obj = match objects.get(id) {
    Some(o) => o
    None => return Err("3MF 里引用了不存在的对象 id=\{id}")
  }
  let out : Array[Triangle] = []
  if obj.triangles.length() > 0 {
    // 共享顶点表:三角形存的是下标,到这里才真正取出坐标
    let n = obj.vertices.length()
    for (i, j, k) in obj.triangles {
      if i < 0 || j < 0 || k < 0 || i >= n || j >= n || k >= n {
        return Err(
          "3MF 对象 id=\{id} 里有三角形的顶点下标越界(顶点表只有 \{n} 个," +
          "引用到了 \{i}/\{j}/\{k})",
        )
      }
      let a = apply_transform(obj.vertices[i], transform)
      let b = apply_transform(obj.vertices[j], transform)
      let c = apply_transform(obj.vertices[k], transform)
      if !finite_vertex(a) || !finite_vertex(b) || !finite_vertex(c) {
        return Err("3MF 对象 id=\{id} 变换后坐标超出有限范围")
      }
      out.push(Triangle::from_vertices(a, b, c))
    }
  }
  for (child_id, child_tf) in obj.components {
    // 组件自己的变换要先做,再套外层的——顺序反了位置就错了
    let combined = match child_tf {
      Some(t) => compose_transform(t, transform)
      None => transform
    }
    for value in combined {
      if not_finite(value) {
        return Err("3MF 对象 id=\{child_id} 的嵌套变换溢出")
      }
    }
    let sub = match build_object(objects, child_id, combined, depth + 1) {
      Ok(v) => v
      Err(msg) => return Err(msg)
    }
    for tri in sub {
      out.push(tri)
    }
  }
  Ok(out)
}

///|
/// 3MF 里的长度单位换算成毫米。
///
/// STL 没有单位概念,一路按毫米处理;3MF 有,不换算的话
/// 一个以英寸建模的模型体积会差 25.4³ ≈ 16387 倍,报告里的数字全是错的。
fn unit_scale_of(unit : String) -> Result[Double, String] {
  match unit {
    "micron" => Ok(0.001)
    "millimeter" => Ok(1.0)
    "centimeter" => Ok(10.0)
    "inch" => Ok(25.4)
    "foot" => Ok(304.8)
    "meter" => Ok(1000.0)
    _ =>
      Err(
        "3MF 里写了一个不认识的单位「\{unit}」," +
        "规范里只有 micron / millimeter / centimeter / inch / foot / meter",
      )
  }
}

///|
/// 单位矩阵。build 里的 item 和 component 都可以不带 transform,
/// 不带就是原样放。
fn identity_transform() -> FixedArray[Double] {
  [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0]
}

///|
/// 把一个点按 3MF 的变换矩阵变换。
///
/// 3MF 写的是 12 个数,代表一个 4×3 矩阵,按行排:
///
/// ```text
/// m00 m01 m02
/// m10 m11 m12
/// m20 m21 m22
/// m30 m31 m32      ← 平移
/// ```
///
/// 用的是**行向量**约定:点当作 1×4 的 `[x y z 1]` 去右乘矩阵:
///
/// ```text
/// x' = x·m00 + y·m10 + z·m20 + m30
/// y' = x·m01 + y·m11 + z·m21 + m31
/// z' = x·m02 + y·m12 + z·m22 + m32
/// ```
///
/// 注意平移在 m30/m31/m32,也就是数组的第 9/10/11 个——**不是前三个**。
/// 按列优先、或者把平移放前面,都会得到完全错误的位置。
fn apply_transform(v : Vec3, m : FixedArray[Double]) -> Vec3 {
  Vec3::{
    x: v.x * m[0] + v.y * m[3] + v.z * m[6] + m[9],
    y: v.x * m[1] + v.y * m[4] + v.z * m[7] + m[10],
    z: v.x * m[2] + v.y * m[5] + v.z * m[8] + m[11],
  }
}

///|
/// 先把点按 `first` 变换、再按 `second` 变换,合成一个矩阵。
///
/// 行向量约定下「先 A 后 B」就是 A×B。4×3 的矩阵要隐式补上第 4 列
/// (前三行填 0、最后一行填 1)才能相乘。
fn compose_transform(
  first : FixedArray[Double],
  second : FixedArray[Double],
) -> FixedArray[Double] {
  let out : Array[Double] = []
  for i = 0; i < 4; i = i + 1 {
    for j = 0; j < 3; j = j + 1 {
      let mut sum = 0.0
      for k = 0; k < 3; k = k + 1 {
        sum = sum + first[i * 3 + k] * second[k * 3 + j]
      }
      // 第 4 行(i=3)还要加上 second 自己的平移
      if i == 3 {
        sum = sum + second[9 + j]
      }
      out.push(sum)
    }
  }
  [
    out[0],
    out[1],
    out[2],
    out[3],
    out[4],
    out[5],
    out[6],
    out[7],
    out[8],
    out[9],
    out[10],
    out[11],
  ]
}

///|
/// 读一个整数属性。
///
/// `parse_double` 收的比整数字面量宽得多:`1.5`、`1e3`、`2_0`、`nan`、`inf`
/// 它都认。所以这里必须自己把关——不把关的话 `` 会
/// 静默变成下标 0,用户拿到一个由错顶点拼出来的网格,而且毫无提示。
///
/// 注意 `inf` 要单独判:`inf.round()` 还是 `inf`,光比 `value != value.round()`
/// 拦不住它。
fn attribute_int(
  element : @xml.XmlElement,
  name : String,
  tag : String,
) -> Result[Int, String] {
  match element.get(name) {
    Some(raw) => {
      let minimum = if name == "id" || name == "objectid" { 1 } else { 0 }
      match profile_decimal_int(raw[:], minimum) {
        Ok(i) => Ok(i)
        Err(msg) =>
          Err(
            "3MF <\{tag}> 的 \{name} 不是个整数或超出允许范围:\{msg}",
          )
      }
    }
    None => Err("3MF 的 <\{tag}> 少了一个必需的属性 \{name}")
  }
}

///|
/// 读 x / y / z 三个坐标属性。
///
/// 坐标也要查有限性:`x="inf"` 或 `x="nan"` 会让包围盒和体积算出一堆垃圾,
/// 而且从报告上看不出是文件坏了——数字就是难看点而已,不如直接报错。
fn attribute_vec3(element : @xml.XmlElement) -> Result[Vec3, String] {
  let out : Array[Double] = []
  for name in ["x", "y", "z"] {
    let raw = match element.get(name) {
      Some(v) => v
      None => return Err("3MF 的  少了坐标属性 \{name}")
    }
    let value = @string.parse_double(raw) catch {
      _ => return Err("3MF 的  里 \{name}=\"\{raw}\" 不是个数字")
    }
    if value.is_nan() || value.is_inf() {
      return Err(
        "3MF 的  里 \{name}=\"\{raw}\" 不是个有限的数字",
      )
    }
    out.push(value)
  }
  Ok(Vec3::new(out[0], out[1], out[2]))
}

///|
/// 读可选的位置变换属性。
///
/// 12 个数,空格分隔。个数不对就报错——少一个数会让整条装配链错位,
/// 而错位后的模型看起来「有东西」但体积完全不对,是最难查的那种错。
fn attribute_transform(
  element : @xml.XmlElement,
) -> Result[FixedArray[Double]?, String] {
  let raw = match element.get("transform") {
    Some(v) => v
    None => return Ok(None)
  }
  let out : Array[Double] = []
  for token in obj_words(raw) {
    if token.length() == 0 {
      continue
    }
    let value = @string.parse_double(token) catch {
      _ => return Err("3MF 的 transform 里『\{token}』不是个数字")
    }
    if not_finite(value) {
      return Err("3MF 的 transform 里『\{token}』不是有限数字")
    }
    out.push(value)
  }
  if out.length() != 12 {
    return Err(
      "3MF 的 transform 应该有 12 个数(4 行 3 列的矩阵),实际有 \{out.length()} 个",
    )
  }
  Ok(
    Some([
      out[0],
      out[1],
      out[2],
      out[3],
      out[4],
      out[5],
      out[6],
      out[7],
      out[8],
      out[9],
      out[10],
      out[11],
    ]),
  )
}