drawing_tools.helpers package#

Subpackages#

Submodules#

drawing_tools.helpers.geometry module#

Generic geometry helper functions.

HORIZONTAL and VERTICAL are the orientation vocabulary orientation_from_direction() returns, and the one every caller compares against; a direction aligned with neither is reported as None rather than by a third value. Orientation types that vocabulary, so a stray string is caught by the type checker rather than at the dictionary lookup it would fail in. AXIS_ALIGNMENT_TOLERANCE is the aperture that decides it, as the sine of the largest angle still read as aligned. It is deliberately not called an angle tolerance: the balloon helpers already export a HORIZONTAL_ANGLE_TOLERANCE measured in radians, and the two must not be confused.

MINIMUM_SIMPLIFIABLE_LOOP_VERTICES is the size below which simplify_closed_polyline() hands its input back; MINIMUM_SIMPLIFIABLE_CHAIN_VERTICES is the same threshold for the open chains it cuts the loop into, and governs no public function.

distance_to_edges and find_edge_at_point() read a point against a SET OF EDGES – how far it lands from the nearest one, and which one that is. A contour, a rectangle’s four sides or a whole view’s geometry are all just such a set, so what lands on them – a leader terminator, a dimension end – is measured the same way.

edge_lies_on_line_of() reads a straight edge against the infinite LINE carrying another, whatever the distance between the two along it: alignment without contact.

find_nearest_touching_object and find_objects_beyond_box read a relation between two BOXES – touching, and which side of one the other sits on. A note or a balloon has no contour to land on: what it occupies IS its box, hence boxes here and edges above. They take any drawing object resolve_bounding_rectangle() can box, detected elements included, which is why they live here rather than in a domain module.

drawing_tools.helpers.geometry.simplify_closed_polyline(points: list[Point2D], tolerance: float) → list[Point2D]#

Draw a closed contour with fewer segments, staying within a tolerance of the original.

Douglas-Peucker: a run of vertices is replaced by a single chord as soon as none of them sits further than tolerance from it, so the result hugs the contour everywhere and keeps only its corners. Applied to the two halves of the loop cut at the vertex farthest from the first. volmdlr.ClosedPolygon2D.simplify_polygon densifies instead, and does not replace it.

A loop can simplify below three vertices — four identical points collapse to one, a run of collinear ones to two — and what is left is then no loop at all. Rather than hand back something a caller cannot close, the input is returned in that case too.

Parameters:
  • points – The loop’s vertices, in order, the first one not repeated at the end.

  • tolerance – How far the result may stray from the input.

Returns:

A new list of the kept vertices, same convention, always at least three of them; a copy of the input when it holds fewer than four or simplifies below three.

drawing_tools.helpers.geometry.points_coincide(p1: Any, p2: Any, tolerance: float = 1e-06) → bool#

Check if two points are at the same position within tolerance.

Parameters:
  • p1 – First point with x and y attributes.

  • p2 – Second point with x and y attributes.

  • tolerance – Maximum distance per axis for coincidence.

Returns:

True if both coordinates match within tolerance.

drawing_tools.helpers.geometry.segments_coincide(seg_a: Any, seg_b: Any, tolerance: float = 1e-06) → bool#

Check if two segments are coincident (same endpoints, possibly reversed).

Parameters:
  • seg_a – First segment with start and end attributes.

  • seg_b – Second segment with start and end attributes.

  • tolerance – Point coincidence tolerance.

Returns:

True if the segments share the same endpoints.

drawing_tools.helpers.geometry.is_full_circle(edge: Edge) → bool#

Check whether an edge is an arc sweeping a full turn.

Parameters:

edge – The edge to test.

Returns:

True for a closed circle (a FullArc2D, or an Arc2D of angle 2 pi).

drawing_tools.helpers.geometry.get_arc_radius(edge: Edge) → float#

Get the radius of an arc edge.

Works around Edge.get_arc_radius, which returns self.radius instead of the geometry’s and therefore raises AttributeError for EVERY arc — an upstream dessia_drawing typo. Its sibling Edge.get_arc_center is fine and is used directly.

Parameters:

edge – An arc edge (Arc2D or FullArc2D); anything else raises AttributeError.

Returns:

The radius.

drawing_tools.helpers.geometry.is_segment_inside_circle(segment: Edge, center: Point2D, radius: float) → bool#

Check whether both endpoints of a straight segment lie inside a circle.

Containment of the whole segment follows only because a disk is convex, so this holds for a LINE SEGMENT: on a curved edge, both endpoints inside says nothing about the middle.

Parameters:
  • segment – The line segment to test.

  • center – The circle center.

  • radius – The circle radius.

Returns:

True when both endpoints lie inside the circle.

drawing_tools.helpers.geometry.orientation_from_direction(direction_x: float, direction_y: float, tolerance: float = 0.09) → Literal['horizontal', 'vertical'] | None#

