From c3c42ec3372db26c9f868a9b0c0149536339875e Mon Sep 17 00:00:00 2001 From: Alexandre CUER Date: Mon, 20 Jul 2026 17:59:59 +0200 Subject: [PATCH 1/2] improve generate_geometry_from_hvac - ability to create walls with different heigths with the heights key - ability to define a ceiling which is not a simple translation of the floor and to user blocks strings when defining the ceiling - ability to delete a wall between two adjacent zones (remove_wall key) - ability to use some variables of the blocks section in zone subsections (floors, roofs, walls, elements) --- conf/bardoux.yml | 14 ++ conf_geometry/bardoux.yml | 313 ++++++++++++++++++++++++++++++ generate_geometry_from_yaml.py | 77 +++++--- src/idfhub/common.py | 12 +- src/idfhub/helpers/geometry.py | 70 +++++-- src/idfhub/helpers/materials.xlsx | Bin 0 -> 9559 bytes 6 files changed, 445 insertions(+), 41 deletions(-) create mode 100644 conf/bardoux.yml create mode 100644 conf_geometry/bardoux.yml create mode 100644 src/idfhub/helpers/materials.xlsx diff --git a/conf/bardoux.yml b/conf/bardoux.yml new file mode 100644 index 0000000..81905a1 --- /dev/null +++ b/conf/bardoux.yml @@ -0,0 +1,14 @@ +building_name: 8_bardoux + +name: raw +suffix: no_hvac + +Terrain: City + +os_ep_path: "C:/openstudioapplication-1.8.0/EnergyPlus" + +zones: [] + +loops: [] + +equipments: [] diff --git a/conf_geometry/bardoux.yml b/conf_geometry/bardoux.yml new file mode 100644 index 0000000..a566cb6 --- /dev/null +++ b/conf_geometry/bardoux.yml @@ -0,0 +1,313 @@ +name: 8_bardoux + +blocks: + ss2_alt: &ss2_alt -8 + ss1_alt: &ss1_alt -4 + rdc_alt: &rdc_alt 0 + r1_alt: &r1_alt 3 + r2_alt: &r2_alt 6 + r3_alt: &r3_alt 9 + r4_alt: &r4_alt 12 + d0: 19 + d1: 3 + d2: 5 + d3: 4 + d4: 4 + d5: 3 + z0: 12 + annexe: + - ["-d1", 0, *rdc_alt] + - ["-d1", "d3+d4", *rdc_alt] + - ["-d1-d2", "d3+d4", *rdc_alt] + - ["-d1-d2", 0, *rdc_alt] + cour_drouet: + - ["-d1", 0, *rdc_alt] + - [0, 0, *rdc_alt] + - [0, "d3+d4", *rdc_alt] + - ["-d1", "d3+d4", *rdc_alt] + car_path: + - ["d0", "d3+d4", *rdc_alt] + - ["d0", "d3+d4+d5", *rdc_alt] + - ["-d1-d2", "d3+d4+d5", *rdc_alt] + - ["-d1-d2", "d3+d4", *rdc_alt] + caves: + - [0, 0, *rdc_alt] + - ["d0", 0, *rdc_alt] + - ["d0", "d3+d4", *rdc_alt] + - [0, "d3+d4", *rdc_alt] + r1_passerelle: + - ["-d1","d3", *r1_alt] + - [0,"d3", *r1_alt] + - [0, "d3+d4", *r1_alt] + - ["-d1", "d3+d4", *r1_alt] + r1_annexe: + - ["-d1-d2", 0, *r1_alt] + - ["-d1", 0, *r1_alt] + - ["-d1", "d3+d4", *r1_alt] + - ["-d1-d2", "d3+d4", *r1_alt] + r1_annexe_b: + - ["-d1-d2", "d3+d4", *r1_alt] + - ["-d1", "d3+d4", *r1_alt] + - ["-d1", "d3+d4+d5", *r1_alt] + - ["-d1-d2", "d3+d4+d5", *r1_alt] + r1_1: + - [0, 0, *r1_alt] + - ["d0", 0, *r1_alt] + - ["d0", "d3+d4", *r1_alt] + - [0, "d3+d4", *r1_alt] + r1_2: + - [0, "d3+d4", *r1_alt] + - ["d0", "d3+d4", *r1_alt] + - ["d0", "d3+d4+d5", *r1_alt] + - [0, "d3+d4+d5", *r1_alt] + h0: 2 + h1: 3 + h2: 2.5 + h3: 1 + h4: 1.8 + toit1_1: + - [0, 0, "z0+h0"] + - [3, 0, "z0+h1"] + - [3, "d3+d4+d5", "z0+h1"] + - [0, "d3+d4+d5", "z0+h0"] + toit1_2: + - [3, 0, "z0+h1"] + - ["3+7", 0, "z0+h2"] + - ["3+7", "d3+d4+d5", "z0+h2"] + - [3, "d3+d4+d5", "z0+h1"] + toit2_1: + - ["3+7", 0, "z0+h2"] + - ["3+7+9", 0, "z0+h3"] + - ["3+7", "d3/2+d4/2+d5/2", "z0+h2"] + toit2_2: + - ["3+7", "d3/2+d4/2+d5/2", "z0+h2"] + - ["3+7+9", "d3+d4+d5", "z0+h3"] + - ["3+7", "d3+d4+d5", "z0+h2"] + toit3_1: + - ["3+7", "d3/2+d4/2+d5/2", "z0+h2"] + - ["3+7+9", 0, "z0+h3"] + - ["3+7+9", "d3/2+d4/2+d5/2", "z0+h4"] + toit3_2: + - ["3+7", "d3/2+d4/2+d5/2", "z0+h2"] + - ["3+7+9", "d3/2+d4/2+d5/2", "z0+h4"] + - ["3+7+9", "d3+d4+d5", "z0+h3"] + +immeuble_8: + ss2: + height: 4 + use_blocks_vars: 1 + walls: + - ["d0", 0, *ss2_alt] + - ["d0", "d3+d4+d5", *ss2_alt] + - ["-d1-d2", "d3+d4+d5", *ss2_alt] + - ["-d1-d2", 0, *ss2_alt] + ss1: + height: 4 + use_blocks_vars: 1 + floors: + - ["d0", 0, *ss1_alt] + - ["d0", "d3+d4+d5", *ss1_alt] + - ["-d1-d2", "d3+d4+d5", *ss1_alt] + - ["-d1-d2", 0, *ss1_alt] + roofs: [annexe, cour_drouet, car_path, caves] + walls: + - ["d0", 0, *ss1_alt] + - ["d0", "d3+d4+d5", *ss1_alt] + - ["-d1-d2", "d3+d4+d5", *ss1_alt] + - ["-d1-d2", 0, *ss1_alt] + rdc: + height: 3 + use_blocks_vars: 1 + walls: + - [0, 0, *rdc_alt] + - ["d0", 0, *rdc_alt] + - ["d0", "d3+d4", *rdc_alt] + - [0, "d3+d4", *rdc_alt] + apertures: + numbers: [0, 2, 1, 0] + widths: [0, 1.4, 2, 0] + heights: [0, 1.5, 3, 0] + sill_heights: [0, 1, 0, 0] + types: + - none + - aperture + - door + - none + rdc_annexe: + height: 3 + use_blocks_vars: 1 + walls: + - ["-d1-d2", 0, *rdc_alt] + - ["-d1", 0, *rdc_alt] + - ["-d1", "d3+d4", *rdc_alt] + - ["-d1-d2", "d3+d4", *rdc_alt] + r1: + height: 3 + use_blocks_vars: 1 + floors: [r1_1, r1_2] + roofs: + - [0, 0, *r2_alt] + - ["d0", 0, *r2_alt] + - ["d0", "d3+d4+d5", *r2_alt] + - [0, "d3+d4+d5", *r2_alt] + walls: + - [0, 0, *r1_alt] + - ["d0", 0, *r1_alt] + - ["d0", "d3+d4+d5", *r1_alt] + - [0, "d3+d4+d5", *r1_alt] + - [0, "d3+d4", *r1_alt] + - [0, "d3", *r1_alt] + remove_wall: [4] + apertures: + numbers: [0, 3, 0, 1, 0, 1] + widths: [0, 1.3, 0, 1.3, 0, 1.3] + heights: [0, 1.5, 0, 1.5, 0, 1.5] + sill_heights: [0, 1, 0, 1, 0, 1] + types: + - none + - aperture + - none + - aperture + - none + - aperture + r1_annexe: + height: 3 + use_blocks_vars: 1 + floors: [r1_passerelle, r1_annexe, r1_annexe_b] + roofs: + - ["-d1-d2", 0, *r2_alt] + - ["-d1", 0, *r2_alt] + - ["-d1", "d3", *r2_alt] + - [0, "d3", *r2_alt] + - [0, "d3+d4", *r2_alt] + - ["-d1", "d3+d4", *r2_alt] + - ["-d1", "d3+d4+d5", *r2_alt] + - ["-d1-d2", "d3+d4+d5", *r2_alt] + walls: + - ["-d1-d2", 0, *r1_alt] + - ["-d1", 0, *r1_alt] + - ["-d1", "d3", *r1_alt] + - [0, "d3", *r1_alt] + - [0, "d3+d4", *r1_alt] + - ["-d1", "d3+d4", *r1_alt] + - ["-d1", "d3+d4+d5", *r1_alt] + - ["-d1-d2", "d3+d4+d5", *r1_alt] + - ["-d1-d2", "d3+d4", *r1_alt] + - ["-d1-d2", "d3", *r1_alt] + remove_wall: [3] + apertures: + numbers: [0, 1, 1, 0, 1, 1, 0, 0, 0, 1] + widths: [0, 2, 1, 0, 1.3, 1.3, 0, 0, 0, 1.3] + heights: [0, 2.5, 2.5, 0, 1.5, 1.5, 0, 0, 0, 1.5] + sill_heights: [0, 0, 0, 0, 1, 1, 0, 0, 0, 1] + constructions: + - none + - window_pvc + - window_pvc + - none + - lb + - lb + - none + - none + - none + - lb + types: + - none + - door + - door + - none + - aperture + - aperture + - none + - aperture + - none + - aperture + r2: + height: 3 + use_blocks_vars: 1 + walls: + - [0, "d3", *r2_alt] + - [0, 0, *r2_alt] + - ["d0", 0, *r2_alt] + - ["d0", "d3+d4+d5", *r2_alt] + - [0, "d3+d4+d5", *r2_alt] + - [0, "d3+d4", *r2_alt] + remove_wall: [5] + apertures: + numbers: [1,0,3,0,1,0] + r2_annexe: + height: 3 + use_blocks_vars: 1 + walls: + - ["-d1-d2", 0, *r2_alt] + - ["-d1", 0, *r2_alt] + - ["-d1", "d3", *r2_alt] + - [0, "d3", *r2_alt] + - [0, "d3+d4", *r2_alt] + - ["-d1", "d3+d4", *r2_alt] + - ["-d1", "d3+d4+d5", *r2_alt] + - ["-d1-d2", "d3+d4+d5", *r2_alt] + - ["-d1-d2", "d3+d4", *r2_alt] + - ["-d1-d2", "d3", *r2_alt] + remove_wall: [3] + apertures: + numbers: [0, 1, 1, 0, 0, 1, 0, 1, 0, 1] + widths: [0, 1.3, 2.5, 0 ,0, 1.3, 0, 1.3, 0, 1.3] + heights: [0, 1.5, 1.5, 0, 0, 1.5, 0, 1.5, 0, 1.5] + sill_heights: [0, 1, 1, 0, 0, 1, 0, 1, 0, 1] + r3: + height: 3 + use_blocks_vars: 1 + walls: + - [0, 0, *r3_alt] + - ["d0", 0, *r3_alt] + - ["d0", "d3+d4+d5", *r3_alt] + - [0, "d3+d4+d5", *r3_alt] + - [0, "d3+d4", *r3_alt] + - [0, "d3", *r3_alt] + apertures: + numbers: [0,3,0,1,1,1] + r4: + use_blocks_vars: 1 + floors: + - [0, 0, *r4_alt] + - ["d0", 0, *r4_alt] + - ["d0", "d3+d4+d5", *r4_alt] + - [0, "d3+d4+d5", *r4_alt] + roofs: [toit1_1, toit1_2, toit2_1, toit2_2, toit3_1, toit3_2] + walls: + - [0, 0, *r4_alt] + - [3, 0, *r4_alt] + - ["3+7", 0, *r4_alt] + - ["d0", 0, *r4_alt] + - ["d0", "d3/2+d4/2+d5/2", *r4_alt] + - ["d0", "d3+d4+d5", *r4_alt] + - ["3+7", "d3+d4+d5", *r4_alt] + - [3, "d3+d4+d5", *r4_alt] + - [0, "d3+d4+d5", *r4_alt] + - [0, "d3+d4", *r4_alt] + - [0, "d3", *r4_alt] + heights: [2,3,2.5,1,1.8,1,2.5,3,2,2,2] + elements: + half_lune_1: + index: 3 + geometry: + - ["d0", "d3/2+d4/2+d5/2-0.8", "z0+0.5"] + - ["d0", "d3/2+d4/2+d5/2", "z0+0.5"] + - ["d0", "d3/2+d4/2+d5/2", "z0+1.2"] + - ["d0", "d3/2+d4/2+d5/2-0.6", "z0+0.9"] + construction: simple_glass_wall + type: aperture + half_lune_2: + index: 4 + geometry: + - ["d0", "d3/2+d4/2+d5/2", "z0+1.2"] + - ["d0", "d3/2+d4/2+d5/2", "z0+0.5"] + - ["d0", "d3/2+d4/2+d5/2+0.8", "z0+0.5"] + - ["d0", "d3/2+d4/2+d5/2+0.6", "z0+0.9"] + construction: simple_glass_wall + type: aperture + apertures: + numbers: [0,0,0,0,0,0,0,0,1,1,1] + heights: [0,0,0,0,0,0,0,0,1.3,1.3,1.3] + sill_heights: [0,0,0,0,0,0,0,0,0.5,0.5,0.5] diff --git a/generate_geometry_from_yaml.py b/generate_geometry_from_yaml.py index 93f2133..d68fcba 100644 --- a/generate_geometry_from_yaml.py +++ b/generate_geometry_from_yaml.py @@ -23,10 +23,10 @@ def prepare( coordinates: list[list[float]], - variables: dict|None = None + variables: dict = {} ) -> list[Point3D]: """prepare for complex_room method""" - if variables is None: + if len(variables) == 0: return [ Point3D(*row) for row in coordinates @@ -50,10 +50,12 @@ def prepare( if not isinstance(level_metadata, dict): continue if "walls" not in level_metadata: - LOGGER.error("no floors key for %s, skipping the level", level_name) + LOGGER.error("no walls key for %s, skipping the level", level_name) continue LOGGER.info("Generating %s", level_name) level_variables = get_variables(level_metadata) + if level_metadata.get("use_blocks_vars", 0) == 1: + level_variables = {**level_variables, **get_variables(BLOCKS)} wall_points: list[list[float]] = [] for x in level_metadata["walls"]: if isinstance(x, str): @@ -63,19 +65,40 @@ def prepare( wall_points = [*wall_points, x] walls = prepare(wall_points, variables=level_variables) - floors = [] - if "floors" in level_metadata: - LOGGER.info("custom floors for %s", level_name) - for x in level_metadata["floors"]: - if isinstance(x, str): - if x in BLOCKS: - floors.append(prepare(BLOCKS[x])) + surfaces: dict[str, list[list[Point3D]]] = {"floors": [], "roofs": []} + for key, surface in surfaces.items(): + if key in level_metadata: + pts: list[list[float]] = [] + LOGGER.info("custom %s for %s", key, level_name) + for x in level_metadata[key]: + if isinstance(x, str): + if x in BLOCKS: + surface.append( + prepare( + BLOCKS[x], + variables=get_variables(BLOCKS) + ) + ) + if isinstance(x, list): + pts.append(x) + if len(pts) > 0: + surface.append( + prepare( + pts, + variables=level_variables + ) + ) + height=level_metadata.get("height", common_height) + if "heights" in level_metadata: + height = level_metadata["heights"] level = complex_room( walls, - height=level_variables["height"], + height=height, identifier=level_name, - floors = floors if floors else None, - use_polyface = not floors + floors = surfaces["floors"], + roofs = surfaces["roofs"], + use_polyface = not surfaces["floors"], + remove_wall=level_metadata.get("remove_wall", []) ) building.append(level) building_dict[level_name] = level @@ -153,30 +176,36 @@ def prepare( aperture_types = apertures["types"] except KeyError: aperture_types = [] - for i, face in enumerate(level_walls): + i = 0 + apm = ApertureManager(site[building_name][level_name]) + # we must loop on numbers and not on level_walls, as some walls may be removed + for j, count in enumerate(numbers): + if j in level_metadata.get("remove_wall", []): + continue try: - count = numbers[i] + face = level_walls[i] except IndexError: - LOGGER.warning("aperture index error") + LOGGER.warning("no index %s in %s", i, level_walls) continue - if not count: + # we have the face, we can increment i + i+=1 + if count == 0: LOGGER.warning("skipping aperture on %s", face) continue - apm = ApertureManager(site[building_name][level_name]) try: - width = widths[i] + width = widths[j] except IndexError: width = apertures.get("width", 1.2) try: - height = heights[i] + height = heights[j] except IndexError: height = apertures.get("height", 1.3) try: - sill_height = sill_heights[i] + sill_height = sill_heights[j] except IndexError: sill_height = apertures.get("sill_height", 1) try: - aperture_type = aperture_types[i] + aperture_type = aperture_types[j] except IndexError: aperture_type = apertures.get("type", "aperture") apm.fix_dim( @@ -185,7 +214,7 @@ def prepare( sill_height = sill_height ) try: - construction_name = constructions[i] + construction_name = constructions[j] except IndexError: construction_name = apertures.get("construction") construction=CONSTLIB.get(construction_name) @@ -199,6 +228,8 @@ def prepare( # now we can add single elements if any elements = level_metadata.get("elements", {}) level_variables = get_variables(level_metadata) + if level_metadata.get("use_blocks_vars", 0) == 1: + level_variables = {**level_variables, **get_variables(BLOCKS)} for element_name, element_metadata in elements.items(): if "geometry" not in element_metadata: continue diff --git a/src/idfhub/common.py b/src/idfhub/common.py index 02285b3..3584a4d 100644 --- a/src/idfhub/common.py +++ b/src/idfhub/common.py @@ -109,8 +109,16 @@ def load_config(repo_root:str, file_name:str = "configuration.yml") -> dict: def get_variables(metadata: dict) -> dict: """return dict of variables""" variables = {} - variables["height"] = metadata.get("height", COMMON_HEIGHT) - variables["altitude"] = metadata.get("altitude", 0) + accepted_keys = [ + "height", + "altitude", + "d0","d1","d2","d3","d4","d5", + "h0","h1","h2","h3","h4","h5", + "z0" + ] + for key in accepted_keys: + if key in metadata: + variables[key] = metadata[key] return variables def eval_expr(expr, variables): diff --git a/src/idfhub/helpers/geometry.py b/src/idfhub/helpers/geometry.py index a02a802..5fc9ef8 100644 --- a/src/idfhub/helpers/geometry.py +++ b/src/idfhub/helpers/geometry.py @@ -57,14 +57,27 @@ def box_room( ) def create_walls( - pts:list[Point3D], - height: float, + *, + pts: list[Point3D], + height: float|list[float] = 3, + remove_wall: list[int] | None = None ) -> list[Face3D]: """create walls from lower and upper points""" - up = Vector3D(0, 0, height) - pts_u = [pt.move(up) for pt in pts] + if remove_wall is None: + remove_wall = [] + if isinstance(height, list): + LOGGER.debug("having a list of heights %s", height) + if len(height) != len(pts): + pts_u = [pt.move(Vector3D(0,0,height[0])) for pt in pts] + else: + pts_u = [pt.move(Vector3D(0,0,height[i])) for i,pt in enumerate(pts)] + else: + up = Vector3D(0, 0, height) + pts_u = [pt.move(up) for pt in pts] i_inds = list(range(len(pts))) j_inds = i_inds[1:] + [0] + if remove_wall: + LOGGER.info("removing wall %s", remove_wall) return [ Face3D([ pts[i], @@ -72,23 +85,30 @@ def create_walls( pts_u[j_inds[i]], pts_u[i] ]) - for i in i_inds + for i in i_inds if i not in remove_wall ] def create_floors_roofs( + *, floors:list[list[Point3D]], - height: float -) -> tuple[list[Point3D], list[Point3D]]: + height: float = 3, + roofs:list[list[Point3D]]|None = None +) -> tuple[list[Face3D], list[Face3D]]: """create floors and roofs""" - up = Vector3D(0, 0, height) + if roofs is None: + roofs = [] all_floors: list[Face3D] = [] for floor in [Face3D(pts) for pts in floors]: if floor.normal.z > 0: LOGGER.debug("flipping to have a floor") all_floors.append(floor.flip()) else: - all_floors.append(floor.flip()) - all_up_pts = [[pt.move(up) for pt in pts] for pts in floors] + all_floors.append(floor) + if len(roofs) == 0: + up = Vector3D(0, 0, height) + all_up_pts = [[pt.move(up) for pt in pts] for pts in floors] + else: + all_up_pts = roofs all_roofs: list[Face3D] = [] for roof in [Face3D(pts) for pts in all_up_pts]: if roof.normal.z < 0: @@ -107,19 +127,37 @@ def create_floors_roofs( def complex_room( pts:list[Point3D], - height:float, + height:float|list[float], identifier:str, floors:list[list[Point3D]]|None = None, - use_polyface:bool = True + roofs:list[list[Point3D]]|None = None, + use_polyface:bool = True, + remove_wall:list[int]|None = None ) -> Room: """create a complex room""" + if floors is None: + floors = [] + if roofs is None: + roofs = [] + if remove_wall is None: + remove_wall = [] # on crée les murs d'après les glacis de points - walls = create_walls(pts, height) + walls = create_walls(pts=pts, height=height, remove_wall=remove_wall) # on crée sol(s) et plafond(s) - if not floors: - all_floors, all_roofs = create_floors_roofs([Face3D(pts)], height) + # si l'utilisateur fournit une liste de hauteurs, il doit définir le plafond dans le yaml + # s'il ne l'a pas fait, on produit un plafond plat + room_height = max(height) if isinstance(height, list) else height + if len(floors) == 0: + all_floors, all_roofs = create_floors_roofs( + floors=[Face3D(pts)], + height=room_height + ) else: - all_floors, all_roofs = create_floors_roofs(floors, height) + all_floors, all_roofs = create_floors_roofs( + floors=floors, + height=room_height, + roofs=roofs + ) if use_polyface: polyface = Polyface3D.from_faces( diff --git a/src/idfhub/helpers/materials.xlsx b/src/idfhub/helpers/materials.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..7b7cd0bce9de77221b1aef3da1b549f311ad002b GIT binary patch literal 9559 zcmeHtg;yNg^7SAgID@;}Ai>=&*kFMGL4v!xy9NkuK>`GK3+@&if(C*+!2-bs=QqiH z@4NTrzV9!1@61|#)~xPbJyYlG+SOI90)~Uf0U!Vn0RR9c!07vX3q2SBAPyb?zy=_~ z>PXt#xtQ9y=)d%IFm-;-;$dq;kqZwC$^pPaum9ipFZMur(uh(o8}Kj9RK)%s;r;y{4qGg2^{rG7a3E#GbLTWboEI+k7SmA4TQ8gy*JLphEddjloE@ z+36+~+~Evr;+!=whFxI`i)%+;9$!Uh?cgO-h#g|@#+M{M4hYL zwD90s=s(lR5(}gZnwkfwSVz_2RdCpS_U#kJ%~KdT>7iJ@rCrA+>02c>X~~L>Gf^U- zvSnT?7|4=R=I3P$@+%q~YU1lsJ~Afpu`8T>x&{CI>JjG>bFbyBO9D95MM>iuezZ2Y zC+K}Rv8@yQ{YT24KgRwfhydR*bGHVP+-Njw-^c8+mi~6F+D8 z&!rUZp^3iXS2&zX!yR-sGXGlXGiXD3aZRMIX2EY+ZItCCK;@}#V$*ddmC=cP@AFJ4 zb5NCv3+syGv&;Z(&V|2r9oLVM_nv*cNWEo)CqqF|@ z1L_$M8&-FFCu<{nd+VRNRj#39zrly&r*HWvndHvvwgHHcmx&uuDsX%0`tI^c2}cB_ zhO?nMq}syjI2hI90VV`RzA!uJ=7!e zL>x35u%*Usxs8=boqls|r|$jrz;q{*|I){VcIDdrqZ+TLY(f6S(E)V=6(PVj<=ks; zyGmbIetcU0dD%Imk!@a$G=<~_b&i5WU5hM?BN5aLT+#HnBd4WEta6qMAuB;mZ~j#| zrd{VRDPFJ=E8UW&=U#ya8OaoHUz9YLr|rB@vz}Q0Qnfm(8%~Og23l&_^>37L0x?hE zUm-smk>F8tSHRa`8u>sPI!%telwF%p^+AHRb5NV5sC8dux-;ZqVZ4xbMBn^q18F$D ztR&&<%dIn5)F-T|2y5??zFIEdJ!StmDm54gv|XAP@<+w@pzeKda9mfnb}i5mnD1-O z8Ycmi@2EJj9-Lg;8*{eAx~_>T7L5c76liH7`dmi72%>ZwGM)B5*n8ZH7d}oL+K5IM?Erj$J_YEo9Fo@ep#3&2G+QY)lANl6 zUUhRHoMslWz^ntw54JlUPBowacE^&m5DE6tuuB~*NFsp@_0}gou_P}TWQ1TcC&$AZ zz6&)hF@VD1>Wlpt?VK+c8q3K8X5@U+W9&A$U>k6l8HVTkM07)lcKZdEKEd>}gY)v)^*aWYsea0}+pNQR^0zflaV+Ik;bD6$gb>)T9rv$&v1B5$$p(oa zyV*jO2ucth^OjIm9`&VY$?3wyulHL%oHb+9FD64FH4(R$NB%ljq;0I%X;IVRocPkK zcJz(rmF;qL%CVO8`Tpl$uH&NLO#r-+K#NhJ-21v%1cu9anOm;YJwD1N|3T^IVLg#l z)Kme%W~t+<+ao!{1mmvWBY#S;V>>tL(BQ;zU#Cp#&6EE`^+1uN)H5i>TcCPK1VDs= zQvDA}{43%AEsrqJY!wm&-H~Iz~_C+^SaU>SJ7q-o@;1m0?voSbCqoWG4AekrF zScm%uWk)FJ>CV{AA_;@cSvY(D)ULs@%d6G|_5hBS6p?Lx(zs*oGU3EXdL_BS~GgI^&x9-ln zLER=P1Cy0<3;Tq*P=pW4koz+vIB(~swW=SlyGYy)GjuMVGLM`en{5pZ%&tOL`k(4R zH$g6n34N+rLI(f{pjZ4$9h@ypOe@*N^Wg;_i(Jqe-70vtxEH@Zk7?&&ag^=dRF>M>=$&(;>Up@FA{y6-RZ{-9qF0kVux`CmcKzu!D zmN7}b%DWPRb>N|PqRksO$@%Jr@A1ykZN|);2_O?UG5=O$yq>{W<7H0Mg)0i$c+vvs z`^mITnl0K%?xWl+Gjb5Snl6tEMg9(17z>!epx~39_$I=ez;wZLd#0}oD2fr@4v`tl z9Ty=Q�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 rNCAL<=TZQ~^JMt}Xz84E+Q^CA^#J=ePd@cW}`Q literal 0 HcmV?d00001 From 16b93170f4cd6b77bce8ec0cc2d31e8bd2be14de Mon Sep 17 00:00:00 2001 From: Alexandre CUER Date: Thu, 23 Jul 2026 18:36:17 +0200 Subject: [PATCH 2/2] improve geometry generation - implement vasistas - dont use polyface in generate_geometry_from_yaml.py : no interest except complicating things - use dict and not list for walls as list is not rich enough. dict permits to manage walls removed by the user in the yaml - structure materials more nicely withs consts and default thickness that can be customized through yaml - ability to mix different wall constructions on a same room/zone --- conf_geometry/bardoux.yml | 34 ++++-- generate_geometry_from_yaml.py | 197 +++++++++++++++++------------- generate_hvac.py | 2 +- src/idfhub/helpers/geometry.py | 18 +-- src/idfhub/helpers/material.py | 181 ++++++++++++++++++++++----- src/idfhub/helpers/materials.xlsx | Bin 9559 -> 11140 bytes src/idfhub/helpers/matlib.py | 7 +- 7 files changed, 303 insertions(+), 136 deletions(-) diff --git a/conf_geometry/bardoux.yml b/conf_geometry/bardoux.yml index a566cb6..d0a0580 100644 --- a/conf_geometry/bardoux.yml +++ b/conf_geometry/bardoux.yml @@ -1,4 +1,5 @@ name: 8_bardoux +glasswool_new: 0.14 blocks: ss2_alt: &ss2_alt -8 @@ -96,6 +97,10 @@ immeuble_8: ss2: height: 4 use_blocks_vars: 1 + constructions: + walls: townhouse_basement + floors: townhouse_basement + roofs: townhouse_basement walls: - ["d0", 0, *ss2_alt] - ["d0", "d3+d4+d5", *ss2_alt] @@ -104,6 +109,10 @@ immeuble_8: ss1: height: 4 use_blocks_vars: 1 + constructions: + walls: townhouse_basement + floors: townhouse_basement + roofs: townhouse_basement floors: - ["d0", 0, *ss1_alt] - ["d0", "d3+d4+d5", *ss1_alt] @@ -118,6 +127,8 @@ immeuble_8: rdc: height: 3 use_blocks_vars: 1 + constructions: + floors: townhouse_basement walls: - [0, 0, *rdc_alt] - ["d0", 0, *rdc_alt] @@ -136,6 +147,8 @@ immeuble_8: rdc_annexe: height: 3 use_blocks_vars: 1 + constructions: + floors: townhouse_basement walls: - ["-d1-d2", 0, *rdc_alt] - ["-d1", 0, *rdc_alt] @@ -275,18 +288,21 @@ immeuble_8: - ["d0", "d3+d4+d5", *r4_alt] - [0, "d3+d4+d5", *r4_alt] roofs: [toit1_1, toit1_2, toit2_1, toit2_2, toit3_1, toit3_2] + vasistas: + numbers: [0,3] + sill_heights: [0,2] walls: - - [0, 0, *r4_alt] - - [3, 0, *r4_alt] - - ["3+7", 0, *r4_alt] + - [0, 0, *r4_alt, recent_light_renovation] + - [3, 0, *r4_alt, recent_light_renovation] + - ["3+7", 0, *r4_alt, recent_light_renovation] - ["d0", 0, *r4_alt] - ["d0", "d3/2+d4/2+d5/2", *r4_alt] - - ["d0", "d3+d4+d5", *r4_alt] - - ["3+7", "d3+d4+d5", *r4_alt] - - [3, "d3+d4+d5", *r4_alt] - - [0, "d3+d4+d5", *r4_alt] - - [0, "d3+d4", *r4_alt] - - [0, "d3", *r4_alt] + - ["d0", "d3+d4+d5", *r4_alt, recent_light_renovation] + - ["3+7", "d3+d4+d5", *r4_alt, recent_light_renovation] + - [3, "d3+d4+d5", *r4_alt, recent_light_renovation] + - [0, "d3+d4+d5", *r4_alt, recent_light_renovation] + - [0, "d3+d4", *r4_alt, recent_light_renovation] + - [0, "d3", *r4_alt, recent_light_renovation] heights: [2,3,2.5,1,1.8,1,2.5,3,2,2,2] elements: half_lune_1: diff --git a/generate_geometry_from_yaml.py b/generate_geometry_from_yaml.py index d68fcba..ed2a9c8 100644 --- a/generate_geometry_from_yaml.py +++ b/generate_geometry_from_yaml.py @@ -9,10 +9,10 @@ from ladybug_geometry.geometry3d import Face3D, Point3D -from src.idfhub.common import get_logger, eval_expr, GEOMETRY, BLOCKS, get_variables +from idfhub.common import get_logger, eval_expr, GEOMETRY, BLOCKS, get_variables -from src.idfhub.helpers.geometry import complex_room, ApertureManager, add_aperture -from src.idfhub.helpers.matlib import CONSTLIB +from idfhub.helpers.geometry import complex_room, ApertureManager, add_aperture +from idfhub.helpers.matlib import CONSTLIB LOGGER = get_logger(log_level=logging.INFO) @@ -22,10 +22,11 @@ common_height = GEOMETRY.get("height", 3) def prepare( - coordinates: list[list[float]], - variables: dict = {} + coordinates: list[list[float|str]], + *, + variables: dict ) -> list[Point3D]: - """prepare for complex_room method""" + """evaluate user formulas and prepare for complex_room method""" if len(variables) == 0: return [ Point3D(*row) @@ -39,6 +40,78 @@ def prepare( for row in coordinates ] +def dispatch_apertures( + *, + apertures: dict[str, list], + manager: ApertureManager, + destination_faces: list[Face3D] +): + """dispatch apertures on destination faces + numbers is a mandatory key in the apertures dict + """ + ap_numbers = apertures["numbers"] + try: + ap_widths = apertures["widths"] + except KeyError: + ap_widths = [] + try: + ap_heights = apertures["heights"] + except KeyError: + ap_heights = [] + try: + ap_sill_heights = apertures["sill_heights"] + except KeyError: + ap_sill_heights = [] + try: + ap_constructions = apertures["constructions"] + except KeyError: + ap_constructions = [] + try: + ap_types = apertures["types"] + except KeyError: + ap_types = [] + for destination_face in destination_faces: + j = int(destination_face.identifier.split("_")[-1]) + try: + ap_count = ap_numbers[j] + except IndexError: + continue + if ap_count == 0: + LOGGER.warning("skipping aperture on %s", destination_face) + continue + try: + ap_width = ap_widths[j] + except IndexError: + ap_width = apertures.get("width", 1.2) + try: + ap_height = ap_heights[j] + except IndexError: + ap_height = apertures.get("height", 1.3) + try: + ap_sill_height = ap_sill_heights[j] + except IndexError: + ap_sill_height = apertures.get("sill_height", 1) + try: + ap_type = ap_types[j] + except IndexError: + ap_type = apertures.get("type", "aperture") + manager.fix_dim( + width = ap_width, + height = ap_height, + sill_height = ap_sill_height + ) + try: + ap_construction_name = ap_constructions[j] + except IndexError: + ap_construction_name = apertures.get("construction") + ap_construction=CONSTLIB.get(ap_construction_name) + manager.face = destination_face + manager.set_u_v_bounds() + manager.add_from_border( + construction=ap_construction, + count=ap_count, + aperture_type=ap_type + ) for building_name, building_metadata in GEOMETRY.items(): if not isinstance(building_metadata, dict): @@ -56,19 +129,19 @@ def prepare( level_variables = get_variables(level_metadata) if level_metadata.get("use_blocks_vars", 0) == 1: level_variables = {**level_variables, **get_variables(BLOCKS)} - wall_points: list[list[float]] = [] + wall_points: list[list[float | str]] = [] for x in level_metadata["walls"]: if isinstance(x, str): if x in BLOCKS: wall_points = [*wall_points, *BLOCKS[x]] if isinstance(x, list): - wall_points = [*wall_points, x] + wall_points = [*wall_points, x[0:3]] walls = prepare(wall_points, variables=level_variables) surfaces: dict[str, list[list[Point3D]]] = {"floors": [], "roofs": []} for key, surface in surfaces.items(): if key in level_metadata: - pts: list[list[float]] = [] + pts: list[list[float | str]] = [] LOGGER.info("custom %s for %s", key, level_name) for x in level_metadata[key]: if isinstance(x, str): @@ -97,7 +170,7 @@ def prepare( identifier=level_name, floors = surfaces["floors"], roofs = surfaces["roofs"], - use_polyface = not surfaces["floors"], + use_polyface = False, remove_wall=level_metadata.get("remove_wall", []) ) building.append(level) @@ -127,9 +200,14 @@ def prepare( continue constructions = level_metadata.get("constructions", {}) level_walls: list[Wall] = [] + level_roofs: list[RoofCeiling] = [] for face in site[building_name][level_name].faces: if isinstance(face.type, Wall): - construction_name = constructions.get("walls") + number = int(face.identifier.split("_")[-1]) + try: + construction_name = level_metadata["walls"][number][3] + except IndexError: + construction_name = constructions.get("walls") construction = CONSTLIB.get(construction_name) if construction is not None: face.properties.energy.construction = construction @@ -145,86 +223,35 @@ def prepare( if isinstance(face.type, RoofCeiling): construction_name = constructions.get("roofs") construction = CONSTLIB.get(construction_name) + if face.boundary_condition == Outdoors(): + level_roofs.append(face) if construction is None: continue if face.boundary_condition != Outdoors(): LOGGER.info("Setting roof construction %s on %s", construction_name, face) face.properties.energy.construction = construction - # now we can add apertures - apertures = level_metadata.get("apertures", {}) - if "numbers" not in apertures: - LOGGER.warning("NO APERTURE ON LEVEL %s", level_name) + + # now we can add apertures and vasistas using an aperture manager + apertures_keys = ["apertures", "vasistas"] + apm = ApertureManager(site[building_name][level_name]) + windows_doors = level_metadata.get("apertures", {}) + if "numbers" not in windows_doors: + LOGGER.warning("NO WINDOW OR DOOR ON LEVEL %s", level_name) else: - numbers = apertures["numbers"] - try: - widths = apertures["widths"] - except KeyError: - widths = [] - try: - heights = apertures["heights"] - except KeyError: - heights = [] - try: - sill_heights = apertures["sill_heights"] - except KeyError: - sill_heights = [] - try: - constructions = apertures["constructions"] - except KeyError: - constructions = [] - try: - aperture_types = apertures["types"] - except KeyError: - aperture_types = [] - i = 0 - apm = ApertureManager(site[building_name][level_name]) - # we must loop on numbers and not on level_walls, as some walls may be removed - for j, count in enumerate(numbers): - if j in level_metadata.get("remove_wall", []): - continue - try: - face = level_walls[i] - except IndexError: - LOGGER.warning("no index %s in %s", i, level_walls) - continue - # we have the face, we can increment i - i+=1 - if count == 0: - LOGGER.warning("skipping aperture on %s", face) - continue - try: - width = widths[j] - except IndexError: - width = apertures.get("width", 1.2) - try: - height = heights[j] - except IndexError: - height = apertures.get("height", 1.3) - try: - sill_height = sill_heights[j] - except IndexError: - sill_height = apertures.get("sill_height", 1) - try: - aperture_type = aperture_types[j] - except IndexError: - aperture_type = apertures.get("type", "aperture") - apm.fix_dim( - width = width, - height = height, - sill_height = sill_height - ) - try: - construction_name = constructions[j] - except IndexError: - construction_name = apertures.get("construction") - construction=CONSTLIB.get(construction_name) - apm.face = face - apm.set_u_v_bounds() - apm.add_from_border( - construction=construction, - count=count, - aperture_type=aperture_type - ) + dispatch_apertures( + apertures=windows_doors, + manager=apm, + destination_faces=level_walls + ) + vasistas = level_metadata.get("vasistas", {}) + if "numbers" not in vasistas: + LOGGER.warning("NO VASISTAS ON LEVEL %s", level_name) + else: + dispatch_apertures( + apertures=vasistas, + manager=apm, + destination_faces=level_roofs + ) # now we can add single elements if any elements = level_metadata.get("elements", {}) level_variables = get_variables(level_metadata) diff --git a/generate_hvac.py b/generate_hvac.py index c853f05..f4eb8bd 100644 --- a/generate_hvac.py +++ b/generate_hvac.py @@ -320,6 +320,7 @@ def basic_zone_sizing(zone_name: str): } USE_AIR = 0 +ground_temperature() for equipment_name in EQUIPMENTS: if PUMP in equipment_name: @@ -338,7 +339,6 @@ def basic_zone_sizing(zone_name: str): equipments[equipment_name] = pipe continue if BOREHOLE in equipment_name: - ground_temperature() borehole = vertical_geoexchanger(equipment_name) equipments[equipment_name] = borehole continue diff --git a/src/idfhub/helpers/geometry.py b/src/idfhub/helpers/geometry.py index 5fc9ef8..335c4dd 100644 --- a/src/idfhub/helpers/geometry.py +++ b/src/idfhub/helpers/geometry.py @@ -61,7 +61,7 @@ def create_walls( pts: list[Point3D], height: float|list[float] = 3, remove_wall: list[int] | None = None -) -> list[Face3D]: +) -> dict[str, Face3D]: """create walls from lower and upper points""" if remove_wall is None: remove_wall = [] @@ -78,15 +78,15 @@ def create_walls( j_inds = i_inds[1:] + [0] if remove_wall: LOGGER.info("removing wall %s", remove_wall) - return [ - Face3D([ + return { + i: Face3D([ pts[i], pts[j_inds[i]], pts_u[j_inds[i]], pts_u[i] ]) for i in i_inds if i not in remove_wall - ] + } def create_floors_roofs( *, @@ -161,7 +161,7 @@ def complex_room( if use_polyface: polyface = Polyface3D.from_faces( - [*all_floors, *walls, *all_roofs], + [*all_floors, *walls.values(), *all_roofs], tolerance = TOLERANCE ) output_room = Room.from_polyface3d( @@ -172,19 +172,19 @@ def complex_room( hb_faces = [] for i, f3d in enumerate(all_floors): hb = Face( - identifier=f'{identifier}_{FLOOR}{i}', + identifier=f'{identifier}_{FLOOR}_{i}', geometry=f3d ) hb_faces.append(hb) for i, f3d in enumerate(all_roofs): hb = Face( - identifier=f'{identifier}_{ROOF}{i}', + identifier=f'{identifier}_{ROOF}_{i}', geometry=f3d ) hb_faces.append(hb) - for i, f3d in enumerate(walls): + for key, f3d in walls.items(): hb = Face( - identifier=f'{identifier}_{WALL}{i}', + identifier=f'{identifier}_{WALL}_{key}', geometry=f3d ) hb_faces.append(hb) diff --git a/src/idfhub/helpers/material.py b/src/idfhub/helpers/material.py index f191aa5..02fdb1c 100644 --- a/src/idfhub/helpers/material.py +++ b/src/idfhub/helpers/material.py @@ -6,10 +6,49 @@ from honeybee_energy.construction.opaque import OpaqueConstruction from honeybee_energy.construction.window import WindowConstruction -osb_20mm = EnergyMaterial( - identifier='OSB_20mm', +from idfhub.common import GEOMETRY + +# constantes qui peuvent être utilisées dans le yml +# pour définir des épaisseurs custom +OSB = "osb" +GLASSWOOL_OLD = "glasswool_old" +GLASSWOOL_NEW = "glasswool_new" +GLASSWOOL_2000 = "glasswool_2000" +PAREMENT = "amiante_ciment" +CONCRETE = "concrete" +PLASTER = "plaster" +AIR_CAVITY = "air_cavity" +PARPAING = "parpaing" +CUT_STONE = "cut_stone" +OLD_MASONRY = "old_masonry" +LIME_PLASTER = "lime_plaster" +GYPSUM_BOARD = "gypsum_board" + +# épaisseurs par défaut +THICKNESS = { + OSB: 0.02, # 2 cm + GLASSWOOL_OLD: 0.1, + GLASSWOOL_NEW: 0.12, + GLASSWOOL_2000: 0.1, + PAREMENT: 0.02, + CONCRETE: 0.2, + PLASTER: 0.02, + AIR_CAVITY: 0.05, + PARPAING: 0.1, + CUT_STONE: 0.6, + OLD_MASONRY: 0.4, + LIME_PLASTER: 0.02, + GYPSUM_BOARD: 0.0127, +} + +def thickness(name): + """fetch the thickness from the yml conf""" + return GEOMETRY.get(name, THICKNESS[name]) + +osb = EnergyMaterial( + identifier=OSB, roughness='MediumRough', - thickness=0.020, # 2 cm + thickness=thickness(OSB), conductivity=0.13, # W/m·K density=600, # kg/m3 specific_heat=1600 # J/kg·K @@ -17,46 +56,46 @@ # Laine de verre ancienne (années 70–90) glasswool_old = EnergyMaterial( - identifier='GlassWool_Old', + identifier=GLASSWOOL_OLD, roughness='Rough', - thickness=0.100, + thickness=thickness(GLASSWOOL_OLD), conductivity=0.050, density=18, specific_heat=840 ) # laine de verre récente glasswool_new = EnergyMaterial( - identifier='GlassWool_New', + identifier=GLASSWOOL_NEW, roughness='MediumRough', - thickness=0.120, + thickness=thickness(GLASSWOOL_NEW), conductivity=0.032, density=12, specific_heat=840 ) # laine de verre des années 2000 glasswool_2000 = EnergyMaterial( - identifier='GlassWool_2000', + identifier=GLASSWOOL_2000, roughness='MediumRough', - thickness=0.100, + thickness=thickness(GLASSWOOL_2000), conductivity=0.040, density=15, specific_heat=840 ) # parement extérieur en amiante parement = EnergyMaterial( - identifier='AmianteCiment_20mm', + identifier=PAREMENT, roughness='Rough', - thickness=0.020, + thickness=thickness(PAREMENT), conductivity=0.35, density=1600, specific_heat=900 ) # plancher en béton -concrete_200mm = EnergyMaterial( - identifier='Concrete_200mm', +concrete = EnergyMaterial( + identifier=CONCRETE, roughness='MediumRough', - thickness=0.20, + thickness=thickness(CONCRETE), conductivity=1.75, density=2300, specific_heat=900 @@ -64,29 +103,93 @@ # éléments pour un mur en parpaing creux plaster = EnergyMaterial( - identifier='Plaster_2cm', - thickness=0.02, + identifier=PLASTER, + thickness=thickness(PLASTER), conductivity=0.7, density=900, specific_heat=1000 ) air_cavity = EnergyMaterial( - identifier='AirCavity_5cm', - thickness=0.05, + identifier=AIR_CAVITY, + thickness=thickness(AIR_CAVITY), conductivity=0.025, # conductivité de l’air density=1.2, specific_heat=1005 ) parpaing = EnergyMaterial( - identifier='Parpaing_10cm', - thickness=0.10, + identifier=PARPAING, + thickness=thickness(PARPAING), conductivity=0.72, density=800, specific_heat=840 ) +# pierre de taille +cut_stone = EnergyMaterial( + identifier=CUT_STONE, + roughness="Rough", + thickness=thickness(CUT_STONE), + conductivity=1.5, + density=2100, + specific_heat=900, + thermal_absorptance=0.9, + solar_absorptance=0.6, + visible_absorptance=0.6 +) + +# maçonnerie ancienne +old_masonry = EnergyMaterial( + identifier=OLD_MASONRY, + roughness="Rough", + thickness=thickness(OLD_MASONRY), + conductivity=1, + density=1700, + specific_heat=900, + thermal_absorptance=0.9, + solar_absorptance=0.7, + visible_absorptance=0.7 +) + +# enduit à la chaux +lime_plaster = EnergyMaterial( + identifier=LIME_PLASTER, + roughness="Smooth", + thickness=thickness(LIME_PLASTER), + conductivity=0.80, + density=1700, + specific_heat=1000, + thermal_absorptance=0.90, + solar_absorptance=0.30, + visible_absorptance=0.30 +) + +# plaque au platre +gypsum_board = EnergyMaterial( + identifier="gypsum_board", + thickness=0.0127, + conductivity=0.16, + density=800, + specific_heat=1090, + thermal_absorptance=0.9, + solar_absorptance=0.5, + visible_absorptance=0.5 +) + +# tuile +roman_tile = EnergyMaterial( + identifier="roman_tile", + roughness="MediumRough", + thickness=0.02, + conductivity=0.85, + density=1900, + specific_heat=840, + thermal_absorptance=0.90, + solar_absorptance=0.75, + visible_absorptance=0.75 +) + # verre glass = EnergyWindowMaterialGlazing( @@ -99,7 +202,7 @@ emissivity_back=0.84 ) -glass_2cm = EnergyWindowMaterialGlazing( +thick_glass_for_wall = EnergyWindowMaterialGlazing( identifier="thick_glass_for_wall", thickness=0.02, # 2 cm conductivity=1.0, # verre @@ -110,12 +213,30 @@ ) gap = EnergyWindowMaterialGas( - identifier="air_gap_16mm", + identifier="air_gap", gas_type="Air", thickness=0.016 ) # compositions +# outside layer always first +townhouse_basement = OpaqueConstruction( + identifier="townhouse_basement", + materials=[ + cut_stone + ] +) + +recent_light_renovation = OpaqueConstruction( + identifier="recent_light_renovation", + materials=[ + osb, + glasswool_new, + gypsum_board + ] +) + + wall_parpaing = OpaqueConstruction( identifier='wall_parpaing', materials=[ @@ -127,28 +248,28 @@ ) wall_osb = OpaqueConstruction( - identifier='Wall_osb', + identifier='wall_osb', materials=[ - osb_20mm, + parement, glasswool_old, - parement + osb ] ) floor_internal = OpaqueConstruction( - identifier='Floor_Internal', + identifier='floor_internal', materials=[ - concrete_200mm + concrete ] ) window_pvc = WindowConstruction( - identifier="Fenetre_PVC_Double", + identifier="fenetre_pvc_double", materials=[glass, gap, glass] ) simple_glass_wall = WindowConstruction( - identifier="paroi murale simple vitrage", - materials=[glass_2cm] + identifier="mur_simple_vitrage", + materials=[thick_glass_for_wall] ) diff --git a/src/idfhub/helpers/materials.xlsx b/src/idfhub/helpers/materials.xlsx index 7b7cd0bce9de77221b1aef3da1b549f311ad002b..a6c95884f3a4160fe30778296a6560a832a0a942 100644 GIT binary patch delta 5889 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 diff --git a/src/idfhub/helpers/matlib.py b/src/idfhub/helpers/matlib.py index 16988cf..6df3d6f 100644 --- a/src/idfhub/helpers/matlib.py +++ b/src/idfhub/helpers/matlib.py @@ -3,7 +3,8 @@ from .material import ( wall_osb, wall_parpaing, floor_internal, - window_pvc, simple_glass_wall + window_pvc, simple_glass_wall, + townhouse_basement, recent_light_renovation ) CONSTLIB = { @@ -11,5 +12,7 @@ "wall_parpaing": wall_parpaing, "floor_internal": floor_internal, "window_pvc": window_pvc, - "simple_glass_wall": simple_glass_wall + "simple_glass_wall": simple_glass_wall, + "townhouse_basement": townhouse_basement, + "recent_light_renovation": recent_light_renovation }