Geometry engine#

papermodels.geometry.joist_array#

Station engine for joist arrays.

A joist array is modelled in the prototype’s own frame instead of by projecting rays and re-testing the result:

  • u is the unit vector along the joist, n the unit normal (the array

    axis). A station s is a position along n; t is a position along u. The joist line at station s is origin + s*n + t*u.

  • The frame is oriented once, by a dominant-axis rule on n (n points

    +x when ``n_x > n_y``, else +y) with u = rotate_cw(n). Inside the engine, points are ordered only by their t / s projections — never by the positive-x bias (order_nodes_positive), whose orientation flips at vertical where 1e-3 grid snapping produces exact x-ties. End “A” of a joist is always its smaller-t end.

  • Every support is a straight line (a beam, or a wall’s centerline). A support

    is present at station s if it crosses the joist line there inside the array region R. Projecting the endpoints of each support’s pieces inside R onto n gives breakpoints; between two consecutive breakpoints the set of supports present is constant, so each interval is classified once.

  • Target stations that fall where supports are missing are moved to the

    nearest valid station (a bisect over the breakpoints, then an outward scan limited to the joist’s own window) — see resolve_stations().

  • Crossings are computed once, by interpolation along the support line, and

    snapped through a shared NodeRegistry; the joist’s support end-points are those canonical nodes. Nothing is re-intersected afterwards.

  • Trib areas are bands between midpoints of adjacent final stations, clipped to

    R, so they tile R with no gaps or overlaps.

Only frame-based arithmetic and rotate_90_vector (characterized in tests/test_helper_characterization.py) are used from the older helpers.

class papermodels.geometry.joist_array.ArrayFrame(origin, u, n)[source]#

Bases: object

The local frame of a joist array.

Parameters:
  • origin (ndarray) – The midpoint of the joist prototype.

  • u (ndarray) – Unit vector along the joist.

  • n (ndarray) – Unit normal to the joist (the array axis), oriented by the dominant-axis rule described in the module docstring.

A point at joist position ‘t’ and station ‘s’ is origin + t*u + s*n.

origin: ndarray#
u: ndarray#
n: ndarray#
classmethod from_prototype(prototype)[source]#

Returns the ArrayFrame for a two-point joist ‘prototype’ line. The result is the same whichever end the prototype was drawn from.

Raises GeometryError if the prototype has zero length.

Parameters:

prototype (LineString)

Return type:

ArrayFrame

ts(xy)[source]#

(t, s) of a point.

Return type:

tuple[float, float]

xy(t, s)[source]#

Returns the global (x, y) coordinates of frame position (‘t’, ‘s’).

Parameters:
Return type:

tuple[float, float]

line(s, t0, t1)[source]#

Returns the joist line at station ‘s’ running from ‘t0’ to ‘t1’.

Parameters:
Return type:

LineString

band(s0, s1, t0, t1)[source]#

Returns the rectangle covering stations ‘s0’ to ‘s1’ and joist positions ‘t0’ to ‘t1’.

Parameters:
Return type:

Polygon

s_range(geom)[source]#

Returns the (min, max) station covered by the coordinates of ‘geom’.

Parameters:

geom (BaseGeometry)

Return type:

tuple[float, float]

t_range(geom)[source]#

Returns the (min, max) joist position covered by the coordinates of ‘geom’.

Parameters:

geom (BaseGeometry)

Return type:

tuple[float, float]

papermodels.geometry.joist_array.extent_region(frame, t_range, extent_line, s_include=0.0)[source]#

The extent-line array region: the parallelogram (a rectangle in the frame) swept by t_range over the extent line’s station range. Replaces the axis-aligned bounding box the former create_extent_polygon used.

s_include (default: the prototype’s own station, 0) is always included.

Parameters:
Return type:

Polygon

papermodels.geometry.joist_array.prototype_extent_region(prototype, extent_line)[source]#

