# RU: Сгенерировано панелью Orca из плана «Жилой дом 11,0×12,0 м, этажей: 2», вариант 1. Запуск: python3 dom_ifc.py  →  dom.ifc
# EN: Generated by the Orca panel from a multi-storey plan. Run: python3 dom_ifc.py  →  dom.ifc
import numpy as np
import ifcopenshell
import ifcopenshell.util.unit
import ifcopenshell.api.aggregate, ifcopenshell.api.attribute, ifcopenshell.api.context
import ifcopenshell.api.feature, ifcopenshell.api.geometry, ifcopenshell.api.material, ifcopenshell.api.pset
import ifcopenshell.api.root, ifcopenshell.api.spatial, ifcopenshell.api.unit
api = ifcopenshell.api

H = 3.200  # RU: высота этажа, м / EN: storey height, m

# RU: (имя этажа, отметка пола, высота стен до пола следующего этажа), м / EN: (storey, floor level, wall height), m
STOREYS = [
    ("1 этаж", 0.000, 3.500),
    ("2 этаж", 3.500, 3.200),
]

# RU: Межэтажные плиты: (этаж над плитой, x0, y0, x1, y1), м; толщина SLAB, низ — на SLAB ниже пола этажа.
# EN: Interfloor slabs: (storey above, x0, y0, x1, y1), m.
SLAB = 0.300
SLABS = [
    (1, 0.300, 0.300, 10.700, 8.100),
]

# RU: (этаж, имя, x1, y1, x2, y2, толщина, наружная), м; тело стены — слева от p1→p2 / EN: walls per storey
WALLS = [
    (0, "НС-1.1", 0.000, 0.000, 11.000, 0.000, 0.300, True),
    (0, "НС-1.2", 0.000, 11.700, 11.000, 11.700, 0.300, True),
    (0, "НС-1.3", 0.300, 0.300, 0.300, 11.700, 0.300, True),
    (0, "НС-1.4", 11.000, 0.300, 11.000, 11.700, 0.300, True),
    (0, "ВП-1.1", 4.780, 0.300, 4.780, 11.700, 0.120, False),
    (0, "ВП-1.2", 5.880, 0.300, 5.880, 11.700, 0.120, False),
    (0, "ВП-1.3", 0.300, 6.240, 4.720, 6.240, 0.120, False),
    (0, "ВП-1.4", 0.300, 9.240, 4.720, 9.240, 0.120, False),
    (1, "НС-2.1", 0.000, 0.000, 11.000, 0.000, 0.300, True),
    (1, "НС-2.2", 0.000, 11.700, 11.000, 11.700, 0.300, True),
    (1, "НС-2.3", 0.300, 0.300, 0.300, 11.700, 0.300, True),
    (1, "НС-2.4", 11.000, 0.300, 11.000, 11.700, 0.300, True),
    (1, "ВП-2.1", 4.780, 0.300, 4.780, 11.700, 0.120, False),
    (1, "ВП-2.2", 5.880, 0.300, 5.880, 11.700, 0.120, False),
    (1, "ВП-2.3", 0.300, 2.990, 4.720, 2.990, 0.120, False),
    (1, "ВП-2.4", 5.820, 6.680, 10.700, 6.680, 0.120, False),
]

