Metadata-Version: 2.2
Name: almeshopt
Version: 0.1.12
Summary: Meshoptimizer wrapper with cython
Requires-Python: <3.15,>=3.11
Requires-Dist: numpy>=2.0.1
Description-Content-Type: text/markdown

## almeshopt

`almeshopt` 是 [meshoptimizer](extern/meshoptimizer/) 的 Python 封装（Cython + CMake + scikit-build-core），提供面向 **NumPy** 的网格索引/顶点重排、渲染性能优化、压缩编解码、简化（LOD）、meshlet 构建等接口。

- **安装**：`pip install almeshopt`
- **导入**：`import almeshopt`
- **实现**：优先加载包内扩展模块 `almeshopt_avx2`（见 `src/almeshopt/__init__.py`）

> 说明：当前发布策略为 **只发布 AVX2 版 wheel**（构建时 `USE_AVX2=ON`）。源码中保留了非 AVX2 的扩展实现入口，但目前不发布对应 wheel。

---

## 快速开始

### 安 装

```bash
pip install -U almeshopt numpy
```

### 典型用法：简化 + 顶点/索引重排

```python
import numpy as np
import almeshopt

# indices: (N*3,) uint32
# vertices: (V,3) float32，必须 C-contiguous
indices = np.array([0, 1, 2, 2, 3, 0], dtype=np.uint32)
vertices = np.array([[0,0,0],[1,0,0],[1,1,0],[0,1,0]], dtype=np.float32)

# 目标三角形数量（这里演示：减半）
new_indices, error = almeshopt.simplify(
    indices,
    vertices,
    target_count=len(indices)//2,
    target_error=1e-2,
)

# 进一步优化：顶点 fetch 重排（返回 new_indices, new_vertices）
opt_indices, opt_vertices = almeshopt.optimize_vertex_fetch(new_indices, vertices)
print(opt_indices.shape, opt_vertices.shape, error)
```

---

## API 参考（当前仓库实际封装的接口）

> 下列函数均来自扩展模块（`src/almeshopt/almeshopt.pyx` / `src/almeshopt/almeshopt_avx2.pyx`），导入统一使用 `import almeshopt`。
>
> 约定：`indices` 通常为 `np.uint32` 的一维数组（长度为三角形数 × 3）；`vertices` 通常为二维数组（例如 `(V, 3)` 的 `np.float32`）。多数接口要求数组为 **C-contiguous**。

### 1）重映射与索引生成
- **`generate_vertex_remap(indices, vertices)`** → `(remap, unique_count)`  
  根据 `indices` + `vertices` 生成顶点重映射表。
- **`remap_vertex_buffer(vertices, remap)`** → `new_vertices`
- **`remap_index_buffer(indices, remap)`** → `new_indices`
- **`generate_shadow_index_buffer(indices, vertices)`** → `shadow_indices`
- **`generate_adjacency_index_buffer(indices, vertices)`** → `adjacency_indices`（每个三角形 6 个索引）

### 2）渲染性能优化
- **`optimize_vertex_cache(indices, vertex_count)`** → `new_indices`
- **`optimize_vertex_cache_strip(indices, vertex_count)`** → `new_indices`
- **`optimize_overdraw(indices, vertices, threshold=1.05)`** → `new_indices`
- **`optimize_vertex_fetch(indices, vertices)`** → `(new_indices, new_vertices)`

### 3）压缩与解压（索引/顶点）
- **`encode_index_buffer(indices, vertex_count)`** → `bytes`
- **`decode_index_buffer(encoded_data, index_count)`** → `np.ndarray[np.uint32]`
- **`encode_vertex_buffer(vertices, level=2)`** → `bytes`  
  支持任意 C-contiguous 的 numpy 数组（例如 `float32/uint16/uint8`），`level` 越高压缩率越好但更慢。
- **`decode_vertex_buffer(encoded_data, vertex_count, vertex_stride, dtype=np.float32)`** → `np.ndarray`  
  `vertex_stride` 单位是 **字节**（例如 `uint16` 的 `XYZ0` padding，stride=8）。
- **`encode_filter_oct(data, bits=8)`** → `np.ndarray[np.uint8]`  
  Octahedral 编码（目前封装为返回字节数组；使用前请确保输入数据形状/stride符合预期）。