The region a joist prototype sweeps along its extent line: the prototype’s t-range over the extent line’s station range, in the prototype’s frame. Replaces the former geom_ops.create_extent_polygon (an axis-aligned bounding box, wrong for any tilted prototype).

Parameters:
Return type:

Polygon

papermodels.geometry.joist_array.fixed_region(frame, support_a, support_b, s_range, cant_a, cant_b, pad=1e-06)[source]#

The plain-prototype array region: the quadrilateral bounded by the outer supports support_a (smaller t) and support_b over s_range, extended outward along u by the (non-negative) cantilevers. Handles non-parallel supports (e.g. a triangular roof layout) without special cases.

pad widens it along u so that, with zero cantilevers, the outer supports are strictly inside rather than on the region’s edges (clipping a line against an edge it is collinear with is float-fragile). s_range must not extend past a point where the supports cross (see converging_s_range()).

Parameters:
Return type:

Polygon

papermodels.geometry.joist_array.converging_s_range(frame, support_a, support_b, s_range, s_prototype)[source]#

Clip s_range where the outer supports meet (e.g. the apex of a triangular roof area), keeping the prototype’s side. Returns (s_range, converges_at_start, converges_at_end); a joist at a converging end would have zero backspan, so none is placed there.

Parameters:
Return type:

tuple[tuple[float, float], bool, bool]

papermodels.geometry.joist_array.clip_support_to_region(support_line, region, registry=None)[source]#

The part of a support line (a beam, or a wall’s full-polygon centerline) inside region. Replaces the extent branch of geom_ops.get_intersection, which took the oriented-bounding-box centerline of the clipped wall piece and could turn perpendicular when the clip was narrower than the wall.

Endpoints are snapped through registry if given. Returns None when the support does not enter the region.

Parameters:
Return type:

BaseGeometry | None

papermodels.geometry.joist_array.support_rectangularity(polygon)[source]#

Area of polygon over the area of its oriented bounding box (1.0 = rectangle).

Parameters:

polygon (Polygon)

Return type:

float

class papermodels.geometry.joist_array.Support(gid, line, source=None)[source]#

Bases: object

A straight support line that joists in an array can bear on.

Parameters:
  • gid (str) – The tag of the supporting element.

  • line (LineString) – The two-point support line (a beam line, or a wall centerline).

  • source (BaseGeometry | None) – The support as drawn (e.g. the wall polygon). Presence in an array region is judged on this geometry. Defaults to ‘line’.

gid: str#
line: LineString#
source: BaseGeometry | None = None#
property geometry: BaseGeometry#

‘source’ if given, else ‘line’.

Type:

The geometry used for presence tests

papermodels.geometry.joist_array.prototype_cantilevers(frame, prototype, supports, tolerance=0.0)[source]#

Signed cantilevers (cant_a, cant_b) of the prototype past its outer supports, measured in the frame: A at the smaller-t end. A cantilever below tolerance — including a negative one, where the prototype was drawn to a wall face short of the centerline — is 0, so joists always reach their outer supports.

Parameters:
Return type:

tuple[float, float]

papermodels.geometry.joist_array.outer_supports(frame, prototype, supports)[source]#

The prototype’s supports at its smaller-t (A) and larger-t (B) ends.

Parameters:
Return type:

tuple[Support, Support]

papermodels.geometry.joist_array.common_s_range(frame, supports)[source]#

Station range over which every support exists (None if they never overlap).

Parameters:
Return type:

tuple[float, float] | None

class papermodels.geometry.joist_array.Interval(s0, s1, present)[source]#

Bases: object

A range of stations, ‘s0’ to ‘s1’, over which the set of supports present is constant.

Parameters:
  • present (tuple[str, ...]) – The tags of the supports present, ordered by joist position ‘t’ at the interval’s midpoint.

  • s0 (float)

  • s1 (float)

s0: float#
s1: float#
present: tuple[str, ...]#
property count: int#

The number of supports present in this interval.

papermodels.geometry.joist_array.usable_supports(frame, supports)[source]#

