Files
IfcOpenShell/src/ifcopenshell-python/ifcopenshell/api/alignment/create_from_csv.py
T
Dion Moult 19a1d88970 Fix all ty diagnostics on ifcviewer-wgpu (ci-lint ty-ios + ty-bonsai)
This branch carried v0.8.0's strict `[tool.ty.rules] all = "error"` config but
not the source fixes that were made upstream to satisfy it, so both ci-lint ty
gates were failing: `poe ty-ios` reported 256 diagnostics and `poe ty-bonsai`
258. Both are now clean.

Most fixes are ported from v0.8.0 and follow two idioms: initialise a name
before a conditional that may not bind it (plus an `assert` where the invariant
is real but not provable), and close an exhaustive `if`/`elif` chain with
`else: assert False, <discriminant>`.

The branch's own newer accessors are preserved throughout - `.file`,
`.declaration`, `file.types()`, `get_max_id()` are kept rather than reverted to
`wrapped_data.*`, and non-ty upstream changes (notably the in-progress geometry
cache removal) are deliberately not pulled in.

Notable fixes that are not straight ports:

* ifcopenshell_wrapper.pyi: `entity_instance.file` was declared as
  `def file(self) -> file`, where the property name shadows the `class file`
  below it, so the annotation resolved to `Unknown`. Every `element.file` in
  the codebase was therefore unchecked. Qualifying it to `ifcopenshell.file`
  restores `.schema` to its Literal union and surfaces no new diagnostics.

* model/wall.py: a duplicated merge fragment in the void-straddle path ran an
  always-true `if void_straddles:` that read `new_opening` from the mutually
  exclusive branch (stale value, or NameError on the first iteration), followed
  by an unreachable duplicate `elif`. Removing it makes the file match v0.8.0.

* light/operator.py: upstream's own fix unpacks three targets from two values
  and raises ValueError unconditionally; corrected to `None, None, None`.

* assign_system.py, validate.py, geom/main.py: walrus-in-genexp is valid at
  runtime (PEP 572 binds in the containing scope) but ty does not model it;
  rewritten as explicit loops, matching upstream.

Verified: poe ty-ios, poe ty-bonsai, ruff check src/ nix/, black --check .,
and compileall -W error at py3.10 (ifcopenshell-python) and py3.11 (bonsai).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 19:14:39 +10:00

101 lines
3.7 KiB
Python

# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2025 Thomas Krijnen <thomas@aecgeeks.com>
#
# This file is part of IfcOpenShell.
#
# IfcOpenShell is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# IfcOpenShell is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import csv
import ifcopenshell
import ifcopenshell.api.alignment
from ifcopenshell import entity_instance
def create_from_csv(file: ifcopenshell.file, filepath: str) -> entity_instance:
"""
Creates an alignment from PI data stored in a CSV file.
The format of the file is:
X1,Y1,R1,X2,Y2,R2 ... Xn-1,Yn-1,Rn-1,Xn,Yn
D1,Z1,L1,D2,Z2,L2 ... Dn-1,Zn-1,Ln-1,Dn,Zn
D1,Z1,L1,D2,Z2,L2 ... Dn-1,Zn-1,Ln-1,Dn,Zn
...
where:
X,Y are PI coordinates
R is the horizontal circular curve radius
D,Z are VPI coordinates as "Distance Along","Elevation"
L is the horizontal length of a parabolic vertical transition curve
R1 and Rn, as well as L1 and Ln are placeholders and not used. They are recommended to have values of 0.0.
R2 and Rn-2 are the radii of the first and last horizontal curves.
L2 and Ln-2 are the length of the first and last vertical curves.
The CSV file contains one horizontal alignment, zero, one, or more vertical alignments
:param filepath: path the to CSV file
:return: IfcAlignment
"""
alignment = None
with open(filepath, newline="") as csvfile:
reader = csv.reader(csvfile)
row_count = 0
for row in reader:
data = list(map(float, row)) # Convert all values to float
coordinates: list[list[float]] = (
[]
) # horizontal coordinates for first row, vertical coordinates for subsequent rows
radii: list[float] = [] # horizontal curve radii for first row, vertical curve length for subsequent rows
row_count += 1
i = 0
while i < len(data):
if i + 1 < len(data):
x, y = float(data[i]), float(data[i + 1])
coordinates.append((x, y)) # Store (X, Y) pair
i += 2
if i < len(data) and (i + 1) % 3 == 0: # Every third element after an (X,Y) pair is R
radii.append(data[i])
i += 1
radii = radii[1:-1] # The first radius value is a placeholder, remove it
if row_count == 1:
alignment = ifcopenshell.api.alignment.create(file, "Alignment_from_CSV")
horizontal_layout = ifcopenshell.api.alignment.get_horizontal_layout(alignment)
ifcopenshell.api.alignment.layout_horizontal_alignment_by_pi_method(
file, horizontal_layout, coordinates, radii
)
else:
# add all subsequent vertical alignments
assert alignment is not None
vertical_layout = ifcopenshell.api.alignment.add_vertical_layout(file, alignment)
ifcopenshell.api.alignment.layout_vertical_alignment_by_pi_method(
file, vertical_layout, coordinates, radii
)
assert alignment is not None
return alignment