# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2022 @Andrej730
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on.  If not, see <http://www.gnu.org/licenses/>.

# fmt: off
# pylint: skip-file

import ifcopenshell
import ifcopenshell.api
import boltspy as bolts
from math import cos, tan, pi
from pathlib import Path
from mathutils import Vector
from itertools import chain

from ifcopenshell.util.shape_builder import ShapeBuilder

V = lambda *x: Vector([float(i) for i in x])

def get_simple_2dcurve_data(coords, fillets=[], fillet_radius=[], closed=True, ifc_file=None):
    """
    Creates simple 2D curve from set of 2d coords and list of points with fillets.
    Simple curve means that all fillets are based on 90 degree angle.

    > coords:        list of 2d coords. Example: ((x0,y0), (x1,y1), (x2, y2))
    > fillets:       list of points from `coords` to base fillet on. Example: (1,)
    > fillet_radius: list of fillet radius for each of corresponding point form `fillets`. Example: (5.,)
                        Note: filler_radius could be just 1 float value if it's the same for all fillets.

    Optional arguments:
    > closed:        boolean whether curve should be closed (whether last point connected to first one). Default: True
    > ifc_file:      ifc file to create IfcIndexedPolyCurve for the function output

    < returns (points, segments, ifc_curve) for the created simple curve
    if both points in e are equally far from pt, then v1 is returned."""
    
    # option to use same fillet radius for all fillets
    if isinstance(fillet_radius, float):
        fillet_radius = [fillet_radius] * len(fillets)
    
    fillets = dict(zip(fillets, fillet_radius))
    segments = []
    points = []
    for co_i, co in enumerate(coords, 0):
        current_point = len(points)
        if co_i in fillets:

            r = fillets[co_i]
            rsb = r * cos(pi/4) # radius shift big
            rss = r - rsb # radius shift small
            
            next_co = coords[(co_i+1) % len(coords)]
            previous_co = coords[co_i-1]

            # identify fillet type (1 of 4 possible types)
            x_direction = 1 if coords[co_i][0] < previous_co[0] or coords[co_i][0] < next_co[0] else -1
            y_direction = 1 if coords[co_i][1] < previous_co[1] or coords[co_i][1] < next_co[1] else -1

            xshift_point = (co[0] + r * x_direction, co[1])
            middle_point = (co[0] + rss * x_direction, co[1] + rss * y_direction)
            yshift_point = (co[0], co[1] + r * y_direction)

            # identify fillet direction
            if co[1] == previous_co[1]:
                points.extend( (xshift_point, middle_point, yshift_point) )
            else:
                points.extend( (yshift_point, middle_point, xshift_point) )
            
            segments.append(  [current_point-1, current_point] )
            segments.append( [current_point, current_point+1, current_point+2] )
        else:
            points.append( co )
            if co_i != 0:
                segments.append( [current_point-1, current_point] )

    if closed:
        segments.append( [0, len(points)-1] )

    # replace negative index
    if segments[0][0] == -1:
        segments[0][0] = len(points) - 1

    # sometime fillet points could match previous or next points in line
    # I remove them at the end to avoid making fillet algorithm even less readable
    points, segments = remove_redundant_points(points, segments)
    ifc_curve = None
    if ifc_file:
        ifc_points = ifc_file.createIfcCartesianPointList2D(points)
        ifc_segments = []
        for segment in segments:
            segment = [i+1 for i in segment]
            if len(segment) == 2:
                ifc_segments.append( ifc_file.createIfcLineIndex( segment ))
            elif len(segment) == 3:
                ifc_segments.append( ifc_file.createIfcArcIndex( segment ))

        ifc_curve = ifc_file.createIfcIndexedPolyCurve(Points=ifc_points, Segments=ifc_segments)
    return (points, segments, ifc_curve)

def remove_redundant_points(points, segments):
    # prevent mutating
    points = [tuple(p) for p in points]
    segments = segments.copy()

    # find duplicate points, reindex them in segments
    # and mark them to delete later
    points_to_remove = []
    prev_point = 0
    for i, p in enumerate(points[1:], 1):
        if p != points[prev_point]:
            prev_point = i
            continue

        valid_segments = []
        for s in segments:
            s = [ps if ps != i else prev_point for ps in s]
            valid_segments.append(s)
        segments = valid_segments
        points_to_remove.append(i)          

    # remove duplicate segments
    valid_segments = [segment for segment in segments if len(set(segment)) != 1]
    points = [point for i, point in enumerate(points) if i not in points_to_remove]   
    # correct the order in segments
    unique_points = sorted(set(chain(*valid_segments)))
    unique_points_translation = {prev: i for i, prev in enumerate(unique_points)}
    valid_segments = [[unique_points_translation[p] for p in s] for s in valid_segments]

    return points, valid_segments



def create_z_profile_lips_curve(ifc_file, FirstFlangeWidth, SecondFlangeWidth, Depth, Girth, WallThickness, FilletRadius):
    x1 = FirstFlangeWidth
    x2 = SecondFlangeWidth
    y = Depth / 2
    g = Girth
    t = WallThickness
    r = FilletRadius

    coords = (
        (-t/2,   y),
        (x2,     y),
        (x2,     y-g),
        (x2-t,   y-g),
        (x2-t,   y-t),
        (t/2,    y-t),
        (t/2,   -y),
        (-x1,   -y),
        (-x1,   -y+g),
        (-x1+t, -y+g),
        (-x1+t, -y+t),
        (-t/2,  -y+t)
    )

    # no additional thickness in outer radius option
    # points, segments, ifc_curve = create_curve_from_coords(coords, fillets = (0, 1, 4, 5, 6, 7, 10, 11), fillet_radius=r, closed=True, ifc_file=ifc_file)
    points, segments, ifc_curve = get_simple_2dcurve_data(coords, 
        fillets =     (0,   1,   4, 5, 6,   7,   10, 11), 
        fillet_radius=(r+t, r+t, r, r, r+t, r+t, r, r), 
        closed=True, ifc_file=ifc_file)

    return ifc_curve

def create_transition_arc_ifc(width, height, ifc_file=None):
    # create an arc in the rectangle with specified width and height
    # if it's not possible to make a complete arc
    # it will create arc with longest radius possible
    # and straight segment in the middle
    fillet_size = (width / 2) / height
    if fillet_size <= 1:
        fillet_radius = height*fillet_size
        curve_coords = [
            (0., 0.),
            (0., height),
            (width*0.5, height),
            (width, height),
            (width, 0.),
        ]
        fillets = (1, 3)
    else:
        fillet_radius = height
        curve_coords = [
            (0., 0.0),
            (0., height),
            (fillet_radius, height),
            (width-fillet_radius, height),
            (width, height),
            (width, 0.0),
        ]
        fillets = (1, 4)
    points, segments, transition_arc = get_simple_2dcurve_data(
        curve_coords, fillets, fillet_radius, closed=False, ifc_file=ifc_file)
    return points, segments, transition_arc