Supports that can bear joists: straight 2-point lines not parallel to u.

Parameters:
Return type:

list[Support]

papermodels.geometry.joist_array.support_spans(frame, supports, region)[source]#

Per support, the station ranges over which it lies inside region: the stations of its drawn geometry inside the region, bounded by the stations of its line (so crossings never extrapolate past the centerline’s ends).

Parameters:
Return type:

dict[str, list[tuple[float, float]]]

papermodels.geometry.joist_array.region_support_crossing(frame, region, support, registry=None)[source]#

Where an array spanning region lands on support: the part of the support’s line over the stations where the support is in the region, with its end nodes snapped through registry. None if the support is not in the region. Used by the graph build (the parent element’s intersection) so it agrees exactly with the spans the joist array uses.

Parameters:
Return type:

LineString | None

papermodels.geometry.joist_array.classify_intervals(frame, supports, region, s_range)[source]#

Partition s_range at every support-span endpoint; each interval carries the supports present in it, ordered by t.

Parameters:
Return type:

list[Interval]

papermodels.geometry.joist_array.interval_at(intervals, s)[source]#

Index of the interval containing s (boundary stations use the inner side).

Parameters:
Return type:

int

papermodels.geometry.joist_array.target_stations(s_min, s_max, spacing, initial_offset=0.0, joist_at_start=True, joist_at_end=True)[source]#

Nominal joist stations over [s_min, s_max]. Joists are laid at spacing from the first station; no gap exceeds spacing (the closing gap to an end joist may be shorter). Replaces the former geom_ops.get_joist_locations, which used a line-to-line distance, ignored joist_at_end and let the last gap reach 1.5x spacing.

Parameters:
Return type:

list[float]

class papermodels.geometry.joist_array.StationEvent(kind, target, final=None, detail='')[source]#

Bases: object

A record of a target station that could not be used as is.

Parameters:
  • kind (str) – “relocated” (moved to the nearest valid station), “dropped” (no valid station was found) or “span_jump” (the set of supports changed).

  • target (float) – The original target station.

  • final (float | None) – The station actually used, if any.

  • detail (str) – A human-readable explanation.

kind: str#
target: float#
final: float | None = None#
detail: str = ''#
papermodels.geometry.joist_array.resolve_stations(targets, intervals, spacing, min_bearing)[source]#

Returns ([(target, final_station)], events). Invalid targets move to the nearest valid station within their window (half a spacing either side, and strictly between the previous final station and the next target); targets with no valid station in the window are dropped.

Parameters:
Return type:

tuple[list[tuple[float, float]], list[StationEvent]]

class papermodels.geometry.joist_array.Crossing(gid, node_id, xy, t)[source]#

Bases: object

The point where a joist meets one of its supports.

Parameters:
  • gid (str) – The tag of the support.

  • node_id (int | None) – The canonical node id in the shared NodeRegistry, if registered.

  • xy (tuple[float, float]) – The global coordinates of the crossing.

  • t (float) – The joist position of the crossing in the array frame.

gid: str#
node_id: int | None#
xy: tuple[float, float]#
t: float#
class papermodels.geometry.joist_array.GeneratedJoist(target, station, geometry, crossings, trib_area=None)[source]#

Bases: object

One joist produced by a joist array.

Parameters:
  • target (float) – The station the joist was requested at by the spacing.

  • station (float) – The station it was actually placed at (differs from ‘target’ when relocated).

  • geometry (LineString) – The joist line.

  • crossings (list[Crossing]) – The joist’s support crossings, ordered by ‘t’ (end A first).

  • trib_area (BaseGeometry | None) – The joist’s tributary area: the band between the midpoints to its neighbouring joists, clipped to the array region.

target: float#
station: float#
geometry: LineString#
crossings: list[Crossing]#
trib_area: BaseGeometry | None = None#
papermodels.geometry.joist_array.joist_at_station(frame, s, present, supports, mode, region, cant_a=0.0, cant_b=0.0, registry=None)[source]#

