Geometry construction#

Magnelio models are built from constructive solid geometry on the Open CASCADE kernel via pythonocc-core (DD-003, DD-016) — primitives, Boolean operations, and a set of verbs that grow, move and modify shapes. The construction layer is engineering infrastructure on top of a third-party kernel, not a numerical-methods contribution. This chapter covers the vocabulary of that construction: which objects are profiles and which are bodies, how a curve or a surface becomes a solid, and what the mesher makes of the result. The API reference lists every class and verb; the tutorials on profile geometry, CAD import and the reflector antenna show them in use.

Bodies, sheets and curves#

Three kinds of object share the geometry namespace:

  • Bodies — Brick, Sphere, Cylinder, Cone, Torus, Loft, imported solids, and everything a verb or a Boolean produces from them. A body carries a material and is what a GeometryModel meshes.

  • Sheets — zero-thickness regions: the planar Face (an axis-normal polygon), a Curve.covered() (any closed planar curve filled in), and the curved Surface. A sheet without a material is a construction profile: it exists to be grown into a body by extruded() or thickened() — and, for the planar ones, revolved() or swept(), or as a section of a Loft. A sheet with a material would be a thin sheet; its physics (an infinitely thin conductor or dielectric film) is not wired, so such a sheet cannot be meshed on its own — model it as a thin body instead.

  • Curves — Curve (polyline, arc, spline, helix) and Path, which draws one segment by segment. A closed planar curve becomes a sheet through covered(); any curve becomes a conductor track through traced() (widened in its plane, then given a metallisation thickness — the direct route from a routed centreline to the copper of a board); a ThinWire is a curve meshed as a sub-cell conductor.

Moving, turning, scaling and mirroring keep these kinds: a rotated sheet is still a sheet and still a profile, a mirrored planar sheet is still planar. Booleans are defined on bodies.

A union of bodies that are prisms along one axis over the same interval — the strips of a feed network, the pads of a layer, a row of posts — is fused in their common plane and raised once, so the result carries no seams between its operands; whatever else a union holds is fused in space, and only where it meets something. In the plane the operands are fused pairwise up a spatial bisection tree, with the seams removed at every node, so a network of thousands of coplanar strips costs seconds rather than the minutes a single fuse of all of them takes. The point set is the same either way; the face count is what the mesher sees.

Lofts: between profiles, and between faces#

Two constructors build a body that changes cross-section along its length. Loft(*sections) takes the profiles themselves — planar sheets or closed curves, as many as the shape needs, in the order the body passes through them — and is the way to draw a horn or a multi-step matching section from sketches. a.lofted(near_a, b, near_b) takes one face of an existing body and one face of another, and bridges them; the profiles are read off the two faces, so the transition fits both parts exactly and follows them when a dimension changes.

Both accept blend="spline" (one smooth surface through all profiles) and blend="ruled" (straight surfaces between neighbours, a stack of frusta). With only two profiles the two are the same surface: a straight run from one outline to the other, which meets each end at whatever angle the straight connection makes — a crease at both joints of a waveguide taper.

The face-to-face verb adds blend="tangent", which leaves each face along its outward normal, so the wall slope at both joints is zero and the transition meets both parts without a crease. It has two regimes, chosen from the two normals:

  • Faces that look at each other (antiparallel normals: the two ends of a taper, coaxial or laterally offset) get a loft whose cross-section eases out of one profile and into the other along a straight axis — the same family of intermediate sections the plain loft carries, redistributed under a law whose derivative vanishes at both ends. The end tangency is exact by construction, not fitted, and the axial position stays linear in the surface parameter at the default tension=1/3. A lateral offset between the two faces comes out as a smooth dog-leg with the sections still parallel to the faces.

  • Faces that point in different directions (an electrode ending on a z-face, the pin it feeds beginning on a y-face) get a sweep of one profile into the other along a curved spine that leaves both faces along their normals, with the profiles held perpendicular to the path.

tension sets how far the blend holds its normal direction before turning, as a fraction of the distance between the faces; a (start, end) pair sets each end on its own. Values well past 2/3 overshoot into a bulge. Two parallel faces that look away from each other are refused: a transition leaving both along their normals would have to pass through both bodies.

From a map to a reflector: parametric surfaces#

Surface.parametric(fn, u=(u0, u1), v=(v0, v1), samples=(nu, nv)) samples a map \((u, v) \mapsto (x, y, z)\) on a grid and passes a degree-3 B-spline surface exactly through the samples (OpenCASCADE’s GeomAPI_PointsToBSplineSurface). The map is any Python function of two parameters — a paraboloid \(z = (x^2 + y^2)/4F\), a hyperboloid, a numerically shaped reflector given as a table — and the parameter domain is the designer’s choice: a reflector rim comes out as an exact circle when the dish is parametrised in polar coordinates about the aperture centre, with no trimming step. A parameter row that collapses onto a single point (the pole of such a parametrisation) is allowed; the surface closes there.

The interpolant is exact at the samples and follows the map to within the spacing-cubed between them: 32 × 32 samples place a 240 mm dish to a few micrometres, 32 × 64 to 10 nm. The sheet stores its samples, not the map — a shape is a value, and, as for imported CAD, the parametric history is not part of a model: a stored project returns the extruded body, not the function that generated it.

Two verbs turn the sheet into metal:

  • extruded(vector=…) sweeps the sheet along a fixed vector (a prism). It is robust for any sheet and, for a perfect conductor, physically equivalent to a normal offset — the field never enters the metal, so only the reflecting surface matters. This is the recommended route for reflectors.

  • thickened(thickness=…) offsets a curved sheet along its own normal (direction="forward" or "backward"; "symmetric" is for planar sheets). The kernel’s offset can fold at very dense sample grids or where the thickness approaches the curvature radius; Magnelio checks the result (topology and volume against area × thickness) and refuses with a pointer to extruded() instead of returning a body of the wrong shape.

What the mesher sees#

The mesher places grid planes where the geometry has features — the faces of bricks, the tangent planes of cylinders and spheres, the edges that lie flat in an axis plane (see the chapter on conformal meshing). A free-form B-spline face contributes only the six planes of its bounding box: the mesher has no analytic handle on it, so the resolution across a reflector is whatever the wavelength rule and MeshControl(max_cell_size=…) give. Set the cell size explicitly for such models. Cross-sections through free-form faces are taken on a triangulation of the body whose points are lifted back onto the exact surface (see the conformal-meshing chapter), so a free-form body meshes at about the cost of the same volume of primitives.

The thin-metallisation detection recognises a flat sheet whose bounding box is thinner than a cell on one axis; a curved shell is thick on every axis of its bounding box and is classified cell by cell like any other body. Give reflector shells a thickness of two cells or more so that the conformal classifier resolves the metal on both faces — for a perfect conductor the thickness has no electromagnetic effect.