### 4）简化（LOD）
- **`simplify(indices, vertices, target_count, target_error=0.01, sloppy=False, vertex_lock=None, options=0)`** → `(new_indices, error)`
- **`simplify_with_attributes(indices, vertices, attributes, weights, target_count, target_error=0.01, vertex_lock=None, options=0)`** → `(new_indices, error)`
- **`simplify_points(vertices, target_count, colors=None, color_weight=1.0)`** → `indices`

#### `options` 位标志

`options` 是按位或组合的整数，直接透传给底层的 `meshopt_simplify` / `meshopt_simplifyWithAttributes`。
常量已在模块级导出，取值与 [`extern/meshoptimizer/src/meshoptimizer.h`](extern/meshoptimizer/src/meshoptimizer.h) 的 `meshopt_SimplifyX` 枚举一致：

| 常量 | 值 | 作用 |
| --- | --- | --- |
| `SIMPLIFY_LOCK_BORDER` | `1 << 0` | 不移动位于拓扑边界上的顶点；简化大网格的局部时很有用 |
| `SIMPLIFY_SPARSE` | `1 << 1` | 输入索引只覆盖网格的稀疏子集时提速；注意 error 会变成相对于子集包围盒 |
| `SIMPLIFY_ERROR_ABSOLUTE` | `1 << 2` | `target_error` 与返回的 error 按**绝对量纲**解释，而非相对网格尺度 |
| `SIMPLIFY_PRUNE` | `1 << 3` | 简化过程中增量剔除彼此断开的小块（孤立碎片） |
| `SIMPLIFY_REGULARIZE` | `1 << 4` | 产出更均匀的三角形大小与形状，代价是几何/属性精度略降 |
| `SIMPLIFY_PERMISSIVE` | `1 << 5` | 实验性：允许跨属性不连续处折叠，除非该顶点被标记为 `SIMPLIFY_VERTEX_PROTECT` |

`vertex_lock` 数组（`uint8`，每顶点一个元素）的取值同样已导出：

| 常量 | 值 | 含义 |
| --- | --- | --- |
| `SIMPLIFY_VERTEX_LOCK` | `1 << 0` | 该顶点不允许移动 |
| `SIMPLIFY_VERTEX_PROTECT` | `1 << 1` | 保护该顶点处的属性不连续；必须与 `SIMPLIFY_PERMISSIVE` 同时使用 |

```python
import almeshopt

# 简化的同时剔除孤立碎片，并锁定边界（适合分块简化大场景）
new_indices, error = almeshopt.simplify(
    indices, vertices, target_count,
    target_error=0.05,
    options=almeshopt.SIMPLIFY_PRUNE | almeshopt.SIMPLIFY_LOCK_BORDER,
)
```

注意事项：

- **`options=0`（默认）与旧版本行为完全一致**，不传该参数的既有代码无需改动。
- `sloppy=True` 走的是 `meshopt_simplifySloppy`，底层没有 options 参数；此时传入非零 `options` 会抛 `ValueError`，而不是被静默忽略。
- 当前 vendored 的 `meshopt_simplify` 签名里**没有** `vertex_lock` 参数，因此 `simplify()` 的 `vertex_lock` 只在 `sloppy=True` 时生效。精确简化路径若需要锁顶点，请改用 `simplify_with_attributes()`。
- `SIMPLIFY_PRUNE` 只在"常规折叠已经停下、但还没达到 `target_count`"时才会介入。若普通简化本来就能达标，加不加这个标志结果相同。

可运行的演示与自检：[`demos/simplify_options.py`](demos/simplify_options.py)。

### 5）空间排序
- **`spatial_sort_triangles(indices, vertices)`** → `new_indices`
- **`spatial_sort_remap(vertices)`** → `remap`

### 6）分析（统计指标）
- **`analyze_vertex_cache(indices, vertex_count, cache_size=16, warp_size=0, primgroup_size=0)`** → `dict`
- **`analyze_overdraw(indices, vertices)`** → `dict`

### 7）Meshlets（Mesh Shading）
- **`build_meshlets(indices, vertices, max_vertices=64, max_triangles=124, cone_weight=0.0)`** → `dict`  
  返回键：`meshlets`（Nx4）、`meshlet_vertices`、`meshlet_triangles`。