The joist at station s and its canonical support crossings.

  • MODE_FIXED: from end A’s support crossing minus cant_a to end B’s plus

    cant_b (constant cantilevers).

  • MODE_CONTAINER: the joist is the container cut along the station line;

    the ends are the container edges (extended to the outer supports if the container stops short of them), so cantilevers and backspans both vary.

Parameters:
Return type:

tuple[LineString, list[Crossing]] | None

papermodels.geometry.joist_array.trib_bands(frame, stations, region, s_range)[source]#

Trib area per station: the band between the midpoints to its neighbours (the array’s outer station limits at the ends), clipped to region. Neighbouring bands share one boundary value, so the bands tile the region.

Parameters:
Return type:

list[BaseGeometry]

class papermodels.geometry.joist_array.ArrayResult(frame, region, s_range, intervals, joists, events=<factory>)[source]#

Bases: object

The full result of generating a joist array.

Parameters:
  • frame (ArrayFrame) – The ArrayFrame of the array.

  • region (BaseGeometry) – The array region R.

  • s_range (tuple[float, float]) – The (min, max) station of the region.

  • intervals (list[Interval]) – The support-presence intervals across the region.

  • joists (list[GeneratedJoist]) – The generated joists, in station order.

  • events (list[StationEvent]) – StationEvents for any relocated or dropped stations.

frame: ArrayFrame#
region: BaseGeometry#
s_range: tuple[float, float]#
intervals: list[Interval]#
joists: list[GeneratedJoist]#
events: list[StationEvent]#
papermodels.geometry.joist_array.build_array(frame, region, supports, s_range, mode, spacing, initial_offset=0.0, joist_at_start=True, joist_at_end=True, cant_a=0.0, cant_b=0.0, min_bearing=0.001, span_jump_tol=None, registry=None)[source]#

Lays out, resolves and builds every joist of one array (see module docstring).

Parameters:
Return type:

ArrayResult

papermodels.geometry.joist_array.span_jumps(frame, joists, supports, tol)[source]#

Flags abrupt changes in the outer supports between consecutive joists. A jump can only happen where an outer support changes identity; the new support’s crossing is compared with the old support’s line extended to the same station, so a single angled support never triggers it.

Parameters:
Return type:

list[StationEvent]

papermodels.geometry.noding#

Phase 2 — tolerance extend/trim noding (design §7).

A preprocessing step that repairs “approximately intersecting” geometry so the arrangement is topologically clean before the STRtree / node-registry build (design §6, step 2). It is governed by noding_abs_tol, an absolute tolerance in real-world units that is deliberately separate from the node canonicalization tolerance (decided, design §9): endpoint repair may need to reach wider than crossing-merging.

The operation, per line endpoint:

  • Query the spatial index for geometry within noding_abs_tol.

  • If the nearest point on a candidate is within noding_abs_tol (and the

    endpoint is not already touching something), relocate the endpoint onto that nearest point — extending the line if it fell short, trimming it if it overshot. Both are the same move: the endpoint goes to the nearest point on the candidate, a correction bounded by noding_abs_tol.

Absolute (not relative) tolerance is deliberate: a relative tolerance would make endpoint behaviour depend on line length, reintroducing scale-dependence.

Cascading snaps. Moving endpoint A onto line L can pull it off line M it previously touched, so a single sweep is not guaranteed to converge (design §7). Each pass works from a frozen snapshot and only moves endpoints that are not already coincident with some geometry; the passes iterate to a fixed point. If the pass cap is reached first, a warning is emitted and the best-so-far arrangement is returned — the cap + warning decision (§9), not a hard error.

Only LineString endpoints move. Polygons (columns) and any fixed_ids (e.g. wall centerlines, a derived spine we do not want dragged around) are valid snap targets but are never themselves moved.

class papermodels.geometry.noding.NodingReport(passes, converged, moves)[source]#

Bases: object

Outcome of a noding run.