class LibraryGenerator:
    def generate(self, output_filename="IFC4 EU Steel.ifc"):
        ifcopenshell.api.pre_listeners = {}
        ifcopenshell.api.post_listeners = {}

        self.materials = {}

        self.file = ifcopenshell.api.run("project.create_file")
        self.project = ifcopenshell.api.run(
            "root.create_entity", self.file, ifc_class="IfcProject", name=f"Non-structural assets library"
        )
        self.library = ifcopenshell.api.run(
            "root.create_entity", self.file, ifc_class="IfcProjectLibrary", name=f"Non-structural assets library"
        )
        ifcopenshell.api.run(
            "project.assign_declaration", self.file, definition=self.library, relating_context=self.project
        )
        unit = ifcopenshell.api.run("unit.add_si_unit", self.file, unit_type="LENGTHUNIT", name="METRE", prefix="MILLI")
        ifcopenshell.api.run("unit.assign_unit", self.file, units=[unit])

        model = ifcopenshell.api.run("context.add_context", self.file, context_type="Model")
        plan = ifcopenshell.api.run("context.add_context", self.file, context_type="Plan")
        self.representations = {
            "body": ifcopenshell.api.run(
                "context.add_context",
                self.file,
                context_type="Model",
                context_identifier="Body",
                target_view="MODEL_VIEW",
                parent=model,
            ),
            "annotation": ifcopenshell.api.run(
                "context.add_context",
                self.file,
                context_type="Plan",
                context_identifier="Annotation",
                target_view="PLAN_VIEW",
                parent=plan,
            ),
        }

        builder = ShapeBuilder(self.file)

        def create_box_objects(width, depth, height, 
            shift_to_center=False, return_representations=False):
            output = []

            rectangle = builder.rectangle(size=V(width, depth))
            box = builder.extrude(builder.profile(rectangle), height)
            
            if return_representations:
                representation_3d = builder.get_representation(
                    context=self.representations['body'],
                    items=box
                )
                output.append(representation_3d)
            else:
                output.append([box])

            rectangle = builder.rectangle(size=V(width, depth))
            if return_representations:
                representation_2d = builder.get_representation(self.representations["annotation"], rectangle)
                output.append(representation_2d)
            else:
                output.append([rectangle])

            if shift_to_center:
                shift_to_center = V(-width/2, -depth/2)
                builder.translate(output[0], shift_to_center.to_3d())
                builder.translate(output[1], shift_to_center)

            return output

        def create_fillet_rectangle(size=None, position=None, fillet_radius=50.):
            kwargs = dict()
            if size:
                kwargs['size'] = size
            if position:
                kwargs['position'] = position
            
            _, _, rectangle = get_simple_2dcurve_data(
                coords=builder.get_rectangle_coords(**kwargs),
                fillets=(0,1,2,3),
                fillet_radius=fillet_radius,
                closed=True,
                ifc_file=self.file
            )
            return rectangle


        # beds
        representation_3d, representation_2d = create_box_objects(680., 1300., 550., return_representations=True)
        self.create_explicit_type('IfcFurnitureType', "Cot mattress", representation_3d, representation_2d)

        def create_fancy_bed_representations(overall_width, overall_depth, overall_height, blanket_tan = tan(pi/8)):
            # 0.85h - blanket starts
            # 0.7w - blanket shifts
            # 0.25h - blanket shift ends
            # o.2w - pillow starts
            bed = builder.rectangle(size=V(overall_width, overall_depth))
            blanket_pillow = builder.polyline((
                [0., overall_depth*0.85],
                # pillow
                [overall_width*0.2, overall_depth*0.85],
                [overall_width*0.2, overall_depth],
                [overall_width*0.2, overall_depth*0.85],

                [overall_width*0.7, overall_depth*0.85],

                # pillow
                [overall_width*0.8, overall_depth*0.85-overall_width*(0.8-0.7)*blanket_tan],
                [overall_width*0.8, overall_depth],
                [overall_width*0.8, overall_depth*0.85-overall_width*(0.8-0.7)*blanket_tan],

                [overall_width, overall_depth*0.85-overall_width*(1-0.7)*blanket_tan],
            ))
            representation_2d = builder.get_representation(self.representations["annotation"], [bed, blanket_pillow])

            curve = builder.rectangle(size=V(overall_width, overall_depth))
            mesh = builder.extrude(builder.profile(curve), overall_height)
            representation_3d = builder.get_representation(
                context=self.representations['body'],
                items=mesh
            ) 
            return representation_3d, representation_2d

        representation_3d, representation_2d = create_fancy_bed_representations(920., 1880., 550.)
        self.create_explicit_type('IfcFurnitureType', "Single bed", representation_3d, representation_2d)
        representation_3d, representation_2d = create_fancy_bed_representations(920., 2030., 550.)
        self.create_explicit_type('IfcFurnitureType', "Long single bed", representation_3d, representation_2d)
        representation_3d, representation_2d = create_fancy_bed_representations(1070., 2030., 550.)
        self.create_explicit_type('IfcFurnitureType', "King single bed", representation_3d, representation_2d)
        representation_3d, representation_2d = create_fancy_bed_representations(1380., 1880., 550.)
        self.create_explicit_type('IfcFurnitureType', "Double bed", representation_3d, representation_2d)
        representation_3d, representation_2d = create_fancy_bed_representations(1530., 2030., 550.)
        self.create_explicit_type('IfcFurnitureType', "Queen bed", representation_3d, representation_2d)
        representation_3d, representation_2d = create_fancy_bed_representations(1830., 2030., 550.)
        self.create_explicit_type('IfcFurnitureType', "King bed", representation_3d, representation_2d)
        representation_3d, representation_2d = create_fancy_bed_representations(2030., 2030., 550.)
        self.create_explicit_type('IfcFurnitureType', "Super King bed", representation_3d, representation_2d)

        # wardrobes
        def create_fancy_wardrobe_representations(overall_width, overall_depth, overall_height):
            curve = builder.rectangle(size=V(overall_width, overall_depth))
            mesh = builder.extrude(builder.profile(curve), overall_height)
            representation_3d = builder.get_representation(
                context=self.representations['body'],
                items=mesh
            ) 

            rect = builder.rectangle(size=V(overall_width, overall_depth))
            cloth = []

            # 1 cloth every 200 mm
            # cloth step = 200m
            # cloth height offset = 0.1h
            # cloth width offset = 0.5step
            cloths_amount = int(overall_width)//200 + ((int(overall_width) % 200) > 200*(0.5+0.1+0.05))
            for i in range(cloths_amount):
                tilt_offset = 0 if i % 2 == 0 else 200*0.2
                c = builder.polyline([
                    (i * 200 + 200*0.5 - tilt_offset, 0.1*overall_depth),
                    (i * 200 + 200*0.5 + tilt_offset, (1-0.1)*overall_depth),
                ])
                cloth.append(c)
            representation_2d = builder.get_representation(self.representations["annotation"], 
                items=[rect, *cloth]
            )
            return representation_3d, representation_2d
        
        representation_3d, representation_2d = create_fancy_wardrobe_representations(1800., 600., 2100.)
        self.create_explicit_type('IfcFurnitureType', "ADG Main Bedroom Wardrobe", representation_3d, representation_2d)

        representation_3d, representation_2d = create_fancy_wardrobe_representations(1000., 600., 2500.)
        self.create_explicit_type('IfcFurnitureType', "Neufert Single Person Wardrobe", representation_3d, representation_2d)

        # chairs
        def create_fancy_chair(width, depth, height, seat_level, 
            return_representations=False):
            thickness = 50.
            shift_to_center = V(-width/2, 0)
            mirror_axis = V(0, 1)
            output = []

            seat = builder.rectangle(size=V(width, depth))
            chair_back = builder.rectangle(size=V(width, thickness))
            leg = builder.rectangle(size=V(thickness, thickness))
            legs = [leg] + builder.mirror(
                leg, 
                mirror_axes=[V(1, 0), V(0, 1), V(1, 1)], 
                mirror_point=V(width / 2, depth / 2), 
                create_copy=True
            )
            builder.translate([seat, chair_back] + legs, shift_to_center)
            builder.mirror([seat, chair_back] + legs, mirror_axis)
            seat = builder.extrude(builder.profile(seat), thickness, 
                position=V(0, 0, seat_level),
                extrusion_vector=V(0, 0, -1))
            chair_back = builder.extrude(builder.profile(chair_back), height-seat_level,
                position=V(0, 0, height),
                extrusion_vector=V(0, 0, -1)
            )
            legs = [builder.extrude(leg, seat_level-thickness) for leg in legs]

            items_3d = [seat, chair_back] + legs
            if return_representations:
                representation_3d = builder.get_representation(
                    context=self.representations['body'],
                    items=items_3d
                )
                output.append(representation_3d)
            else:
                output.append(items_3d)

            second_arc_depth = depth*(1-0.10)
            second_arc_width = width - (depth-second_arc_depth)*2
            polyline = builder.polyline( (V(0,0), V(width, 0) ) )
            _, _, first_semicircle = create_transition_arc_ifc(
                width, depth, ifc_file=self.file)

            _, _, second_semicircle = create_transition_arc_ifc(
                second_arc_width, second_arc_depth, ifc_file=self.file)
            builder.translate(second_semicircle, V((width-second_arc_width)/2, 0))

            builder.translate([polyline, first_semicircle, second_semicircle], shift_to_center)
            builder.translate([polyline, first_semicircle, second_semicircle], V(0, -depth))

            items_2d = [polyline, first_semicircle, second_semicircle]

            if return_representations:
                representation_2d = builder.get_representation(
                    self.representations["annotation"],
                    items_2d
                )
                output.append(representation_2d)
            else:
                output.append(items_2d)

            return output


        representation_3d, representation_2d = create_fancy_chair(600., 400., 900., 450., True)
        self.create_explicit_type('IfcFurnitureType', "Neufert Retail Dining Chair", representation_3d, representation_2d)

        representation_3d, representation_2d = create_fancy_chair(300., 300., 800., 800., True)
        self.create_explicit_type('IfcFurnitureType', "Neufert Retail Bar Stool", representation_3d, representation_2d)

        # table
        representation_3d, representation_2d = create_box_objects(
            700., 500., 550.,
            shift_to_center=True, return_representations=True)
        self.create_explicit_type('IfcFurnitureType', "Generic Large Bedside Table", representation_3d, representation_2d)

        representation_3d, representation_2d = create_box_objects(
            600., 450., 550., 
            shift_to_center=True, return_representations=True)
        self.create_explicit_type('IfcFurnitureType', "Generic Medium Bedside Table", representation_3d, representation_2d)

        representation_3d, representation_2d = create_box_objects(
            500., 380., 500., 
            shift_to_center=True, return_representations=True)
        self.create_explicit_type('IfcFurnitureType', "Generic Small Bedside Table", representation_3d, representation_2d)


        def create_fancy_rectangle_table(width, depth, height, return_representations=False):
            output = []
            leg_size = 50.
            countertop_thickness = 50.
            shift_to_center = V(-width/2, -depth/2)
            rectangle = builder.rectangle(size=V(width, depth))
            countertop = builder.extrude(
                builder.profile(rectangle), countertop_thickness, 
                V(0, 0, height-countertop_thickness))

            leg_curve = builder.rectangle(size=V(leg_size, leg_size))
            legs_curves = [leg_curve] + builder.mirror(
                leg_curve,
                mirror_axes=[V(1, 0), V(0, 1), V(1, 1)],
                mirror_point=V(width/2, depth/2),
                create_copy=True)
            legs_profiles = [builder.profile(leg) for leg in legs_curves]
            legs = [builder.extrude(leg, height-countertop_thickness) for leg in legs_profiles]
            items = [countertop] + legs
            builder.translate(items, shift_to_center.to_3d())

            if return_representations:
                representation_3d = builder.get_representation(
                    context=self.representations['body'],
                    items=items
                )
                output.append(representation_3d)
            else:
                output.append(items)

            rectangle = builder.rectangle(position=shift_to_center, size=V(width, depth))
            if return_representations:
                representation_2d = builder.get_representation(self.representations["annotation"], rectangle)
                output.append(representation_2d)
            else:
                output.append([rectangle])

            return output

        representation_3d, representation_2d = create_fancy_rectangle_table(750., 750., 450., True)
        self.create_explicit_type('IfcFurnitureType', "Generic Medium Coffee Table", representation_3d, representation_2d)


        # tables
        def create_rectangle_table_with_chairs(
            table_width, table_depth, table_height,
            chair_width, chair_depth, chair_height, seat_level,
            table_chair_gap, number_of_seats, side_seats = False
        ):
            items_table_3d, items_table_2d = create_fancy_rectangle_table(table_width, table_depth, table_height)
            items_chair_3d, items_chair_2d = create_fancy_chair(chair_width, chair_depth, chair_height, seat_level)

            if side_seats:
                number_of_seats -= 2
            seats_per_side = number_of_seats // 2
            per_chair_gap = (table_width - chair_width * seats_per_side)/(seats_per_side+1)

            # 3d representation
            items_chairs_3d = []
            if side_seats:
                items_chair_3d_transformed = builder.rotate(items_chair_3d, angle=90, create_copy=True)
                builder.translate(items_chair_3d_transformed, V(table_width/2+table_chair_gap+chair_depth, 0, 0))
                items_chairs_3d.append(items_chair_3d_transformed)
                items_chairs_3d.append(builder.mirror(items_chair_3d_transformed, mirror_axes=V(1, 0), create_copy=True))

            # create_copy condiition is needed to avoid having one unused chair in ifc
            for i in range(seats_per_side):
                coords = V(
                    -table_width/2 + per_chair_gap + i*(chair_width+per_chair_gap) + chair_width/2, 
                        table_depth/2+table_chair_gap+chair_depth, 0)
                cur_items_chair_3d = builder.translate(
                    items_chair_3d,
                    coords, 
                    create_copy = i != seats_per_side - 1)
                items_chairs_3d.append(cur_items_chair_3d)
                items_chairs_3d.append(builder.mirror(cur_items_chair_3d, mirror_axes=V(0, 1), create_copy=True))

            items_chairs_3d = list(chain(*items_chairs_3d))
            representation_3d = builder.get_representation(
                self.representations["body"], items=items_table_3d+items_chairs_3d)

            # 2d representation
            items_chairs_2d = []
            if side_seats:
                items_chair_2d_transformed = builder.rotate(items_chair_2d, angle=90, create_copy=True)
                builder.translate(items_chair_2d_transformed, V(table_width/2+table_chair_gap+chair_depth, 0))
                items_chairs_2d.append(items_chair_2d_transformed)
                items_chairs_2d.append(builder.mirror(items_chair_2d_transformed, mirror_axes=V(1, 0), create_copy=True))

            # create_copy condiition is needed to avoid having one unused chair in ifc
            for i in range(seats_per_side):
                coords = V(
                    -table_width/2 + per_chair_gap + i*(chair_width+per_chair_gap) + chair_width/2, 
                        table_depth/2+table_chair_gap+chair_depth)
                cur_items_chair_2d = builder.translate(
                    items_chair_2d,
                    coords, 
                    create_copy = i != seats_per_side - 1)
                items_chairs_2d.append(cur_items_chair_2d)
                items_chairs_2d.append(builder.mirror(cur_items_chair_2d, mirror_axes=V(0, 1), create_copy=True))

            items_chairs_2d = list(chain(*items_chairs_2d))

            representation_2d = builder.get_representation(
                self.representations["annotation"], items=items_table_2d+items_chairs_2d)

            return representation_3d, representation_2d

        # short 4-seater table
        overall_width, overall_depth, overall_height = (1750., 1750., 900.)
        table_width, table_depth, table_height = (850., 850., 780.)
        table_chair_gap = 50.
        chair_width, chair_depth, chair_height, seat_level = (400., 400., 900., 450.)
        representation_3d, representation_2d = create_rectangle_table_with_chairs(
            table_width, table_depth, table_height,
            chair_width, chair_depth, chair_height, seat_level,
            table_chair_gap, number_of_seats = 4, side_seats=True
        )
        self.create_explicit_type('IfcFurnitureType', "Neufert Retail 4-Seater Square Table", 
            representation_3d, representation_2d)

        # wide 4-seater table
        overall_width, overall_depth, overall_height = (1300., 2000., 900.)
        table_width, table_depth, table_height = (1300., 800., 780.)
        table_chair_gap = 50.
        chair_width, chair_depth, chair_height, seat_level = (450., 450., 900., 450.)
        representation_3d, representation_2d = create_rectangle_table_with_chairs(
            table_width, table_depth, table_height,
            chair_width, chair_depth, chair_height, seat_level,
            table_chair_gap, number_of_seats = 4, side_seats=False
        )

        self.create_explicit_type('IfcFurnitureType', "Neufert Residential 4-Seater Rectangular Table", 
            representation_3d, representation_2d)

        # 2-seater table
        overall_width, overall_depth, overall_height = (625., 1750., 900.)
        table_width, table_depth, table_height = (625., 800., 780.)
        table_chair_gap = 50.
        chair_width, chair_depth, chair_height, seat_level = (400., 400., 900., 450.)
        representation_3d, representation_2d = create_rectangle_table_with_chairs(
            table_width, table_depth, table_height,
            chair_width, chair_depth, chair_height, seat_level,
            table_chair_gap, number_of_seats = 2
        )

        self.create_explicit_type('IfcFurnitureType', "Neufert Retail 2-Seater Rectangular Table", 
            representation_3d, representation_2d)

        # 4-seater table
        overall_width, overall_depth, overall_height = (1250., 1750., 900.)
        table_width, table_depth, table_height = (1250., 800., 780.)
        table_chair_gap = 50.
        chair_width, chair_depth, chair_height, seat_level = (400., 400., 900., 450.)
        representation_3d, representation_2d = create_rectangle_table_with_chairs(
            table_width, table_depth, table_height,
            chair_width, chair_depth, chair_height, seat_level,
            table_chair_gap, number_of_seats = 4
        )

        self.create_explicit_type('IfcFurnitureType', "Neufert Retail 4-Seater Rectangular Table", 
            representation_3d, representation_2d)

        # 6-seater table
        overall_width, overall_depth, overall_height = (1875., 1750., 900.)
        table_width, table_depth, table_height = (1875., 800., 780.)
        table_chair_gap = 50.
        chair_width, chair_depth, chair_height, seat_level = (400., 400., 900., 450.)
        representation_3d, representation_2d = create_rectangle_table_with_chairs(
            table_width, table_depth, table_height,
            chair_width, chair_depth, chair_height, seat_level,
            table_chair_gap, number_of_seats = 6
        )

        self.create_explicit_type('IfcFurnitureType', "Neufert Retail 6-Seater Rectangular Table", 
            representation_3d, representation_2d)

        # 8-seater table
        overall_width, overall_depth, overall_height = (2500., 1750., 900.)
        table_width, table_depth, table_height = (2500., 800., 780.)
        table_chair_gap = 50.
        chair_width, chair_depth, chair_height, seat_level = (400., 400., 900., 450.)
        representation_3d, representation_2d = create_rectangle_table_with_chairs(
            table_width, table_depth, table_height,
            chair_width, chair_depth, chair_height, seat_level,
            table_chair_gap, number_of_seats = 8
        )

        self.create_explicit_type('IfcFurnitureType', "Neufert Retail 8-Seater Rectangular Table", 
            representation_3d, representation_2d)

        # 12-seater table
        overall_width, overall_depth, overall_height = (3750., 1750., 900.)
        table_width, table_depth, table_height = (3750., 800., 780.)
        table_chair_gap = 50.
        chair_width, chair_depth, chair_height, seat_level = (400., 400., 900., 450.)
        representation_3d, representation_2d = create_rectangle_table_with_chairs(
            table_width, table_depth, table_height,
            chair_width, chair_depth, chair_height, seat_level,
            table_chair_gap, number_of_seats = 12
        )

        self.create_explicit_type('IfcFurnitureType', "Neufert Retail 12-Seater Rectangular Table", 
            representation_3d, representation_2d)

        def create_fancy_circular_table(diameter, height, return_representations=False):
            radius = diameter / 2
            width, depth = radius, radius
            output = []
            leg_size = 50.
            countertop_thickness = 50.
            shift_to_center = V(-width/2, -depth/2)
            circle = builder.circle(radius=radius)
            countertop = builder.extrude(
                builder.profile(circle), countertop_thickness, 
                V(0, 0, height-countertop_thickness))

            leg_curve = builder.circle(radius=leg_size)
            leg = builder.extrude(builder.profile(leg_curve), height-countertop_thickness)
            items = [countertop, leg]

            if return_representations:
                representation_3d = builder.get_representation(
                    context=self.representations['body'],
                    items=items
                )
                output.append(representation_3d)
            else:
                output.append(items)

            circle = builder.circle(radius=radius)
            if return_representations:
                representation_2d = builder.get_representation(self.representations["annotation"], circle)
                output.append(representation_2d)
            else:
                output.append([circle])

            return output

        def create_circular_table_with_chairs(
            table_diameter, table_height,
            chair_width, chair_depth, chair_height, seat_level,
            table_chair_gap, number_of_seats, side_seats = False
        ):
            items_table_3d, items_table_2d = create_fancy_circular_table(table_diameter, table_height)
            items_chair_3d, items_chair_2d = create_fancy_chair(chair_width, chair_depth, chair_height, seat_level)

            per_chair_angle = 360 / number_of_seats
            table_radius = table_diameter / 2

            # 3d rerpresentation
            items_chairs_3d = []

            # create_copy condiition is needed to avoid having one unused chair in ifc
            for i in range(number_of_seats):
                last_seat = i == number_of_seats - 1
                cur_items_chair_3d = builder.translate(
                    items_chair_3d,
                    V(0, table_radius + table_chair_gap + chair_depth, 0),
                    create_copy=not last_seat)
                builder.rotate(
                    cur_items_chair_3d,
                    per_chair_angle*i)
                items_chairs_3d.append(cur_items_chair_3d)

            items_chairs_3d = list(chain(*items_chairs_3d))
            representation_3d = builder.get_representation(
                self.representations["body"], items=items_table_3d+items_chairs_3d)

            # 2d representation
            items_chairs_2d = []

            # create_copy condiition is needed to avoid having one unused chair in ifc
            for i in range(number_of_seats):
                last_seat = i == number_of_seats - 1
                cur_items_chair_2d = builder.translate(
                    items_chair_2d,
                    V(0, table_radius + table_chair_gap + chair_depth),
                    create_copy=not last_seat)
                builder.rotate(
                    cur_items_chair_2d,
                    per_chair_angle*i)
                items_chairs_2d.append(cur_items_chair_2d)

            items_chairs_2d = list(chain(*items_chairs_2d))

            representation_2d = builder.get_representation(
                self.representations["annotation"], items=items_table_2d+items_chairs_2d)

            return representation_3d, representation_2d

        # circular 2-seater table
        overall_width, overall_depth, overall_height = (1500., 1500., 900.)
        table_diameter, table_height = (600., 780.)
        table_chair_gap = 50.
        chair_width, chair_depth, chair_height, seat_level = (400., 400., 900., 450.)
        representation_3d, representation_2d = create_circular_table_with_chairs(
            table_diameter, table_height,
            chair_width, chair_depth, chair_height, seat_level,
            table_chair_gap, number_of_seats = 2
        )
        self.create_explicit_type('IfcFurnitureType', "Neufert Retail 2-Seater Circular Table", 
            representation_3d, representation_2d)

        # circular 3-seater table
        overall_width, overall_depth, overall_height = (1700., 1700., 900.)
        table_diameter, table_height = (800., 780.)
        table_chair_gap = 50.
        chair_width, chair_depth, chair_height, seat_level = (400., 400., 900., 450.)
        representation_3d, representation_2d = create_circular_table_with_chairs(
            table_diameter, table_height,
            chair_width, chair_depth, chair_height, seat_level,
            table_chair_gap, number_of_seats = 3
        )
        self.create_explicit_type('IfcFurnitureType', "Neufert Retail 3-Seater Circular Table", 
            representation_3d, representation_2d)

        # circular 4-seater table
        overall_width, overall_depth, overall_height = (1800., 1800., 900.)
        table_diameter, table_height = (900., 780.)
        table_chair_gap = 50.
        chair_width, chair_depth, chair_height, seat_level = (400., 400., 900., 450.)
        representation_3d, representation_2d = create_circular_table_with_chairs(
            table_diameter, table_height,
            chair_width, chair_depth, chair_height, seat_level,
            table_chair_gap, number_of_seats = 4
        )
        self.create_explicit_type('IfcFurnitureType', "Neufert Retail 4-Seater Circular Table", 
            representation_3d, representation_2d)

        # circular 5-seater table
        overall_width, overall_depth, overall_height = (2000., 2000., 900.)
        table_diameter, table_height = (1100., 780.)
        table_chair_gap = 50.
        chair_width, chair_depth, chair_height, seat_level = (400., 400., 900., 450.)
        representation_3d, representation_2d = create_circular_table_with_chairs(
            table_diameter, table_height,
            chair_width, chair_depth, chair_height, seat_level,
            table_chair_gap, number_of_seats = 5
        )
        self.create_explicit_type('IfcFurnitureType', "Neufert Retail 5-Seater Circular Table", 
            representation_3d, representation_2d)

        # circular 6-seater table
        overall_width, overall_depth, overall_height = (2150., 2150., 900.)
        table_diameter, table_height = (1250., 780.)
        table_chair_gap = 50.
        chair_width, chair_depth, chair_height, seat_level = (400., 400., 900., 450.)
        representation_3d, representation_2d = create_circular_table_with_chairs(
            table_diameter, table_height,
            chair_width, chair_depth, chair_height, seat_level,
            table_chair_gap, number_of_seats = 6
        )
        self.create_explicit_type('IfcFurnitureType', "Neufert Retail 6-Seater Circular Table", 
            representation_3d, representation_2d)

        # circular 8-seater table
        overall_width, overall_depth, overall_height = (2300., 2300., 900.)
        table_diameter, table_height = (1400., 780.)
        table_chair_gap = 50.
        chair_width, chair_depth, chair_height, seat_level = (400., 400., 900., 450.)
        representation_3d, representation_2d = create_circular_table_with_chairs(
            table_diameter, table_height,
            chair_width, chair_depth, chair_height, seat_level,
            table_chair_gap, number_of_seats = 8
        )
        self.create_explicit_type('IfcFurnitureType', "Neufert Retail 8-Seater Circular Table", 
            representation_3d, representation_2d)

        # circular 10-seater table
        overall_width, overall_depth, overall_height = (2450., 2450., 900.)
        table_diameter, table_height = (1550., 780.)
        table_chair_gap = 50.
        chair_width, chair_depth, chair_height, seat_level = (400., 400., 900., 450.)
        representation_3d, representation_2d = create_circular_table_with_chairs(
            table_diameter, table_height,
            chair_width, chair_depth, chair_height, seat_level,
            table_chair_gap, number_of_seats = 10
        )
        self.create_explicit_type('IfcFurnitureType', "Neufert Retail 10-Seater Circular Table", 
            representation_3d, representation_2d)

        # circular 12-seater table
        overall_width, overall_depth, overall_height = (2750., 2750., 900.)
        table_diameter, table_height = (1850., 780.)
        table_chair_gap = 50.
        chair_width, chair_depth, chair_height, seat_level = (400., 400., 900., 450.)
        representation_3d, representation_2d = create_circular_table_with_chairs(
            table_diameter, table_height,
            chair_width, chair_depth, chair_height, seat_level,
            table_chair_gap, number_of_seats = 12
        )
        self.create_explicit_type('IfcFurnitureType', "Neufert Retail 12-Seater Circular Table", 
            representation_3d, representation_2d)

        # circular 14-seater table
        overall_width, overall_depth, overall_height = (3100., 3100., 900.)
        table_diameter, table_height = (2200., 780.)
        table_chair_gap = 50.
        chair_width, chair_depth, chair_height, seat_level = (400., 400., 900., 450.)
        representation_3d, representation_2d = create_circular_table_with_chairs(
            table_diameter, table_height,
            chair_width, chair_depth, chair_height, seat_level,
            table_chair_gap, number_of_seats = 14
        )
        self.create_explicit_type('IfcFurnitureType', "Neufert Retail 14-Seater Circular Table", 
            representation_3d, representation_2d)

        # circular 16-seater table
        overall_width, overall_depth, overall_height = (3400., 3400., 900.)
        table_diameter, table_height = (2500., 780.)
        table_chair_gap = 50.
        chair_width, chair_depth, chair_height, seat_level = (400., 400., 900., 450.)
        representation_3d, representation_2d = create_circular_table_with_chairs(
            table_diameter, table_height,
            chair_width, chair_depth, chair_height, seat_level,
            table_chair_gap, number_of_seats = 16
        )
        self.create_explicit_type('IfcFurnitureType', "Neufert Retail 16-Seater Circular Table", 
            representation_3d, representation_2d)

        # sofas
        
        def create_sofa(width, depth, height, seat_level, number_of_seats=1):
            thickness = 175
            armrest_height = height * 0.7
            shift_to_center = V(-width/2, -depth)
            mirror_axis = V(0, 1)
            seat_width = (width-thickness*2)/number_of_seats

            # 3d representation
            armrest = builder.rectangle(size=V(thickness, depth))
            armrest = builder.extrude(armrest, armrest_height)
            armrests = [armrest, builder.translate(armrest, V(width-thickness, 0, 0), create_copy=True)]

            seats = []
            for i in range(number_of_seats):
                seat = builder.rectangle(
                    size=V(seat_width, depth-thickness), 
                    position=V(thickness + seat_width*i, 0)
                )
                seat = builder.extrude(seat, seat_level)
                seats.append(seat)

            seat_back = builder.rectangle(
                size=V(width - thickness*2, thickness), 
                position=V(thickness, depth-thickness)
            )
            seat_back = builder.extrude(seat_back, height)

            items = armrests + seats + [seat_back]
            builder.translate(items, shift_to_center.to_3d())
            # builder.mirror(items, mirror_axis)
            representation_3d = builder.get_representation(
                self.representations["body"], items=items)

            # 2d representation
            armrest = builder.rectangle(size=V(thickness, depth))
            armrests = [armrest, builder.translate(armrest, V(width-thickness, 0), create_copy=True)]

            seats = []
            for i in range(number_of_seats):
                seat = builder.rectangle(
                    size=V(seat_width, depth-thickness), 
                    position=V(thickness + seat_width*i, 0)
                )
                seats.append(seat)

            seat_back = builder.rectangle(
                size=V(width - thickness*2, thickness), 
                position=V(thickness, depth-thickness)
            )

            items = armrests + seats + [seat_back]
            builder.translate(items, shift_to_center)
            representation_2d = builder.get_representation(
                self.representations["annotation"], items=items)

            return representation_3d, representation_2d


        overall_width, overall_depth, overall_height, seat_level = (900., 900., 900., 450.)
        representation_3d, representation_2d = create_sofa(overall_width, overall_depth, overall_height, seat_level)
        self.create_explicit_type('IfcFurnitureType', "Generic 1-Seater Sofa", 
            representation_3d, representation_2d)

        overall_width, overall_depth, overall_height, seat_level, number_of_seats = (1500., 900., 900., 450., 2)
        representation_3d, representation_2d = create_sofa(overall_width, overall_depth, overall_height, seat_level, number_of_seats)
        self.create_explicit_type('IfcFurnitureType', "Generic 2-Seater Sofa", 
            representation_3d, representation_2d)

        overall_width, overall_depth, overall_height, seat_level, number_of_seats = (2100., 900., 900., 450., 3)
        representation_3d, representation_2d = create_sofa(overall_width, overall_depth, overall_height, seat_level, number_of_seats)
        self.create_explicit_type('IfcFurnitureType', "Generic 3-Seater Sofa", 
            representation_3d, representation_2d)

        # showers

        def create_shower_or_bathtube(width, depth, height, bathtube=False):
            items_box, items_rectangle = create_box_objects(width, depth, height)
            rect_offset = 50.
            fillets_radius = 50.
            sink_radius = 25.
            coords = [
                (rect_offset, rect_offset),
                (rect_offset, depth-rect_offset),
                (width-rect_offset, depth-rect_offset),
                (width-rect_offset, rect_offset)
            ]
            if bathtube:
                fillets_radius = [fillets_radius*5] * 2 + [fillets_radius] * 2
            else:
                fillets_radius = [fillets_radius] * 4
            _, _, fillet_rectangle = get_simple_2dcurve_data(
                coords=coords,
                fillets=(0, 1, 2, 3), 
                fillet_radius=fillets_radius,
                closed=True,
                ifc_file=self.file
            )

            circle_position = V(
                width-rect_offset*2-sink_radius, 
                (depth-rect_offset*2-sink_radius) if not bathtube else depth / 2
            )
            sink_circle = builder.circle(
                circle_position, 
                radius=sink_radius)

            items_2d = items_rectangle + [fillet_rectangle, sink_circle]
            representation_2d = builder.get_representation(
                    self.representations["annotation"], items=items_2d)
            representation_3d = builder.get_representation(
                    self.representations["body"], items=items_box)

            return representation_3d, representation_2d

        overall_width, overall_depth, overall_height = (900., 750., 2100.)
        representation_3d, representation_2d = create_shower_or_bathtube(overall_width, overall_depth, overall_height)
        self.create_explicit_type('IfcFurnitureType', "Neufert Shower 75x90", 
            representation_3d, representation_2d)

        overall_width, overall_depth, overall_height = (800., 800., 2100.)
        representation_3d, representation_2d = create_shower_or_bathtube(overall_width, overall_depth, overall_height)
        self.create_explicit_type('IfcFurnitureType', "Neufert Shower 80x80", 
            representation_3d, representation_2d)

        overall_width, overall_depth, overall_height = (900., 900., 2100.)
        representation_3d, representation_2d = create_shower_or_bathtube(overall_width, overall_depth, overall_height)
        self.create_explicit_type('IfcFurnitureType', "Neufert Shower 90x90", 
            representation_3d, representation_2d)

        # bathtubs
        overall_width, overall_depth, overall_height = (1600., 700., 550.)
        representation_3d, representation_2d = create_shower_or_bathtube(overall_width, overall_depth, overall_height, bathtube=True)
        self.create_explicit_type('IfcFurnitureType', "Neufert Small Bathtub", 
            representation_3d, representation_2d)

        overall_width, overall_depth, overall_height = (1700., 750., 550.)
        representation_3d, representation_2d = create_shower_or_bathtube(overall_width, overall_depth, overall_height, bathtube=True)
        self.create_explicit_type('IfcFurnitureType', "Neufert Medium Bathtub", 
            representation_3d, representation_2d)

        overall_width, overall_depth, overall_height = (1800., 800., 550.)
        representation_3d, representation_2d = create_shower_or_bathtube(overall_width, overall_depth, overall_height, bathtube=True)
        self.create_explicit_type('IfcFurnitureType', "Neufert Large Bathtub", 
            representation_3d, representation_2d)

        def create_toilet(width, depth, height, seat_level = 0):
            shift_to_center = V(-width/2, 0)
            mirror_axis = V(0, 1)

            cistern_used = not bool(seat_level)
            if cistern_used:
                cistern_height = 0
                cistern_depth = 0
                seat_level = height
            else:
                cistern_height = height - seat_level
                cistern_depth = 200.

            # 2d representation
            depth_offset = depth * 0.1
            width_offset = width * 0.1
            items_2d = []
            items_2d_to_mirror = []
            coords = [
                (0., 0.),
                (0., depth-depth_offset-cistern_depth),
                (width_offset, depth-depth_offset-cistern_depth),
                (width_offset, depth-cistern_depth),
                (width-width_offset, depth-cistern_depth),
                (width-width_offset, depth-depth_offset-cistern_depth),
                (width, depth-depth_offset-cistern_depth),
                (width, 0.)
            ]
            fillets = (0, 1, 6, 7)
            fillet_radius = (width/2, width/5, width/5, width/2)
            _, _, seat = get_simple_2dcurve_data(
                coords, fillets, fillet_radius=fillet_radius, 
                closed=True, ifc_file=self.file)
            seat_first_part = ifcopenshell.util.element.copy_deep(self.file, seat)

            seat_depth_offset = (depth-cistern_depth)*0.3
            seat_width_offset = 0.7*width/2 if cistern_depth else width/2
            seat_start_width_offset = 0.6*width

            if cistern_height:
                cistern = builder.rectangle(size=V(width, cistern_depth), position=shift_to_center)
                cistern_3d = ifcopenshell.util.element.copy_deep(self.file, cistern)
                items_2d_to_mirror.append(cistern)
            else:
                back_wall_polyline = builder.polyline(
                    ((-seat_start_width_offset/2, 0.), (seat_start_width_offset/2, 0.))
                )
                items_2d.append(back_wall_polyline)
            
            # seat
            seat_polyline = builder.polyline(
                ((seat_start_width_offset/2, 0.), (seat_width_offset, seat_depth_offset)), 
                position_offset=V(0, cistern_depth))

            seat_curve = builder.create_ellipse_curve(
                seat_width_offset, depth-seat_depth_offset-cistern_depth,
                trim_points_mask=(0,2))
            builder.translate(seat_curve, V(0, cistern_depth+seat_depth_offset))

            items_2d_to_mirror.extend([seat_polyline, seat_curve])
            items_2d_to_mirror.append(builder.mirror(seat_polyline, mirror_axes=V(1, 0), create_copy=True))

            # seat_second_line
            second_line_height = (depth-cistern_depth)*0.7
            second_line_width = (width-cistern_depth)*0.75
            second_curve_y = cistern_depth+second_line_height/2*1.6
            seat_second_curve = builder.create_ellipse_curve(second_line_width/2, second_line_height/2)
            builder.translate(seat_second_curve, V(0, second_curve_y))
            items_2d_to_mirror.append(seat_second_curve)

            # bowl
            bowl_width = width/2*0.25
            bowl_depth = (depth-cistern_depth)*0.1
            bowl_points = (
                (-bowl_width/2, 0.), 
                (-bowl_width/2, -bowl_depth/2),
                (bowl_width/2, -bowl_depth/2),
                (bowl_width/2, 0.), 
            )
            bowl_polyline = builder.polyline(bowl_points)
            bowl_curve = builder.create_ellipse_curve(
                bowl_width/2, bowl_depth/2,
                trim_points_mask=(0,2))
            builder.translate([bowl_polyline, bowl_curve], V(0, second_curve_y))
            items_2d_to_mirror.extend([bowl_polyline, bowl_curve])

            builder.mirror(items_2d_to_mirror, mirror_axes=mirror_axis)
            items_2d += items_2d_to_mirror
            
            representation_2d = builder.get_representation(
                context=self.representations['annotation'],
                items=items_2d
            )

            # 3d representation
            items_3d = []
            items_3d_to_mirror = []

            # seat
            seat_main_curve_points = builder.get_rectangle_coords(
                size=V(width, depth-cistern_depth), 
                position=V(0, cistern_depth) + shift_to_center
            )
            fillet_radius = min(width/2, (depth-cistern_depth)/2)
            _, _, seat_main_curve = get_simple_2dcurve_data(
                seat_main_curve_points,
                fillets=(1, 2),
                fillet_radius=fillet_radius,
                closed=True,
                ifc_file=self.file
            )
            seat_main_curve_mask = builder.circle(
                center=V(0, depth-fillet_radius), 
                radius=fillet_radius*0.75)
            seat_main_curve_profile = builder.profile(
                seat_main_curve, 
                inner_curves=seat_main_curve_mask)

            seat_level_offset = seat_level/2 / 4
            seat_first_part = builder.extrude(
                seat_main_curve_profile, seat_level/2-seat_level_offset, 
                V(0, 0, seat_level/2+seat_level_offset))

            seat_main_curve = ifcopenshell.util.element.copy_deep(self.file, seat_main_curve)
            seat_second_part = builder.extrude(
                seat_main_curve, seat_level_offset, 
                V(0, 0, seat_level/2))
            
            items_3d_to_mirror.extend([seat_first_part, seat_second_part])

            seat_leg = builder.rectangle(
                size=V(width, depth/3),
                position=V(0, depth/3)+shift_to_center
            )
            seat_leg = builder.extrude(seat_leg, seat_level/2)
            items_3d_to_mirror.append(seat_leg)

            # cistern
            if cistern_height:
                cistern_3d = builder.extrude(
                    cistern_3d, cistern_height+seat_level/2, 
                    position=V(0, 0, seat_level/2))
                items_3d_to_mirror.append(cistern_3d)

            builder.translate(items_3d, shift_to_center.to_3d())

            builder.mirror(items_3d_to_mirror, mirror_axes=mirror_axis)
            items_3d += items_3d_to_mirror

            representation_3d = builder.get_representation(
                context=self.representations['body'],
                items=items_3d
            )

            return representation_3d, representation_2d

        overall_width, overall_depth, overall_height, seat_level = (550., 650., 770., 400)
        representation_3d, representation_2d = create_toilet(overall_width, overall_depth, overall_height, seat_level)
        self.create_explicit_type('IfcFurnitureType', "Neufert Toilet with Cistern", 
            representation_3d, representation_2d)

        overall_width, overall_depth, overall_height = (400., 530., 400.)
        representation_3d, representation_2d = create_toilet(overall_width, overall_depth, overall_height)
        self.create_explicit_type('IfcFurnitureType', "Generic Toilet without Cistern", 
            representation_3d, representation_2d)

        def create_urinal(width, depth, height):
            shift_to_center = V(-width/2, 0)
            mirror_axis = V(0, 1)

            representation_3d, representation_2d = create_box_objects(width, depth, height, return_representations=True)

            back_wall_depth = depth*0.1
            back_wall_border_mask_height = height * 0.1
            back_wall_border_mask_depth = depth * 0.1
            back_wall_border_mask_width = width * 0.2

            bottom_height = back_wall_border_mask_height / 2
            bottom_mask_depth = depth*0.1
            bottom_mask_width = width*0.2
            bottom_mask_height = height*0.2
            
            items_3d_to_center = []

            # back wall
            back_wall = builder.rectangle(V(width, height))
            # example of rotating and extruding by Y axis btw
            back_wall_extruded = builder.extrude(
                back_wall, back_wall_depth+back_wall_border_mask_depth, 
                position_z_axis=V(0, -1, 0),
                extrusion_vector=V(0, 0, -1))
            items_3d_to_center.append(back_wall_extruded)

            # bottom part
            base_curve = builder.create_ellipse_curve(
                width/2, depth-(back_wall_depth+bottom_mask_depth), 
                trim_points_mask=(0,2)
            )
            builder.translate(base_curve, V(0, back_wall_depth+bottom_mask_depth))
            triangle_bottom_extruded = builder.extrude(base_curve, bottom_height)

            second_curve = builder.create_ellipse_curve(
                (width-bottom_mask_width)/2, depth-(back_wall_depth+bottom_mask_depth)-bottom_mask_depth, 
                trim_points_mask=(0,2)
            )
            builder.translate(second_curve, V(0, back_wall_depth+bottom_mask_depth))
            base_curve = ifcopenshell.util.element.copy_deep(self.file, base_curve)
            mask_curve_profile = builder.profile(base_curve, inner_curves=second_curve)
            triangle_bottom_wall_mask = builder.extrude(
                mask_curve_profile, bottom_mask_height, 
                position=V(0, 0, bottom_height))


            builder.translate(items_3d_to_center, shift_to_center.to_3d())
            items_3d = items_3d_to_center + [triangle_bottom_extruded, triangle_bottom_wall_mask]
            builder.mirror(items_3d, mirror_axis)

            representation_3d = builder.get_representation(
                self.representations['body'],
                items_3d
            )

            # 2d representation
            curve_offset_depth = depth * 0.1
            curve_offset_width = width * 0.2
            items_2d = []
            trim_points = builder.get_trim_points_from_mask(width/2, 0., (0, 2))

            base_curve = builder.create_ellipse_curve(width/2, depth, 
                trim_points=((width/2, 0.), (-width/2, 0.))
            )

            items_2d.append(base_curve)
            items_2d.append(builder.polyline(trim_points))

            items_2d.append(builder.circle(radius=back_wall_depth*0.5, center=V(0, back_wall_depth*2.5)))

            second_curve = builder.create_ellipse_curve(
                (width-bottom_mask_width)/2, depth-(back_wall_depth+bottom_mask_depth)-bottom_mask_depth, 
                trim_points_mask=(0,2)
            )
            builder.translate(second_curve, V(0, 2*curve_offset_depth))
            items_2d.append(second_curve)

            curve_coords = builder.get_trim_points_from_mask(curve_offset_width/2, curve_offset_depth, (3, 2))
            second_curve = builder.curve_between_two_points(curve_coords)
            builder.translate(second_curve, V(-width/2+curve_offset_width, 2*curve_offset_depth))
            polyline = builder.polyline(
                ((-width/2+curve_offset_width, 0.), (width/2-curve_offset_width, 0.)),
                position_offset=V(0, curve_offset_depth)
            )
            items_2d.extend([second_curve, polyline])
            items_2d.append(builder.mirror(second_curve, V(1,0), create_copy=True))

            builder.mirror(items_2d, mirror_axis)

            representation_2d = builder.get_representation(
                self.representations['annotation'],
                items_2d
            )

            return representation_3d, representation_2d

        overall_width, overall_depth, overall_height = (350., 350., 650.)
        representation_3d, representation_2d = create_urinal(overall_width, overall_depth, overall_height)
        self.create_explicit_type('IfcFurnitureType', "Neufert Small Urinal", 
            representation_3d, representation_2d)

        overall_width, overall_depth, overall_height = (400., 400., 650.)
        representation_3d, representation_2d = create_urinal(overall_width, overall_depth, overall_height)
        self.create_explicit_type('IfcFurnitureType', "Neufert Medium Urinal", 
            representation_3d, representation_2d)

        overall_width, overall_depth, overall_height = (450., 450., 650.)
        representation_3d, representation_2d = create_urinal(overall_width, overall_depth, overall_height)
        self.create_explicit_type('IfcFurnitureType', "Neufert Large Urinal", 
            representation_3d, representation_2d)

        def create_basin(width, depth, height):
            second_width = 0.9 * width
            second_depth = 0.7 * depth
            faucet_depth = depth - (depth - second_depth)/2/2
            faucet_length = 120.
            shift_to_center = V(-width/2, -depth)

            # representation 2d
            rectangle_main = create_fillet_rectangle(size=V(width, depth))
            rectangle_main_3d = ifcopenshell.util.element.copy_deep(self.file, rectangle_main)

            rectangle_second = create_fillet_rectangle(
                size=V(second_width, second_depth),
                position=V((width-second_width)/2, (depth-second_depth)*0.3),
                fillet_radius=(50., 150., 150., 50.)
            )
            rectangle_second_3d = ifcopenshell.util.element.copy_deep(self.file, rectangle_second)

            faucet = builder.polyline( (
                (width/2, faucet_depth), 
                (width/2, faucet_depth-faucet_length)) )
            items_2d = [rectangle_main, rectangle_second, faucet]
            builder.translate(items_2d, shift_to_center)
            representation_2d = builder.get_representation(self.representations['annotation'], items_2d)

            # representation 3d
            basin_downside_thickness = 100.

            rectangle_profile = builder.profile(rectangle_main_3d, inner_curves=rectangle_second_3d)
            main_basin = builder.extrude(rectangle_profile, height-basin_downside_thickness, position=V(0, 0, basin_downside_thickness))
            basin_downside = builder.extrude(rectangle_second_3d, basin_downside_thickness)

            items_3d = [main_basin, basin_downside]
            builder.translate(items_3d, shift_to_center.to_3d())
            representation_3d = builder.get_representation(self.representations['body'], items_3d)
            return representation_3d, representation_2d

        overall_width, overall_depth, overall_height = (450., 400., 250.)
        representation_3d, representation_2d = create_basin(overall_width, overall_depth, overall_height)
        self.create_explicit_type('IfcFurnitureType', "Neufert Extra Small Basin", 
            representation_3d, representation_2d)

        overall_width, overall_depth, overall_height = (500., 450., 250.)
        representation_3d, representation_2d = create_basin(overall_width, overall_depth, overall_height)
        self.create_explicit_type('IfcFurnitureType', "Neufert Small Basin", 
            representation_3d, representation_2d)

        overall_width, overall_depth, overall_height = (550., 500., 250.)
        representation_3d, representation_2d = create_basin(overall_width, overall_depth, overall_height)
        self.create_explicit_type('IfcFurnitureType', "Neufert Medium Basin", 
            representation_3d, representation_2d)

        overall_width, overall_depth, overall_height = (600., 550., 250.)
        representation_3d, representation_2d = create_basin(overall_width, overall_depth, overall_height)
        self.create_explicit_type('IfcFurnitureType', "Neufert Large Basin", 
            representation_3d, representation_2d)

        def create_desk(width, depth, height, return_representations=False):
            leg_width = 50.
            countertop_height = 50.
            backside_offset = 50.
            back_side_height = 0.6*height
            output = []

            # 3d representation
            items_3d = []
            leg = builder.rectangle(size=V(leg_width, depth))
            leg = builder.extrude(leg, height-countertop_height)
            items_3d.append(leg)
            items_3d.append(builder.translate(leg, V(width-leg_width, 0, 0), create_copy=True))

            countertop = builder.rectangle(size=V(width, depth))
            countertop = builder.extrude(countertop, countertop_height, V(0, 0, height-countertop_height))
            items_3d.append(countertop)

            backside = builder.rectangle(
                size=V(width-leg_width*2, backside_offset), 
                position=V(backside_offset, height-backside_offset*4))
            backside = builder.extrude(backside, back_side_height, position=V(0, 0, height-back_side_height-backside_offset))
            items_3d.append(backside)

            if return_representations:
                representation_3d = builder.get_representation(self.representations['body'], items_3d)
                output.append(representation_3d)
            else:
                output.append(items_3d)

            # 2d representation
            rectangle = builder.rectangle(V(width, depth))
            if return_representations:
                representation_2d = builder.get_representation(self.representations['annotation'], rectangle)
                output.append(representation_2d)
            else:
                output.append([rectangle])

            return output

        overall_width, overall_depth, overall_height = (1560., 780., 780.)
        representation_3d, representation_2d = create_desk(overall_width, overall_depth, overall_height, return_representations=True)
        self.create_explicit_type('IfcFurnitureType', "Neufert Commercial Writing Desk", 
            representation_3d, representation_2d)

        overall_width, overall_depth, overall_height = (1400., 700., 760.)
        representation_3d, representation_2d = create_desk(overall_width, overall_depth, overall_height, return_representations=True)
        self.create_explicit_type('IfcFurnitureType', "Neufert Commercial Office Desk", 
            representation_3d, representation_2d)

        overall_width, overall_depth, overall_height = (1000., 600., 730.)
        representation_3d, representation_2d = create_desk(overall_width, overall_depth, overall_height, return_representations=True)
        self.create_explicit_type('IfcFurnitureType', "Generic Small Desk", 
            representation_3d, representation_2d)

        overall_width, overall_depth, overall_height = (1200., 700., 750.)
        representation_3d, representation_2d = create_desk(overall_width, overall_depth, overall_height, return_representations=True)
        self.create_explicit_type('IfcFurnitureType', "Generic Medium Desk", 
            representation_3d, representation_2d)

        overall_width, overall_depth, overall_height = (1500., 800., 750.)
        representation_3d, representation_2d = create_desk(overall_width, overall_depth, overall_height, return_representations=True)
        self.create_explicit_type('IfcFurnitureType', "Generic Large Desk", 
            representation_3d, representation_2d)

        def create_desk_with_chair(desk_width, desk_depth, desk_height,
            chair_width, chair_depth, chair_height, seat_level,
            desk_chair_gap):
            desk_items_3d, desk_items_2d = create_desk(desk_width, desk_depth, desk_height)
            chair_items_3d, chair_items_2d = create_fancy_chair(chair_width, chair_depth, chair_height, seat_level)

            # 2d representation
            builder.translate(desk_items_2d, V(0, chair_depth+desk_chair_gap))
            builder.translate(chair_items_2d, V(desk_width/2, 0))
            builder.mirror(chair_items_2d, mirror_axes=V(0, 1))
            items_2d = desk_items_2d + chair_items_2d

            # 3d representation
            builder.translate(desk_items_3d, V(0, chair_depth+desk_chair_gap, 0))
            builder.translate(chair_items_3d, V(desk_width/2, 0, 0))
            builder.mirror(chair_items_3d, mirror_axes=V(0, 1))
            items_3d = desk_items_3d + chair_items_3d

            representation_3d = builder.get_representation(self.representations['body'], items_3d)
            representation_2d = builder.get_representation(self.representations['annotation'], items_2d)
            return representation_3d, representation_2d

        overall_width, overall_depth, overall_height = (1560., 1630., 900.,)
        desk_width, desk_depth, desk_height = (1560., 780., 780.,)
        desk_chair_gap = 50.
        chair_width, chair_depth, chair_height, seat_level = (400., 400., 900., 450.)
        representation_3d, representation_2d = create_desk_with_chair(
            desk_width, desk_depth, desk_height,
            chair_width, chair_depth, chair_height, seat_level,
            desk_chair_gap
        )
        self.create_explicit_type('IfcFurnitureType', "Neufert Commercial Writing Desk and Chair", 
            representation_3d, representation_2d)

        overall_width, overall_depth, overall_height = (1400., 1450., 900.)
        desk_width, desk_depth, desk_height = (1400., 700., 760.,)
        desk_chair_gap = 50.
        chair_width, chair_depth, chair_height, seat_level = (400., 400., 900., 450.)
        representation_3d, representation_2d = create_desk_with_chair(
            desk_width, desk_depth, desk_height,
            chair_width, chair_depth, chair_height, seat_level,
            desk_chair_gap
        )
        self.create_explicit_type('IfcFurnitureType', "Neufert Commercial Office Desk and Chair", 
            representation_3d, representation_2d)

        def create_dishwasher(width, depth, height):
            items_3d, items_2d = create_box_objects(width, depth, height)
            circle_first_r = width*0.1
            circle_second_r = width*0.3
            center = V(width/2, depth/2)
            k = center.y / center.x

            circle_first = builder.circle(center=center, radius=circle_second_r)
            circle_second = builder.circle(center=center, radius=circle_first_r)

            polyline = builder.polyline(
                [(0., 0.),
                center-center.normalized()*circle_second_r]
            )
            mirrored_polylines = builder.mirror(
                polyline,
                mirror_axes=[V(1, 0), V(0, 1), V(1, 1)],
                mirror_point=center,
                create_copy=True)
            items_2d.extend([circle_first, circle_second, polyline] + mirrored_polylines)

            representation_3d = builder.get_representation(self.representations['body'], items_3d)
            representation_2d = builder.get_representation(self.representations['annotation'], items_2d)
            
            return representation_3d, representation_2d

        overall_width, overall_depth, overall_height = (500., 570., 820.)
        representation_3d, representation_2d = create_dishwasher(overall_width, overall_depth, overall_height)
        self.create_explicit_type('IfcFurnitureType', "Neufert Dishwasher", 
            representation_3d, representation_2d)

        def create_cooktop(width, depth, height):
            items_3d, items_2d = create_box_objects(width, depth, height)
            ring_radius = width*0.1
            ring_offset = V(width*0.2, depth*0.2)
            center = V(width/2, depth/2)

            ring = builder.circle(ring_offset, ring_radius)
            mirrored_rings = builder.mirror(
                ring,
                mirror_axes=[V(1, 0), V(0, 1), V(1, 1)],
                mirror_point=center,
                create_copy=True)
            items_2d.extend([ring] + mirrored_rings)

            representation_3d = builder.get_representation(self.representations['body'], items_3d)
            representation_2d = builder.get_representation(self.representations['annotation'], items_2d)
            
            return representation_3d, representation_2d

        overall_width, overall_depth, overall_height = (580., 510., 50.)
        representation_3d, representation_2d = create_cooktop(overall_width, overall_depth, overall_height)
        self.create_explicit_type('IfcFurnitureType', "Neufert Cooktop 58x51", 
            representation_3d, representation_2d)

        overall_width, overall_depth, overall_height = (750., 510., 50.)
        representation_3d, representation_2d = create_cooktop(overall_width, overall_depth, overall_height)
        self.create_explicit_type('IfcFurnitureType', "Neufert Cooktop 75x51", 
            representation_3d, representation_2d)

        overall_width, overall_depth, overall_height = (890., 510., 50.)
        representation_3d, representation_2d = create_cooktop(overall_width, overall_depth, overall_height)
        self.create_explicit_type('IfcFurnitureType', "Neufert Cooktop 89x51", 
            representation_3d, representation_2d)

        overall_width, overall_depth, overall_height = (1750., 600., 850.)
        representation_3d, representation_2d = create_box_objects(
            overall_width, overall_depth, overall_height,
            return_representations=True)
        self.create_explicit_type('IfcFurnitureType', "Neufert Small Kitchen Bench", 
            representation_3d, representation_2d)

        overall_width, overall_depth, overall_height = (1950., 600., 850.)
        representation_3d, representation_2d = create_box_objects(
            overall_width, overall_depth, overall_height,
            return_representations=True)
        self.create_explicit_type('IfcFurnitureType', "Neufert Medium Kitchen Bench", 
            representation_3d, representation_2d)

        overall_width, overall_depth, overall_height = (2200., 600., 850.)
        representation_3d, representation_2d = create_box_objects(
            overall_width, overall_depth, overall_height,
            return_representations=True)
        self.create_explicit_type('IfcFurnitureType', "Neufert Large Kitchen Bench", 
            representation_3d, representation_2d)

        overall_width, overall_depth, overall_height = (450., 400., 2030.)
        representation_3d, representation_2d = create_box_objects(
            overall_width, overall_depth, overall_height,
            return_representations=True)
        self.create_explicit_type('IfcFurnitureType', "Neufert Small Full Height Cupboard", 
            representation_3d, representation_2d)

        overall_width, overall_depth, overall_height = (550., 500., 2030.)
        representation_3d, representation_2d = create_box_objects(
            overall_width, overall_depth, overall_height,
            return_representations=True)
        self.create_explicit_type('IfcFurnitureType', "Neufert Medium Full Height Cupboard", 
            representation_3d, representation_2d)

        overall_width, overall_depth, overall_height = (600., 600., 2030.)
        representation_3d, representation_2d = create_box_objects(
            overall_width, overall_depth, overall_height,
            return_representations=True)
        self.create_explicit_type('IfcFurnitureType', "Neufert Large Full Height Cupboard", 
            representation_3d, representation_2d)

        overall_width, overall_depth, overall_height = (600., 700., 900.)
        representation_3d, representation_2d = create_box_objects(
            overall_width, overall_depth, overall_height,
            return_representations=True)
        self.create_explicit_type('IfcFurnitureType', "Generic Kitchen Floor Module", 
            representation_3d, representation_2d)

        overall_width, overall_depth, overall_height = (600., 600., 2400.)
        representation_3d, representation_2d = create_box_objects(
            overall_width, overall_depth, overall_height,
            return_representations=True)
        self.create_explicit_type('IfcFurnitureType', "Generic Full Height Cupboard", 
            representation_3d, representation_2d)

        overall_width, overall_depth, overall_height = (750., 650., 1900.)
        representation_3d, representation_2d = create_box_objects(
            overall_width, overall_depth, overall_height,
            return_representations=True)
        self.create_explicit_type('IfcFurnitureType', "Generic Small Fridge Zone", 
            representation_3d, representation_2d)

        overall_width, overall_depth, overall_height = (900., 700., 1900.)
        representation_3d, representation_2d = create_box_objects(
            overall_width, overall_depth, overall_height,
            return_representations=True)
        self.create_explicit_type('IfcFurnitureType', "Generic Medium Fridge Zone", 
            representation_3d, representation_2d)

        overall_width, overall_depth, overall_height = (1000., 750., 1900.)
        representation_3d, representation_2d = create_box_objects(
            overall_width, overall_depth, overall_height,
            return_representations=True)
        self.create_explicit_type('IfcFurnitureType', "Generic Large Fridge Zone", 
            representation_3d, representation_2d)

        def create_sink(width, depth, height, add_table):
            shift_to_center = V(-width/2, -depth)
            rect_offset = 50.
            sink_offset = V(width/2, 0) if add_table else V(0, 0)

            # 2d representation
            main_rectangle = builder.rectangle(size=V(width, depth))
            sink_radius = 25.
            
            faucet_depth = depth - rect_offset/4
            faucet_length = 120.

            fillet_rectangle = create_fillet_rectangle(
                size=V(width-rect_offset, depth-rect_offset)-sink_offset,
                position=V(rect_offset/2, rect_offset/2)+sink_offset
            )

            circle_position = sink_offset + V((width-sink_offset.x)/2, depth-rect_offset*2-sink_radius)
            sink_circle = builder.circle(
                circle_position, 
                radius=sink_radius)

            faucet = builder.polyline( (
                ((width-sink_offset.x)/2, faucet_depth), 
                ((width-sink_offset.x)/2, faucet_depth-faucet_length)) )
            builder.translate(faucet, sink_offset)

            items_2d = [main_rectangle, fillet_rectangle, sink_circle, faucet]
            builder.translate(items_2d, shift_to_center)

            # 3d representation
            items_3d = []

            sink_height = 0.8*height

            if add_table:
                table = builder.rectangle(size=V(width, depth)-sink_offset)
                table = builder.extrude(table, height)
                items_3d.append(table)
            
            sink_table = builder.rectangle(size=V(width, depth)-sink_offset, position=sink_offset)
            sink_mask = create_fillet_rectangle(
                size=V(width-rect_offset, depth-rect_offset)-sink_offset,
                position=V(rect_offset/2, rect_offset/2)+sink_offset
            )
            sink_profile = builder.profile(sink_table, inner_curves=sink_mask)
            sink_table_extruded = builder.extrude(sink_profile, sink_height, position=V(0, 0, height-sink_height))
            items_3d.append(sink_table_extruded)

            sink_table_bottom = builder.extrude(sink_table, height-sink_height)
            items_3d.append(sink_table_bottom)
            builder.translate(items_3d, shift_to_center.to_3d())

            representation_2d = builder.get_representation(
                    self.representations["annotation"], items=items_2d)
            representation_3d = builder.get_representation(
                    self.representations["body"], items=items_3d)
            

            return representation_3d, representation_2d

        sinks_data = {
            "Neufert Sink 86x44": (860, 440, 180, False), 
            "Neufert Sink 110x44": (1110, 440, 180, True), 
            "Neufert Sink 124x44": (1240, 440, 180, True), 
            "Generic Small Sink": (450, 460, 210, False), 
            "Generic Medium Sink": (800, 460, 210, True), 
            "Generic Large Sink": (1100, 490, 220, True),
        }

        for sink_name in sinks_data:
            parameters = [float(i) for i in sinks_data[sink_name][:3]]
            add_table = sinks_data[sink_name][3]
            
            overall_width, overall_depth, overall_height = parameters
            representation_3d, representation_2d = create_sink(
                overall_width, overall_depth, overall_height,
                add_table)
            self.create_explicit_type('IfcFurnitureType', sink_name, 
                representation_3d, representation_2d)

        overall_width, overall_depth, overall_height = (2200., 600., 900.)
        representation_3d, representation_2d = create_box_objects(
            overall_width, overall_depth, overall_height,
            return_representations=True)
        self.create_explicit_type('IfcFurnitureType', "Generic 1-Bedroom Island Bench", 
            representation_3d, representation_2d)

        overall_width, overall_depth, overall_height = (2400., 700., 900.)
        representation_3d, representation_2d = create_box_objects(
            overall_width, overall_depth, overall_height,
            return_representations=True)
        self.create_explicit_type('IfcFurnitureType', "Generic 2-Bedroom Island Bench", 
            representation_3d, representation_2d)

        overall_width, overall_depth, overall_height = (3000., 700., 900.)
        representation_3d, representation_2d = create_box_objects(
            overall_width, overall_depth, overall_height,
            return_representations=True)
        self.create_explicit_type('IfcFurnitureType', "Generic 3-Bedroom Island Bench", 
            representation_3d, representation_2d)

        overall_width, overall_depth, overall_height = (400., 500., 900.)
        representation_3d, representation_2d = create_box_objects(
            overall_width, overall_depth, overall_height,
            return_representations=True)
        self.create_explicit_type('IfcFurnitureType', "Generic Small Laundry Tub With Cupboard", 
            representation_3d, representation_2d)

        overall_width, overall_depth, overall_height = (600., 500., 900.)
        representation_3d, representation_2d = create_box_objects(
            overall_width, overall_depth, overall_height,
            return_representations=True)
        self.create_explicit_type('IfcFurnitureType', "Generic Medium Laundry Tub With Cupboard", 
            representation_3d, representation_2d)

        def create_washing_machine(width, depth, height):
            items_3d, items_2d = create_box_objects(width, depth, height)
            circle_first_r = width*0.1
            circle_second_r = width*0.3
            center = V(width/2, depth/2)

            circle_first = builder.circle(center=center, radius=circle_second_r)
            circle_second = builder.circle(center=center, radius=circle_first_r)

            items_2d.extend([circle_first, circle_second])

            representation_3d = builder.get_representation(self.representations['body'], items_3d)
            representation_2d = builder.get_representation(self.representations['annotation'], items_2d)
            
            return representation_3d, representation_2d

        overall_width, overall_depth, overall_height = (595., 680., 850.)
        representation_3d, representation_2d = create_washing_machine(
            overall_width, overall_depth, overall_height)
        self.create_explicit_type('IfcFurnitureType', "Neufert Washing Machine", 
            representation_3d, representation_2d)

        def create_dryer_machine(width, depth, height):
            items_3d, items_2d = create_box_objects(width, depth, height)
            circle_first_r = width*0.1
            circle_second_r = width*0.25
            center_second_circle = V(width/2, height-circle_second_r*2.5)

            circle_first = builder.circle(center=V(width/2, circle_first_r*1.5), radius=circle_first_r)
            circle_second = builder.circle(center=center_second_circle, radius=circle_second_r)

            polyline = builder.polyline([
                V( -circle_second_r*0.6, -(circle_second_r**2 - (circle_second_r*0.6)**2)**0.5) + center_second_circle,
                V( -circle_second_r*0.1, (circle_second_r**2 - (circle_second_r*0.1)**2)**0.5) + center_second_circle
            ])
            mirrored_polyline = builder.mirror(polyline, mirror_point=center_second_circle, mirror_axes=V(1, 0), create_copy=True)
            items_2d.extend([circle_first, circle_second, polyline, mirrored_polyline])

            representation_3d = builder.get_representation(self.representations['body'], items_3d)
            representation_2d = builder.get_representation(self.representations['annotation'], items_2d)
            
            return representation_3d, representation_2d

        overall_width, overall_depth, overall_height = (595., 680., 850.)
        representation_3d, representation_2d = create_dryer_machine(
            overall_width, overall_depth, overall_height)
        self.create_explicit_type('IfcFurnitureType', "Neufert Drier", 
            representation_3d, representation_2d)

        parking_lots_data = {
            "30 Deg Bay Class 1/1A No Overhang": (2100, 5400, 2000),
            "30 Deg Bay Class 1/1A With Overhang": (2100, 5400, 2000),
            "30 Deg Bay Class 1/1A With Wheelstop": (2100, 5400, 2000),
            "30 Deg Bay Class 2 No Overhang": (2300, 5400, 2000),
            "30 Deg Bay Class 2 With Overhang": (2300, 5400, 2000),
            "30 Deg Bay Class 2 With Wheelstop": (2300, 5400, 2000),
            "30 Deg Bay Class 3 No Overhang": (2500, 5400, 2000),
            "30 Deg Bay Class 3 With Overhang": (2500, 5400, 2000),
            "30 Deg Bay Class 3 With Wheelstop": (2500, 5400, 2000),
            "30 Deg Bay Class 3A No Overhang": (2500, 5400, 2000),
            "30 Deg Bay Class 3A With Overhang": (2500, 5400, 2000),
            "30 Deg Bay Class 3A With Wheelstop": (2500, 5400, 2000),
            "45 Deg Bay Class 1/1A No Overhang": (2400, 5400, 2000),
            "45 Deg Bay Class 1/1A With Overhang": (2400, 5400, 2000),
            "45 Deg Bay Class 1/1A With Wheelstop": (2400, 5400, 2000),
            "45 Deg Bay Class 2 No Overhang": (2500, 5400, 2000),
            "45 Deg Bay Class 2 With Overhang": (2500, 5400, 2000),
            "45 Deg Bay Class 2 With Wheelstop": (2500, 5400, 2000),
            "45 Deg Bay Class 3 No Overhang": (2600, 5400, 2000),
            "45 Deg Bay Class 3 With Overhang": (2600, 5400, 2000),
            "45 Deg Bay Class 3 With Wheelstop": (2600, 5400, 2000),
            "45 Deg Bay Class 3A No Overhang": (2600, 5400, 2000),
            "45 Deg Bay Class 3A With Overhang": (2600, 5400, 2000),
            "45 Deg Bay Class 3A With Wheelstop": (2600, 5400, 2000),
            "60 Deg Bay Class 1/1A No Overhang": (2400, 5400, 2000),
            "60 Deg Bay Class 1/1A With Overhang": (2400, 5400, 2000),
            "60 Deg Bay Class 1/1A With Wheelstop": (2400, 5400, 2000),
            "60 Deg Bay Class 2 No Overhang": (2500, 5400, 2000),
            "60 Deg Bay Class 2 With Overhang": (2500, 5400, 2000),
            "60 Deg Bay Class 2 With Wheelstop": (2500, 5400, 2000),
            "60 Deg Bay Class 3 No Overhang": (2600, 5400, 2000),
            "60 Deg Bay Class 3 With Overhang": (2600, 5400, 2000),
            "60 Deg Bay Class 3 With Wheelstop": (2600, 5400, 2000),
            "60 Deg Bay Class 3A No Overhang": (2600, 5400, 2000),
            "60 Deg Bay Class 3A With Overhang": (2600, 5400, 2000),
            "60 Deg Bay Class 3A With Wheelstop": (2600, 5400, 2000),
            "90 Deg Bay Class 1 No Overhang": (2400, 5400, 2000),
            "90 Deg Bay Class 1 With Overhang": (2400, 4800, 2000),
            "90 Deg Bay Class 1 With Wheelstop": (2400, 5400, 2000),
            "90 Deg Bay Class 1A No Overhang": (2400, 5400, 2000),
            "90 Deg Bay Class 1A With Overhang": (2400, 4800, 2000),
            "90 Deg Bay Class 1A With Wheelstop": (2400, 5400, 2000),
            "90 Deg Bay Class 2 No Overhang": (2500, 5400, 2000),
            "90 Deg Bay Class 2 With Overhang": (2500, 4800, 2000),
            "90 Deg Bay Class 2 With Wheelstop": (2500, 5400, 2000),
            "90 Deg Bay Class 3 No Overhang": (2600, 5400, 2000),
            "90 Deg Bay Class 3 With Overhang": (2600, 4800, 2000),
            "90 Deg Bay Class 3 With Wheelstop": (2600, 5400, 2000),
            "90 Deg Bay Class 3A Option 1 No Overhang": (2600, 5400, 2000),
            "90 Deg Bay Class 3A Option 1 With Overhang": (2600, 4800, 2000),
            "90 Deg Bay Class 3A Option 1 With Wheelstop": (2600, 5400, 2000),
            "90 Deg Bay Class 3A Option 2 No Overhang": (2700, 5400, 2000),
            "90 Deg Bay Class 3A Option 2 With Overhang": (2700, 4800, 2000),
            "90 Deg Bay Class 3A Option 2 With Wheelstop": (2700, 5400, 2000),
            "90 Deg Bay Motorcycle": (1200, 2500, 2000),
            "3000 Aisle Parallel Bay": (2100, 6300, 2000),
            "3300 Aisle Parallel Bay": (2100, 6100, 2000),
            "3600 Aisle Parallel Bay": (2100, 5900, 2000),
            "3000 Aisle Parallel Obstructed End Bay": (2100, 6600, 2000),
            "3300 Aisle Parallel Obstructed End Bay": (2100, 6400, 2000),
            "3600 Aisle Parallel Obstructed End Bay": (2100, 6200, 2000),
            "3000 Aisle Parallel Unobstructed End Bay": (2100, 5400, 2000),
            "3300 Aisle Parallel Unobstructed End Bay": (2100, 5400, 2000),
            "3600 Aisle Parallel Unobstructed End Bay": (2100, 5400, 2000),
            "3000 Aisle Parallel Unobstructed End Bay": (2100, 5000, 2000),
            "3300 Aisle Parallel Unobstructed End Bay": (2100, 5000, 2000),
            "3600 Aisle Parallel Unobstructed End Bay": (2100, 5000, 2000)
        }

        for parking_lot_name in parking_lots_data:
            parameters = [float(i) for i in parking_lots_data[parking_lot_name]]
            
            overall_width, overall_depth, overall_height = parameters
            representation_3d, representation_2d = create_box_objects(
                overall_width, overall_depth, overall_height,
                return_representations=True)
            self.create_explicit_type('IfcSpaceType', parking_lot_name, 
                representation_3d, representation_2d)

        self.file.write(output_filename)

    def create_explicit_type(self, ifc_class, name, representation_3d, representation_2d):
        element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class=ifc_class, name=name)
        ifcopenshell.api.run("geometry.assign_representation", self.file, product=element, representation=representation_3d)        
        ifcopenshell.api.run("geometry.assign_representation", self.file, product=element, representation=representation_2d)        
        ifcopenshell.api.run("project.assign_declaration", self.file, definition=element, relating_context=self.library)
        return element

    def create_layer_set_type(self, name, data):
        element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class=data["type"], name=name)
        element.Description = data["Description"]
        rel = ifcopenshell.api.run("material.assign_material", self.file, product=element, type="IfcMaterialLayerSet")
        layer_set = rel.RelatingMaterial
        for layer_data in data["Layers"]:
            layer = ifcopenshell.api.run(
                "material.add_layer", self.file, layer_set=layer_set, material=self.materials[layer_data[1]]["ifc"]
            )
            layer.Name = layer_data[0]
            layer.LayerThickness = layer_data[2]
        ifcopenshell.api.run("project.assign_declaration", self.file, definition=element, relating_context=self.library)
        return element

    def create_layer_type(self, ifc_class, name, thickness):
        element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class=ifc_class, name=name)
        rel = ifcopenshell.api.run("material.assign_material", self.file, product=element, type="IfcMaterialLayerSet")
        layer_set = rel.RelatingMaterial
        layer = ifcopenshell.api.run("material.add_layer", self.file, layer_set=layer_set, material=self.materials["TBD"]["ifc"])
        layer.LayerThickness = thickness
        ifcopenshell.api.run("project.assign_declaration", self.file, definition=element, relating_context=self.library)
        return element

    def create_profile_type(self, ifc_class, name, profile):
        element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class=ifc_class, name=name)
        rel = ifcopenshell.api.run("material.assign_material", self.file, product=element, type="IfcMaterialProfileSet")
        profile_set = rel.RelatingMaterial
        material_profile = ifcopenshell.api.run(
            "material.add_profile", self.file, profile_set=profile_set, material=self.material
            # material=self.materials["TBD"]["ifc"]
        )
        ifcopenshell.api.run("material.assign_profile", self.file, material_profile=material_profile, profile=profile)
        ifcopenshell.api.run("project.assign_declaration", self.file, definition=element, relating_context=self.library)

    def create_type(self, ifc_class, name, representations):
        element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class=ifc_class, name=name)
        for rep_name, obj_name in representations.items():
            obj = bpy.data.objects.get(obj_name)
            representation = ifcopenshell.api.run(
                "geometry.add_representation",
                self.file,
                context=self.representations[rep_name],
                blender_object=obj,
                geometry=obj.data,
                total_items=max(1, len(obj.material_slots)),
            )
            styles = []
            for slot in obj.material_slots:
                style = ifcopenshell.api.run("style.add_style", self.file, name=slot.material.name)
                ifcopenshell.api.run(
                    "style.add_surface_style",
                    self.file,
                    style=style,
                    ifc_class="IfcSurfaceStyleRendering",
                    attributes=tool.Style.get_surface_rendering_attributes(slot.material),
                )
                styles.append(style)
            if styles:
                ifcopenshell.api.run(
                    "style.assign_representation_styles", self.file, shape_representation=representation, styles=styles
                )
            ifcopenshell.api.run(
                "geometry.assign_representation", self.file, product=element, representation=representation
            )
        ifcopenshell.api.run("project.assign_declaration", self.file, definition=element, relating_context=self.library)


if __name__ == "__main__":
    path = Path(__file__).parents[1] / "blenderbim/bim/data/libraries"
    LibraryGenerator().generate(output_filename=str(path / "IFC4 AU Library.ifc"))
