Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
2f59963
Make the invoke tasks usable, and stop release deleting the IPOPT tree
petrasvestartas Aug 27, 2026
fb85c7b
Deploy docs from main, one-command setup, and fix the fork URLs
petrasvestartas Aug 27, 2026
7dc170d
One requirements file, plainly named, and point the CMake error at in…
petrasvestartas Aug 27, 2026
97271cb
Command-first installation docs, and the same setup flow everywhere
petrasvestartas Aug 27, 2026
9106428
Fix the Windows IPOPT launch: cmd.exe splits on && before bash sees it
petrasvestartas Aug 27, 2026
28ea1ac
Put ucrt64\bin on PATH for the extension build, and stream the IPOPT …
petrasvestartas Aug 27, 2026
547e115
Load the extension's MSYS2 runtimes in editable installs, and pytest …
petrasvestartas Aug 27, 2026
68a77da
Bundle the MinGW runtime DLLs at install time, not import time
petrasvestartas Aug 27, 2026
27c231c
Plain pip install -e . rebuilds the extension; nothing global, nothin…
petrasvestartas Aug 27, 2026
c54dc4a
Drop the CMAKE_ARGS instruction from the manual build notes
petrasvestartas Aug 27, 2026
b805813
VERBOSITY 3, not 2: coinbrew demotes ThirdParty builds one level
petrasvestartas Aug 27, 2026
6556f7d
Cap mkdocs below 2: Material declares itself incompatible with the re…
petrasvestartas Aug 27, 2026
d966fcb
Say to browse the docs with docs-serve, not from disk
petrasvestartas Aug 27, 2026
7fc08ae
One docs task: invoke docs serves; --no-serve builds the deploy artifact
petrasvestartas Aug 27, 2026
4c21918
Two-section installation page: Install and Development
petrasvestartas Aug 27, 2026
68a6949
Per-platform Development sections: Windows, macOS, Linux, commands only
petrasvestartas Aug 27, 2026
774a7a3
Trim the install page: drop the sales line and the Manual build section
petrasvestartas Aug 27, 2026
629e4c4
Fix the viewers for compas_viewer 2.x, and make every example solve
petrasvestartas Aug 27, 2026
78f84ec
Mesh arrows with conical heads, matching the documentation screenshots
petrasvestartas Aug 27, 2026
bd0792f
Match the examples and viewer to the published screenshots, with era-…
petrasvestartas Aug 27, 2026
aa94525
Restore the published force distributions: stop the barrier where the…
petrasvestartas Aug 27, 2026
010b433
Record the completed IPOPT bisection: the flip is exactly 3.14.12
petrasvestartas Aug 27, 2026
ec91b24
Recalibrate mu_target to 5e-6: the tall example's distribution matche…
petrasvestartas Aug 27, 2026
ef8e5c1
Calibrate mu_target to the published figures: 1e-5, near-uniform arrows
petrasvestartas Aug 27, 2026
e8773f8
Cross-platform invoke setup: macOS deployment target, CLT check, PATH…
petrasvestartas Aug 27, 2026
64b1955
mac setup
petrasvestartas Aug 27, 2026
215a007
Windows regression after the mac setup work, one portability fix
petrasvestartas Aug 27, 2026
bf6e24f
task double print
petrasvestartas Aug 27, 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
15 changes: 11 additions & 4 deletions .github/workflows/pipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ name: pipeline
# release.yml (v* tags, publish: true). Always the full chain: IPOPT compiled from
# source, a compas_cra wheel for every platform and CPython with the solver built
# into it, each smoke-tested, the full test suite run against one of those wheels,
# and the docs built. With publish: true it also deploys the docs.
# and the docs built. The docs are deployed from every push to main, and from a release.
#
# The release guard, the PyPI upload and the GitHub release are in release.yml, not
# here: trusted publishing cannot match a job that lives in a reusable workflow, so it
Expand Down Expand Up @@ -145,7 +145,7 @@ jobs:
IPOPT_PREFIX='${{ github.workspace }}\build\ipopt\stage'
EXTRA_LINK_DIRS='D:\msys64\ucrt64\lib'
CMAKE_GENERATOR=Ninja
CMAKE_ARGS='-DCMAKE_C_COMPILER=D:/msys64/ucrt64/bin/gcc.exe -DCMAKE_CXX_COMPILER=D:/msys64/ucrt64/bin/g++.exe -DFORTRAN_COMPILER=D:/msys64/ucrt64/bin/gfortran.exe'
CMAKE_ARGS='-DCMAKE_C_COMPILER=D:/msys64/ucrt64/bin/gcc.exe -DCMAKE_CXX_COMPILER=D:/msys64/ucrt64/bin/g++.exe -DFORTRAN_COMPILER=D:/msys64/ucrt64/bin/gfortran.exe -DBUNDLE_RUNTIME_DLLS=OFF'
run: |
python -m pip install -q cibuildwheel
python -m cibuildwheel --output-dir dist
Expand Down Expand Up @@ -211,12 +211,19 @@ jobs:
sudo apt-get update -qq && sudo apt-get install -y -qq libegl1 libgl1
python -m pip install -q --upgrade pip
python -m pip install -q "$(ls wheelhouse/compas_cra-*-cp312-*.whl)[dev,docs]"
# `invoke docs`, not a second copy of the mkdocs command: the task in tasks.py is the
# one contributors run, and CI diverging from it is how a green local build turns red
# here (or worse, the reverse)
- name: Build docs
run: |
mkdocs build --strict --site-dir dist/docs
invoke docs --no-serve
test -f dist/docs/index.html
# every push to main, not releases only: the site is the documentation of what is on
# main, and a docs fix that only ships with the next tag is a docs fix nobody reads.
# This costs no extra CI - build.yml already runs this whole pipeline on every push
# to main, so the wheel the docs are built against exists either way.
- name: Deploy docs
if: inputs.publish
if: inputs.publish || github.ref == 'refs/heads/main'
uses: peaceiris/actions-gh-pages@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
Expand Down
36 changes: 35 additions & 1 deletion CHANGELOG.md

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions CITATION.cff
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ authors:
- family-names: "Kao"
given-names: "Gene Ting-Chun"
orcid: https://orcid.org/0000-0002-4275-1246
title: "petrasvestartas/compas_cra: Release v0.2.0"
title: "BlockResearchGroup/compas_cra: Release v0.2.0"
version: v0.2.0
doi: 10.5281/zenodo.7043136
date-released: 2022-09-02
url: "https://github.com/petrasvestartas/compas_cra"
url: "https://github.com/BlockResearchGroup/compas_cra"
57 changes: 56 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,30 @@
# ipopt executable, no DLLs, no separate solver distribution.

