Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
1c6fa21
Add core typing input vocabulary
tomvanmele Nov 4, 2026
2a507b2
Align retained tessellation buffer typing
tomvanmele Nov 4, 2026
76fc544
Make BRep queries fail independently
tomvanmele Nov 4, 2026
e54f8ae
Set COMPAS 3.0.0 candidate version
tomvanmele Nov 4, 2026
c2d48c9
Accept NumPy triangulation inputs
tomvanmele Nov 4, 2026
26ff56e
Add structural triangle mesh input
tomvanmele Nov 4, 2026
0e35297
Use structural mesh Boolean contracts
tomvanmele Nov 4, 2026
85f8bc6
Simplify structural Boolean dispatch
tomvanmele Nov 4, 2026
fe188dc
Use structural mesh intersection contracts
tomvanmele Nov 5, 2026
834d88e
Use structural mesh remeshing contracts
tomvanmele Nov 5, 2026
f0f7aea
Use structural libigl plugin contracts
tomvanmele Nov 5, 2026
9b1518f
Finalize structural curvature contracts
tomvanmele Nov 5, 2026
f41ec3c
Enable typing integration PR checks
tomvanmele Nov 5, 2026
a51c6a4
Merge branch 'initiative/repository-modernisation' into initiative/ty…
tomvanmele Nov 5, 2026
c58d1a8
Separate minimum NumPy runtime checks
tomvanmele Nov 5, 2026
07522a0
Merge branch 'initiative/repository-modernisation' into initiative/ty…
tomvanmele Nov 5, 2026
2853295
Avoid interpreter-specific math error text
tomvanmele Nov 5, 2026
4973c5f
Verify typed wheel distributions
tomvanmele Nov 5, 2026
21fdcaa
Complete core structural typing contract
tomvanmele Nov 6, 2026
71c288c
Complete geometry and typing alignment
tomvanmele Nov 6, 2026
f626151
Load repository tasks explicitly in tests
tomvanmele Nov 6, 2026
3c1a2e8
Type-check visualization examples in CI
tomvanmele Nov 6, 2026
99b82a6
Publish verified core candidate wheel
tomvanmele Nov 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/constraints/numpy-min.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
numpy==1.26.0
32 changes: 32 additions & 0 deletions .github/verify_python_wheel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"""Verify the contents required from a COMPAS Python wheel."""

from __future__ import annotations

import sys
from pathlib import Path
from zipfile import ZipFile


def find_wheel(path: Path) -> Path:
"""Resolve exactly one wheel from a file or directory."""
if path.is_file():
return path
wheels = sorted(path.glob("*.whl"))
if len(wheels) != 1:
raise RuntimeError(f"Expected exactly one wheel in {path}, found {len(wheels)}.")
return wheels[0]


def verify_wheel(path: Path) -> None:
"""Verify that a wheel contains the PEP 561 marker."""
wheel = find_wheel(path)
with ZipFile(wheel) as archive:
names = set(archive.namelist())
if "compas/py.typed" not in names:
raise RuntimeError(f"{wheel.name} does not contain compas/py.typed.")