Parameters:
passes: int#
converged: bool#
moves: int#
papermodels.geometry.noding.node_geometries(geometries, noding_abs_tol, max_passes=10, fixed_ids=None, suppress_warnings=False)[source]#

Return (repaired_geometries, report).

geometries is mapped GeomId -> geometry (walls already reduced to their centerline, matching the model’s index representation). Line endpoints are extended/trimmed onto nearby geometry within noding_abs_tol.

fixed_ids geometries are never moved but remain snap targets.

Parameters:
  • geometries (dict[str, BaseGeometry])

  • noding_abs_tol (float)

  • max_passes (int)

  • fixed_ids (set[str] | None)

  • suppress_warnings (bool)

Return type:

tuple[dict[str, BaseGeometry], NodingReport]

papermodels.geometry.load_projection#

Robust projection of loading areas onto a member.

load_distribution.get_distributed_loads_from_projected_polygons rotates the member and its loading polygons to horizontal and then derives singularity functions from the polygon edges. After an arbitrary rotation, an edge that is geometrically vertical keeps a ~1e-17 x-offset between its endpoints, which the singularity step turns into a ramp of slope ~1e16; the reconstructed load then collapses to zero length (e.g. a rectangular trib band about a joist at 30 deg).

This wrapper performs the same rotation itself (identical convention: rotate about the member’s positive-x start node, which lands at x = 0), snaps the rotated polygons to SNAP_GRID so vertical edges are exactly vertical, and hands load_distribution an exactly-horizontal member so its own rotation is the identity. Output is unchanged wherever the library was already correct.

This is a local guard; the underlying fix belongs in load_distribution.

papermodels.geometry.load_projection.project_loading_areas(member, applied_loading_areas)[source]#

Drop-in replacement for ld.get_distributed_loads_from_projected_polygons(member, applied_loading_areas).

Parameters:
  • applied_loading_areas (list) – list of (Polygon, LoadingGeometry-or-None) pairs.

  • member (LineString)

Return type:

list

papermodels.geometry.geom_ops#

Low-level geometry operations on shapely geometries used throughout papermodels.

papermodels.geometry.geom_ops.get_intersection(above, below, below_tag, above_extent_polygon=None)[source]#

Returns the details of the intersection

Parameters:
Return type:

tuple[Point | LineString, LineString | Polygon, str, LineString | Polygon | None] | None

papermodels.geometry.geom_ops.check_corresponds(above, below)[source]#

Returns the ratio of overlap between geometry above and the geometry below.

A return value of 1.0 represents full correspondence with above and below A return value of 0.0 indicates no correspondence with above and below A return value in between represents an off-set between the two

If ‘above’ and ‘below’ are Polygons: the ratio represents (above & below).area / below.area. If ‘above’ and ‘below’ are LineString: the ratio represents (above & below).length / below.length. If ‘above’ is a Polygon and ‘below’ is a LineString: the ratio represents the 1.0 - distance(above.centroid, below)

In all cases, a return value of 1.0 represents a “full bearing ratio” (100% of the area of the element corresponds with the element on the plane below). This ratio represents the accuracy of the alignment of the sketch from plane to plane and does not necessarily represent the bearing area at a connection. For example, a ratio of 1.0 between two polygons representing columns may indicate that there is no “slope” in the column and that the bottom of the column has been sketched so that it is directly under the top of the column.

Parameters:
Return type:

float

papermodels.geometry.geom_ops.get_local_intersection_ordinates(start_node, intersections)[source]#

Returns the relative distances of the Points in ‘intersections’ relative to the ‘start_node’.

Parameters:
Return type:

list[float]

papermodels.geometry.geom_ops.get_linestring_start_node(ls)[source]#

Returns a Point representing the starting node of the ‘ls’ LineString when the nodes are ordered with a +ve X bias.

Parameters:

ls (LineString)

Return type:

Point

papermodels.geometry.geom_ops.clean_polygon_supports(support_geoms, joist_prototype, extent_polygon=None)[source]#