- **`compute_meshlet_bounds(meshlet_vertices, meshlet_triangles, vertices)`** → `dict`

### 8）Triangle Strip
- **`stripify(indices, vertex_count)`** → `strip_indices`

---

## 示例（仓库内 demos）

目录：[demos/](demos/)

- **点云压缩（带颜色）**：`demos/ply_compress.py` / `demos/ply_decompress.py`  
  主要展示 `encode_vertex_buffer` / `decode_vertex_buffer` 的用法（uint16 几何 + uint8 颜色）。
- **GLTF/GLB 简化（带 UV）**：`demos/mesh_simp_with_texture.py`  
  展示 `generate_vertex_remap`、`remap_*`、`simplify`、`optimize_vertex_fetch` 的组合使用。
- **简化 options 位标志**：`demos/simplify_options.py`  
  演示并自检 `SIMPLIFY_*` 常量、向后兼容性与 `SIMPLIFY_PRUNE` 的剔除效果，无外部依赖，直接 `python demos/simplify_options.py` 即可运行。

---

## “地形切片 / Cesium Terrain”接口说明

你可能在仓库中看到了 `dem_to_cesium_terrain_*.plan.md` 之类的规划文档，但**当前仓库代码里并没有** `TerrainTiler` / `.terrain` 编码器等实现文件（例如 `terrain_tiler.py`、`quantized_mesh.*`、`terrain_mesher.*` 均不存在）。  
因此，本包当前对外 API **只包含 meshoptimizer 封装**（见上文 API 参考），不包含地形瓦片管线接口。

如果你希望把地形切片管线也纳入 `almeshopt` 并发布为 wheel，我们可以再按“规划文档 → 实际代码”补齐对应 C++/Cython/Python 层实现与测试。

---

## 构建与发布（维护者）

### 构建 wheel（Windows + Linux manylinux via Docker Desktop）
仓库提供 PowerShell 脚本，目录：[scripts/](scripts/)

- **一键构建并上传（默认 cp311/cp312/cp313/cp314；Windows+Linux）**

```powershell
.\scripts\release.ps1
```

- **只构建 Windows wheel**

```powershell
.\scripts\release.ps1 -Platform windows -SkipUpload
```

- **只构建 Linux manylinux x86_64（Docker Desktop）**

```powershell
.\scripts\release.ps1 -Platform linux -SkipUpload
```

- **本机（Visual Studio 2026 + 老版 CMake）构建**

系统 CMake 若低于能识别当前 VS generator 的版本（例如 CMake 3.25 最高只认 "Visual Studio 17 2022"），
上面的脚本会在 configure 阶段失败。此时改用 Ninja generator 的专用脚本：

```powershell
.\scripts\build_wheels_homepc.ps1 -PythonVersions 3.11 -Clean
.\scripts\build_wheels_homepc.ps1 -BuildMode compat   # USE_AVX2=OFF
```

它会用 vswhere 探测 VS 安装路径、导入 VsDevCmd 环境，并强制 `CMAKE_GENERATOR=Ninja`。

### 上传到 PyPI
推荐使用环境变量 `PYPI_API_TOKEN`：

```powershell
$env:PYPI_API_TOKEN="pypi-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
.\scripts\upload_pypi.ps1 -SkipExisting
```

> 中文终端显示：脚本中已包含 `chcp 65001` + UTF-8 输出设置，并默认禁用 `twine` 进度条以避免 GBK 控制台编码问题。

---

## 常见问题（FAQ）

### 1）为什么 `pip install almeshopt` 但内部模块叫 `almeshopt_avx2`？
`almeshopt_avx2` 是包内部的扩展模块名；对用户而言 **安装名与导入名都始终是 `almeshopt`**。`almeshopt/__init__.py` 会优先加载 AVX2 扩展。

### 2）数组需要什么 dtype / shape？
不同接口对 dtype/shape 有要求；最通用的组合是：
- `indices`: `np.uint32`，形状 `(T*3,)`
- `vertices`: `np.float32`，形状 `(V,3)` 或拼接属性后的 `(V, k)`
并确保 `np.ascontiguousarray(...)`。