Tell whether a direction runs horizontally, vertically, or neither.

Works for anything that has a direction: a segment through its direction vector, a text through (cos(angle), sin(angle)). The off-axis component is compared to the tolerance scaled by the vector’s own norm, so the tolerance reads as the sine of the largest angle still accepted as aligned whatever the length of the vector passed in. A direction of zero length points nowhere and is reported as such.

Parameters:
  • direction_x – Horizontal component of the direction.

  • direction_y – Vertical component of the direction.

  • tolerance – Sine of the maximum angle to an axis for the direction to count as aligned with it.

Returns:

HORIZONTAL, VERTICAL, or None for an oblique or null direction.

drawing_tools.helpers.geometry.resolve_bounding_rectangle(candidate: Any) → BoundingRectangle | None#

Return the box of a drawing object: its frame’s box when it has one, else its own.

The fallback is what the corpus requires, not a defensive precaution: 7699 of the 8323 symbols of the condor drawings expose frame.entity_type == "NONE" and a frame.bounding_rectangle of None, while all 7699 do have a usable box of their own. Reading the frame first still matters where a frame exists – a balloon’s frame is its circle, which is what an overlay must highlight rather than the wider box of the symbol and its text.

Two predicates of the package deliberately do NOT use this helper, because what they test is the SHAPE of a frame and the fallback would make them accept frameless notes: view_balloon_detector._is_note_type_balloon needs a circular frame, and table.detection.is_symbol_with_rectangle_frame a rectangular one.

Parameters:

candidate – Any drawing object – an entity, a frame, a detected element.

Returns:

The resolved box, or None when neither the frame nor the object itself has one.

drawing_tools.helpers.geometry.nearest_scored(scored_candidates: list[tuple]) → tuple | None#

Return the entry whose first item – a distance – is the smallest, None when there is none.

An entry carries the candidate second and whatever else its caller measured after that.

Parameters:

scored_candidates – Entries opening on a distance.

Returns:

The nearest entry, or None on an empty list.

drawing_tools.helpers.geometry.distance_to_edges(edges: list[Edge], point: Point2D) → float | None#

Return how far point lands from the nearest of edges, None when there is none.

Parameters:
  • edges – The edges measured against, a contour or any other set.

  • point – The point measured.

Returns:

The smallest distance, or None on an empty list.

drawing_tools.helpers.geometry.find_edge_at_point(edges: list[Edge], point: Point2D, max_distance: float) → Edge | None#

Return the edge point lands on, None when it lands on none of them.

Which edge is what places the point on the drawing: the nearest one wins, so the answer stays deterministic where two edges meet.

Parameters:
  • edges – The edges searched, typically the contours of everything looked for at once.

  • point – The point placed.

  • max_distance – How far the point may sit from an edge, in drawing units.

Returns:

The nearest edge within that distance, or None.

drawing_tools.helpers.geometry.edge_lies_on_line_of(edge: Edge, reference_edge: Edge, max_offset: float) → bool#

Return whether the straight edge lies on the infinite line carrying the straight reference_edge.

Lying on it means parallel to it, the sine of their angle within AXIS_ALIGNMENT_TOLERANCE, with both ends within max_offset of the line. The two may overlap, touch, or lie far apart along it.

Parameters:
  • edge – The edge tested, typically a side of a detected element.

  • reference_edge – The edge whose line is read, typically an extension line.

  • max_offset – How far each end of edge may sit from that line, in drawing units.

Returns:

False when either edge is not a straight segment.

drawing_tools.helpers.geometry.find_nearest_touching_object(reference_box: BoundingRectangle, candidates: list, max_gap: float) → Any | None#

Return the object whose box comes nearest reference_box within max_gap, None when none does.

Two boxes touching is what ties a drawing object to the one naming it – a boxed note to the balloon beside it, a caption to its symbol – and nothing else does: the export carries no such link. Candidates without a resolvable box are skipped, the gap being measured between boxes.

Parameters:
  • reference_box – The box the gap is measured from.

  • candidates – Any drawing objects resolve_bounding_rectangle() can box.

  • max_gap – Largest gap still counted as touching, in drawing units.

Returns:

The nearest touching object, or None when none is within the gap.

drawing_tools.helpers.geometry.find_objects_beyond_box(reference_box: BoundingRectangle, direction: Any, candidates: list) → list#

Return the objects sitting past reference_box along direction, box centers compared.

The side is read on a direction the caller measures on the drawing itself – never on an absolute one – so a row read left to right and its mirror answer the same.

Parameters:
  • reference_box – The box the side is read from.

  • direction – Vector pointing at the side kept. Its norm is not used.

  • candidates – Any drawing objects resolve_bounding_rectangle() can box.

Returns:

The candidates whose box center sits on that side, in the order given.

Module contents#

Package initialization.