Converts any Polygon in support_geoms into LineStrings. The LineStrings are created depending on where the joist prototype lands within the polygon.

Assumption: the Polygon represents a single rectangle which represents a wall or something similar.

The resulting LineString will either be located on the inside face of the rectangle support or along the centerline.

Generating the centerline assumes that the Polygon is a rectangle. Results will be unpredictable for Polygons of other shapes.

Parameters:
papermodels.geometry.geom_ops.get_projected_support_centroid(frame_element_geometry, polygon_point_support)[source]#

Returns a point that represents the centroid of the polygon point support projected onto the vector of the frame_element_geometry. The purpose of this function is to effectively “snap” the frame_element_geometry to the centroids of the its polygon point supports.

This effect is desireable when papermodels is used to create “design spans” where the frame elements are intended to span from center-of-support to center-of-support.

Parameters:
Return type:

Point

papermodels.geometry.geom_ops.get_projected_support_centerline(frame_element_geometry, polygon_linear_support)[source]#

Returns a point that represents the either the direct intersection of the frame_element_geometry with the centerline or, if no intersection is present, the projection of the nearest end coordinate to the centerline onto the center line. The purpose of this function is to effectively “snap” the frame_element_geometry to the centroids of the its polygon linear supports.

This effect is desireable when papermodels is used to create “design spans” where the frame elements are intended to span from center-of-support to center-of-support.

Parameters:
Return type:

Point

papermodels.geometry.geom_ops.get_joist_extents(joist_prototype, joist_supports, trib_area=None, extent_polygon=None, eps=1e-06)[source]#

Returns the extents for the supports “A” and “B”. Each extent is represented by a tuple of Point objects which represent the “i” (start) and “j” (end) locations on the supports given in ‘joist_supports’ which support the ‘joist_prototype’.

‘joist_supports’ is an ORDERED list of LineString where each LineString only has one line segment

(the relevant line segment which provides the support to ‘joist_prototype’)

‘trib_area’ if passed, the intersection of the trib area and the support geoms

will be used to determine the extent locations.

‘extent_polygon’, an optional parameter which changes the behaviour of this function

for situations when the extents of the total extent polygon are required, regardless of the support locations within it.

‘eps’ is a small tolerance amount to deal with floating point error in the extent

calcualtion.

Parameters:
Return type:

list[tuple[Point, Point]]

papermodels.geometry.geom_ops.intersecting_support_extents(joist_prototype, supports)[source]#

Returns a joist extent taking into account the self-intersection of the supports.

Parameters:
Return type:

list[tuple[LineString, LineString]]

papermodels.geometry.geom_ops.get_cantilever_segments(joist_prototype, ordered_supports, rel_tol=0.05, abs_tol=None)[source]#

Returns a dictionary containing the cantilever lengths over-hanging supports “A” and “B”, respectively. Returns a length of 0.0 if the length is less than the tolerance.

If ‘abs_tol’ is given, then ‘rel_tol’ is ignored

Parameters:
Return type:

dict[str, float]

papermodels.geometry.geom_ops.find_extent_intersections(element_geoms, extent_geoms)[source]#

Returns a list of LineString representing the extent geometries put into the order of the element geometries. If an extent geometry intersects with an element_geometry, the resulting list will have the extent geometry in the same corresponding list position that the element geometry is in. If there is no such intersection, then that list position will be None.

Parameters:
Return type:

list[LineString | None]

papermodels.geometry.geom_ops.get_system_bounds(joist_prototype, joist_supports, normal=True, extent_polygon=None)[source]#

Returns the minx, miny, maxx, maxy bounding box of all the LineStrings in ‘joist_supports’, taken as a group.

Parameters:
Return type:

tuple[float, float, float, float]

papermodels.geometry.geom_ops.get_magnitude(bounds)[source]#

Returns the distance of the “min” and “max” coordinates described in ‘bounds’

Parameters:

bounds (tuple[float, float, float, float]) – represents the minx, miny, maxx, maxy values of the “min” and “max” coordinates

