Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/papermodels/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,10 @@

from . import datatypes
from . import paper
from . import scales
from . import filters
from . import collectors

from .datatypes.geometry_graph import GeometryGraph

__version__ = "0.18.0"
1 change: 1 addition & 0 deletions src/papermodels/collectors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .datatypes.joist_models import JoistArrayModel, CollectorTribModel
24 changes: 17 additions & 7 deletions src/papermodels/datatypes/element.py
Original file line number Diff line number Diff line change
Expand Up @@ -277,17 +277,22 @@ def get_collector_extents(self, relative: bool = True) -> dict[str, tuple]:
for idx, poly_support_geom in enumerate(support_geoms):
clean_support_geom = support_geoms[idx]
cleaned_supports_map.update({clean_support_geom: poly_support_geom})
ordered_support_geoms = geom_ops.sort_supports(
self.geometry, support_geoms
)
try:
ordered_support_geoms = geom_ops.sort_supports(
self.geometry, support_geoms
)
except (AssertionError, ValueError):
raise geom_ops.GeometryError(
f"Element {self.tag} appears to have only one support intersection."
)
try:
extents = geom_ops.get_joist_extents(
self.geometry,
ordered_support_geoms,
self.trib_area,
extent_polygon=self.extent_polygon,
)
except (AssertionError, ValueError) as e:
except (geom_ops.GeometryError, AssertionError, ValueError) as e:
raise AssertionError(
f"No intersection within joist extents: {self.tag=}"
)
Expand Down Expand Up @@ -1311,9 +1316,14 @@ def trim_cantilevers(element: Element, abs_tol: Optional[float] = 0.02):
[Point(geometry.coords[0]), Point(geometry.coords[-1])]
)
)
cantilevers = geom_ops.get_cantilever_segments(
ordered_geom, support_geoms, abs_tol=abs_tol
)
try:
cantilevers = geom_ops.get_cantilever_segments(
ordered_geom, support_geoms, abs_tol=abs_tol
)
except (AssertionError, NotImplementedError, ValueError):
raise geom_ops.GeometryError(
f"Received an unexpected geometry for {element.tag} during cantilever trimming."
)
# ordered_geom = LineString(
# geom_ops.order_nodes_positive(
# [Point(geometry.coords[0]), Point(geometry.coords[-1])]
Expand Down
9 changes: 8 additions & 1 deletion src/papermodels/datatypes/geometry_graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -861,6 +861,7 @@ def from_pdf_file(
save_tagged_pdf_file: bool = False,
tag_pdf_file_mode: str = "append",
show_skipped: bool = False,
show_unimplemented: bool = False,
):
"""
Returns a GeometryGraph built from that annotations in the provided PDF file
Expand Down Expand Up @@ -893,8 +894,12 @@ def from_pdf_file(
'process_gravity_frame': Processes the geometry for a gravity frame by fully
resolving in-plane connectivity
'show_skipped': Shows the skipped annotations that occured during pdf.load_pdf_annotations
'show_unimplemented': Shows the annotations that were read but are not implemented in the
parser yet.
"""
annotations = pdf.load_pdf_annotations(pdf_filepath, show_skipped)
annotations = pdf.load_pdf_annotations(
pdf_filepath, show_skipped, show_unimplemented
)
graph = cls.from_annotations(
annotations,
legend_identifier,
Expand Down Expand Up @@ -1097,6 +1102,7 @@ def plot_elements(
plot_trib_areas: bool = False,
plot_extent_polygons: bool = False,
plot_tags: bool = False,
plot_elems_by_tag: Optional[list[str]] = None,
):
"""
Plots all elements in the graph that are on 'page_idx'
Expand All @@ -1110,6 +1116,7 @@ def plot_elements(
plot_trib_areas=plot_trib_areas,
plot_extent_polygons=plot_extent_polygons,
plot_tags=plot_tags,
plot_elems_by_tag=plot_elems_by_tag,
)

def create_loaded_elements(self) -> dict[str, LoadedElement]:
Expand Down
26 changes: 22 additions & 4 deletions src/papermodels/datatypes/joist_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -435,9 +435,14 @@ def __init__(
break
self._supports = ordered_supports
else:
self._supports = geom_ops.sort_supports(
self.joist_prototype, self.joist_supports.keys()
)
try:
self._supports = geom_ops.sort_supports(
self.joist_prototype, self.joist_supports.keys()
)
except (AssertionError, ValueError):
raise geom_ops.GeometryError(
f"Element {self.element.tag} appears to have only one support intersection:\n{self.element.intersections_below}"
)

self.joist_support_tags = [ib.other_tag for ib in element.intersections_below]
self.id = element.tag
Expand Down Expand Up @@ -676,6 +681,18 @@ def generate_joist_geom(self, index: int):
end_b, self.vector_parallel, self._cantilever_tolerance / 10
)
joist_geom = LineString([end_a, end_b])
# TODO:
# This will superficially break one of the tests. Do I want to add this in?

# if not all([joist_geom.intersects(support) for support in self._supports]):
# print(self.element.tag)
# end_a = geom_ops.project_node(
# end_a, -self.vector_parallel, self._cantilever_tolerance / 10
# )
# end_b = geom_ops.project_node(
# end_b, self.vector_parallel, self._cantilever_tolerance / 10
# )
# joist_geom = LineString([end_a, end_b])
if joist_geom.length <= self._cantilever_tolerance:
return None
return joist_geom
Expand Down Expand Up @@ -747,7 +764,8 @@ def generate_trib_area(self, index: int) -> Polygon:
)
else:
trib_area_right = Polygon()
return trib_area_left | trib_area_right
trib_area = trib_area_left | trib_area_right
return trib_area

def show_svg(self, use_ipython_display: bool = True):
"""
Expand Down
1 change: 1 addition & 0 deletions src/papermodels/filters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .datatypes.element import create_element_filter
17 changes: 14 additions & 3 deletions src/papermodels/geometry/geom_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -360,8 +360,16 @@ def get_joist_extents(
right_coords = []
support_intersection = intersection_all(joist_supports)
for joist_support in joist_supports:
joist_support = joist_support.intersection(box(*supports_bbox), grid_size=1e-3)

joist_support_trim = joist_support.intersection(
box(*supports_bbox), grid_size=1e-3
)
if not joist_support_trim.geom_type == "LineString":
raise GeometryError(
f"It seems that a support does not fully intersect with the supports bounding box.\n"
"Redraw your supports for this element to ensure they are either properly orthogonal to the element "
"or are very clearly at an angle to the element."
)
joist_support = joist_support_trim
start_coord, end_coord = joist_support.coords
start_coord, end_coord = Point(start_coord), Point(end_coord)

Expand Down Expand Up @@ -750,7 +758,8 @@ def get_system_bounds(
if overlap_poly is not None:
if extent_polygon is not None:
overlap_poly = extent_polygon.intersection(overlap_poly)
overlap_polys.append(overlap_poly)
if overlap_poly.geom_type == "Polygon":
overlap_polys.append(overlap_poly)
return MultiPolygon(overlap_polys).bounds


Expand Down Expand Up @@ -822,6 +831,8 @@ def sort_supports(
"""
all_supports = MultiLineString(supports)
joist_intersections = joist_prototype.intersection(all_supports, grid_size=1e-3)
if joist_intersections.geom_type == "Point":
joist_intersections = all_supports.intersection(joist_prototype)
assert joist_intersections.geom_type != "Point"
assert not joist_intersections.is_empty
ordered_intersections = order_nodes_positive(joist_intersections.geoms)
Expand Down
19 changes: 15 additions & 4 deletions src/papermodels/paper/pdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@


def load_pdf_annotations(
pdf_path: pathlib.Path | str, show_skipped: bool = False
pdf_path: pathlib.Path | str,
show_skipped: bool = False,
show_unimplemented: bool = False,
) -> list[Annotation]:
"""
Returns a lists of pdf annotations keyed by page index.
Expand All @@ -30,7 +32,11 @@ def load_pdf_annotations(
continue
for annot_idx, annot in enumerate(page_data.obj.Annots):
pm_annot = pike_annotation_to_pm_annotation(
annot, annot_idx, page_num, rotate
annot,
annot_idx,
page_num,
rotate,
display_unimplemented_annots=show_unimplemented,
)
if pm_annot is not None:
annots_in_pdf.append(pm_annot)
Expand Down Expand Up @@ -109,7 +115,11 @@ def compare_annotations(


def pike_annotation_to_pm_annotation(
annot, annot_idx: int, page_idx: int, rotate: Optional[int] = None
annot,
annot_idx: int,
page_idx: int,
rotate: Optional[int] = None,
display_unimplemented_annots: bool = False,
) -> Optional[Annotation]:
"""
Returns either an Annotation object or None. None is returned if:
Expand Down Expand Up @@ -159,7 +169,8 @@ def pike_annotation_to_pm_annotation(
vertices = tuple(annot.get("/L", tuple()))
annot_type = "Line"
else:
print(f"Cannot read (yet): {annot_type}")
if display_unimplemented_annots:
print(f"Cannot read (yet): {annot_type}")
return None
if rotate == 90:
vertex_array = geom_ops.vertices_to_array(vertices)
Expand Down
24 changes: 20 additions & 4 deletions src/papermodels/paper/plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ def plot_elements(
plot_trib_areas: bool = False,
plot_extent_polygons: bool = False,
plot_tags: bool = False,
plot_elems_by_tag: Optional[list[str]] = None,
) -> Figure:
"""
Plots the elements in matplotlib. Size and dpi can be adjusted
Expand Down Expand Up @@ -61,6 +62,7 @@ def plot_elements(
"extent_face": (0.8, 0.7, 0.0),
"prototype_line": "yellow",
}
highlight_tags = False
plot_objs = []
for element in elements:
plot_attrs = get_element_plotting_attributes(element, is_subelement=True)
Expand All @@ -82,9 +84,17 @@ def plot_elements(
max_extent = np.maximum(max_extent, np.max(xy, axis=1))

# For tagging
initial_positions_x.append(po["anchor_point"][0])
initial_positions_y.append(po["anchor_point"][1])
tags.append(po["tag"])

if plot_elems_by_tag is None:
tags.append(po["tag"])
initial_positions_x.append(po["anchor_point"][0])
initial_positions_y.append(po["anchor_point"][1])
else:
if po["tag"] in plot_elems_by_tag:
highlight_tags = True
tags.append(po["tag"])
initial_positions_x.append(po["anchor_point"][0])
initial_positions_y.append(po["anchor_point"][1])

if po["is_poly"]:
ax.add_patch(
Expand Down Expand Up @@ -161,6 +171,9 @@ def plot_elements(
min_extent[1] - plot_margin_metric * 0.05,
max_extent[1] + plot_margin_metric * 0.05,
)
text_color = "k"
if highlight_tags:
text_color = "r"
ta.allocate(
ax=ax,
x=initial_positions_x,
Expand All @@ -169,7 +182,7 @@ def plot_elements(
# x_lines=lines_x,
# y_lines=lines_y,
textsize=8,
textcolor="k",
textcolor=text_color,
linecolor="k",
avoid_label_lines_overlap=True,
avoid_crossing_label_lines=True,
Expand Down Expand Up @@ -246,12 +259,15 @@ def plot_annotations(
figsize: int | float | tuple[int | float, int | float] = (17, 11),
dpi: float = 100,
plot_tags: bool = False,
plot_annots_by_tag: Optional[list[str]] = None,
) -> Figure:
"""
Plots that annotations, 'annots' in matplotlib. Size and dpi can be adjusted
to make the plot bigger/smaller. Size is in inches and dpi stands for
"dots per inch". For a biggish plot, values of size=12, dpi=200 gives
good results.


"""
if isinstance(figsize, (int, float)):
figsize = (figsize, figsize)
Expand Down
89 changes: 89 additions & 0 deletions src/papermodels/scales.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
from decimal import Decimal

Decimal()


class Scale(Decimal):

def __new__(cls, factor, name):
instance = super().__new__(cls, factor)
instance.name = name
return instance

def __repr__(self):
return f"Scale: {self.name} | factor: {self}"


SCALE_1_TO_1000 = Scale(
Decimal("1") / Decimal("72") * Decimal("25.4") * Decimal("1000"), "1:1000 Scale"
)
SCALE_1_TO_500 = Scale(
Decimal("1") / Decimal("72") * Decimal("25.4") * Decimal("500"), "1:500 Scale"
)
SCALE_1_TO_200 = Scale(
Decimal("1") / Decimal("72") * Decimal("25.4") * Decimal("200"), "1:200 Scale"
)
SCALE_1_TO_100 = Scale(
Decimal("1") / Decimal("72") * Decimal("25.4") * Decimal("100"), "1:100 Scale"
)
SCALE_1_TO_50 = Scale(
Decimal("1") / Decimal("72") * Decimal("25.4") * Decimal("50"), "1:50 Scale"
)
SCALE_1_TO_20 = Scale(
Decimal("1") / Decimal("72") * Decimal("25.4") * Decimal("20"), "1:20 Scale"
)
SCALE_1_TO_10 = Scale(
Decimal("1") / Decimal("72") * Decimal("25.4") * Decimal("10"), "1:10 Scale"
)

SCALE_1_TO_1000_M = Scale(
Decimal("1") / Decimal("72") * Decimal("25.4") * Decimal("1000") / Decimal("1000"),
"1:1000 Scale, in meters",
)
SCALE_1_TO_500_M = Scale(
Decimal("1") / Decimal("72") * Decimal("25.4") * Decimal("500") / Decimal("1000"),
"1:500 Scale, in meters",
)
SCALE_1_TO_200_M = Scale(
Decimal("1") / Decimal("72") * Decimal("25.4") * Decimal("200") / Decimal("1000"),
"1:200 Scale, in meters",
)
SCALE_1_TO_100_M = Scale(
Decimal("1") / Decimal("72") * Decimal("25.4") * Decimal("100") / Decimal("1000"),
"1:100 Scale, in meters",
)
SCALE_1_TO_50_M = Scale(
Decimal("1") / Decimal("72") * Decimal("25.4") * Decimal("50") / Decimal("1000"),
"1:50 Scale, in meters",
)
SCALE_1_TO_20_M = Scale(
Decimal("1") / Decimal("72") * Decimal("25.4") * Decimal("20") / Decimal("1000"),
"1:20 Scale, in meters",
)
SCALE_1_TO_10_M = Scale(
Decimal("1") / Decimal("72") * Decimal("25.4") * Decimal("10") / Decimal("1000"),
"1:10 Scale, in meters",
)

SCALE_32ND_INCH = Scale(Decimal("1") / Decimal("72") * Decimal("32"), '1/32" = 1\'-0"')
SCALE_16TH_INCH = Scale(Decimal("1") / Decimal("72") * Decimal("16"), '1/16" = 1\'-0"')
SCALE_EIGHTH_INCH = Scale(Decimal("1") / Decimal("72") * Decimal("8"), '1/8" = 1\'-0"')
SCALE_QUARTER_INCH = Scale(Decimal("1") / Decimal("72") * Decimal("4"), '1/4" = 1\'-0"')
SCALE_HALF_INCH = Scale(Decimal("1") / Decimal("72") * Decimal("2"), '1/2" = 1\'-0"')
SCALE_ONE_INCH = Scale(Decimal("1") / Decimal("72"), '1" = 1\'-0"')

SCALE_3_32ND_INCH = Scale(
Decimal("1") / Decimal("72") * Decimal("32") / Decimal("3"), '3/32" = 1\'-0"'
)
SCALE_3_16TH_INCH = Scale(
Decimal("1") / Decimal("72") * Decimal("16") / Decimal("3"), '3/16" = 1\'-0"'
)
SCALE_3_EIGHTH_INCH = Scale(
Decimal("1") / Decimal("72") * Decimal("8") / Decimal("3"), '3/8" = 1\'-0"'
)
SCALE_3_QUARTER_INCH = Scale(
Decimal("1") / Decimal("72") * Decimal("4") / Decimal("3"), '3/4" = 1\'-0"'
)
SCALE_3_HALF_INCH = Scale(
Decimal("1") / Decimal("72") * Decimal("2") / Decimal("3"), '3/2" = 1\'-0"'
)