cmake_minimum_required(VERSION 3.18...3.30)

# Windows: this extension can only be built with MinGW gcc. It links libipopt.a and
# libcoinmumps.a, which are GCC-built C++/Fortran static archives - MSVC cannot link
# them (different C++ ABI), so letting CMake default to Visual Studio produces a
# LNK1181 ten minutes in. Find the MSYS2 toolchain and say so up front instead. An
# explicitly configured compiler (CI passes one) always wins.
if(WIN32 AND NOT DEFINED CMAKE_CXX_COMPILER AND NOT DEFINED ENV{CXX})
set(_msys2_candidates "$ENV{MSYS2_ROOT}" "C:/msys64" "D:/msys64")
foreach(_root IN LISTS _msys2_candidates)
if(_root AND EXISTS "${_root}/ucrt64/bin/g++.exe")
set(CMAKE_C_COMPILER "${_root}/ucrt64/bin/gcc.exe" CACHE FILEPATH "")
set(CMAKE_CXX_COMPILER "${_root}/ucrt64/bin/g++.exe" CACHE FILEPATH "")
set(FORTRAN_COMPILER "${_root}/ucrt64/bin/gfortran.exe")
message(STATUS "MinGW toolchain: ${_root}/ucrt64")
break()
endif()
endforeach()
if(NOT DEFINED CMAKE_CXX_COMPILER)
message(FATAL_ERROR "No MinGW toolchain found (MSYS2 UCRT64 with gcc). "
"This extension links GCC-built static archives and cannot be built with MSVC. "
"Run `invoke setup`, or install MSYS2 and set MSYS2_ROOT.")
endif()
endif()

