Changelog¤
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
Unreleased¤
Added¤
-
Added
docs/fabrication.md, the part list for the shop, and aFabricationsection inmkdocs.ymlholding it. One row per part family indata/cantilevers_baked_model.json- all 237 elements - with quantity, the oriented bounding box of the finished part in millimetres (longest side first, a range where the family varies), material, and emptyImageand3D modelcells to fill in as the per-part files come out of the scripts. Material is only filled where the code states it: the column is glulam, the base is the Sherpa Power Base150402_PB_L-140-Cnamed inSupportElement. -
Added
compas_tf.viewer.dump_scene, which writes the Rhino bundle by walking the finished viewer scene, andscene_nodes, the flattening behind it. Each object contributes the one colour Rhino can show - an explicitcolor=, else the colour of the buffer its geometry is drawn in, so a polyline keeps itslinecolorinstead of inheriting a surface grey.
Changed¤
- Changed every example to draw through
viewer.scene-viewer.scene.add(item, parent=group, ...)andviewer.scene.add_group(name, parent=group)- instead of a group handle's ownadd. A group handle'saddis core compas'Group.add, which goes straight toSceneObject.addand never seescompas_viewer.Scene.add'sfacecolor->surfacecolortranslation, sofacecolor=on a group was silently dropped (example_model_1's construction planes drew grey, not blue). - Changed
SchoringElement.from_points_and_vectors,from_points_and_vectors_no_foot_no_headandpolylines_to_modelsto build aTFModelinstead of a plaincompas_model.models.Model.duplicate()- re-clone every element with a fresh guid so the copies can be merged alongside the original - is defined on this project'sBaseModel, not upstream, soexample_model_10_shoring.pydied on'Model' object has no attribute 'duplicate'and tookexample_model_11_full.pywith it (it readsshoring_model.json). These were the last plain-Modelconstructions insrc/. - Changed the examples'
color=(0.85, 0.85, 0.85)tofacecolor=.compas.scene.GeometryObject.__init__fillspointcolor,linecolorANDsurfacecolorfromcolor, so onecolor=painted the edges the same grey as the faces and the wireframe disappeared. The viewer's own defaults -linecolor0.2 grey,surfacecolor0.9 grey - only survive whencoloris left unset. Examples 4, 5, 6, 7, 10 and 11 were drawing flat because of it.
Removed¤
- Removed
compas_tf.viewer.TeeScene,SceneRecorder,dump_bundleand the_patch_group_nestingmonkeypatch that gavecompas_viewer'sGroupanadd_group. The fab examples drew through the tee so the Rhino bundle could be recorded alongside;dump_scenereads the scene itself, so the examples use the stock viewer API and the bundle is what the viewer actually shows. The tee also dropped a group's style kwargs on the live side and kept only a fixed list of style keys.
[0.1.12] 2026-08-10¤
Added¤
Changed¤
- Changed
docs/installation.mdto say how to get uv, per platform -winget install --id=astral-sh.uvor theastral.sh/uv/install.ps1script on Windows,astral.sh/uv/install.shor Homebrew elsewhere - with links to the repository and the official instructions. The page told you to runuv initwithout ever saying whereuvcomes from. Needspymdownx.tabbed, whichmkdocs.ymldid not have. - Changed the example pages to the minimum that is not already in the script beside them - 445 lines to 287. What is left per page is the screenshot, the script, its output, and only the facts the code does not show: the deflection numbers, why the contacts need a second STEP, why
neighbors=takes types.040_project_setupgets the uv install link back and drops the--optional viewer/uv sync --extrapair for a plainuv add compas_viewer, both verified by running them into a scratch project. - Changed the
releaseworkflow's build matrix to Python 3.10-3.12. It still ran 3.9, whichrequires-python = ">=3.10"now rejects, and thepublishjob needs the build - so the tag went up and nothing was published.
Removed¤
- Removed
examples/example_model_15_fab_formwork.py,example_model_17_quantities.pyand their entries intools/run_examples.py. Both were 0-byte files that the runner counted as passing and the pipeline page described as if they did something. The chain is 21 examples. - Removed
tests/test_placeholder.py-assert True, the stub that kept pytest from failing on an empty suite before there were real tests. 11 remain.
[0.1.11] 2026-08-10¤
Added¤
- Added
compas_tf.viewer.zoom_to, which frames the camera on the geometry beforeviewer.show(). On a model in millimetres the viewer opens at[-10, -10, 10]with a far plane of 1000 (scaled bycamera.scale, which starts at 1), so a 6015 mm building is entirely clipped until you press F. compas_viewer's ownzoom_selectedcannot be called for you - it reads the scene objects' bounding boxes, which do not exist until the renderer has run - so this does the same arithmetic fromelement.aabb/brep.aabbinstead, keeping the camera's existing view direction. Usingtarget - positionaszoom_selecteddoes is degenerate before the first render: the default position sits almost on the origin, so on a model centred 1.5 m up the camera ends up underneath the building.
Changed¤
- Changed
find_groups_with_names(neighbors=...)to accept a tuple of element types, or a predicate, as well as a bool.Trueadmits anything that touches the selection, which is rarely what an assembly means: across the seam a bay touchesouter_ribs_1_1,outer_ribs_0_3,inner_beams_2_1,inner_beams_0_3and two oculus plates, so the extraction came out with 2-part fragments ofquarter_model_1,quarter_model_3andoculus_modelhanging off it. The filter applies to both passes, which matters more than it looks: underTruethe oculus arrives by contact and thenconnector_wedge_7's cylinders arrive because their boxes overlapoculus_3- one wrong admission widening what the box pass tests against. - Changed
example_model_21_extract_bay.pyto name only the hardware that mounts the cantilever on its column (ConnectorElement,DowelCylinderElement). The wedges and their bolts are inner-beam hardware andOuterRibConnectorElementjoins one quarter to the next, so the bay is 46 elements and 143 contacts rather than 64 and 168.docs/examples/030_extract_baygets the viewer screenshot of it. - Changed
example_model_18to_22to drop the timing scaffolding and the comment blocks the documentation now carries. - Changed the docs to say how big anything is - 237 elements and 733 contacts in the building, 185 in the floor, 34 in a quarter, 46 in a bay - in the tutorial tree and the screenshot captions.
- Changed
requires-pythonto>=3.10. It said>=3.9, whichshapely >= 2.1rules out, and was briefly capped at<3.13on the assumption thatcompas_occtandcompas_manifoldstop at 3.12 - they ship acp312-abi3wheel, built against the stable ABI, so it installs on every CPython from 3.12 on. Verified up to 3.14; the cap was what broke the docs workflow, which now pins 3.12 to match the local environment rather than out of necessity.
Removed¤
[0.1.10] 2026-08-10¤
Added¤
- Added
examples/example_model_22_read_brep_adjacency.pyanddocs/examples/025_read_brep_adjacency, which read the STEP model, the contact STEP and its JSON sidecar together.020_read_brepcould draw the contacts but not say which two elements any of them joins, because STEP drops per-shape names; the sidecar describes face i in record i, so the pair is what turns 733 anonymous faces into 585 named joints, an area per pair and a type table.
Changed¤
- Changed
docs/index.mdto a two-sentence description of what the package does, the project presentation, and direct download links for the current STEP model, its contacts and their sidecar. The code sample it opened with belonged in the examples, and there was no link to either the presentation or the model. - Changed
requirements-dev.txtto depend onpytestdirectly. It arrived transitively withsphinx_compas2_theme, so dropping that for mkdocs left thebuildworkflow failing withpytest: command not found- lint green, tests never run. - Changed
find_groups_with_names(neighbors=True)to find the elements that have no interactions at all. It was a graph walk only, and the contact search skips the fasteners (skip=involving(DowelCylinderElement, ConnectorCylinderElement), 74% of the contacts for no structural information), which leaves 64 of the 237 elements with no edge for a walk to follow: a bay came out with its connectors but without the 32 dowels and 32 cylinders that bolt them on. Geometry is the only signal left for those, so an unlinked element is now included when its bounding box lands inside the bay - applied to the unlinked only, because on elements the graph does describe a box test is far too loose, one diagonal rib's box swallowing half the floor. Bay 0 goes from 49 elements to 77 (+16 cylinders, +12 dowels). - Changed
example_model_21_extract_bay.pyback toneighbors=True, which is now worth passing.
Removed¤
- Removed
data/bay_model.stpand the outputs of examples that no longer exist (floor_model_booleans.json,schoring_models.json,orient_2d.json,unwrap_beds.json/.png,example_2_floor_model.obj,example_model_7_contacts_columns.obj), plus the twoPLACEHOLDERfiles that kept the now-populateddocs/_imagesanddocs/examplesin git.
[0.1.9] 2026-08-10¤
Added¤
- Added
BaseModel.find_groups_with_names- several named groups extracted at once as one standalone model, for lifting an assembly (one column plus the quarter it carries) out of the building. The plural matters: contacts BETWEEN the groups only survive if the groups come out together, sofind_group_with_nametwice plusmergekeeps every internal contact and drops exactly the joint that connects them. Each group keeps its ancestor chain, pruned to what was asked for, so the extracted parts stay where they are in the world.neighbors=Truealso brings in the elements that interact with the extracted ones - the fasteners live in their own top-level groups, so a bay extracted by name alone has none - one step out only, or the whole model follows one edge at a time. On the cantilevers model: 49 elements and 208 contacts against 36 and 125 without. - Added
examples/example_model_21_extract_bay.py, and the documentation the reading side never had:docs/examples/010_read_model,020_read_brep,030_extract_bay,040_project_setup(a consumer project from scratch) and050_pipeline, plus a writtentutorialand a populated API reference.
Changed¤
- Changed
examples/example_model_19_read_model.pyandexample_model_20_read_brep.pyto the minimum API that does the job. What the comment blocks explained - baking, Brep contacts against mesh contacts, the STEP name/order sidecar, the deflection - is now prose in the docs, where it is read once rather than scrolled past in every example. - Changed
README.md, which still described acompas_viewerfork and anexample_0_watch_viewer.pythat no longer exist.
Removed¤
[0.1.8] 2026-08-10¤
Added¤
Changed¤
- Changed
PlateElementto build its loft on first use instead of in__init__.compute_elementgeometryis the only reader and it is@baked, so a baked plate returned its stored mesh and the loft was thrown away unused - yet all 145 plates paid for it on every load.compas.json_loadof the cantilevers model drops from 3.3 s to 0.60 s (the rawjson.loadsof the same 3.9 MB file is 0.09 s).
Removed¤
[0.1.7] 2026-08-10¤
Added¤
- Added
compas_tf.contacts- contact detection on Brep faces instead of mesh faces:brep_brep_contacts,prepare_faces, theBrepContactselement-pair detector (with a per-element Brep and face cache), theinvolving/betweenskip predicates, and thecontact_holesaccessor. The face pairs are prefiltered on opposite normals and boundary AABBs rather than byBrep.overlap, which would tessellate every Brep first - identical results, 3.8x faster, and no dependence onTOL.lineardeflection. - Added
TFModel.compute_contacts_brep, the one-call Brep version of the contact search, all-pairs or restricted to named groups. - Added
TFModel.clear_contacts,TFModel.contact_pairs,TFModel.contact_breps,TFModel.contact_adjacency,TFModel.contacts_to_stepandTFModel.contacts_to_json. STEP drops per-shape names, so the contact faces carry no adjacency; it goes in a JSON sidecar keyed by index, which STEP does preserve. - Added a
contactmethodhook toTFModel.compute_contactsandBaseModel.compute_contacts_between_groups, so the spatial search can be driven by something other thanelement.compute_contacts. - Added a
cacheargument toTFModel.element_brepsandTFModel.to_step, to reuse Breps already built by a contact search.
Changed¤
- Changed
example_model_18_write_model_and_brep.pyto compute contacts between all elements on the Brep faces, skipping the fasteners, and to write them to both the model JSON and their own STEP file. - Changed
example_model_19_read_model.pyandexample_model_20_read_brep.pyto read the contacts instead of recomputing them. - Changed
example_model_20_read_brep.pyto setTOL.lineardeflection = 1.0: at the 0.001 default the Breps tessellate to 2.93M triangles in 67 s, at 1.0 to 19.8k in 2.0 s, for the same picture. The old comment claiming the count was independent of the deflection was wrong.
Removed¤
[0.1.6] 2026-08-10¤
Added¤
Changed¤
Removed¤
[0.1.5] 2026-08-10¤
Added¤
Changed¤
Removed¤
[0.1.4] 2026-08-09¤
Added¤
Changed¤
Removed¤
[0.1.3] 2026-08-09¤
Added¤
Changed¤
Removed¤
[0.1.2] 2026-08-09¤
Added¤
Changed¤
Removed¤
[0.1.1] 2026-08-09¤
Added¤
- Column head version based on column centered at the corner of a quarter.
- Dated whole-model OBJ export (
floor_model_<YYYY-MM-DD>.obj, one named object per element) inexamples/example_2_floor_model_booleans.py. FloorModel.add_column_connections()— places aFloorColumnConnectionElementon each column and carves the connector pocket out of the column and that quarter's two outer ribs via boolean difference.FloorGuide.corner_point_column()— column/support grid placement, moved off the removedFloorBuilder.compas_manifoldas a runtime dependency (declared inrequirements.txt).
Changed¤
FloorGuideis now the single parametric source of the floor.FloorModelis constructed from a guide (FloorModel(guide=...), wasbuilder=...) and serializes it under the"guide"key.- All mesh booleans now use
compas_manifoldexclusively (difference, union, chain) — thecompas_cgalfallback and dependency were removed.compas_cgalno longer inrequirements.txt. compute_contacts_inner_beamsno longer callsprecompute_boolean_modifiersinternally (the caller does it once), removing a duplicate boolean pass.- Top docstring of
example_2is a raw string (fixes the\cSyntaxWarning). PolylineLoft.to_mesh/multiple_to_meshrepair capped-loft face winding so lofted plates (e.g. t-sections) come out watertight.PlateElement.compute_contactssupports bothpolygon_polygon_overlapsignatures acrosscompas_modelversions.- Corner column-connection cutter is now drawn yellow instead of orange in the viewer.
Removed¤
FloorBuilder(floor_builder.py) and the legacy element family built on it:QuarterFloorElement(quarter_floor.py),ColumnHeadElement(column_head.py),OculusElement(oculus.py).FloorModel.add_oculus()(used the removedOculusElement; oculus geometry now comes fromguide.oculus) andbuild_model()fromexamples/model.py.