├── .gitignore ├── LICENSE ├── README.md ├── TODO.md ├── ifc_model ├── __init__.py ├── building.py ├── geometry │ ├── arbitrary_closed_profile_def.py │ ├── arbitrary_profile_def_with_voids.py │ ├── circle_profile_def.py │ ├── extrude_area_solid.py │ ├── face.py │ ├── faceted_brep.py │ ├── i_shape_profile_def.py │ ├── point.py │ ├── rectangle_profile_def.py │ ├── representation.py │ ├── representation_item.py │ └── segment.py ├── product.py ├── project.py ├── relations.py ├── representation.py ├── site.py ├── space.py └── storey.py └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | test/buildings/*.ifc 2 | 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | *$py.class 7 | 8 | # TODO: create real test data 9 | test/ 10 | ifcopenshell/ 11 | 12 | # C extensions 13 | *.so 14 | 15 | # Distribution / packaging 16 | .Python 17 | env/ 18 | build/ 19 | develop-eggs/ 20 | dist/ 21 | downloads/ 22 | eggs/ 23 | .eggs/ 24 | lib/ 25 | lib64/ 26 | parts/ 27 | sdist/ 28 | var/ 29 | wheels/ 30 | *.egg-info/ 31 | .installed.cfg 32 | *.egg 33 | 34 | # PyInstaller 35 | # Usually these files are written by a python script from a template 36 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 37 | *.manifest 38 | *.spec 39 | 40 | # Installer logs 41 | pip-log.txt 42 | pip-delete-this-directory.txt 43 | 44 | # Unit test / coverage reports 45 | htmlcov/ 46 | .tox/ 47 | .coverage 48 | .coverage.* 49 | .cache 50 | nosetests.xml 51 | coverage.xml 52 | *.cover 53 | .hypothesis/ 54 | 55 | # Translations 56 | *.mo 57 | *.pot 58 | 59 | # Django stuff: 60 | *.log 61 | local_settings.py 62 | 63 | # Flask stuff: 64 | instance/ 65 | .webassets-cache 66 | 67 | # Scrapy stuff: 68 | .scrapy 69 | 70 | # Sphinx documentation 71 | docs/_build/ 72 | 73 | # PyBuilder 74 | target/ 75 | 76 | # Jupyter Notebook 77 | .ipynb_checkpoints 78 | 79 | # pyenv 80 | .python-version 81 | 82 | # celery beat schedule file 83 | celerybeat-schedule 84 | 85 | # SageMath parsed files 86 | *.sage.py 87 | 88 | # dotenv 89 | .env 90 | 91 | # virtualenv 92 | .venv 93 | venv/ 94 | ENV/ 95 | 96 | # Spyder project settings 97 | .spyderproject 98 | .spyproject 99 | 100 | # Rope project settings 101 | .ropeproject 102 | 103 | # mkdocs documentation 104 | /site 105 | 106 | # mypy 107 | .mypy_cache/ 108 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Andreas Bresser 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # IFC model 2 | wrapper around [IfcOpenShell](http://ifcopenshell.org/) to load and store data from IFC into a smaller JSON-format for easier access across different platforms. 3 | 4 | ⚠ Note that we ignore all geometric representations and focus on the more complex elements (buildings, storeys, spaces, products) because the representation is already handled by ifc-openshell and can be export (e.g. for Blender) 5 | 6 | See [ifc-blender](https://github.com/brean/ifc_blender) for a usage example. 7 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | - idea: remove all classes and create some more generic items, maybe with a description and some mapping to get some shortcuts (e.g. just get all polylines if we just want to draw) 2 | ifcopenshell already defines the ifc-model structure - I could create a JSON-structure iterating over all items, using something like https://gist.github.com/brean/872b32ce3617a1a25e1c245d7b75df35 3 | (maybe in combination with a whitelist to keep the filesize down, like this) 4 | ```python 5 | KNOWN_ITEMS = { 6 | 'IfcBuilding': {'name': 'Name'} 7 | } 8 | 9 | class IfcElement(dict): 10 | def __init__(parent): 11 | self.parent = parent 12 | 13 | def from_ifc(ifc_data): 14 | self.ifc_data = ifc_data 15 | ifc_type = ifc_data.is_a() 16 | if ifc_type in KNOWN_ITEMS: 17 | ifc_items = KNOWN_ITEMS[ifc_type] 18 | for k, v in ifc_items.items(): 19 | self[k] = ifc_items[v] 20 | ``` 21 | 22 | 23 | ```python 24 | # alternative: 25 | class IfcProduct(): 26 | def __init__(self, parent): 27 | self.parent = parent 28 | self.member = { 29 | 'name': 'str', 30 | 'id': 'func' # special case 31 | } 32 | 33 | def from_ifc(ifc_data): 34 | pass 35 | 36 | class Building(IfcProduct): 37 | def __init__(self, parent): 38 | super(Building, self).__init__(parent) 39 | 40 | ifc_map = { 41 | Building: ['IfcBuilding'] 42 | } 43 | 44 | class Parser(object): 45 | def from_ifc(ifc_data, parent): 46 | self.ifc_data = ifc_data 47 | ifc_type = ifc_data.is_a() 48 | for cls,ifc_types in ifc_map.items(): 49 | if ifc_type in ifc_types: 50 | cls(parent) # get the parent, call from_ifc recursively 51 | cls.from_ifc(ifc_data) 52 | 53 | ``` 54 | - idea: configure which data to export to JSON/configure structure to keep the json-file small (if you only need the product names and ids for example but not their representation) 55 | -------------------------------------------------------------------------------- /ifc_model/__init__.py: -------------------------------------------------------------------------------- 1 | #!/bin/python 2 | # -*- coding: UTF-8 -*- 3 | # Ifc-Model based on the ifc openshell 4 | 5 | 6 | __all__ = ['building', 'product', 'project', 'relations', 'representation', 'site', 'space', 'storey'] 7 | -------------------------------------------------------------------------------- /ifc_model/building.py: -------------------------------------------------------------------------------- 1 | from .relations import Relations 2 | from .storey import Storey 3 | 4 | class Building(Relations): 5 | def from_ifc(self, ifc_data): 6 | assert ifc_data.is_a('IfcBuilding') 7 | super(Building, self).from_ifc(ifc_data) 8 | self.name = ifc_data.Name 9 | self.storeys = self.cls_from_ifc( 10 | Storey, 11 | self.get_related_objects(ifc_data) 12 | ) 13 | 14 | def from_json(self, data): 15 | super(Building, self).from_json(data) 16 | self.name = data['name'] 17 | self.storeys = self.cls_from_json(Storey, data['storeys']) 18 | 19 | def to_json(self): 20 | data = super(Building, self).to_json() 21 | data['name'] = self.name 22 | data['storeys'] = [s.to_json() for s in self.storeys] 23 | return data 24 | 25 | def __init__(self, site): 26 | self.site = site 27 | -------------------------------------------------------------------------------- /ifc_model/geometry/arbitrary_closed_profile_def.py: -------------------------------------------------------------------------------- 1 | from .relations import Relations 2 | from .point import Point 3 | from .segment import Segment 4 | 5 | class ArbitraryClosedProfileDef(Relations): 6 | def __init__(self, repr): 7 | self.repr = repr 8 | self.type = 'ArbitraryClosedProfileDef' 9 | self.points = None 10 | self.segments = None 11 | 12 | def to_json(self): 13 | data = super(ArbitraryClosedProfileDef, self).to_json() 14 | data['type'] = self.type 15 | if self.points: 16 | data['points'] = [p.to_json() for p in self.points] 17 | if self.segments: 18 | data['segments'] = [s.to_json() for s in self.segments] 19 | data['outer_curve_type'] = self.outer_curve_type 20 | return data 21 | 22 | def from_json(self, data): 23 | super(ArbitraryClosedProfileDef, self).from_json(data) 24 | self.type = data['type'] 25 | self.outer_curve_type = data['outer_curve_type'] 26 | if 'points' in data: 27 | self.points = self.cls_from_json(Point, data['points']) 28 | if 'segments' in data: 29 | self.segments = self.cls_from_json(Segment, data['segments']) 30 | 31 | def from_ifc(self, ifc_data): 32 | assert ifc_data.is_a('IfcArbitraryClosedProfileDef') 33 | # print(json.dumps(debug(ifc_data), indent=2)) 34 | self.outer_curve_type = ifc_data.OuterCurve.is_a()[3:] 35 | if self.outer_curve_type == 'CompositeCurve': 36 | self.segments = self.cls_from_ifc(Segment, ifc_data.OuterCurve.Segments) 37 | else: 38 | # just connects the points 39 | self.points = self.cls_from_ifc(Point, ifc_data.OuterCurve.Points) 40 | super(ArbitraryClosedProfileDef, self).from_ifc(ifc_data) 41 | -------------------------------------------------------------------------------- /ifc_model/geometry/arbitrary_profile_def_with_voids.py: -------------------------------------------------------------------------------- 1 | from .arbitrary_closed_profile_def import ArbitraryClosedProfileDef 2 | 3 | class ArbitraryProfileDefWithVoids(ArbitraryClosedProfileDef): 4 | def __init__(self, repr): 5 | self.repr = repr 6 | self.type = 'ArbitraryProfileDefWithVoids' 7 | self.points = None 8 | self.segments = None 9 | -------------------------------------------------------------------------------- /ifc_model/geometry/circle_profile_def.py: -------------------------------------------------------------------------------- 1 | from .relations import Relations 2 | 3 | class CircleProfileDef(Relations): 4 | def __init__(self, repr): 5 | self.repr = repr 6 | self.type = 'CircleProfileDef' 7 | 8 | def to_json(self): 9 | data = super(CircleProfileDef, self).to_json() 10 | data['type'] = self.type 11 | return data 12 | -------------------------------------------------------------------------------- /ifc_model/geometry/extrude_area_solid.py: -------------------------------------------------------------------------------- 1 | from .representation_item import RepresentationItem 2 | from .arbitrary_closed_profile_def import ArbitraryClosedProfileDef 3 | from .arbitrary_profile_def_with_voids import ArbitraryProfileDefWithVoids 4 | from .rectangle_profile_def import RectangleProfileDef 5 | from .i_shape_profile_def import IShapeProfileDef 6 | from .circle_profile_def import CircleProfileDef 7 | 8 | ''' 9 | see also faceted_brep 10 | ''' 11 | class ExtrudedAreaSolid(RepresentationItem): 12 | def __init__(self, repr): 13 | self.repr = repr 14 | self.type = 'ExtrudedAreaSolid' 15 | 16 | def area_from_class(self, name): 17 | classes = { 18 | 'ArbitraryClosedProfileDef': ArbitraryClosedProfileDef, 19 | 'ArbitraryProfileDefWithVoids': ArbitraryProfileDefWithVoids, 20 | 'RectangleProfileDef': RectangleProfileDef, 21 | 'IShapeProfileDef': IShapeProfileDef, 22 | 'CircleProfileDef': CircleProfileDef 23 | } 24 | return classes[name](self) 25 | 26 | def from_ifc(self, ifc_data): 27 | assert ifc_data.is_a('IfcExtrudedAreaSolid') 28 | super(ExtrudedAreaSolid, self).from_ifc(ifc_data) 29 | # TODO: ifc_data.Position is a Axis2Placement3D, maybe get an own class? 30 | self.location = ifc_data.Position.Location.Coordinates 31 | self.direction = None 32 | if ifc_data.Position.RefDirection: 33 | self.direction = ifc_data.Position.RefDirection.DirectionRatios 34 | self.axis = None 35 | if self.ifc_data.Position.Axis: 36 | self.axis = self.ifc_data.Position.Axis.DirectionRatios 37 | area_type = self.ifc_data.SweptArea.is_a() 38 | self.depth = self.ifc_data.Depth 39 | self.area = self.area_from_class(area_type[3:]) 40 | self.area.from_ifc(self.ifc_data.SweptArea) 41 | 42 | def from_json(self, data): 43 | super(ExtrudedAreaSolid, self).from_json(data) 44 | self.area = self.area_from_class(data['area']['type']) 45 | self.depth = data['depth'] 46 | self.axis = data['axis'] 47 | self.location = data['location'] 48 | self.direction = data['direction'] 49 | self.area.from_json(data['area']) 50 | 51 | def to_json(self): 52 | data = super(ExtrudedAreaSolid, self).to_json() 53 | data['type'] = self.type 54 | data['depth'] = self.depth 55 | data['location'] = self.location 56 | data['axis'] = self.axis 57 | data['direction'] = self.direction 58 | data['area'] = self.area.to_json() 59 | return data 60 | -------------------------------------------------------------------------------- /ifc_model/geometry/face.py: -------------------------------------------------------------------------------- 1 | from .relations import Relations 2 | 3 | class Face(Relations): 4 | # TODO Bounds[0].Bound.Polygon[i].Coordinates (only Faceted Brep) 5 | pass 6 | -------------------------------------------------------------------------------- /ifc_model/geometry/faceted_brep.py: -------------------------------------------------------------------------------- 1 | from .representation_item import RepresentationItem 2 | 3 | ''' 4 | see also extrude_area_solid 5 | ''' 6 | class FacetedBrep(RepresentationItem): 7 | def __init__(self, repr): 8 | self.repr = repr 9 | self.type = 'FacetedBrep' 10 | 11 | def to_json(self): 12 | data = super(FacetedBrep, self).to_json() 13 | return data 14 | 15 | def from_json(self, data): 16 | super(FacetedBrep, self).from_json(data) 17 | 18 | def from_ifc(self, ifc_data): 19 | assert ifc_data.is_a('IfcFacetedBrep') 20 | super(FacetedBrep, self).from_ifc(ifc_data) 21 | self.location = (0, 0, 0) 22 | self.outer_faces = [] 23 | # TODO: Face 24 | #for face in ifc_data.Outer.CfsFaces: 25 | # self.faces.append() 26 | -------------------------------------------------------------------------------- /ifc_model/geometry/i_shape_profile_def.py: -------------------------------------------------------------------------------- 1 | from .relations import Relations 2 | 3 | class IShapeProfileDef(Relations): 4 | def __init__(self, repr): 5 | self.repr = repr 6 | self.type = 'IShapeProfileDef' 7 | 8 | def to_json(self): 9 | data = super(IShapeProfileDef, self).to_json() 10 | data['type'] = self.type 11 | return data 12 | -------------------------------------------------------------------------------- /ifc_model/geometry/point.py: -------------------------------------------------------------------------------- 1 | from .relations import Relations 2 | 3 | class Point(Relations): 4 | def from_json(self, data): 5 | super(Point, self).from_json(data) 6 | self.coords = data['coords'] 7 | self.type = data['type'] 8 | 9 | def to_json(self): 10 | data = super(Point, self).to_json() 11 | data['coords'] = self.coords 12 | data['type'] = self.type 13 | return data 14 | 15 | def from_ifc(self, ifc_data): 16 | assert ifc_data.is_a('IfcCartesianPoint') 17 | super(Point, self).from_ifc(ifc_data) 18 | self.type = ifc_data.is_a()[3:] 19 | self.coords = ifc_data.Coordinates 20 | 21 | def __init__(self, parent): 22 | self.parent = parent 23 | -------------------------------------------------------------------------------- /ifc_model/geometry/rectangle_profile_def.py: -------------------------------------------------------------------------------- 1 | from .relations import Relations 2 | 3 | class RectangleProfileDef(Relations): 4 | def __init__(self, repr): 5 | self.repr = repr 6 | self.type = 'RectangleProfileDef' 7 | 8 | def to_json(self): 9 | data = super(RectangleProfileDef, self).to_json() 10 | data['type'] = self.type 11 | return data 12 | -------------------------------------------------------------------------------- /ifc_model/geometry/representation.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from .relations import Relations 3 | from .extrude_area_solid import ExtrudedAreaSolid 4 | from .faceted_brep import FacetedBrep 5 | 6 | class Representation(Relations): 7 | def from_json(self, json_data): 8 | super(Representation, self).from_json(json_data) 9 | self.shapes = [] 10 | for shape_data in json_data['shapes']: 11 | shape = self.inst_from_type(shape_data['type']) 12 | shape.from_json(shape_data) 13 | self.shapes.append(shape) 14 | self.boxes = [] 15 | 16 | def inst_from_type(self, cls_type): 17 | classes = { 18 | 'ExtrudedAreaSolid': ExtrudedAreaSolid, 19 | 'FacetedBrep': FacetedBrep 20 | } 21 | return classes[cls_type](self) 22 | 23 | def from_ifc(self, ifc_data): 24 | assert ifc_data.is_a('IfcRepresentation'), ifc_data.is_a() 25 | super(Representation, self).from_ifc(ifc_data) 26 | self.shapes = [] 27 | self.boxes = [] # ignored for now 28 | 29 | ignore = [ 30 | 'IfcPolyline', 31 | 'IfcMappedItem', 32 | 'IfcFaceBasedSurfaceModel', 33 | 'IfcBooleanClippingResult' 34 | ] 35 | for ifc_item in self.ifc_data.Items: 36 | item_type = ifc_item.is_a() 37 | if item_type in ignore: 38 | logging.info('ignore ' + item_type) 39 | continue 40 | elif item_type == 'IfcBoundingBox': 41 | # TODO: fill self.boxes 42 | # (ignored for now cause it needs to be specified in export) 43 | logging.info('ignore ' + item_type + ' for ' + self.parent.name) 44 | continue 45 | shape = self.inst_from_type(item_type[3:]) 46 | shape.from_ifc(ifc_item) 47 | self.shapes.append(shape) 48 | 49 | def check_ignore(self): 50 | representation = self.ifc_data 51 | if not representation.is_a('IfcShapeRepresentation'): 52 | logging.info('ignoring ' + representation.is_a() + 53 | ' in ' + self.parent) 54 | return True 55 | return False # do NOT ignore 56 | 57 | def to_json(self): 58 | data = super(Representation, self).to_json() 59 | data['boxes'] = [b.to_json() for b in self.boxes] 60 | data['shapes'] = [s.to_json() for s in self.shapes] 61 | return data 62 | 63 | def __init__(self, parent): 64 | self.parent = parent 65 | -------------------------------------------------------------------------------- /ifc_model/geometry/representation_item.py: -------------------------------------------------------------------------------- 1 | from .relations import Relations 2 | 3 | ''' 4 | see faceted_brep and extrude_area_solid 5 | ''' 6 | class RepresentationItem(Relations): 7 | def to_json(self): 8 | data = super(RepresentationItem, self).to_json() 9 | data['type'] = self.type 10 | data['location'] = self.location 11 | return data 12 | 13 | def from_json(self, data): 14 | super(RepresentationItem, self).from_json(data) 15 | self.location = data['location'] 16 | -------------------------------------------------------------------------------- /ifc_model/geometry/segment.py: -------------------------------------------------------------------------------- 1 | from .relations import Relations 2 | from .point import Point 3 | 4 | ''' 5 | segment either an IfcPolyline or an IfcTrimmedCurve 6 | ''' 7 | class Segment(Relations): 8 | def from_json(self, data): 9 | super(Segment, self).from_json(data) 10 | self.type = data['type'] 11 | if self.type == 'Polyline': 12 | self.points = self.cls_from_json(Point, data['points']) 13 | elif self.type == 'TrimmedCurve': 14 | self.center = Point(self) 15 | self.center.from_json(data['center']) 16 | self.radius = data['radius'] 17 | 18 | 19 | def to_json(self): 20 | data = super(Segment, self).to_json() 21 | data['type'] = self.type 22 | if self.type == 'Polyline': 23 | data['points'] = [p.to_json() for p in self.points] 24 | elif self.type == 'TrimmedCurve': 25 | data['center'] = self.center.to_json() 26 | data['radius'] = self.radius 27 | return data 28 | 29 | def from_ifc(self, ifc_data): 30 | assert ifc_data.is_a('IfcCompositeCurveSegment') 31 | super(Segment, self).from_ifc(ifc_data) 32 | ifc_curve = ifc_data.ParentCurve 33 | self.type = ifc_curve.is_a()[3:] 34 | if self.type == 'Polyline': 35 | self.points = self.cls_from_ifc(Point, ifc_data.ParentCurve.Points) 36 | elif self.type == 'TrimmedCurve': 37 | # TODO: this is just start and end point - direct line, no curve 38 | self.center = Point(self) 39 | self.center.from_ifc(ifc_curve.BasisCurve.Position.Location) 40 | self.radius = ifc_curve.BasisCurve.Radius 41 | else: 42 | raise Exception(ifc_curve.is_a(), ifc_curve.id()) 43 | 44 | def __init__(self, parent): 45 | self.parent = parent 46 | -------------------------------------------------------------------------------- /ifc_model/product.py: -------------------------------------------------------------------------------- 1 | from .relations import Relations 2 | from .representation import Representation 3 | 4 | class Product(Relations): 5 | def from_ifc(self, ifc_data): 6 | assert ifc_data.is_a('IfcProduct') 7 | super(Product, self).from_ifc(ifc_data) 8 | self.name = ifc_data.Name 9 | ifc_type = self.ifc_data.is_a() 10 | assert ifc_type.startswith('Ifc') 11 | self.ifc_type = ifc_type[3:].lower() 12 | 13 | self.representations = [] 14 | if self.ifc_data.Representation: 15 | self.representations = self.cls_from_ifc( 16 | Representation, 17 | self.ifc_data.Representation.Representations 18 | ) 19 | 20 | 21 | def from_json(self, data): 22 | super(Product, self).from_json(data) 23 | self.name = data['name'] 24 | self.ifc_type = data['type'] 25 | self.representations = self.cls_from_json(Representation, data['representations']) 26 | 27 | def to_json(self): 28 | data = super(Product, self).to_json() 29 | data['name'] = self.name 30 | data['type'] = self.ifc_type 31 | data['representations'] = [r.to_json() for r in self.representations] 32 | return data 33 | 34 | def __init__(self, parent): 35 | self.parent = parent 36 | -------------------------------------------------------------------------------- /ifc_model/project.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | from .relations import Relations 4 | from .site import Site 5 | 6 | class Project(Relations): 7 | def from_ifc(self, ifc_data): 8 | assert ifc_data.is_a('IfcProject') 9 | super(Project, self).from_ifc(ifc_data) 10 | # immutable types 11 | self.global_id = ifc_data.GlobalId 12 | self.long_name = ifc_data.LongName 13 | self.name = ifc_data.Name 14 | # parse more complex data structures 15 | self.sites = self.cls_from_ifc(Site, self.ifc_file.by_type('ifcsite')) 16 | 17 | def from_json(self, data): 18 | super(Project, self).from_json(data) 19 | self.global_id = data['global_id'] 20 | self.long_name = data['long_name'] 21 | self.name = data['name'] 22 | self.sites = self.cls_from_json(Site, data['sites']) 23 | 24 | def to_json(self): 25 | data = super(Project, self).to_json() 26 | data['global_id'] = self.global_id 27 | data['long_name'] = self.long_name 28 | data['name'] = self.name 29 | data['sites'] = [site.to_json() for site in self.sites] 30 | return data 31 | 32 | def open_ifc(self, filename): 33 | import ifcopenshell 34 | self.filename, ext = os.path.splitext(filename) 35 | assert ext == '.ifc' 36 | self.ifc_file = ifcopenshell.open(filename) 37 | ifc_projects = self.ifc_file.by_type('ifcproject') 38 | # only one project is allows per ifc file! 39 | assert len(ifc_projects) == 1, 'Only one IfcProject per Ifc-file is allowed' 40 | ifc_project = ifc_projects[0] 41 | self.ifc_data = ifc_project 42 | self.from_ifc(ifc_project) 43 | 44 | def save_json(self): 45 | filename = self.filename + '.json' 46 | json.dump(self.to_json(), open(filename, 'w'), indent=2, sort_keys=True) 47 | return filename 48 | -------------------------------------------------------------------------------- /ifc_model/relations.py: -------------------------------------------------------------------------------- 1 | ''' 2 | helper functions to resolve ifc relations 3 | ''' 4 | class Relations(object): 5 | ''' 6 | get relating objects from IfcRelDecomposes -> IfcRelAggregates 7 | see /ifckernel/lexical/ifcreldecomposes.htm 8 | ''' 9 | def get_related_objects(self, obj, is_a='IfcRelAggregates'): 10 | related_objects = [] 11 | # IsDecomposedBy is a set of IfcRelDecomposes 12 | # (IfcRelAggregates or IfcRelNests) 13 | for decomp in obj.IsDecomposedBy: 14 | if not decomp.is_a(is_a): 15 | logging.warn('No {} according to standard!'.format(is_a)) 16 | # IfcRelNets and IfcRelAggregates require to have at least 1 17 | # element is set! 18 | # see /ifckernel/lexical/ifcrelnests.htm 19 | # and /ifckernel/lexical/ifcrelaggregates.htm 20 | assert len(decomp.RelatedObjects) >= 1 21 | # list of IfcObjectDefinition, e.g. storey, space or building 22 | related_objects += decomp.RelatedObjects 23 | return related_objects 24 | 25 | ''' 26 | get containing objects from IfcRelContainedInSpatialStructure -> IfcProduct 27 | see /ifcproductextension/lexical/ifcrelcontainedinspatialstructure.htm 28 | ''' 29 | def get_related_elements(self, obj, is_a='IfcRelContainedInSpatialStructure'): 30 | elements = [] 31 | # ContainsElements is an IfcRelContainedInSpatialStructure 32 | # (IfcSpace and IfcStorey) 33 | for elem in obj.ContainsElements: 34 | if is_a: 35 | assert elem.is_a(is_a) 36 | # RelatedElements required to have at least 1 element set! 37 | # see /ifcproductextension/lexical/ifcrelcontainedinspatialstructure.htm 38 | assert len(elem.RelatedElements) >= 1 39 | # list of IfcProduct 40 | elements += elem.RelatedElements 41 | return elements 42 | 43 | def to_json(self): 44 | data = {'id': self.id} 45 | return data 46 | 47 | def from_ifc(self, ifc_data): 48 | self.ifc_data = ifc_data 49 | self.id = ifc_data.id() 50 | 51 | def cls_from_ifc(self, cls, ifc_list): 52 | data = [] 53 | for ifc_data in ifc_list: 54 | inst = cls(self) 55 | data.append(inst) 56 | inst.from_ifc(ifc_data) 57 | return data 58 | 59 | def cls_from_json(self, cls, json_list): 60 | data = [] 61 | for json_data in json_list: 62 | inst = cls(self) 63 | data.append(inst) 64 | inst.from_json(json_data) 65 | return data 66 | 67 | def from_json(self, data): 68 | self.id = data['id'] 69 | -------------------------------------------------------------------------------- /ifc_model/representation.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from .relations import Relations 3 | # basic representation to get mesh-id, we do not parse the representation itself. 4 | # (for now, see geometry/) 5 | 6 | class Representation(Relations): 7 | def __init__(self, parent): 8 | self.parent = parent 9 | 10 | def from_json(self, json_data): 11 | super(Representation, self).from_json(json_data) 12 | 13 | def from_ifc(self, ifc_data): 14 | assert ifc_data.is_a('IfcRepresentation'), ifc_data.is_a() 15 | super(Representation, self).from_ifc(ifc_data) 16 | -------------------------------------------------------------------------------- /ifc_model/site.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from .relations import Relations 3 | from .building import Building 4 | #from .representation import Representation 5 | 6 | class Site(Relations): 7 | def from_ifc(self, ifc_data): 8 | assert ifc_data.is_a('IfcSite') 9 | super(Site, self).from_ifc(ifc_data) 10 | 11 | self.global_id = ifc_data.GlobalId 12 | self.name = ifc_data.Name 13 | 14 | ifc_project = self.project.ifc_data 15 | assert ifc_project 16 | if ifc_data.Decomposes: 17 | assert len(ifc_data.Decomposes) == 1, \ 18 | 'Only one IfcProject per IFC file ' \ 19 | '(only one per IfcSite) is allowed' 20 | ifc_decomposes = ifc_data.Decomposes[0] 21 | #assert ifc_decomposes.id() == ifc_project.id(), \ 22 | # 'mismatching project id ({}) for this site ({}).'.format( 23 | # ifc_project.id(),ifc_decomposes.id() 24 | # ) 25 | else: 26 | # TODO: bug report to Autodesk? 27 | logging.warn('IfcSite is not connected to the IfcProject. ' \ 28 | 'We assume there is only one connection, this is ' \ 29 | 'not conform with the the IFC specification') 30 | self.buildings = self.cls_from_ifc( 31 | Building, 32 | self.get_related_objects(ifc_data) 33 | ) 34 | 35 | #self.representations = [] 36 | #if self.ifc_data.Representation: 37 | # self.representations = self.cls_from_ifc( 38 | # Representation, 39 | # self.ifc_data.Representation.Representations 40 | # ) 41 | 42 | def from_json(self, data): 43 | super(Site, self).from_json(data) 44 | self.global_id = data['global_id'] 45 | self.name = data['name'] 46 | self.buildings = self.cls_from_json(Building, data['buildings']) 47 | #self.representations = self.cls_from_json(Representation, data['representations']) 48 | 49 | def to_json(self): 50 | data = super(Site, self).to_json() 51 | data['global_id'] = self.global_id 52 | data['name'] = self.name 53 | data['buildings'] = [b.to_json() for b in self.buildings] 54 | #data['representations'] = [r.to_json() for r in self.representations] 55 | return data 56 | 57 | def __init__(self, project): 58 | self.project = project 59 | -------------------------------------------------------------------------------- /ifc_model/space.py: -------------------------------------------------------------------------------- 1 | from .relations import Relations 2 | from .product import Product 3 | from .representation import Representation 4 | 5 | class Space(Relations): 6 | def from_ifc(self, ifc_data): 7 | assert ifc_data.is_a('IfcSpace') 8 | super(Space, self).from_ifc(ifc_data) 9 | self.name = ifc_data.Name 10 | self.representations = [] 11 | if self.ifc_data.Representation: 12 | self.representations = self.cls_from_ifc( 13 | Representation, 14 | self.ifc_data.Representation.Representations 15 | ) 16 | self.products = self.cls_from_ifc( 17 | Product, 18 | self.get_related_elements(ifc_data) 19 | ) 20 | 21 | 22 | def from_json(self, data): 23 | super(Space, self).from_json(data) 24 | self.name = data['name'] 25 | self.products = self.cls_from_json(Product, data['products']) 26 | self.representations = self.cls_from_json(Representation, data['representations']) 27 | 28 | def to_json(self): 29 | data = super(Space, self).to_json() 30 | data['name'] = self.name 31 | data['products'] = [p.to_json() for p in self.products] 32 | data['representations'] = [r.to_json() for r in self.representations] 33 | return data 34 | 35 | def __init__(self, storey): 36 | self.storey = storey 37 | -------------------------------------------------------------------------------- /ifc_model/storey.py: -------------------------------------------------------------------------------- 1 | from .relations import Relations 2 | from .product import Product 3 | from .space import Space 4 | from .representation import Representation 5 | 6 | ''' 7 | spaces/rooms and products, 8 | see /ifcproductextension/lexical/ifcbuildingstorey.htm 9 | ''' 10 | class Storey(Relations): 11 | def from_ifc(self, ifc_data): 12 | assert ifc_data.is_a('IfcBuildingStorey') 13 | super(Storey, self).from_ifc(ifc_data) 14 | self.spaces = self.cls_from_ifc( 15 | Space, 16 | self.get_related_objects(ifc_data) 17 | ) 18 | self.name = ifc_data.Name 19 | self.long_name = ifc_data.LongName 20 | self.products = self.cls_from_ifc( 21 | Product, 22 | self.get_related_elements(ifc_data) 23 | ) 24 | self.elevation = ifc_data.Elevation 25 | self.representations = [] 26 | if self.ifc_data.Representation: 27 | self.representations = self.cls_from_ifc( 28 | Representation, 29 | self.ifc_data.Representation.Representations 30 | ) 31 | 32 | def from_json(self, data): 33 | super(Storey, self).from_json(data) 34 | self.name = data['name'] 35 | self.long_name = data['long_name'] 36 | self.elevation = data['elevation'] 37 | self.spaces = self.cls_from_json(Space, data['spaces']) 38 | self.products = self.cls_from_json(Product, data['products']) 39 | self.representations = self.cls_from_json(Representation, data['representations']) 40 | 41 | def to_json(self): 42 | data = super(Storey, self).to_json() 43 | data['name'] = self.name 44 | data['long_name'] = self.long_name 45 | data['elevation'] = self.elevation 46 | data['spaces'] = [s.to_json() for s in self.spaces] 47 | data['products'] = [p.to_json() for p in self.products] 48 | data['representations'] = [r.to_json() for r in self.representations] 49 | return data 50 | 51 | def __init__(self, building): 52 | self.building = building 53 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | 4 | setup( 5 | name='ifc_model', 6 | author='Andreas Bresser', 7 | author_email='self@andreasbresser.de', 8 | url='https://github.com/brean/ifc_model', 9 | long_description='', 10 | version='dev', 11 | packages=find_packages(), 12 | include_package_data=True, 13 | install_requires=[]) --------------------------------------------------------------------------------