Return type:

float

papermodels.geometry.geom_ops.get_direction_vector(ls)[source]#

Returns a numpy array representing the normalized +ve direction vector of the LineString ‘ls’.

Parameters:

ls (LineString) – A LineString with two or more points. If there are more than two points, it is assumed that all points are co-linear.

Return type:

ndarray

papermodels.geometry.geom_ops.sort_supports(joist_prototype, supports)[source]#

Returns a list of the supports arranged so that the vector of the joist spanning between them is going to be in the +ve direction (positive X bias). See the docstring for get_start_end_nodes for more explanation of the +ve vector direction.

Parameters:
Return type:

dict[str, LineString]

papermodels.geometry.geom_ops.get_start_end_nodes(ls)[source]#

Returns the “i” and “j” nodes for the coordinates comprising ‘ls’ in such a way that it produces a +ve vector when j_node - i_node is performed. See docstring for order_nodes_positive for more information about the +ve vector.

Parameters:

ls (LineString) – A LineString whose points are assumed to be co-linear if there are more than two.

Return type:

tuple[Point, Point]

papermodels.geometry.geom_ops.order_nodes_positive(points)[source]#

Returns the ‘i_node’ and ‘j_node’ in the order of “A” and “B” node where “A” and “B” node generate a +ve vector when B - A.

A +ve vector is a vector that has an angle, theta, when measured from horizontal, with the following range: -pi / 2 < theta <= pi/2. This can also be thought of as a vector with a “positive x bias” because such a vector will never point in the -ve x direction.

Parameters:

points (list[Point])

Return type:

tuple[Point]

papermodels.geometry.geom_ops.project_node(node, vector, magnitude)[source]#

Returns a Point representing ‘node’ projected along ‘vector’ for a distance of ‘magnitude’.

Parameters:
  • node (Point) – a point in 2D or 3D space

  • vector (ndarray) – a normalized vector in 2D or 3D space

  • magnitude (float) – the distance along ‘vector’ that ‘node’ should be projected

papermodels.geometry.geom_ops.scale_vertices(vertices, scale, paper_origin=None, round_precision=4)[source]#

Scale the vertices in relation to the origin or in relation to ‘paper_origin’.

If ‘paper_origin’ is provided, then the annotation coordinates will have their origin reset to ‘paper_origin’. Note that ‘paper_origin’ is the unscaled coordinate space (i.e. in points)

Parameters:
Return type:

tuple[Decimal | float]

papermodels.geometry.geom_ops.vertices_to_array(vertices)[source]#

Returns a numpy array representing ‘vertices’ but reshaped to (n, 2)

Parameters:

vertices (list[Decimal])

Return type:

Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | complex | bytes | str | _NestedSequence[complex | bytes | str]

papermodels.geometry.geom_ops.flatten_vertex_array(v, precision=6)[source]#

Returns a flattened version of ‘v’ in the format of (x1, y1, x2, y2, x3, y3, …, xn, yn) where ‘v’ is either a row or column-based vector of shape (2, n) or (n, 2) rounded to ‘precision’.

The returned array is in the format of a tuple of Decimal objects for use in pdf annotations

Parameters:

v (Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | complex | bytes | str | _NestedSequence[complex | bytes | str])

Return type:

tuple[Decimal]

papermodels.geometry.geom_ops.rotate_90_vector(v, precision=6, ccw=True)[source]#

Rotate the vector components, ‘x1’ and ‘y1’ by 90 degrees.

Parameters:
  • precision (int) – round result to this many decimal places

  • ccw – if True, rotate counter-clockwise (clockwise, otherwise)

  • v (Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | complex | bytes | str | _NestedSequence[complex | bytes | str])

Return type:

tuple[float, float]

papermodels.geometry.geom_ops.rotate_90_coords(v, precision=6, ccw=True)[source]#

Rotate the vector components, ‘x1’ and ‘y1’ by 90 degrees.

