fix(understand): what the field test found — P&ID lengths need no equipment, caret notation, numbered rooms
Run on a real P&ID pair (kept outside the repository), the pipe takeoff wrote
an empty table. Three causes, each fixed with a test that failed first:
- Measured on the P&ID, a run still needed equipment tags at both ends,
which only routing on the layout needs. Few pipes on a field P&ID end on
a tagged outline. On the P&ID each piece now goes to the room it lies in:
cut at the room outlines when the P&ID has room faces, else to the
nearest room label. Diameters are kept piece by piece. On the layout a
takeoff that could not route every run now says how many, why, the drawn
length left to the control rows, and that length_source='pid' with
force=True measures on the P&ID instead.
- A TEXT stores its line breaks in caret notation ('6^JROOM'), which
plain() left as it was, so a room label's number read as '6^J...'.
plain() now decodes ^J, ^M, ^I and '^ ', leaving MTEXT stacks
('\S1^2;') alone.
- Labels that only mention a room were read as rooms: a control cabinet
naming the room it serves, or a note pointing into a room. An unnumbered
label that names equipment is no room. Without room faces, when the
drawing numbers its rooms, only numbered labels take pieces.
On the field pair the takeoff now fills its table, and every room key is a
real room number. The comparison with the hand-made workbook stays with
the user's files. README collected tests: 4981. The suite passes (4980, 1
xfailed).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
307ccc799f
commit
ac32700ba8
@@ -240,6 +240,71 @@ def test_a_run_with_a_free_end_is_c(pair):
|
||||
assert reasons == {"end_not_on_equipment"}
|
||||
|
||||
|
||||
def test_a_layout_takeoff_says_how_much_it_could_not_route(pair):
|
||||
# the free-ended run is not routed: the warning names the count, the drawn
|
||||
# length it leaves to the control rows and the P&ID alternative
|
||||
pid, layout, _network = pair
|
||||
records = pid_records() + [rec("L3", "LINE", PIPE, ((0.0, 600.0), (0.0, 1600.0)))]
|
||||
drawing = snap(records, 4, "pid.dxf")
|
||||
result = pipe_rows(
|
||||
build_network(drawing, layers=[PIPE]), drawing, layout, length_source="layout"
|
||||
)
|
||||
(warning,) = [w for w in result["warnings"] if "could not be routed" in w]
|
||||
assert warning.startswith("1 of 3 runs could not be routed on the layout")
|
||||
assert "1.0 m drawn on the P&ID" in warning # 1000 mm
|
||||
assert "length_source='pid'" in warning
|
||||
|
||||
|
||||
def test_pid_lengths_need_no_equipment_at_the_ends():
|
||||
# A product line that ends in the open - no tag, no equipment - still has a
|
||||
# drawn length: measured on the P&ID it is a row, and each piece goes to the
|
||||
# P&ID room it lies in (the wall at x = 4000 cuts the second line).
|
||||
walls = [
|
||||
rec(
|
||||
"W",
|
||||
"LWPOLYLINE",
|
||||
WALLS,
|
||||
((0, 0), (8000, 0), (8000, 4000), (0, 4000)),
|
||||
closed=True,
|
||||
bbox=(0.0, 0.0, 8000.0, 4000.0),
|
||||
),
|
||||
rec("X", "LINE", WALLS, ((4000, 0), (4000, 4000))),
|
||||
text("R1", "ROOM 101", (1000.0, 3000.0), 250.0),
|
||||
text("R2", "ROOM 102", (5000.0, 3000.0), 250.0),
|
||||
]
|
||||
pipes = [
|
||||
rec("p1", "LINE", PIPE, ((1000.0, 1000.0), (3000.0, 1000.0))), # 2000, room 101
|
||||
rec("p2", "LINE", PIPE, ((3000.0, 1000.0), (6000.0, 1000.0))), # 1000 + 2000
|
||||
text("d", "Ø51", (2000.0, 1100.0), 50.0),
|
||||
]
|
||||
drawing = snap(walls + pipes, 4, "pid.dxf")
|
||||
result = pipe_rows(build_network(drawing, layers=[PIPE]), drawing, None, services=["product"])
|
||||
rows = {(row["room"], row["diameter"]): row for row in result["rows"]}
|
||||
assert rows[(room_key("ROOM 101"), "Ø51")]["net_m"] == pytest.approx(3.0, abs=EPS)
|
||||
assert rows[(room_key("ROOM 102"), "Ø51")]["net_m"] == pytest.approx(2.0, abs=EPS)
|
||||
assert result["totals"]["unroutable_m"] == 0.0
|
||||
(run,) = result["runs"]
|
||||
assert run["tags"] == [] and run["status"] in ("A", "B")
|
||||
|
||||
|
||||
def test_without_room_faces_a_piece_goes_to_the_nearest_numbered_room():
|
||||
# No walls: pieces go to the nearest room label. 'TO ROOM STORAGE' is a note
|
||||
# pointing at a room, not a room: when the drawing numbers its rooms, only
|
||||
# the numbered labels are candidates, so the line at x 8500-9500 is room 102.
|
||||
records = [
|
||||
text("R1", "ROOM 101", (0.0, 3000.0), 250.0),
|
||||
text("R2", "ROOM 102", (12000.0, 3000.0), 250.0),
|
||||
text("N", "TO ROOM STORAGE", (9000.0, 1200.0), 250.0),
|
||||
rec("p", "LINE", PIPE, ((8500.0, 1000.0), (9500.0, 1000.0))),
|
||||
text("d", "Ø51", (9000.0, 1100.0), 50.0),
|
||||
]
|
||||
drawing = snap(records, 4, "pid.dxf")
|
||||
result = pipe_rows(build_network(drawing, layers=[PIPE]), drawing, None, services=["product"])
|
||||
assert [(row["room"], row["net_m"]) for row in result["rows"]] == [
|
||||
(room_key("ROOM 102"), pytest.approx(1.0, abs=EPS))
|
||||
]
|
||||
|
||||
|
||||
def test_other_services_are_left_out_unless_asked(pair):
|
||||
pid, layout, network = pair
|
||||
assert DEFAULT_SERVICES == ("product", "cip_supply", "cip_return")
|
||||
|
||||
Reference in New Issue
Block a user