Grasshopper re-write: caps, placement, booleans...#99
Open
ofloveandhate wants to merge 44 commits into
Open
Conversation
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… JSON Add an additive Python exporter and thin Grasshopper components so surfaces (nonsingular pieces as meshes + embedded curves) and standalone curves can be loaded into Rhino/Grasshopper. The export ships ONE unified vertex set; meshes carry only triangle indices into it and curves only ordered vertex-index lists into it, so meshes and embedded curves refer to the same points in Rhino (enabling joins, closed-solid detection, and exact curve/mesh intersections). Python (python/bertini_real/), all additive: - Curve.export_gh_json + CurvePiece.to_point_indices (index-space twin of to_points) - Surface.export_gh_json, SurfacePiece.to_gh_dict, Surface._curve_type_for_name, module-level _mesh_triangles - data.gather_and_export_gh convenience - fix latent crash in CurvePiece.to_points on unsampled curves (len(None)) - pytest suite under python/tests/ (pure-logic + fixture-based invariants) C# (grasshopper/bertini_real/): - GhExport/GhPiece/GhMesh/GhEmbeddedCurve/GhCurvePiece DTOs - GhJsonIO helpers (build meshes on the shared cloud, no cull/compact) - SurfaceReadGhJson and CurveReadGhJson components Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
python/scripts/export_for_grasshopper.py wraps gather_and_export_gh / export_gh_json into a runnable CLI. Runs from a decomposition's working directory (repo idiom) or takes an explicit output_dim_X_comp_Y folder; auto-detects curve (dim 1) vs surface (dim 2) and writes br_gh_export.json for the Read GH JSON components. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Splits the parallel Curves + Curve Types trees from "Surface Read GH JSON" into one output per type (Critical, Sphere, Singular, Midslice, Critslice, Other), preserving per-piece tree paths, so a given curve type can be grabbed off a single wire without manual tree filtering / get-item juggling. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add Decomposition._sphere_dict (center + radius) to the curve and surface JSON exports, a GhSphere DTO, GhJsonIO.ToSphereBrep, and a new Sphere (Brep) output on the "Surface Read GH JSON" component (appended last so existing wirings are unaffected). Extend the export tests to cover the sphere. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sphere Caps builds the faceted spherical cap(s) that close a surface piece where it meets the bounding sphere. It caps the piece mesh's OWN on-sphere naked boundary loop (not the separately-sampled sphere curve), so the cap shares the piece's boundary vertices and respects raw-with-raw / sampled-with-sampled. It works in mesh topology space (merging coincident/nodal vertices), skips degenerate edges and zero-area triangles, keeps the smaller-area cap, and warns on open/non-manifold boundaries. Close Piece joins a piece with its cap(s), merges coincident vertices to weld, unifies normals, and reports whether each result is a closed solid. Also verify in export_gh_json that each piece's sphere curves are closed loops, warning otherwise (surfaces a decomposition problem before Grasshopper). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The cap was a single fan to one apex, which looks coarse and pinches to a point on a small (e.g. unit) sphere. Add a Resolution input (radial rings, default 4): the cap is now subdivided into concentric rings slerped along the sphere from the boundary toward the pole, so it follows the sphere surface. Ring 0 keeps the exact shared boundary vertices, so the watertight weld with the piece is unaffected (validated: euler 2, watertight at R=4 on whitney/dingdong). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sphere Caps culled triangles by area (< tol*tol), which at higher Resolution killed legitimate thin triangles in the closely-spaced rings near the boundary, punching tiny holes so the joined piece was no longer closed (observed at resolution >= 8). Cull only triangles with an actually-collapsed edge (coincident vertices) instead; thin-but-valid triangles are kept. Validated watertight at R=7..20 on whitney/dingdong. Close Piece now also outputs the result's Naked Edges and reports the loop count, to make any future non-closed case diagnosable (seam vs interior crack vs uncapped boundary). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Spreads per-piece meshes apart so they can be seen separated: each piece (tree branch) is translated radially by Factor x (its bounding-box center - the overall center), so Factor 0 leaves them in place and larger values separate them. Optional Center override; outputs the moved meshes, per-piece translations, and the center used. (Named "Spread" rather than "Explode" since in Grasshopper explode means decomposing a thing into its constituents.) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rrect direction for a plug/socket it took me forever to understand this. wish i'd known years ago. i think some people told me -- Melody Chan, particularly, in Toronto in 2023 -- but i didn't get it. now i do. the committed code is a bit messy, but enough to get it integrated in a workflow.
…rities' into feature/grasshopper_frenzy # Conflicts: # python/bertini_real/surface/__init__.py
Extract the nodal-singularity connector computation (locations, tangent-cone directions from the Hessian, per-piece parities, singularities-on-pieces) out of write_piece_data into a reusable Surface.singularity_connector_data(); write_piece_data now delegates to it. Remove the dead centroid direction method (tangent-cone only). export_gh_json embeds this under a "singularities" key, and Surface Read GH JSON exposes Sing Locations / Sing Directions / Sing Parities / Sing On Pieces. This removes the need for the separate br_surf_piece_data.json -- one file, one reader. Add bertini2>=3.0.0 to install_requires (the tangent-cone direction needs the parser). Verified on raw nordstrands_weird: 9 pieces, 11 singularities, each joining two pieces. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…eces Promote the prototype geometry (validated all along as sanity checks) into supported Python so users without a Rhino/Grasshopper license get the same pipeline via trimesh: - module functions in surface: sphere_cap_meshes (cap a mesh's own on-sphere boundary loops, smaller-area side, radial-ring subdivision), join_meshes (weld via vertex merge), spread_pieces (exploded view). - SurfacePiece methods: as_mesh, sphere_caps, as_closed_mesh (cap + join). - python/scripts/close_pieces.py drives it end to end: split -> cap -> close -> optional spread -> export STLs, and reports watertightness + singularity connector count. Watertight solids require a sampled decomposition (the raw blocky mesh is non-manifold); the tools report this honestly rather than faking it. Verified: whitney pieces watertight, nordstrands_weird (raw) caps all sphere boundaries and yields its 11 connectors. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
as_mesh_raw fanned every face boundary edge to the face midpoint without skipping
degenerate curve edges. Those degenerate edges (left==mid or mid==right) come from the
critical point slices -- at a critical value the fiber degenerates, so some 1-cells
collapse to a point and are stored with a repeated vertex (the codebase already flags
these via is_edge_degenerate, and to_points skips them). Fanning one produced a zero-area
triangle like (a,a,mid) whose self-edge {a,mid} was counted twice, making the mesh
non-manifold (edges shared by 4 faces) and adding duplicate faces, so raw pieces could not
close into watertight solids.
Skip only the degenerate half of each edge's fan (len(set)==3), keeping the valid triangle
so no hole is introduced. Also honor keep_all_vertices (process=False) like as_mesh_smooth,
so the raw faces index the global/unified vertex set instead of a merged reindexed one.
Result: raw pieces are now clean manifolds that cap+join to watertight solids -- verified
9/9 on raw nordstrands_weird and on whitney. Adds tests/test_raw_mesh.py.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Surface Place Components no longer parses its own br_surf_piece_data.json. It now takes the singularity data as inputs directly from Surface Read GH JSON -- Sing Locations, Sing Directions, Sing Parities (per singularity, per piece) and Sing On Pieces (per piece) -- so the whole Grasshopper graph is fed by one file and one reader instead of a second JSON path. Same placement behavior and per-piece connector/diagnostic outputs; geometry inputs are now item access, and the pieceID user-string is the piece index. ComponentGuid unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Surface Group By Piece no longer reads br_surf_piece_data.json or routes connectors by a
pieceID user-string. Since meshes (Surface Read GH JSON) and connectors (Surface Place
Components) are already per-piece trees keyed by {piece_index}, it now just merges those
trees branch-by-branch: each output branch is the piece's mesh(es) followed by its
connectors. No file input; outputs the grouped tree plus the piece indices.
This makes the whole Grasshopper graph single-source (br_gh_export.json + Surface Read GH
JSON). The Data/PieceData DTOs are now unused, so remove them.
Consistency pass toward uniform component I/O: per-piece data is a DataTree keyed
{piece_index}; Meshes nickname is "M" everywhere (Group By Piece was "Ms"); the reader's
Mesh Mode nickname is "MM" to stop colliding with the Meshes output's "M".
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
grasshopper/docs/usage.puml: br_gh_export.json -> Surface Read GH JSON, the three
fan-out branches (Untangle Curves; Sphere Caps -> Close Piece -> Spread Pieces; Surface
Place Components), and the Group By Piece join, with notes for the one-file/one-reader
rule and the per-piece {piece} DataTree convention.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
SolveInstance read DA.GetData(22) for socketLength but the component registers only 7
inputs (0-6), so dropping the component on the canvas threw IndexOutOfRangeException
('Input parameter index [22] too high'). It should read index 2.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Spread Pieces took a Mesh tree, so it could not consume Surface Group By Piece's output (a tree whose branches mix a piece's mesh with its connector Breps). Change the input and output to a generic geometry tree (IGH_GeometricGoo): each branch's bounding box gives the piece center, and every item in the branch is translated together, so a piece and its connectors stay assembled while spreading. Still accepts bare mesh trees (Surface Read GH JSON / Close Piece). Input/output renamed Meshes -> Geometry (M -> G); GUID unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Spread separates one piece per tree branch; if the per-piece tree is flattened upstream to a single branch, the lone center equals the overall center and every translation is zero -- so nothing moves regardless of Factor, with no feedback. Emit a warning in that case pointing at the likely flatten, instead of silently passing the geometry through. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…s helper The boolean step is an ordered fold over a list of signed operations, not two buckets: start from a piece solid and apply each feature in order, +1 union / -1 subtract, each acting on the running result (order matters). Operations default to subtract, so feeding only negatives just subtracts them (the short-term need); the same machinery does the alternating pos/neg/pos/neg case later. - Python: surface.mesh_boolean_fold(solid, features, signs) via trimesh's exact 'manifold' backend; manifold3d added to install_requires (soft-imported). tests/test_booleans.py (incl. order-matters). - Grasshopper: Boolean Piece folds Solid + Features (meshes or Breps, auto-meshed) + signed Operations per piece, warns on non-closed solids, reports failed steps. Connectors To Features weaves the plug/socket trees into one ordered Features tree + signs (pos,neg per connector) so no manual Merge/Weave/sign wiring. "Feature" in the solid-modeling sense: an ordered additive/subtractive op on a body. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
usage.puml now shows Connectors To Features (weave plug/socket trees -> ordered Features + Operations) and Boolean Piece (fold Solid + Features + signed Operations -> Result), the Close Piece -> Boolean Piece solid wire, and a note that Boolean Piece is an ordered fold. Also reflects Spread's Geometry port. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Mesh.CreateFromBrep returns one mesh per Brep face, so appending a capped cylinder's lateral + cap meshes left the seams unwelded -> an open cutter -> mesh boolean difference silently did nothing. Weld coincident vertices (CombineIdentical) so a capped Brep becomes a closed cutter. Also warn when a feature mesh isn't closed, since an open feature is the usual reason a boolean appears to do nothing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Surface/Curve Read GH JSON now take a Scale (default 1) applied about the world origin on import. Scaling the unified vertex set means meshes and curves come out scaled automatically; the sphere and singularity locations are scaled too (directions stay unit), so the whole model lands in one consistent, larger frame -- no downstream Scale component, and connectors still place correctly at the scaled singularities. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Features is now optional; an empty feature list is an identity fold, so the Solid is output unchanged instead of the component returning nothing. Makes it safe to leave Features unconnected (or feed empty branches) while wiring up the graph. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Meshing a Brep cutter (Mesh.CreateFromBrep, one mesh per face) can yield an open mesh -- an uncapped cylinder tube, or a non-conforming seam between the lateral face and the caps that doesn't weld. An open cutter makes the mesh boolean no-op. After welding, if the cutter is still not closed, FillHoles() to cap it into a closed solid. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Connectors are now sized upstream (and the reader's Scale sets the model frame), so Place Components scaling them by Size (default 0.01) only shrank them. Drop the Size input and the scale step in moveComponents; connectors are oriented to the singularity direction and translated to the location at their true size. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Drop the "at least one required" error and the "only positive geometry" remark. Any combination (none through all of plug/socket +/-) is valid -- place whatever is supplied, leave the rest empty. Positives-only (union bodies, no holes) is a normal workflow. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
New "Utility" subcategory alongside Curve/Surface. Centered Closed Cylinder makes a capped cylinder centered on a plane origin along its Z axis (-Length/2..+Length/2), so it's ready to use as a connector / boolean cutter (centered to straddle its placement point, closed so mesh booleans bite) without wiring circle + cylinder + cap each time. Inputs Radius, Length, Plane (default WorldXY); output a closed Brep. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Surface-subcategory helper mapping an integer to a Surface Read GH JSON Mesh Mode string (0 = auto, 1 = smooth, 2 = raw), so a numeric slider can drive the reader's Mesh Mode input. Out-of-range indices clamp with a warning. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Grasshopper documents: .gh is GH's binary format (no useful diff/merge), .ghx is XML text (diffable). Prefer saving docs as .ghx for version control. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add an integer Sides input. 1 keeps the true round cylinder; 3/4/5/... build a capped, centered regular-polygon prism (triangle/square/pentagon/...) via an extruded n-gon profile. Radius is the circumradius. Sides < 1 or == 2 (degenerate) error out. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A sibling to Sphere Caps that fills each on-sphere boundary loop with a FLAT fan to the loop centroid instead of a cap hugging the sphere -- so a Bertini-computed cylinder gets flat disk ends, and blocky decompositions get faceted flat caps. The sphere still shows the cut. Cap is built on the piece's own boundary vertices (welds watertight in Close Piece); Resolution adds concentric linearly-interpolated rings. Verified watertight on whitney/ dingdong. Factor the shared on-sphere boundary-loop detection and degenerate-skip triangle add out of Sphere Caps into a Capping helper, used by both cap components. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
flat_cap_meshes (fan to each on-sphere loop's centroid), SurfacePiece.flat_caps, and a flat= flag on SurfacePiece.as_closed_mesh (resolution defaults 4 spherical / 1 flat). The Rhino-free pipeline now offers both cap styles. Test: whitney pieces close watertight with flat caps too. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- python/docs/tutorials/capping_and_joining.rst: tutorial on the Python pieces -> caps -> close -> boolean flow, using Ding Dong. Shows spherical vs flat caps, and subtracting a square hole from one piece while adding a slightly smaller square rod to the other so they snap together. Rendered figures + the make_images.py generator. - close_pieces.py: add --flat (flat caps) and let resolution default per cap style. - join_meshes: fix_normals so a watertight result is a proper "volume" (consistent winding); manifold3d booleans require this, and it fixes inverted/negative-volume pieces. - usage.puml: note Flat Caps as the drop-in alternative to Sphere Caps. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a note pointing users without Rhino to the pure-Python pipeline tutorial (python/docs/tutorials/capping_and_joining.rst) and scripts/close_pieces.py. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Colors piece meshes by a scalar expression in x,y,z evaluated at each vertex (via Grasshopper's GH_ExpressionParser), normalized over all meshes (or an explicit Domain) and mapped through a color gradient onto mesh vertex colors. Default blue->red spectrum; optional Colours stops. Outputs colored meshes, per-vertex Values, and the Domain used. Rhino meshes carry vertex colors and Spread Pieces preserves them, so this works before or after spreading. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A directional alternative to Spread Pieces: each piece is translated by Factor times the sum of unit vectors along its incident singularities' connector directions, oriented away from each singularity (the way the piece slides off its rod). Opposite-side connectors cancel, so a central hub barely moves while leaf pieces slide out along their rods -- showing how the pieces would assemble. Takes Sing Locations/Directions/On Pieces from the reader. May replace the radial Spread if it proves out. Validated on nordstrand. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ectors) Spread Pieces used Factor as a proportional multiplier of each piece's offset from the center, while Spread By Connectors uses Factor as an absolute model-unit distance. Make Spread Pieces absolute too: move each piece Factor units along its outward unit direction. Default Factor 1.0. Now F means the same thing in both spreads. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ltiplier) Both Spread Pieces and Spread By Connectors now expose the input as Distance (D) instead of Factor (F), since it is an absolute model-unit distance rather than a multiplier. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ion) Replace the local per-piece sum with a breadth-first traversal of the connector graph (singularities are edges joining exactly two pieces). Each piece's displacement is its parent's displacement plus a Distance step along the connecting axis, oriented so the child slides off its rod away from the parent -- so chains telescope outward, a hub stays put, cycles are spanning-treed, and each piece moves exactly one step per graph edge regardless of its other connections. Root defaults to the most-connected piece; a Root input fixes a chosen piece; disconnected groups are each rooted independently (with a remark). Validated on nordstrand: hub fixed, all leaves slide out uniformly. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ylinder Reflect the newer components in usage.puml: Color By Function (vertex coloring before spread), Spread Pieces vs Spread By Connectors (radial vs assembly explosion, Distance), the Utility Centered Closed Cylinder feeding connectors/features, and Place Components without the removed Size input. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Cap winding followed the arbitrary boundary-loop traversal direction, so roughly half the caps faced inward (toward the sphere center). The welded solid then had a seam fold where piece and cap disagreed, and UnifyNormals (seeded from an arbitrary face) could leave the whole piece inside-out. - SurfaceSphereCaps: CapWindsInward() tests the cone-fan normals against the outward radial direction and reverses the loop when inward, so caps are outward by construction and agree with the (outward) piece. - SurfaceClosePiece: after UnifyNormals, flip the solid when IsClosed and Mesh.Volume() < 0, guaranteeing outward normals (mirrors trimesh.fix_normals). Verified on nordstrands_weird_1: all 18 caps (9 pieces x smooth+raw) now wind outward; previously 9 were flipped. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Grasshopper pipeline for Bertini_real surfaces and curves
This branch (re)-builds a complete Grasshopper pipeline for working with Bertini_real decompositions
in Rhino, plus a Rhino-free Python pipeline for the same operations.
Architecture
br_gh_export.jsonper decomposition; C#stays a thin reader. A unified vertex set is the spine — meshes and embedded curves all index
the same point cloud, so joins, closed solids, and curve∩mesh intersections work exactly.
br_gh_export.jsonfolds in singularity/connector data (locations, directions, parities,per-piece assignments), retiring the old
br_surf_piece_data.json.New Grasshopper components
Readers / data: Surface Read GH JSON, Curve Read GH JSON (with Scale input), Untangle
Curves By Type, Mesh Mode selector
Capping / closing: Sphere Caps (radial rings slerped along sphere; caps the piece mesh's own
on-sphere boundary — not the sphere curve — to avoid T-junctions), Flat Caps (flat fan to loop
centroid, for cylinder ends), Close Piece (weld + UnifyNormals + IsClosed + Naked Edges
diagnostic)
Booleans: Boolean Piece (ordered fold: Solid + Features + signed Operations, +1 union /
−1 subtract), Connectors To Features (weaves plug/socket trees into an ordered Features +
Operations sequence)
Assembly / display: Spread Pieces (radial explosion, absolute distance), Spread By
Connectors (BFS the connector graph from the most-connected hub; each piece offset = parent's
offset + Distance along the connector axis — handles chains, cycles, and disconnected groups),
Color By Function (colors piece meshes by a scalar expression in x,y,z via
GH_ExpressionParser), Surface Place Components (rewired to per-piece trees; Size input removed —
connectors placed at true scale; accepts any subset of plug/socket geometries), Surface Group By
Piece (rewired to per-piece trees)
Utility subcategory: Centered Closed Cylinder (Sides: 1 = round, 3+ = N-gon prism)
Python additions (
python/bertini_real/)surface:sphere_cap_meshes,flat_cap_meshes,join_meshes,spread_pieces,mesh_boolean_fold(trimeshmanifoldbackend),as_closed_mesh,SurfacePiece.as_mesh/sphere_caps/flat_caps/to_gh_dict,Surface.export_gh_jsoncurve:Curve.export_gh_json,CurvePiece.to_point_indicesdecomposition:_sphere_dictdata:gather_and_export_gh()export_for_grasshopper.py,close_pieces.py,prep_surf_for_grasshopper.pymanifold3d(mesh booleans),bertini2>=3.0.0Tutorial
python/docs/tutorials/capping_and_joining.rst— Ding Dong example showing sphere vs. flatcaps, then subtracting a square hole from one piece and adding a square rod to the other so
they snap together. Figures generated by
make_images.py.Bug fixes (pre-existing bugs)
curve/__init__.py— crash into_pointson unsampled curves:len(None)whenself.sampled_points is Nonecaused a crash. Fixed by guarding the sampled-points path.surface/__init__.py—as_mesh_rawproduced non-manifold geometry: Degeneratetriangles from critical-slice curve edges (repeated vertex indices) were included, and
keep_all_verticeswas not honored. Fixed: skip triangles with a repeated vertex index;pass
keep_all_vertices=Truetoextract_points. Raw pieces are now manifold and closewatertight (verified 9/9 nordstrand pieces).
Pipeline diagram
grasshopper/docs/usage.puml(PlantUML) documents the full single-source pipeline.Render with
plantuml grasshopper/docs/usage.puml.Gratitude and thanks
This PR builds off of the excellent work and contributions from my students at UWEC: Caden Jorgens @StellarRaccoon, Danya Morman, Morgan Fiebig, Briar Weston, Foong Min Wong @foongminwong. You have my gratitude, at helping make this possible. Caden, I finally got the plugs and sockets to go in the correct direction -- we were looking for the tangent cone! Danya, we did great work on skeletons and made that fun rainbow Whitney Umbrella (sadly the penetrant on the piece melted to some styrofoam and i had to discard the object). Morgan, we did so much work on triangulations, and now it's really paying off. And Foong Min, we printed so many things, and you really wrote most of the foundational Python code that now really gets to shine. Thank you all, for the things I listed, and the things I didn't.