# RU: (индекс стены, смещение от p1, ширина, низ, высота, класс заполнения), м / EN: openings
OPENINGS = [
    (0, 4.830, 0.880, 0.000, 2.100, "IfcDoor"),
    (4, 0.200, 0.800, 0.000, 2.100, "IfcDoor"),
    (5, 0.200, 0.800, 0.000, 2.100, "IfcDoor"),
    (4, 6.200, 0.700, 0.000, 2.100, "IfcDoor"),
    (4, 9.200, 0.700, 0.000, 2.100, "IfcDoor"),
    (0, 1.610, 1.800, 0.800, 1.300, "IfcWindow"),
    (2, 2.100, 1.800, 0.800, 1.300, "IfcWindow"),
    (0, 7.360, 1.800, 0.800, 1.300, "IfcWindow"),
    (3, 1.950, 1.800, 0.800, 1.300, "IfcWindow"),
    (3, 7.650, 1.800, 0.800, 1.300, "IfcWindow"),
    (1, 2.110, 0.800, 1.200, 0.900, "IfcWindow"),
    (12, 0.200, 0.700, 0.000, 2.100, "IfcDoor"),
    (13, 0.200, 0.800, 0.000, 2.100, "IfcDoor"),
    (12, 2.950, 0.800, 0.000, 2.100, "IfcDoor"),
    (13, 6.640, 0.800, 0.000, 2.100, "IfcDoor"),
    (11, 0.860, 1.500, 0.800, 1.300, "IfcWindow"),
    (11, 4.080, 1.500, 0.800, 1.300, "IfcWindow"),
    (10, 3.440, 1.500, 0.800, 1.300, "IfcWindow"),
    (10, 6.330, 1.500, 0.800, 1.300, "IfcWindow"),
    (10, 9.210, 1.500, 0.800, 1.300, "IfcWindow"),
    (11, 6.930, 1.500, 0.800, 1.300, "IfcWindow"),
    (11, 9.410, 1.500, 0.800, 1.300, "IfcWindow"),
]

# RU: (этаж, номер, название, площадь в чистоте, м²) / EN: spaces per storey
SPACES = [
    (0, "1.1", "Прихожая", 11.17),
    (0, "1.2", "Кухня", 25.90),
    (0, "1.3", "Гостиная", 54.95),
    (0, "1.4", "Санузел", 12.56),
    (0, "1.5", "Котельная", 10.20),
    (1, "2.1", "Холл 2 этажа", 11.17),
    (1, "2.2", "Санузел 2 этажа", 11.73),
    (1, "2.3", "Спальня", 30.75),
    (1, "2.4", "Спальня 3", 37.45),
    (1, "2.5", "Детская", 23.62),
]

# RU: Лестница: прямоугольник марша (x0, y0, x1, y1), м — в первом этаже, на высоту этажа. / EN: The stair flight box.
STAIR = (4.720, 8.100, 5.820, 11.700)

m = ifcopenshell.file(schema="IFC4")
project = api.root.create_entity(m, ifc_class="IfcProject", name="Жилой дом 11,0×12,0 м, этажей: 2")
api.unit.assign_unit(m)
# Geometry helpers accept metres, whereas Elevation, OverallWidth/Height and
# IfcQuantityLength values are expressed in the declared IFC project unit.
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(m)
model = api.context.add_context(m, context_type="Model")
body = api.context.add_context(m, context_type="Model", context_identifier="Body", target_view="MODEL_VIEW", parent=model)
site = api.root.create_entity(m, ifc_class="IfcSite", name="Участок")
building = api.root.create_entity(m, ifc_class="IfcBuilding", name="Жилой дом")
api.aggregate.assign_object(m, relating_object=project, products=[site])
api.aggregate.assign_object(m, relating_object=site, products=[building])
storeys = []
for name, z, hs in STOREYS:
    s = api.root.create_entity(m, ifc_class="IfcBuildingStorey", name=name)
    api.attribute.edit_attributes(m, product=s, attributes={"Elevation": z / unit_scale})
    api.aggregate.assign_object(m, relating_object=building, products=[s])
    storeys.append((s, z, hs))
ext_mat = api.material.add_material(m, name="Газобетон D500")
int_mat = api.material.add_material(m, name="Перегородка 120 мм")

walls = []
for k, name, x1, y1, x2, y2, t, ext in WALLS:
    s, z, hs = storeys[k]
    wall = api.root.create_entity(m, ifc_class="IfcWall", name=name)
    # RU: Тело присваиваем явно — create_2pt_wall его только возвращает (см. одноэтажный скрипт). / EN: Assign the body.
    wrep = api.geometry.create_2pt_wall(m, element=wall, context=body, p1=(x1, y1), p2=(x2, y2), elevation=z, height=hs, thickness=t)
    api.geometry.assign_representation(m, product=wall, representation=wrep)
    api.spatial.assign_container(m, relating_structure=s, products=[wall])
    api.material.assign_material(m, products=[wall], material=ext_mat if ext else int_mat)
    walls.append((wall, np.array([x1, y1]), np.array([x2, y2]), t, k))