if __name__ == "__main__":
if len(sys.argv) != 2:
raise SystemExit("Usage: verify_python_wheel.py WHEEL_OR_DIRECTORY")
verify_wheel(Path(sys.argv[1]))
38 changes: 37 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,10 @@ jobs:
- uses: compas-dev/compas-actions/setup-python@5c64e6e2b576a1232d28fed47d9478ca8eb63b2d # v2 candidate
with:
python-version: ${{ matrix.python }}
extras: dev
# Pyright covers the authoritative visualization examples as part of
# the public documentation contract, so their declared dependencies
# must be present in the typing environment.
extras: dev,viz
- name: Check style
run: uv run ruff check .
- name: Check formatting
Expand All @@ -51,6 +54,39 @@ jobs:
run: uv run pyright
- name: Run tests
run: uv run pytest
- name: Build typed wheel
if: matrix.os == 'ubuntu-latest' && matrix.python == '3.12'
shell: bash
run: |
mkdir -p "${RUNNER_TEMP}/compas-source" "${RUNNER_TEMP}/compas-wheelhouse"
git archive HEAD | tar -xf - -C "${RUNNER_TEMP}/compas-source"
uv run python -m build \
--wheel \
--outdir "${RUNNER_TEMP}/compas-wheelhouse" \
"${RUNNER_TEMP}/compas-source"
- name: Verify typed wheel
if: matrix.os == 'ubuntu-latest' && matrix.python == '3.12'
shell: bash
run: |
uv run python .github/verify_python_wheel.py "${RUNNER_TEMP}/compas-wheelhouse"
uv venv "${RUNNER_TEMP}/compas-wheel"
uv pip install \
--python "${RUNNER_TEMP}/compas-wheel/bin/python" \
"${RUNNER_TEMP}/compas-wheelhouse"/*.whl
(
cd "${RUNNER_TEMP}"
"${GITHUB_WORKSPACE}/.venv/bin/pyright" \
--pythonpath "${RUNNER_TEMP}/compas-wheel/bin/python" \
"${GITHUB_WORKSPACE}/tests/typing/installed_consumer.py"
)
- name: Upload verified candidate wheel
if: matrix.os == 'ubuntu-latest' && matrix.python == '3.12'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: candidate-linux-py312
path: ${{ runner.temp }}/compas-wheelhouse/*.whl
if-no-files-found: error
retention-days: 14

numpy-compatibility:
name: NumPy ${{ matrix.numpy }}
Expand Down
25 changes: 17 additions & 8 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

* Added serializable strategies and structural validation for polygonization, ephemeral `Tessellation` buffers, mixed-face triangulation, and planar Delaunay triangulation.
* Added `polygonize(strategy)` and deterministic `tessellate(strategy=None)` operations for continuous geometry. Shape and surface defaults use `Native()`, `UGrid(64)`, or `UVGrid(64, 64)` according to parameter dimensionality.
* Added `FaceTriangulation` for mixed polygonal collections, explicit projected ear clipping for bounded near-planar polygons, and minimum-area dynamic programming for twisted 3D polygons.
* Added a verified Linux Python 3.12 candidate wheel artifact for framework-wide pre-release integration.
* Added the public `compas.typing` structural vocabulary with NumPy-aware `PointLike`, `VectorLike`, collection, and neutral `VerticesFaces` aliases.
* Added the PEP 561 `py.typed` marker and installed-wheel static-consumer verification.
* Added structural validation for polygonization, ephemeral `Tessellation` buffers, mixed-face triangulation, and planar Delaunay triangulation.
* Added producer-specific `to_polygonal_surface` and exactly composed `to_tessellation` conversions for structured shapes and untrimmed surfaces.
* Added named polygon triangulators, including explicit projected ear clipping for bounded near-planar polygons and minimum-area dynamic programming for twisted 3D polygons.
* Added parameter-derived continuous AABBs for analytic shapes and surfaces, minimum-volume OBBs where established, and certified conservative control-net bounds for positive-weight NURBS surfaces.
* Added `TOL.update()` method for explicit global state modification.
* Added `TOL.temporary()` context manager for scoped changes.
Expand All @@ -20,9 +23,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed

* Migrated core geometry constructors and plugin contracts from private overlapping type aliases to semantic structural inputs, including copied NumPy coordinates and integral face indices.
* Added a committed uv development lock, documented direct quality commands while retaining Invoke convenience tasks, and migrated CI, release, artifact, and documentation mechanics to immutably pinned shared actions.
* Standardized triangle-processing plugin inputs and outputs on the ephemeral `Tessellation` representation. See the geometry discretization and tessellation migration guide for the COMPAS 3 replacements.
* Renamed planar Delaunay operations to `triangulate_planar_points` and `triangulate_planar_domain`, with core-owned validation before backend dispatch.
* Standardized numerical triangle-mesh plugin inputs and outputs on structural `(vertices, triangles)` buffers. `Tessellation` remains an ephemeral discrete representation for geometry producers and scene handoff, but is not required at numerical backend boundaries.
* Defined core triangle-mesh Gaussian curvature as the dimensionless angle deficit, using $\pi - \sum_i \theta_i$ at boundary vertices and $2\pi - \sum_i \theta_i$ at interior vertices.
* Replaced strategy-selected planar Delaunay operations with `delaunay_triangulation`, `constrained_delaunay_triangulation`, and `conforming_delaunay_triangulation`, returning structural buffers with core-owned validation.
* Replaced backend-specific `NurbsCurve` and `NurbsSurface` substitution with stable core wrappers and operation-level plugin contracts.
* Modernized internal path, URL, text-stream, and binary-stream handling and made ownership of opened resources explicit.
* Corrected variance, standard-deviation, Gram-Schmidt, determinant, inverse, matrix-decomposition, and projection edge-case behavior in `compas.linalg`.
Expand Down Expand Up @@ -52,7 +57,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* Consolidated best-fit algorithms in `bestfit.py`, removed `_numpy` public names, made numerical imports lazy, switched the canonical plane fit to PCA, and standardized fitted radii as scalar floats.
* Consolidated hull and ICP algorithms under unsuffixed APIs, corrected convex-hull polyhedron construction and stable ICP returns, and moved optional Shapely polygon Boolean defaults into `booleans.py`.
* Added deferred topology validation to `PlanarRegion` triangulation, with clear errors for invalid boundary loops, holes outside or touching the exterior, and overlapping, touching, or nested holes.
* Added a zero-copy, triangle-only `Tessellation` result with shared vertex indexing for triangles, normals, and texture coordinates. Continuous defining geometry uses explicit `polygonize(strategy)` for persistent `PolygonalSurface` results and fresh `tessellate(strategy=None)` for ephemeral triangle results; polygonal collections use composite `FaceTriangulation` policies.
* Added a zero-copy, triangle-only `Tessellation` result with shared vertex indexing for triangles, normals, and texture coordinates. Continuous structured geometry uses producer-specific conversion keywords, BReps use deflection keywords, and polygonal collections accept named or duck-typed triangulation callables.
* Changed `Mesh.triangulated()` to return an independent mesh of the same concrete type, preserving mesh data, existing keys and attributes, and editable topology while leaving other aggregate triangulation results as `Tessellation`.
* Removed the public tessellation and triangulation strategy hierarchies and the old `polygonize`/`tessellate` operation aliases in favor of one explicit public path per operation.
* Defined discretization as the broad category of operations producing finite representations of continuous geometry, polygonization as the persistent `PolygonalSurface` operation, and tessellation as the ephemeral indexed-triangle operation.
* Renamed the NURBS surface defining-point grid from `points` to `control_points`.
* Made shape and surface strategy arguments non-mutating and added aligned analytic normals and normalized UV coordinates to surface tessellations.
Expand All @@ -71,10 +78,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Removed

* Removed implicit and misspelled tessellation conversions, scalar resolution overrides, implicit quad splitting, and raw triangle-buffer plugin boundaries in favor of explicit strategies and result types.
* Removed the private `compas._typing` module and its ambiguous coordinate and tuple-valued mesh aliases.
* Removed implicit and misspelled tessellation conversions, scalar resolution overrides, and implicit quad splitting in favor of explicit producer controls, named triangulators, and result types.
* Removed the unsupported core triangle-mesh mean and principal curvature plugin contracts and the obsolete tessellation-specific pluggable decorator.
* Removed stored shape and surface resolution state, derived discretization caches, generic sampled geometry properties, `Shape.to_vertices_and_faces()`, and `Mesh.from_shape()`.
* Removed unused runtime tessellation capability discovery and speculative approximation-error schemas.
* Removed `Shape.to_polyhedron()` because `Polyhedron` represents a validated closed solid rather than the generic polygonal discretization of a shape. Use `polygonize(strategy)`, `tessellate(strategy)`, or convert the result to a mesh.
* Removed `Shape.to_polyhedron()` because `Polyhedron` represents a validated closed solid rather than the generic polygonal discretization of a shape. Use `to_polygonal_surface()`, `to_tessellation()`, or convert the result to a mesh.
* Removed the private `compas._os` compatibility module and its obsolete Python 2, IronPython, CAD-host, Conda-bootstrapper, and Windows elevation helpers.
* Removed the deprecated `compas.linalg.close` and `compas.linalg.allclose` helpers in favor of `TOL.is_close` and `TOL.is_allclose`.
* Removed the `compas.rpc` package and the `compas_rpc` command-line entry point.
Expand Down
5 changes: 2 additions & 3 deletions docs/assets/examples/_plotter.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
# type: ignore

# import compas
from compas_viewer.scene import Tag

Expand Down Expand Up @@ -48,7 +46,8 @@ def viewer_add_face():

def viewer_add_halfvector(viewer, mesh: Mesh, halfedge, color=None, basewidth=0.01, arrowsize=5, distance=0.1):
line: Line = mesh.edge_line(halfedge)
line = line.offset(line.length * distance)
offset = line.length * distance
line = Line(line.point_from_start(offset), line.point_from_end(offset))

color = color or Color.black()

Expand Down
2 changes: 1 addition & 1 deletion docs/assets/examples/basics.datastructures.cellnetworks.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from compas.geometry import Line
from compas.geometry import Polygon

cell_network: CellNetwork = CellNetwork.from_json(compas.get("cellnetwork_example.json")) # type: ignore
cell_network: CellNetwork = CellNetwork.from_json(compas.get("cellnetwork_example.json"))

viewer = Viewer(show_grid=False)

Expand Down
5 changes: 2 additions & 3 deletions docs/assets/examples/basics.datastructures.meshes.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
# type: ignore

from compas_viewer import Viewer

from compas.colors import Color
Expand All @@ -10,8 +8,9 @@
viewer = Viewer(viewmode="top", width=1600, height=900)

red = Color.red()
light_gray = Color(0.95, 0.95, 0.95)

viewer.scene.add(mesh, facecolor=(0.95, 0.95, 0.95), lineswidth=2)
viewer.scene.add(mesh, facecolor=light_gray, lineswidth=2)
viewer.scene.add(mesh.edge_line((30, 31)).translated([0, 0, 0.1]), linecolor=red, lineswidth=10)

for edge in mesh.edge_loop((30, 31)):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
# Base Box
# =============================================================================

box = Box(10).to_mesh()
box = Box(10).to_polygonal_surface().to_mesh()

# =============================================================================
# Cutting Planes
Expand Down
2 changes: 0 additions & 2 deletions docs/assets/examples/colors.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
# type: ignore

from compas_viewer import Viewer

from compas.colors import Color
Expand Down
2 changes: 0 additions & 2 deletions docs/assets/examples/colors_lightness.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
# type: ignore

from compas_viewer import Viewer

from compas.colors import Color
Expand Down
2 changes: 0 additions & 2 deletions docs/assets/examples/colors_maps.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
# type: ignore

from compas_viewer import Viewer

from compas.colors import Color
Expand Down
2 changes: 0 additions & 2 deletions docs/assets/examples/colors_maps_from-one-color.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
# type: ignore

from compas_viewer import Viewer

from compas.colors import Color
Expand Down
2 changes: 0 additions & 2 deletions docs/assets/examples/colors_maps_from-rgb.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
# type: ignore

from compas_viewer import Viewer

from compas.colors import ColorMap
Expand Down
2 changes: 0 additions & 2 deletions docs/assets/examples/colors_maps_from-rgb_simple.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
# type: ignore

from compas_viewer import Viewer

from compas.colors import ColorMap
Expand Down
2 changes: 0 additions & 2 deletions docs/assets/examples/colors_maps_from-three-colors.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
# type: ignore

from compas_viewer import Viewer

from compas.colors import Color
Expand Down
2 changes: 0 additions & 2 deletions docs/assets/examples/colors_maps_from-two-colors.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
# type: ignore

from compas_viewer import Viewer

from compas.colors import Color
Expand Down
2 changes: 0 additions & 2 deletions docs/assets/examples/colors_saturation.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
# type: ignore

from compas_viewer import Viewer

from compas.colors import Color
Expand Down
6 changes: 1 addition & 5 deletions docs/assets/examples/quadmesh_planarize.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
# type: ignore
from compas_viewer import Viewer

import compas
from compas.colors import Color
from compas.colors import ColorMap
from compas.datastructures import Mesh

# ! mesh_flatness is not available in compas.geometry
from compas.datastructures import mesh_flatness
from compas.geometry import quadmesh_planarize

mesh = Mesh.from_obj(compas.get("tubemesh.obj"))
Expand All @@ -16,7 +12,7 @@
V2 = quadmesh_planarize((V, F), 500, 0.005)

mesh = Mesh.from_vertices_and_faces(V2, F)
dev = mesh_flatness(mesh, maxdev=0.02)
dev = {face: mesh.face_flatness(face, maxdev=0.02) for face in mesh.faces()}

cmap = ColorMap.from_two_colors(Color.white(), Color.blue())
facecolor = {}
Expand Down
2 changes: 1 addition & 1 deletion docs/assets/examples/scene.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,5 +79,5 @@
y += 5


scene.print_hierarchy()
print(scene.tree.get_hierarchy_string())
scene.draw()
Loading
Loading