project(compas_cra LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
Expand All @@ -21,7 +45,7 @@ if(NOT DEFINED IPOPT_PREFIX)
endif()
endif()
if(NOT EXISTS "${IPOPT_PREFIX}/include/coin-or/IpTNLP.hpp")
message(FATAL_ERROR "No IPOPT build at IPOPT_PREFIX=${IPOPT_PREFIX}; run packaging/build_ipopt.sh first")
message(FATAL_ERROR "No IPOPT build at IPOPT_PREFIX=${IPOPT_PREFIX}; run `invoke setup` (or packaging/build_ipopt.sh) first")
endif()
message(STATUS "IPOPT_PREFIX: ${IPOPT_PREFIX}")

Expand Down Expand Up @@ -107,3 +131,34 @@ if(APPLE)
endif()

install(TARGETS _core LIBRARY DESTINATION compas_cra/_native)

# Local (editable) installs on Windows: put the MinGW runtime DLLs next to the module,
# so the environment is self-contained - the same thing delvewheel does to the wheels
# in CI, done at install time. OFF by default: the CI wheels get delvewheel's repair
# and must not carry a second, unmangled copy. `invoke setup` turns it on.
# ON by default on Windows so that a plain `pip install -e .` produces a working
# environment; the CI wheel builds pass OFF explicitly because delvewheel repairs them
if(WIN32)
option(BUNDLE_RUNTIME_DLLS "Install the MinGW runtime DLLs next to the extension" ON)
else()
option(BUNDLE_RUNTIME_DLLS "Install the MinGW runtime DLLs next to the extension" OFF)
endif()
if(WIN32 AND BUNDLE_RUNTIME_DLLS)
get_filename_component(_toolchain_bin "${FORTRAN_COMPILER}" DIRECTORY)
# the dependency closure of _core outside the system DLLs, computed with objdump;
# openblas pulls in gomp, gfortran pulls in quadmath
set(_runtime_dlls
libgcc_s_seh-1.dll
libgfortran-5.dll
libgomp-1.dll
libopenblas.dll
libquadmath-0.dll
libstdc++-6.dll
libwinpthread-1.dll)
foreach(_dll IN LISTS _runtime_dlls)
if(NOT EXISTS "${_toolchain_bin}/${_dll}")
message(FATAL_ERROR "BUNDLE_RUNTIME_DLLS: ${_dll} not found in ${_toolchain_bin}")
endif()
install(FILES "${_toolchain_bin}/${_dll}" DESTINATION compas_cra/_native)
endforeach()
endif()
33 changes: 18 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# COMPAS CRA

![build](https://github.com/petrasvestartas/compas_cra/workflows/build/badge.svg)
[![GitHub - License](https://img.shields.io/github/license/petrasvestartas/compas_cra.svg)](./LICENSE)
![build](https://github.com/BlockResearchGroup/compas_cra/workflows/build/badge.svg)
[![GitHub - License](https://img.shields.io/github/license/BlockResearchGroup/compas_cra.svg)](./LICENSE)
[![pip downloads](https://img.shields.io/pypi/dm/compas_cra)](https://pypi.python.org/project/compas_cra)
[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/compas_cra.svg)](https://pypi.python.org/project/compas_cra)
[![PyPI - Latest Release](https://img.shields.io/pypi/v/compas_cra.svg)](https://pypi.python.org/project/compas_cra)
Expand Down Expand Up @@ -32,20 +32,23 @@ The solver is compiled into the package, so an install from source builds it, an
needs IPOPT staged first:

```bash
git clone https://github.com/petrasvestartas/compas_cra.git
git clone https://github.com/BlockResearchGroup/compas_cra.git
cd compas_cra
packaging/build_ipopt.sh # ~15 minutes, once
pip install -e ".[dev]"

uv venv --python 3.12
source .venv/Scripts/activate # .venv/bin/activate on macOS and Linux

uv pip install invoke compas_invocations2
invoke setup # toolchain + IPOPT (~15 minutes, once) + editable install
invoke test
```

`build_ipopt.sh` needs a Fortran compiler and a static BLAS/LAPACK — on Debian/Ubuntu
`sudo apt install build-essential gfortran libopenblas-dev git curl make patch pkg-config`,
on macOS `brew install gcc bash`, on Windows an [MSYS2](https://www.msys2.org) UCRT64
shell. Without a local toolchain, `CIBW_BUILD="cp312-*" cibuildwheel` builds the wheel
inside the manylinux container instead — the build is configured in `pyproject.toml`,
so that one command reproduces CI. Full instructions, per platform, are in
[the installation docs](./docs/installation.rst).
`invoke setup` handles the platform differences — MSYS2 packages on Windows, Homebrew
on macOS, and on Linux it prints the `apt`/`dnf` line to run first. Without a local
toolchain, `CIBW_BUILD="cp312-*" cibuildwheel` builds the wheel inside the manylinux
container instead — the build is configured in `pyproject.toml`, so that one command
reproduces CI. Full instructions, per platform, are in
[the installation docs](./docs/installation.md).

### Rhino 8

Expand Down Expand Up @@ -91,7 +94,7 @@ To find out more about CRA, please refer to our paper in the CAD Computer-Aided

## Coupled Rigid-Block Analysis: Stability-Aware Design of Complex Discrete-Element Assemblies

![image](https://github.com/petrasvestartas/compas_cra/blob/main/docs/_images/cra_bridge.png?raw=true)
![image](https://github.com/BlockResearchGroup/compas_cra/blob/main/docs/assets/images/cra_bridge.png?raw=true)

### Abstract

Expand Down Expand Up @@ -147,8 +150,8 @@ by designing complex and scaffolding-free physical models.

### Read the docs

[https://github.com/petrasvestartas/compas_cra](https://github.com/petrasvestartas/compas_cra)
[https://github.com/BlockResearchGroup/compas_cra](https://github.com/BlockResearchGroup/compas_cra)

### Examples to reproduce our paper results

See examples in [docs](https://github.com/petrasvestartas/compas_cra/latest/examples.html) or try them in [docs/examples](https://github.com/petrasvestartas/compas_cra/blob/main/docs/examples).
See examples in [docs](https://blockresearchgroup.github.io/compas_cra/examples/) or try them in [docs/examples](https://github.com/BlockResearchGroup/compas_cra/blob/main/docs/examples).
39 changes: 19 additions & 20 deletions docs/contributions.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,53 +7,52 @@ Contributions are welcome and very much appreciated!
We accept code contributions through pull requests.
In short, this is how that works.

1. Fork [the repository](https://github.com/petrasvestartas/compas_cra) and clone the fork.
2. Create a virtual environment using your tool of choice (e.g. `virtualenv`, `conda`, etc).
3. Build the solver once — it is compiled into the package, so an install from source
needs a staged IPOPT tree. See [Installation](installation.md) for the compilers and
libraries this needs on your platform, and for the Docker route if you would rather
not install them:
1. Fork [the repository](https://github.com/BlockResearchGroup/compas_cra) and clone the fork.
2. Create an environment with [uv](https://docs.astral.sh/uv) and build the solver —
it is compiled into the package, so an install from source needs a staged IPOPT
tree, and `invoke setup` takes care of the whole sequence
(see [Installation](installation.md) for the details, and for the Docker route):

```bash
packaging/build_ipopt.sh
uv venv --python 3.12
source .venv/Scripts/activate # .venv/bin/activate on macOS and Linux
uv pip install invoke compas_invocations2
invoke setup
```

4. Install the package and its development dependencies:
Only working on the documentation or on Python code? `uv pip install -r requirements.txt`
is enough for `invoke docs` and `invoke lint` — no solver build needed.

```bash
pip install -e ".[dev]"
```

5. Make sure all tests pass:
3. Make sure all tests pass:

```bash
invoke test
```

6. Start making your changes to the **main** branch (or branch off of it).
7. Make sure all tests still pass:
4. Start making your changes to the **main** branch (or branch off of it).
5. Make sure all tests still pass:

```bash
invoke test
```

8. Add yourself to the **Contributors** section in `AUTHORS.md`.
9. Commit your changes and push your branch to GitHub.
10. Create a [pull request](https://help.github.com/articles/about-pull-requests/) through the GitHub website.
6. Commit your changes and push your branch to GitHub.
7. Create a [pull request](https://help.github.com/articles/about-pull-requests/) through the GitHub website.

During development, use [pyinvoke](http://docs.pyinvoke.org/) tasks on the
command line to ease recurring operations:

* `invoke clean`: Clean all generated artifacts.
* `invoke check`: Run various code and documentation style checks.
* `invoke docs`: Build the documentation.
* `invoke docs-serve`: Serve the documentation locally with live reload.
* `invoke docs`: Serve the documentation at `localhost:8000` with live reload.
* `invoke test`: Run all tests and checks in one swift command.
* `invoke`: Show available tasks.

## Bug reports

When [reporting a bug](https://github.com/petrasvestartas/compas_cra/issues)
When [reporting a bug](https://github.com/BlockResearchGroup/compas_cra/issues)
please include:

* Operating system name and version.
Expand All @@ -63,7 +62,7 @@ please include:
## Feature requests and feedback

The best way to send feedback is to file an issue on
[GitHub](https://github.com/petrasvestartas/compas_cra/issues).
[GitHub](https://github.com/BlockResearchGroup/compas_cra/issues).
If you are proposing a feature:

* Explain in detail how it would work.
Expand Down
5 changes: 4 additions & 1 deletion docs/examples/06_arch_penalty.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@

assembly_interfaces_numpy(assembly, nmax=10, amin=1e-2, tmax=1e-2)

cra_penalty_solve(assembly, mu=0.7, verbose=True, timer=True)
# d_bnd=1e-2: with the default 1e-3 displacement bound this solve exhausts the
# iteration cap; at 1e-2 it converges in ~65 iterations, on resultants that agree
# with cra_solve to 1e-3 on the standard arch
cra_penalty_solve(assembly, mu=0.7, d_bnd=1e-2, verbose=True, timer=True)
cra_view(
assembly,
resultant=True,
Expand Down
13 changes: 9 additions & 4 deletions docs/examples/09_bridge.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,21 @@

assembly_interfaces_numpy(assembly, amin=1e-6, tmax=1e-4)

# cra_solve(assembly, verbose=True, density=d, d_bnd=dispbnd, eps=overlap, mu=mu)
cra_penalty_solve(assembly, verbose=True, density=d, d_bnd=dispbnd, eps=overlap, mu=mu)
# the published screenshot was made with cra_solve; the penalty variant stays one
# uncomment away, as it was in 2022
cra_solve(assembly, verbose=True, density=d, d_bnd=dispbnd, eps=overlap, mu=mu)
# cra_penalty_solve(assembly, verbose=True, density=d, d_bnd=dispbnd, eps=overlap, mu=mu)
cra_view(
assembly,
resultant=True,
nodal=False,
grid=True,
weights=True,
forcesdirect=False,
forcesline=True,
# the published screenshot shows cone-headed resultant arrows (forcesdirect), even
# though the 2022 file said forcesline - the image was made with uncommitted flags;
# the image is the contract
forcesdirect=True,
forcesline=False,
displacements=True,
dispscale=1,
scale=0.5 / d,
Expand Down
8 changes: 6 additions & 2 deletions docs/examples/13_curve-3-blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@
from compas_cra.equilibrium import cra_solve
from compas_cra.viewers import cra_view

density = 1
# The published screenshot's original parameters. They only solve in the barrier-
# interior regime the solver now runs in (mu_target - see cra_native.py); under strict
# optimization they exhaust the iteration cap on every IPOPT generation, which is why
# the author had switched to density=1 as a workaround days after the screenshot.
density = 0.1

FILE_I = os.path.join(compas_cra.SAMPLE, "curve-3-blocks.json")

Expand All @@ -25,6 +29,6 @@
grid=True,
displacements=True,
dispscale=0,
scale=1,
scale=5,
density=density,
)
2 changes: 1 addition & 1 deletion docs/examples/index.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Examples

All example files can be found in
[docs/examples](https://github.com/petrasvestartas/compas_cra/blob/main/docs/examples).
[docs/examples](https://github.com/BlockResearchGroup/compas_cra/blob/main/docs/examples).
Loading
Loading