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
1 change: 0 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,5 @@ graphviz = [
dev = [
"black>=25.1.0",
"ipykernel>=6.30.1",
"pygraphviz>=1.14",
"pytest-check>=2.5.4",
]
21 changes: 10 additions & 11 deletions src/papermodels/datatypes/element.py
Original file line number Diff line number Diff line change
Expand Up @@ -284,16 +284,13 @@ def get_collector_extents(self, relative: bool = True) -> dict[str, tuple]:
self.trib_area,
extent_polygon=self.extent_polygon,
)
except (geom_ops.GeometryError, AssertionError, ValueError) as e:
print(
f"{GeometryCollection(ordered_support_geoms).intersection(self.geometry).wkt=}"
except (geom_ops.GeometryError, AssertionError, ValueError, TypeError):
support_intersections = f"{GeometryCollection(ordered_support_geoms).intersection(self.geometry).wkt=}"
geometry = f"{self.geometry.wkt=}"
tag = f"{self.tag=}"
raise geom_ops.GeometryError(
f"Debug information:{tag=}\n{geometry=}\n{support_intersections=}"
)
print(f"{self.geometry.wkt=}")
# raise AssertionError(
# f"No intersection within joist extents: {self.tag=}"
# )
print(f"{self.tag=}")
raise e
tagged_extents = {}
for idx, extent in enumerate(extents):
support_geom = ordered_support_geoms[idx]
Expand Down Expand Up @@ -730,7 +727,6 @@ def _get_transfer_loads(self, precision: int):
transfer_type = intersection_above.other_reaction_type
source_member = intersection_above.other_tag
reaction_idx = intersection_above.other_index
# print(transfer_type, source_member, reaction_idx)
if reaction_idx is None:
raise ValueError(
"The .other_index attribute within the .intersections_above list"
Expand Down Expand Up @@ -1374,7 +1370,10 @@ def trim_cantilevers(element: Element, abs_tol: Optional[float] = 0.02):
start_point = cantilevers["A_intersection"]
if (cantilevers["B"] == 0.0) and (cantilevers["B"] != cantilevers["B_orig"]):
end_point = cantilevers["B_intersection"]
new_geometry = LineString([start_point, end_point]) # type: ignore
try:
new_geometry = LineString([start_point, end_point]) # type: ignore
except TypeError:
raise geom_ops.GeometryError(f"{element=}")
new_element.geometry = new_geometry
intersection_checks = [
new_geometry.intersects(ib.other_geometry)
Expand Down
37 changes: 35 additions & 2 deletions src/papermodels/datatypes/geometry_graph.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations
from typing import Optional, TypeAlias, Callable
from collections import Counter
from copy import deepcopy
from decimal import Decimal
import pathlib
Expand Down Expand Up @@ -261,6 +262,11 @@ def remove_excess_correspondent_load_paths(self):
frame element transferring load to the supporting frame element. The correct load
path should be |FB0.1 -> FB0.2 -> column| instead of |FB0.1 -> column| with
|FB0.1 -> FB0.2 -> column| also.
4. A Polygon node, with "linear" reaction type, that is intersecting with LineString
elements that run perpendicular to it. This can occur if a beam is drawn to
transfer out a wall from above but that same beam has other beams framing into
it perpendicular. We do not want the wall to transfer out to these other beams
at the intersection points.

Modifications to the implementation of this function can adjust how load paths are
conceptually created. For example, to implement baloon framing, the second rule
Expand Down Expand Up @@ -382,9 +388,26 @@ def remove_excess_correspondent_load_paths(self):
element.geometry.geom_type == "LineString"
and intersection_points_in_polygon_below
):
for edge in intersection_points_in_polygon_below:
inters_below_set = set(intersection_points_in_polygon_below)
for edge in inters_below_set:
self.remove_edge(*edge)

# Rule 4
if (
element.geometry.geom_type == "Polygon"
and element.reaction_type == "linear"
and "intersection" in edge_properties
):
center_line = geom.get_rectangle_centerline(element.geometry)
for idx, dep in enumerate(dependents):
dep_geom = self.nodes[dep]["element"].geometry
if dep_geom.geom_type == "LineString":
is_roughly_parallel = geom.check_2d_linestring_parallel(
center_line, dep_geom, tol=0.01
)
if not is_roughly_parallel:
self.remove_edge(element.tag, dep)

def add_intersection_indexes_below(self):
sorted_nodes = nx.topological_sort(self)
orphaned_nodes = self.orphaned_elements
Expand Down Expand Up @@ -1036,6 +1059,7 @@ def from_annotations(
structural_element_entries = {}
parsed_annotations_acc = {}
raw_annotations_acc = {}
tag_checker = []
for annots_in_page in annots_by_page:
if scale is not None:
scaled_annots_in_page = scale_annotations(annots_in_page, scale)
Expand All @@ -1050,18 +1074,27 @@ def from_annotations(
parsed_annotations_acc = parsed_annotations | parsed_annotations_acc
raw_annotations_acc = raw_annotations | raw_annotations_acc
for annot, annot_attrs in parsed_annotations.items():
tag = annot_attrs["tag"]
if "occupancy" in annot_attrs:
load_entries.update({annot: annot_attrs})
elif "trib area" in annot_attrs.get("type", "").lower():
trib_area_entries.update({annot: annot_attrs})
elif "extent" in annot_attrs.get("type", "").lower():
extent_entries.update({annot: annot_attrs})
else:
tag_checker.append(tag)
structural_element_entries.update({annot: annot_attrs})
structural_element_entries = correlate_extents(
structural_element_entries, extent_entries
)

tag_counter = Counter(tag_checker)
tag_counter.pop(None) # Exclude None tags from the check
duplicate_tags = [tag for tag in tag_counter if tag_counter[tag] > 1]
if duplicate_tags:
raise ValueError(
"Geometry graph could not be built because the following"
f" duplicate tags were found: {duplicate_tags}"
)
if not structural_element_entries and not legend_entries:
raise ValueError(
"No structural element entities were found.\n"
Expand Down
15 changes: 13 additions & 2 deletions src/papermodels/datatypes/joist_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -621,6 +621,11 @@ def generate_joist_geom(self, index: int):
intersecting_supports, grid_size=1e-3
)
ordered_intersections = geom_ops.order_nodes_positive(support_locs)
if len(ordered_intersections) < 2:
return None
raise geom_ops.GeometryError(
f"Joist prototype {self.element.tag} is not intersecting correctly."
)
support_a_loc, support_b_loc = (
ordered_intersections[0],
ordered_intersections[-1],
Expand All @@ -634,12 +639,18 @@ def generate_joist_geom(self, index: int):
end_a = support_a_loc = self._extents[0][0]
end_b = support_b_loc = self._extents[-1][0]
# stand-in values for so that the variable intersecting_supports exists
intersecting_supports = [0, 1]
intersecting_supports = [
0,
1,
] # bug: These allow joists to exist beyond the edge of the support for start and end joists
elif index == len(self.joist_locations) - 1:
end_a = support_a_loc = self._extents[0][1]
end_b = support_b_loc = self._extents[-1][1]
# stand-in values for so that the variable intersecting_supports exists
intersecting_supports = [0, 1]
intersecting_supports = [
0,
1,
] # bug: These allow joists to exist beyond the edge of the support for start and end joists

cant_a = self._cantilevers["A"]
cant_b = self._cantilevers["B"]
Expand Down
4 changes: 3 additions & 1 deletion src/papermodels/geometry/geom_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -807,7 +807,9 @@ def get_joist_locations(
if initial_offset:
joist_locs.append(initial_offset)
distance_remaining -= initial_offset
while distance_remaining > spacing:
while (
distance_remaining > 1.5 * spacing
): # Use 1.5*spacing instead of 1.0*spacing to prevent "sliver joists" at the end
distance_remaining -= spacing
joist_locs.append(distance - distance_remaining)
else:
Expand Down
2 changes: 2 additions & 0 deletions src/papermodels/paper/annotations.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ def parse_annotations(
annot_attrs["geometry"] = annot_geom
annot_attrs["page_label"] = annot.page
annot_attrs["tag"] = existing_annot_tag
if annot_geom is None:
raise ValueError(f"{annot=}")
for annot_key, annot_attr in annot_attributes.items():
annot_attrs[annot_key] = str_to_int(
annot_attr.split("<")[0]
Expand Down
45 changes: 35 additions & 10 deletions src/papermodels/paper/pdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,8 +144,25 @@ def pike_annotation_to_pm_annotation(
annot_type = str(annot["/Subj"])
else:
return None
if annot_type.lower() in ("polygon", "polyline", "circle", "ellipse"):
if annot_type.lower() in ("polygon", "polyline"):
vertices = tuple(annot.get("/Vertices", tuple()))
elif annot_type.lower() in ("circle", "ellipse"):
annot_type = "Polygon"
rect = tuple(annot.get("/Rect", tuple()))
x1, y1, x2, y2 = rect
h = (x1 + x2) / 2
k = (y1 + y2) / 2
a = (x2 - x1) / 2
b = (y2 - y1) / 2
n = 16
x_vertices = float(h) + float(a) * np.cos(np.linspace(0, 2 * np.pi, n))
y_vertices = float(k) + float(b) * np.sin(np.linspace(0, 2 * np.pi, n))
vertices = []
for idx, x_vertex in enumerate(x_vertices):
vertices.append(Decimal(x_vertex))
y_vertex = y_vertices[idx]
vertices.append(Decimal(y_vertex))

elif annot_type.lower() in (
"rectangle",
"square",
Expand Down Expand Up @@ -243,21 +260,29 @@ def parse_content_stream(stream: str) -> dict[str, list]:
operators will only have one entry).
"""
commands = {}
operand_with_operator = re.compile(r"[0-9.\s]+[a-zA-Z]+")
operators = re.compile(f"[a-zA-Z]+")
operands = re.compile(r"[\d.]+")
operand_with_operator = re.compile(r"([\d+\s*|\d+\.\d+\s*]*)([A-Za-z]{1,2})\s*")
operators_pattern = re.compile(r"[a-zA-Z]+")
operands_pattern = re.compile(r"[\d.]+")
floats_pattern = re.compile(r"^\d+\.\d+$")
integers_pattern = re.compile(r"^\d+$")

matches = operand_with_operator.findall(stream)
for match in matches:
operator = operators.findall(match)[0]
operand = operands.findall(match)
# operator = operators.findall(match)[0]
# operand = operands.findall(match)
operands, operator = match
numerical_operands = []
for element in operand:
if "." in element:
operands_matches = operands_pattern.findall(operands)
for element in operands_matches:
element = element.strip()
float_match = floats_pattern.search(element)
integer_match = integers_pattern.search(element)
if float_match is not None:
elem = Decimal(element)
else:
numerical_operands.append(elem)
elif integer_match is not None:
elem = int(element)
numerical_operands.append(elem)
numerical_operands.append(elem)
commands.update({operator: numerical_operands})
return commands

Expand Down
45 changes: 45 additions & 0 deletions src/papermodels/scales.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,48 @@ def __repr__(self):
SCALE_3_HALF_INCH = Scale(
Decimal("1") / Decimal("72") * Decimal("2") / Decimal("3"), '3/2" = 1\'-0"'
)

SCALE_32ND_INCH_M = Scale(
Decimal("1") / Decimal("72") * Decimal("32") * Decimal("0.3048"),
'1/32" = 1\'-0" IN METERS',
)
SCALE_16TH_INCH_M = Scale(
Decimal("1") / Decimal("72") * Decimal("16") * Decimal("0.3048"),
'1/16" = 1\'-0" IN METERS',
)
SCALE_EIGHTH_INCH_M = Scale(
Decimal("1") / Decimal("72") * Decimal("8") * Decimal("0.3048"),
'1/8" = 1\'-0" IN METERS',
)
SCALE_QUARTER_INCH_M = Scale(
Decimal("1") / Decimal("72") * Decimal("4") * Decimal("0.3048"),
'1/4" = 1\'-0" IN METERS',
)
SCALE_HALF_INCH_M = Scale(
Decimal("1") / Decimal("72") * Decimal("2") * Decimal("0.3048"),
'1/2" = 1\'-0" IN METERS',
)
SCALE_ONE_INCH_M = Scale(
Decimal("1") / Decimal("72") * Decimal("0.3048"), '1" = 1\'-0"'
)

SCALE_3_32ND_INCH_M = Scale(
Decimal("1") / Decimal("72") * Decimal("32") / Decimal("3") * Decimal("0.3048"),
'3/32" = 1\'-0" IN METERS',
)
SCALE_3_16TH_INCH_M = Scale(
Decimal("1") / Decimal("72") * Decimal("16") / Decimal("3") * Decimal("0.3048"),
'3/16" = 1\'-0" IN METERS',
)
SCALE_3_EIGHTH_INCH_M = Scale(
Decimal("1") / Decimal("72") * Decimal("8") / Decimal("3") * Decimal("0.3048"),
'3/8" = 1\'-0" IN METERS',
)
SCALE_3_QUARTER_INCH_M = Scale(
Decimal("1") / Decimal("72") * Decimal("4") / Decimal("3") * Decimal("0.3048"),
'3/4" = 1\'-0" IN METERS',
)
SCALE_3_HALF_INCH_M = Scale(
Decimal("1") / Decimal("72") * Decimal("2") / Decimal("3") * Decimal("0.3048"),
'3/2" = 1\'-0" IN METERS',
)
Loading
Loading