Parameters:
  • precision (int) – round result to this many decimal places

  • ccw – if True, rotate counter-clockwise (clockwise, otherwise)

  • v (Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | complex | bytes | str | _NestedSequence[complex | bytes | str])

Return type:

tuple[float, float]

papermodels.geometry.geom_ops.check_2d_linestring_parallel(ls1, ls2, tol=1e-06)[source]#

Returns True if ls1 and ls2 are parallel within an absolute tolerance

Parameters:
Return type:

bool

papermodels.geometry.geom_ops.rotate_to_horizontal(line, geoms)[source]#

Rotate the line so that it is horizonatla. Bring the geomswiith it

Parameters:
papermodels.geometry.geom_ops.explode_polygon(p)[source]#

Explodes the exterior of the polygon in to a list of individual line segments

Parameters:

p (Polygon)

Return type:

list[LineString]

papermodels.geometry.geom_ops.get_rectangle_centerline(p, on_long_edge=False)[source]#

Returns the centerline of the Polygon ‘p’, which is assumed to represent a rectangle with a long dimension and a short dimension.

By default (on_long_edge=False) the result is the long-axis spine — the line connecting the midpoints of the two short end faces. With on_long_edge=True it is the short-axis line connecting the midpoints of the two long edges instead.

The centerline is derived from the polygon’s oriented bounding box (OBB, the minimum-area rotated rectangle) rather than from the polygon’s own edges. This makes the result correct at any orientation and robust to vertex noise, extra vertices, and clipped corners — an approximately rectangular input yields the same spine as the clean rectangle would (see ‘get_wall_centerline’ and docs/node_canonicalization_design.md §5.4). The result is created with a +ve X-bias (see ‘order_nodes_positive’).

Parameters:
Return type:

LineString

papermodels.geometry.geom_ops.get_wall_centerline(wall)[source]#

Returns the long-axis centerline (spine) of a wall Polygon of arbitrary orientation.

This is the oriented-bounding-box long-axis spine — identical to get_rectangle_centerline(wall, on_long_edge=False). It is kept as a named entry point for wall handling (docs/node_canonicalization_design.md §5.4); both functions now share the same robust OBB implementation.

Parameters:

wall (Polygon)

Return type:

LineString

papermodels.geometry.geom_ops.calculate_trapezoid_area_sums(member_loads)[source]#

Returns a list of the sums of the areas of the trapezoids in ‘traps’

Parameters:

member_loads (list[list[list[tuple]]])

Return type:

list[float]

papermodels.geometry.geom_ops.trapezoid_area(h, b2, b1)[source]#

Returns the area of the trapezoid.

Parameters:
Return type:

float

papermodels.geometry.geom_ops.get_vector_angle(v1, v2)[source]#

Returns the angle between two vectors

Return type:

float

papermodels.geometry.geom_ops.cross_product_2d(v1, v2)[source]#

Returns the z-component of the cross product of 2D vectors ‘v1’ and ‘v2’.

papermodels.geometry.geom_ops.min_with_none(x, key=None)[source]#

Returns min but ignoring None

Parameters:

x (list)

papermodels.geometry.geom_ops.create_linestring(points)[source]#

Returns a LineString through ‘points’.

Parameters:

points (list[tuple])

Return type:

LineString

papermodels.geometry.geom_ops.create_multipoint(points)[source]#

Returns a MultiPoint of ‘points’.

Parameters:

points (list[tuple])

Return type:

MultiPoint

papermodels.geometry.geom_ops.create_polygon(points)[source]#

Returns a Polygon with ‘points’ as its exterior ring.

Parameters:

points (list[tuple])

Return type:

Polygon

papermodels.geometry.geom_ops.create_multipolygon(polygons)[source]#

Returns a MultiPolygon of ‘polygons’.

Parameters:

polygons (list[Polygon])

Return type:

MultiPolygon

papermodels.geometry.geom_ops.create_convex_hull(points)[source]#

Returns the convex hull of ‘points’.

Parameters:

points (list[Point])

Return type:

Polygon