for wi, off, width, sill, height, cls in OPENINGS:
    wall, a, b, t, k = walls[wi]
    s, z, _hs = storeys[k]
    d = (b - a) / np.linalg.norm(b - a)
    n = np.array([-d[1], d[0]])
    opening = api.root.create_entity(m, ifc_class="IfcOpeningElement")
    rep = api.geometry.add_wall_representation(m, context=body, length=width, height=height, thickness=t + 0.1)
    api.geometry.assign_representation(m, product=opening, representation=rep)
    mat = np.eye(4)
    mat[:2, 0], mat[:2, 1] = d, n
    mat[:3, 3] = [*(a + d * off - n * 0.05), z + sill]
    api.geometry.edit_object_placement(m, product=opening, matrix=mat)
    api.feature.add_feature(m, feature=opening, element=wall)
    filling = api.root.create_entity(m, ifc_class=cls)
    api.attribute.edit_attributes(m, product=filling, attributes={"OverallWidth": width / unit_scale, "OverallHeight": height / unit_scale})
    api.feature.add_filling(m, opening=opening, element=filling)
    api.spatial.assign_container(m, relating_structure=s, products=[filling])

for k, num, name, area in SPACES:
    s, z, _hs = storeys[k]
    space = api.root.create_entity(m, ifc_class="IfcSpace", name=num)
    api.attribute.edit_attributes(m, product=space, attributes={"LongName": name})
    api.aggregate.assign_object(m, relating_object=s, products=[space])
    qto = api.pset.add_qto(m, product=space, name="Qto_SpaceBaseQuantities")
    api.pset.edit_qto(m, qto=qto, properties={"NetFloorArea": area, "Height": H / unit_scale})

# RU: Перекрытия — IfcSlab (FLOOR) коробкой под полом этажа, в контейнере этажа над плитой. / EN: Slabs as boxes.
slab_mat = api.material.add_material(m, name="Плита перекрытия")
for k, sx0, sy0, sx1, sy1 in SLABS:
    s, z, _hs = storeys[k]
    slab = api.root.create_entity(m, ifc_class="IfcSlab", name=f"Плита {k + 1} этажа", predefined_type="FLOOR")
    rep = api.geometry.add_wall_representation(m, context=body, length=sx1 - sx0, height=SLAB, thickness=sy1 - sy0)
    api.geometry.assign_representation(m, product=slab, representation=rep)
    mat = np.eye(4)
    mat[:3, 3] = [sx0, sy0, z - SLAB]
    api.geometry.edit_object_placement(m, product=slab, matrix=mat)
    api.spatial.assign_container(m, relating_structure=s, products=[slab])
    api.material.assign_material(m, products=[slab], material=slab_mat)

# RU: Лестница — тело марша коробкой (упрощённо) в первом этаже. / EN: The stair as a simple box in storey 1.
x0, y0, x1, y1 = STAIR
stair = api.root.create_entity(m, ifc_class="IfcStair", name="Лестница")
rep = api.geometry.add_wall_representation(m, context=body, length=y1 - y0, height=storeys[1][1], thickness=x1 - x0)
api.geometry.assign_representation(m, product=stair, representation=rep)
mat = np.eye(4)
mat[:2, 0], mat[:2, 1] = [0.0, 1.0], [-1.0, 0.0]
mat[:3, 3] = [x1, y0, 0.0]
api.geometry.edit_object_placement(m, product=stair, matrix=mat)
api.spatial.assign_container(m, relating_structure=storeys[0][0], products=[stair])

m.write("dom.ifc")
print(f"dom.ifc: {len(storeys)} этажа, {len(walls)} стен, {len(OPENINGS)} проёмов, {len(SPACES)} помещений")
