linemesh¶
- class nekmeshpy.linemesh.LineMesh[source]¶
Bases:
objectA 1-D mesh: a
PointMeshof the shared points with(L,2)line connectivity and a sparse per-lineelement_tags.Its point tags are the point mesh’s own element tags – a point is one object the whole mesh shares, so naming it is naming that object, not naming it once per incident line.
point_tagsreads them through.- EDGE_POINTS = array([[0], [1]])¶
- __init__(point_mesh, lines, interior=None, element_tags=None)[source]¶
Construct from the rung below:
point_mesh(aPointMesh, or a bare(N,3)array promoted to an untagged one), the requiredlines(L,2)connectivity, the per-lineinteriornodes, and an optionalTagsnaming whichever lines are tagged.Passing an array is the authoring form and the one every factory building fresh geometry wants – there are no point tags to carry yet. Pass a
PointMeshwhen there are: an operation rebuilding this mesh around moved coordinates has to hand the tags on rather than let the promotion drop them.
- property corners: NDArray[int64]¶
(L,2)incidence into the point mesh – the same arraylinesholds, under the name the rungs above use for this slot (cornersonQuadMesh/HexMesh).A point is its own corner, so at this rung the incidence into the rung below and the corner connectivity are one table; the two names exist so code that walks every rung can ask for either without a special case.
- property points: NDArray[float64]¶
The
(N,3)coordinates – a derived view of the point mesh’s own array, somesh.points[:] = Xrepositions every rung built on it.
- property point_tags: Tags¶
The tags on the shared points, over point ids.
This is
point_mesh’s ownelement_tagsread through, not a table of its own: rung N’s side tags are rung N-1’s element tags all the way up the ladder, and the bottom is no exception.
Operations¶
linemesh.assemble¶
Variable-arity LineMesh operations – the only ones that build a numbering.
- class nekmeshpy.linemesh.assemble.Seam[source]¶
Bases:
NamedTupleOne stated interface between two line meshes – the line rung’s counterpart of
hexmesh.Seam, naming point groups.A slice at this rung is a single point, so
loft’sfirst_tag/last_tagname the two chain ends and are the natural way to make a seam addressable here.aandbname a mesh by its position inattach’smeshesor by the mesh itself; a mesh appearing twice must be named by index.
- nekmeshpy.linemesh.assemble.attach(meshes, seams)[source]¶
Join line meshes at the point groups each
Seamnames, in one pass.No tolerance: within a named pair of groups the pairing is nearest-neighbour, and what proves it is bijectivity – equal counts plus an injective map is a one-to-one correspondence however far apart the two sides sit.
The line rung has nothing below it but points, so a weld here reconciles nothing: every high-order node of a line is private, so the interiors simply concatenate. That also means moving a shared end under
own=leaves the incident lines’ interiors where they were – the same caveat the rungs above carry.Welding two chain ends together is the common case, and closing a loop is the same call with both ends stated:
ring = linemesh.attach([arc_p, arc_q], [Seam(0, "A1", 1, "A1"), Seam(0, "A2", 1, "A2")])
- nekmeshpy.linemesh.assemble.components(mesh)[source]¶
The mesh split into its connected pieces – one
LineMeshper group of lines reachable through shared points, in the order their first line appears.A single chain or loop comes back as a one-element list; what makes this worth calling is the mesh that turns out to be two.
- nekmeshpy.linemesh.assemble.loft(points, *, loop=False, interior=None, element_tags=None, first_tag=None, last_tag=None, order=1)[source]¶
Loft a stack of point “profiles” into a 1-D mesh – the bottom rung of the uniform sweep primitive shared with
QuadMesh.loftandHexMesh.loft.A slice here is a single point, so the three tag arguments the upper rungs take as “one tag or an
Tagsover the slice” reduce to one tag each:element_tagsnames every lofted line, andfirst_tag/last_tagname the chain’s two end points.
- nekmeshpy.linemesh.assemble.loft_fn(f, fractions, *, loop=False, order=1, element_tags=None, first_tag=None, last_tag=None)[source]¶
Loft a curve given as its own analytic parametrization –
loftwith the profiles evaluated rather than handed in, so every node (corners and the private high-orderinterior) comes from callingfand nothing is ever placed on a chord.
- nekmeshpy.linemesh.assemble.loft_spline(points, *, loop=False, element_tags=None, first_tag=None, last_tag=None, order=1)[source]¶
loftwith the high-order nodes read off a cubic spline through the whole stack instead of the chord of their own element.Same arguments, same numbering, same tags – and the corners are the points given, untouched, because the spline interpolates them. Only the private
interiornodes differ:loftplaces them on the straight line between their element’s two endpoints, which is the “high order in storage, linear in geometry” trap, while here they sit on a curve fitted through the neighbouring points as well. A chain of points around a bend comes out bent rather than faceted.Nothing is resampled: this is not a smoother, and it does not move what it was given.
- nekmeshpy.linemesh.assemble.merge(meshes, *, tol=1e-07)[source]¶
Merge line meshes into one, welding coincident topological end points (the degree-1 chain ends – the 1-D analogue of the boundary vertices
QuadMesh.merge/HexMesh.mergeweld).tolis a fraction ofconform.bbox_scale– the largest of the x/y/z ranges over every point handed in – so the coincidence radius istol * bbox_scale. Not a distance: if you know one, divide byconform.bbox_scale(...).
- nekmeshpy.linemesh.assemble.refine(mesh)[source]¶
Uniform H-refinement: split every line into 2 at its own true midpoint.
Exact at any order – the midpoint, and every child’s own curved interior, are read off the stored polynomial map via the internal
core.interporder-N kernel, not a straight-line average of the corners. Linei’s new midpoint is point idmesh.n_points + i; child2*i + q(q= 0 nearer the first corner, 1 the second) is the half of lineion that side. One call is one level – callrefineagain for the next.No conformality check is needed here: unlike the quad/hex rungs, a line’s two children only touch its own corners, which are already-shared points – there is no seam between two different parent lines to verify.
- nekmeshpy.linemesh.assemble.remove(mesh, which)[source]¶
The complement of
select(): everythingwhichdoes not name.
- nekmeshpy.linemesh.assemble.select(mesh, which)[source]¶
The named lines as a curve of their own, renumbered from zero.
whichis a tag string (every line carrying it), a(L,)boolean mask, or an array of line ids. Kept lines hold their relative order and their tags; points no kept line touches are dropped. The inverse ofmerge(), and one of the three operations that manufacture a numbering.
linemesh.shape¶
Shape factories for the LineMesh rung – the ones owning a shape model rather
than being generic over any input.
- nekmeshpy.linemesh.shape.arc(radius, n, *, center=(0.0, 0.0, 0.0), normal=(0.0, 0.0, 1.0), start_theta=0.0, end_theta=3.141592653589793, element_tag='', first_tag=None, last_tag=None, order=1)[source]¶
An open circular arc:
nline elements overn+1points evenly spaced in angle fromstart_thetatoend_thetaon the circle ofradiusaboutcenter, in the plane with the givennormal(default+z).first_tag/last_tagname the two end points, which is what makes an end addressable bylinemesh.attach.
- nekmeshpy.linemesh.shape.arclength_fractions(f, n, *, t_range=(0.0, 1.0), samples=1001)[source]¶
The
(n+1,)parameter values spanningt_range– fromt_range[0]tot_range[1]– at whichfmust be evaluated for the resultingn+1points to be evenly spaced by arc length: hand the result straight tolinemesh.assemble.loft_fnas itsfractions, with no further scaling (loft_fn(f, arclength_fractions(f, n, t_range=...), order=N)).
- nekmeshpy.linemesh.shape.circle(radius, n, *, center=(0.0, 0.0, 0.0), normal=(0.0, 0.0, 1.0), start_theta=0.0, element_tag='', order=1)[source]¶
A closed loop of
npoints evenly spaced on a circle ofradiusaboutcenterin the plane with the givennormal(default+z).
- nekmeshpy.linemesh.shape.line(start, end, fractions, *, element_tag='', first_tag=None, last_tag=None, order=1)[source]¶
A straight open line from
starttoendsampled at normalized arc-lengthfractionsin[0, 1](0= start,1= end): the graded-edge sibling ofcircle/rectangle.first_tag/last_tagname the two end points, as onloft– a slice at this rung is a single point. That is what makes an end addressable bylinemesh.attach.
- nekmeshpy.linemesh.shape.on_surface(curve, surface, *, order=1, element_tag='')[source]¶
Mesh a
SurfaceCurveby evaluatingsurfaceon it – one element between consecutive nodes ofcurve.fr, exact on the surface at every node.- Parameters:
curve (SurfaceCurve)
surface (Callable[[NDArray[float64]], NDArray[float64]])
order (int)
element_tag (str)
- Return type:
- nekmeshpy.linemesh.shape.path_fractions(path, *, target_length=None, layers=None, fractions=None)[source]¶
Resolve a
Pathand exactly one oftarget_length/layers/fractionsinto the sweep stations themselves.
- nekmeshpy.linemesh.shape.rectangle(width, height, n, *, center=(0.0, 0.0, 0.0), normal=(0.0, 0.0, 1.0), side_tags=None, element_tags=None, order=1)[source]¶
A closed rectangle loop of the given
widthxheightaboutcenterin the plane with the givennormal(default+z), discretized intonline elements (nmust be a positive multiple of 4):n // 4evenly spaced per side, running CCW from the lower-left corner (bottom / right / top / left), corners always landing on a point.
linemesh.morph¶
Fixed-arity, rung-preserving LineMesh operations (delta 0).
- nekmeshpy.linemesh.morph.blend(a, b, fractions)[source]¶
Linearly morph between two conformal profiles
aandb(equal point count and identicallinesconnectivity – which is exactly what makes both open or both closed), one profile per fractiontwith points(1-t)*a + t*b– sot=0reproducesaandt=1reproducesb. Each profile returned carriesa’spoint_tagsand emptyelement_tags.
- nekmeshpy.linemesh.morph.mirror(mesh, normal, point=array([0., 0., 0.]))[source]¶
A new curve reflected through the plane with
normalthroughpoint.This is the one rung where a mirror is only the coordinate map. The rungs above pair it with a re-winding, because a reflection has determinant
-1and a quad or a hex has a signed Jacobian to put back the right way round; a line element has no such sign, and the region fills take their orientation from the loop they are handed rather than from its traversal direction, so a reflected loop fills exactly as its original did. What does invert is the direction of travel – compose withreverse()if a caller depends on it.Use it to build the half you can mesh and recover the whole:
linemesh.merge([half, mirror(half, normal=(1, 0, 0))]).
- nekmeshpy.linemesh.morph.offset(mesh, distance, normal=None, crease=np.float64(0.5235987755982988))[source]¶
A new curve displaced by
distancealong its in-plane normal – the tangent (computed from the underlying GLL nodes, so high-order interior nodes follow the same rule as corners) rotated a quarter turn within the plane given bynormal.normalneed not be supplied for a planar curve: it defaults to the curve’s own least-squares plane. At a point shared by more than one line (a chain’s interior corners, or a junction where several lines meet), the offset direction is the average of every incident line’s own direction at that point, renormalized – so a private high-order node (touched by exactly one line) is simply displaced by its own element’s direction.This is the building block for skinning: loft a curve and its
offsetcopy to get a thin, perpendicular boundary-layer quad strip.
- nekmeshpy.linemesh.morph.reverse(mesh)[source]¶
The same curve traversed the other way, by relabelling rather than re-placing – so the high-order nodes ride along on the true geometry instead of being straight-subdivided between the reversed points.
An open chain sends point
itoN-1-i. A closed one pins point0and sendsito(N-i) mod Ninstead: the first map would also rotate the ring, so linekwould come back spanningk-1 -> kwhere it spannedk -> k+1, and every factory that meshes a loop exactly reads linekas the segment leaving pointk. Both maps are involutions, soreverseis its own inverse either way.
- nekmeshpy.linemesh.morph.rotate(mesh, angle, axis=array([0., 0., 1.]), center=array([0., 0., 0.]))[source]¶
A new curve rotated by
angleradians about the line throughcenteralongaxis(right-handed,axisneed not be normalized). The map is rigid, so element quality is unchanged.
- nekmeshpy.linemesh.morph.scale(mesh, factor, center=array([0., 0., 0.]))[source]¶
A new curve scaled about
centerbyfactor– a scalar (uniform) or a(3,)per-axis vector. Every factor must be positive.
- nekmeshpy.linemesh.morph.transform(mesh, matrix, offset=array([0., 0., 0.]))[source]¶
A new curve with every node mapped through the affine
p @ matrix.T + offset.
- nekmeshpy.linemesh.morph.transform_fn(mesh, fn)[source]¶
A new curve with every node – corners and the private high-order
interioralike – mapped through an arbitraryfn;transform()for a warp that is not affine.fnis called once per node block with an(N, 3)array and must return(N, 3). Connectivity and tags ride through verbatim.This is the one rung – with
PointMeshbelow it – where the map is only coordinates: a line element has no signed Jacobian, the same reasonmirror()needs no re-winding here. A curvedfnkeeps a curved curve curved; a straight-subdivided one (order 1, or built from an explicit point array) stays faceted between its corners –fnmoves the nodes it is given, it does not add any.
linemesh.query¶
Read-only LineMesh queries – the operations that leave the ladder.
- nekmeshpy.linemesh.query.bounds(mesh, *, high_order=False)[source]¶
The axis-aligned bounding box of the mesh’s nodes.
At
high_order=Falsethat is the corners only, which does not enclose a curved element; passhigh_order=Trueto include the stored interior nodes. Neither bounds the polynomial itself – a curve can bulge past its own nodes – so read this as an extent, not a guarantee.
- nekmeshpy.linemesh.query.boundary_elements(mesh)[source]¶
Sorted unique line ids with at least one degree-1 end point – the 1-D sibling of
QuadMesh.boundary_elements/HexMesh.boundary_elements, and vectorized the same way.- Parameters:
mesh (LineMesh)
- Return type:
NDArray[int64]
- nekmeshpy.linemesh.query.boundary_points(mesh)[source]¶
Sorted unique topological boundary point ids: the degree-1 ends. Empty for a closed loop; the two ends for an open chain.
- Parameters:
mesh (LineMesh)
- Return type:
NDArray[int64]
- nekmeshpy.linemesh.query.centroid(mesh, *, high_order=False)[source]¶
The length-weighted centroid
integral x ds / integral ds– the mass property, not the mean of the points (which would weight a dense region of nodes over a long sparse one).
- nekmeshpy.linemesh.query.element_blocks(mesh)[source]¶
(L, order+1, 3)– each line’s own node block, assembled natively from the shared corner points and this mesh’s private interior nodes. Nothing is resampled or deduplicated: the block is what the B-rep already stores, gathered into the element lattice order (start corner -> interior -> end corner).The line rung of a query every rung above spells the same way – see
quadmesh.element_blocks.- Parameters:
mesh (LineMesh)
- Return type:
NDArray[float64]
- nekmeshpy.linemesh.query.element_lengths(mesh, *, high_order=False)[source]¶
(L,)arc length of each line element.Straight chord by default;
high_order=Trueintegrates the curve the mesh actually stores, which is the reading that differs on anything built atorder > 1. The line rung ofquadmesh.element_areas/hexmesh.element_volumes.
linemesh.tag¶
Vocabulary-only LineMesh operations: rename the tags, touch nothing else.
Separate from morph, which is the delta-0 rung for
operations on the geometry. These change neither coordinates nor connectivity nor
numbering – only what the two tag tables call things – so a caller reading the
sibling list can tell at a glance that a retag cannot have moved a node.
- nekmeshpy.linemesh.tag.retag_element(mesh, tags)[source]¶
The same mesh with its
element_tagschanged, by whattagsis: a mapping renames the existing names (a tag the map does not name is left alone, the map applies simultaneously, a key naming nothing raises, a rename toNO_TAGuntags); a string names every element; aTagsor per-element sequence replaces the table. Geometry and the side tags are untouched.
- nekmeshpy.linemesh.tag.retag_point(mesh, mapping)[source]¶
The same mesh with its
point_tagsrenamed throughmapping, rows kept in stored order (seeretag_element()for the map’s own rules).Renaming a tag to
""drops its rows rather than storing an empty name: a side-tag table is a named subset, so leaving it is leaving the table. That is how a boundary name that has stopped meaning anything is retired.