├── .gitignore
├── LICENSE
├── README.md
├── __init__.py
├── __main__.py
├── mvd.py
├── mvd_examples
├── Example-CV100.mvdxml
├── Example-CV104.mvdxml
├── Example-CV106.mvdxml
├── building.mvdxml
├── officials
│ └── ReferenceView_V1-2.mvdxml
├── wall_extraction.mvdxml
└── xset.mvdxml
├── mvdxml_expression.py
└── sparql.py
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | *.egg-info/
24 | .installed.cfg
25 | *.egg
26 | MANIFEST
27 |
28 | # PyInstaller
29 | # Usually these files are written by a python script from a template
30 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
31 | *.manifest
32 | *.spec
33 |
34 | # Installer logs
35 | pip-log.txt
36 | pip-delete-this-directory.txt
37 |
38 | # Unit test / coverage reports
39 | htmlcov/
40 | .tox/
41 | .coverage
42 | .coverage.*
43 | .cache
44 | nosetests.xml
45 | coverage.xml
46 | *.cover
47 | .hypothesis/
48 | .pytest_cache/
49 |
50 | # Translations
51 | *.mo
52 | *.pot
53 |
54 | # Django stuff:
55 | *.log
56 | local_settings.py
57 | db.sqlite3
58 |
59 | # Flask stuff:
60 | instance/
61 | .webassets-cache
62 |
63 | # Scrapy stuff:
64 | .scrapy
65 |
66 | # Sphinx documentation
67 | docs/_build/
68 |
69 | # PyBuilder
70 | target/
71 |
72 | # Jupyter Notebook
73 | .ipynb_checkpoints
74 |
75 | # pyenv
76 | .python-version
77 |
78 | # celery beat schedule file
79 | celerybeat-schedule
80 |
81 | # SageMath parsed files
82 | *.sage.py
83 |
84 | # Environments
85 | .env
86 | .venv
87 | env/
88 | venv/
89 | ENV/
90 | env.bak/
91 | venv.bak/
92 |
93 | # Spyder project settings
94 | .spyderproject
95 | .spyproject
96 |
97 | # Rope project settings
98 | .ropeproject
99 |
100 | # mkdocs documentation
101 | /site
102 |
103 | # mypy
104 | .mypy_cache/
105 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU LESSER GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 |
9 | This version of the GNU Lesser General Public License incorporates
10 | the terms and conditions of version 3 of the GNU General Public
11 | License, supplemented by the additional permissions listed below.
12 |
13 | 0. Additional Definitions.
14 |
15 | As used herein, "this License" refers to version 3 of the GNU Lesser
16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU
17 | General Public License.
18 |
19 | "The Library" refers to a covered work governed by this License,
20 | other than an Application or a Combined Work as defined below.
21 |
22 | An "Application" is any work that makes use of an interface provided
23 | by the Library, but which is not otherwise based on the Library.
24 | Defining a subclass of a class defined by the Library is deemed a mode
25 | of using an interface provided by the Library.
26 |
27 | A "Combined Work" is a work produced by combining or linking an
28 | Application with the Library. The particular version of the Library
29 | with which the Combined Work was made is also called the "Linked
30 | Version".
31 |
32 | The "Minimal Corresponding Source" for a Combined Work means the
33 | Corresponding Source for the Combined Work, excluding any source code
34 | for portions of the Combined Work that, considered in isolation, are
35 | based on the Application, and not on the Linked Version.
36 |
37 | The "Corresponding Application Code" for a Combined Work means the
38 | object code and/or source code for the Application, including any data
39 | and utility programs needed for reproducing the Combined Work from the
40 | Application, but excluding the System Libraries of the Combined Work.
41 |
42 | 1. Exception to Section 3 of the GNU GPL.
43 |
44 | You may convey a covered work under sections 3 and 4 of this License
45 | without being bound by section 3 of the GNU GPL.
46 |
47 | 2. Conveying Modified Versions.
48 |
49 | If you modify a copy of the Library, and, in your modifications, a
50 | facility refers to a function or data to be supplied by an Application
51 | that uses the facility (other than as an argument passed when the
52 | facility is invoked), then you may convey a copy of the modified
53 | version:
54 |
55 | a) under this License, provided that you make a good faith effort to
56 | ensure that, in the event an Application does not supply the
57 | function or data, the facility still operates, and performs
58 | whatever part of its purpose remains meaningful, or
59 |
60 | b) under the GNU GPL, with none of the additional permissions of
61 | this License applicable to that copy.
62 |
63 | 3. Object Code Incorporating Material from Library Header Files.
64 |
65 | The object code form of an Application may incorporate material from
66 | a header file that is part of the Library. You may convey such object
67 | code under terms of your choice, provided that, if the incorporated
68 | material is not limited to numerical parameters, data structure
69 | layouts and accessors, or small macros, inline functions and templates
70 | (ten or fewer lines in length), you do both of the following:
71 |
72 | a) Give prominent notice with each copy of the object code that the
73 | Library is used in it and that the Library and its use are
74 | covered by this License.
75 |
76 | b) Accompany the object code with a copy of the GNU GPL and this license
77 | document.
78 |
79 | 4. Combined Works.
80 |
81 | You may convey a Combined Work under terms of your choice that,
82 | taken together, effectively do not restrict modification of the
83 | portions of the Library contained in the Combined Work and reverse
84 | engineering for debugging such modifications, if you also do each of
85 | the following:
86 |
87 | a) Give prominent notice with each copy of the Combined Work that
88 | the Library is used in it and that the Library and its use are
89 | covered by this License.
90 |
91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license
92 | document.
93 |
94 | c) For a Combined Work that displays copyright notices during
95 | execution, include the copyright notice for the Library among
96 | these notices, as well as a reference directing the user to the
97 | copies of the GNU GPL and this license document.
98 |
99 | d) Do one of the following:
100 |
101 | 0) Convey the Minimal Corresponding Source under the terms of this
102 | License, and the Corresponding Application Code in a form
103 | suitable for, and under terms that permit, the user to
104 | recombine or relink the Application with a modified version of
105 | the Linked Version to produce a modified Combined Work, in the
106 | manner specified by section 6 of the GNU GPL for conveying
107 | Corresponding Source.
108 |
109 | 1) Use a suitable shared library mechanism for linking with the
110 | Library. A suitable mechanism is one that (a) uses at run time
111 | a copy of the Library already present on the user's computer
112 | system, and (b) will operate properly with a modified version
113 | of the Library that is interface-compatible with the Linked
114 | Version.
115 |
116 | e) Provide Installation Information, but only if you would otherwise
117 | be required to provide such information under section 6 of the
118 | GNU GPL, and only to the extent that such information is
119 | necessary to install and execute a modified version of the
120 | Combined Work produced by recombining or relinking the
121 | Application with a modified version of the Linked Version. (If
122 | you use option 4d0, the Installation Information must accompany
123 | the Minimal Corresponding Source and Corresponding Application
124 | Code. If you use option 4d1, you must provide the Installation
125 | Information in the manner specified by section 6 of the GNU GPL
126 | for conveying Corresponding Source.)
127 |
128 | 5. Combined Libraries.
129 |
130 | You may place library facilities that are a work based on the
131 | Library side by side in a single library together with other library
132 | facilities that are not Applications and are not covered by this
133 | License, and convey such a combined library under terms of your
134 | choice, if you do both of the following:
135 |
136 | a) Accompany the combined library with a copy of the same work based
137 | on the Library, uncombined with any other library facilities,
138 | conveyed under the terms of this License.
139 |
140 | b) Give prominent notice with the combined library that part of it
141 | is a work based on the Library, and explaining where to find the
142 | accompanying uncombined form of the same work.
143 |
144 | 6. Revised Versions of the GNU Lesser General Public License.
145 |
146 | The Free Software Foundation may publish revised and/or new versions
147 | of the GNU Lesser General Public License from time to time. Such new
148 | versions will be similar in spirit to the present version, but may
149 | differ in detail to address new problems or concerns.
150 |
151 | Each version is given a distinguishing version number. If the
152 | Library as you received it specifies that a certain numbered version
153 | of the GNU Lesser General Public License "or any later version"
154 | applies to it, you have the option of following the terms and
155 | conditions either of that published version or of any later version
156 | published by the Free Software Foundation. If the Library as you
157 | received it does not specify a version number of the GNU Lesser
158 | General Public License, you may choose any version of the GNU Lesser
159 | General Public License ever published by the Free Software Foundation.
160 |
161 | If the Library as you received it specifies that a proxy can decide
162 | whether future versions of the GNU Lesser General Public License shall
163 | apply, that proxy's public statement of acceptance of any version is
164 | permanent authorization for you to choose that version for the
165 | Library.
166 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## python-mvdxml
2 |
3 | A mvdXML checker and w3c SPARQL converter, as an IfcOpenShell submodule or stand-alone.
4 |
5 | WARNING: While this repository has many useful building blocks to build software around mvdXML and IFC, there are many mvdXML dialects and not all variants are likely to be fully supported.
6 |
7 | ### Quickstart
8 |
9 | #### Extraction
10 |
11 | ```python
12 | import ifcopenshell
13 | from ifcopenshell.mvd import mvd
14 |
15 | mvd_concept = mvd.open_mvd("examples/wall_extraction.mvdxml")
16 | file = ifcopenshell.open("Duplex_A_20110505.ifc")
17 |
18 | all_data = mvd.get_data(mvd_concept, file, spreadsheet_export=True)
19 |
20 | non_respecting_entities = mvd.get_non_respecting_entities(file, all_data[1])
21 | respecting_entities = mvd.get_respecting_entities(file, all_data[1])
22 |
23 |
24 | ```
25 |
26 | ```python
27 | # Create a new file
28 | new_file = ifcopenshell.file(schema=file.schema)
29 | proj = file.by_type("IfcProject")[0]
30 | new_file.add(proj)
31 |
32 | for e in respecting_entities:
33 | new_file.add(e)
34 |
35 | new_file.write("new_file.ifc")
36 | ```
37 |
38 | ```python
39 | # Visualize results
40 | mvd.visualize(file, non_respecting_entities)
41 | ```
42 |
43 | ##### Validation
44 |
45 | ~~~py
46 | import ifcopenshell
47 |
48 | from ifcopenshell.mvd import mvd
49 | from colorama import Fore
50 | from colorama import Style
51 |
52 | concept_roots = list(ifcopenshell.mvd.concept_root.parse(MVDXML_FILENAME))
53 | file = ifcopenshell.open(IFC_FILENAME)
54 |
55 | tt = 0 # total number of tests
56 | ts = 0 # total number of successful tests
57 |
58 | for concept_root in concept_roots:
59 | print("ConceptRoot: ", concept_root.entity)
60 | for concept in concept_root.concepts():
61 | tt = tt + 1
62 | print("Concept: ", concept.name)
63 | try:
64 |
65 | if len(concept.template().rules) > 1:
66 | attribute_rules = []
67 | for rule in concept.template().rules:
68 | attribute_rules.append(rule)
69 | rules_root = ifcopenshell.mvd.rule("EntityRule", concept_root.entity, attribute_rules)
70 | else:
71 | rules_root = concept.template().rules[0]
72 | ts = ts + 1
73 | finst = 0 #failed instances
74 |
75 | for inst in file.by_type(concept_root.entity):
76 | try:
77 | data = mvd.extract_data(rules_root, inst)
78 | valid, output = mvd.validate_data(concept, data)
79 | if not valid:
80 | finst = finst + 1
81 | print("[VALID]" if valid else Fore.RED +"[failure]"+Style.RESET_ALL, inst)
82 | print(output)
83 | except Exception as e:
84 | print(Fore.RED+"EXCEPTION: ", e, Style.RESET_ALL,inst)
85 | print ()
86 | print (int(finst), "out of", int(len(file.by_type(concept_root.entity))), "instances failed the check")
87 | print ("---------------------------------")
88 | except Exception as e:
89 | print("EXCEPTION: "+Fore.RED,e,Style.RESET_ALL)
90 | print("---------------------------------")
91 | print("---------------------------------")
92 | print("---------------------------------")
93 |
94 | tf = tt-ts # total number of failed tests
95 |
96 | print ("\nRESULTS OVERVIEW")
97 | print ("Total number of tests: ",tt)
98 | print ("Total number of executed tests: ", ts)
99 | print ("Total number of failed tests: ", tf)
100 | ~~~
101 |
--------------------------------------------------------------------------------
/__init__.py:
--------------------------------------------------------------------------------
1 | from . import mvdxml_expression
2 |
3 | from xml.dom.minidom import parse, Element
4 |
5 | class rule(object):
6 | """
7 | A class for representing an mvdXML EntityRule or AttributeRule
8 | """
9 | parent = None
10 |
11 | def __init__(self, tag, attribute, nodes, bind=None, optional=False):
12 | self.tag, self.attribute, self.nodes, self.bind = tag, attribute, nodes, bind
13 | self.optional = optional
14 |
15 | def to_string(self, indent=0):
16 | # return "%s%s%s[%s](%s%s)%s" % ("\n" if indent else "", " "*indent, self.tag, self.attribute, "".join(n.to_string(indent+2) for n in self.nodes), ("\n" + " "*indent) if len(self.nodes) else "", (" -> %s" % self.bind) if self.bind else "")
17 | return "<%s %s%s>" % (self.tag, f"{self.bind}=" if self.bind else "", self.attribute)
18 |
19 | def __repr__(self):
20 | return self.to_string()
21 |
22 | class template(object):
23 | """
24 | Representation of an mvdXML template
25 | """
26 |
27 | def __init__(self, concept, root, constraints=None, rules=None, parent=None):
28 | self.concept, self.root, self.constraints, self.parent = concept, root, (constraints or []), parent
29 | self.rules = rules or []
30 | self.entity = str(root.attributes['applicableEntity'].value)
31 | try:
32 | self.name = root.attributes['name'].value
33 | except:
34 | self.name = None
35 |
36 | def bind(self, constraints):
37 | return template(self.concept, self.root, constraints, self.rules)
38 |
39 | def parse(self, visited=None):
40 | for rules in self.root.getElementsByTagNameNS("*", "Rules"):
41 | for r in rules.childNodes:
42 | if not isinstance(r, Element): continue
43 | self.rules.append(self.parse_rule(r, visited=visited))
44 |
45 | def traverse(self, fn, root=None, with_parents=False):
46 | def visit(n, p=root, ps=[root]):
47 | if with_parents:
48 | close = fn(rule=n, parents=ps)
49 | else:
50 | close = fn(rule=n, parent=p)
51 |
52 | for s in n.nodes:
53 | visit(s, n, ps + [n])
54 |
55 | if close:
56 | close()
57 |
58 | for r in self.rules:
59 | visit(r)
60 |
61 | def parse_rule(self, root, visited=None):
62 | def visit(node, prefix="", visited=None, parent=None):
63 | r = None
64 | n = node
65 | nm = None
66 | p = prefix
67 | optional = False
68 | visited = set() if visited is None else visited
69 |
70 | if node.localName == "AttributeRule":
71 | r = node.attributes["AttributeName"].value
72 | try:
73 | nm = node.attributes["RuleID"].value
74 | except:
75 | # without binding, it's wrapped in a SPARQL OPTIONAL {} clause
76 | # Aim is to insert this clause once as high in the stack as possible
77 | # All topmost attribute rules are optional anyway as in the binding requirements on existence is specified
78 |
79 | def child_has_ruleid_or_prefix(node):
80 | if type(node).__name__ == "Element":
81 | if "RuleID" in node.attributes or "IdPrefix" in node.attributes:
82 | return True
83 | for n in node.childNodes:
84 | if child_has_ruleid_or_prefix(n): return True
85 |
86 | optional = node.parentNode.localName == "Rules" or not child_has_ruleid_or_prefix(node)
87 | elif node.localName == "EntityRule":
88 | r = node.attributes["EntityName"].value
89 | elif node.localName == "Template":
90 | ref = node.attributes['ref'].value
91 | # we break infinite recursion using this set
92 | if ref not in visited:
93 | n = self.concept.template(ref, visited=visited | {ref}).root
94 | try:
95 | p = p + node.attributes["IdPrefix"].value
96 | except:
97 | pass
98 | elif node.localName == "Constraint":
99 | r = mvdxml_expression.parse(node.attributes["Expression"].value)
100 | elif node.localName == "EntityRules": pass
101 | elif node.localName == "AttributeRules": pass
102 | elif node.localName == "Rules": pass
103 | elif node.localName == "Constraints": pass
104 | elif node.localName == "References": pass
105 | elif node.localName == "Definitions": return
106 | elif node.localName == "SubTemplates": return # @todo perhaps just traverse them?
107 | else:
108 | raise ValueError(node.localName)
109 |
110 | def _(n):
111 | for subnode in n.childNodes:
112 | if not isinstance(subnode, Element): continue
113 | for x in visit(subnode, p, visited=visited): yield x
114 |
115 | if r:
116 | R = rule(node.localName, r, list(_(n)), (p + nm) if nm else nm, optional=optional)
117 | for rr in R.nodes:
118 | rr.parent = R
119 | yield R
120 | else:
121 | for subnode in n.childNodes:
122 | if not isinstance(subnode, Element): continue
123 | for x in visit(subnode, p, visited=visited): yield x
124 |
125 | return list(visit(root, visited=visited))[0]
126 |
127 | class concept_or_applicability(object):
128 | """
129 | Representation of either a mvdXML Concept or the Applicability node. Basically a structure
130 | for the hierarchical TemplateRule
131 | """
132 |
133 | def __init__(self, root, c):
134 | self.root = root
135 | self.concept_node = c
136 | try:
137 | self.name = c.attributes["name"].value
138 | except:
139 | # probably applicability and not concept
140 | self.name = "Applicability"
141 |
142 | def template(self, id=None, visited=None):
143 | if id is None:
144 | id = self.concept_node.getElementsByTagNameNS("*","Template")[0].attributes['ref'].value
145 |
146 | for node in self.root.dom.getElementsByTagNameNS('*',"ConceptTemplate"):
147 | if node.attributes["uuid"].value == id:
148 | t = template(self, node)
149 | t.parse(visited=visited)
150 | t_with_rules = t.bind(self.rules())
151 | return t_with_rules
152 |
153 | def rules(self):
154 | # Get the top most TemplateRule and traverse
155 | try:
156 | rules = self.concept_node.getElementsByTagNameNS("*","TemplateRules")[0]
157 | except:
158 | return []
159 |
160 | def visit(rules):
161 | def _():
162 | for i, r in enumerate([c for c in rules.childNodes if isinstance(c, Element)]):
163 | if i:
164 | yield rules.attributes["operator"].value
165 | if r.localName == "TemplateRules":
166 | yield visit(r)
167 | elif r.localName == "TemplateRule":
168 | yield mvdxml_expression.parse(r.attributes["Parameters"].value)
169 | else:
170 | raise Exception()
171 |
172 | return list(_())
173 |
174 | return visit(rules)
175 |
176 | class concept_root(object):
177 | def __init__(self, dom, root):
178 | self.dom, self.root = dom, root
179 | self.name = root.attributes['name'].value
180 | self.entity = str(root.attributes['applicableRootEntity'].value)
181 |
182 | def applicability(self):
183 | return concept_or_applicability(self, self.root.getElementsByTagNameNS("*","Applicability")[0])
184 |
185 | def concepts(self):
186 | for c in self.root.getElementsByTagNameNS("*","Concept"):
187 | yield concept_or_applicability(self, c)
188 |
189 | @staticmethod
190 | def parse(fn):
191 | dom = parse(fn)
192 | if len(dom.getElementsByTagNameNS("*","ConceptRoot")):
193 | for root in dom.getElementsByTagNameNS("*","ConceptRoot"):
194 | CR = concept_root(dom, root)
195 | yield CR
196 | else:
197 | for templ in dom.getElementsByTagNameNS("*","ConceptTemplate"):
198 | t = template(None, templ)
199 | t.parse()
200 | yield t
201 |
--------------------------------------------------------------------------------
/__main__.py:
--------------------------------------------------------------------------------
1 | from __future__ import print_function
2 |
3 | if __name__ == "__main__":
4 | import sys
5 | from . import concept_root
6 |
7 | if len(sys.argv) == 2:
8 | mvdfn = sys.argv[1]
9 | for mvd in concept_root.parse(mvdfn):
10 |
11 | def dump(rule, parents):
12 | print(" " * len(parents), rule.tag, rule.attribute)
13 |
14 | for c in mvd.concepts():
15 | print(c.name)
16 | print()
17 |
18 | t = c.template()
19 | print("RootEntity", t.entity)
20 | t.traverse(dump, with_parents=True)
21 | print(" ".join(map(str, t.constraints)))
22 |
23 | print()
24 |
25 | elif len(sys.argv) == 3:
26 | from . import sparql
27 | mvdfn,ttlfn = sys.argv[1:]
28 | sparql.derive_prefix(ttlfn)
29 | ttlfn = sparql.infer_subtypes(ttlfn)
30 | for mvd in concept_root.parse(mvdfn):
31 | sparql.executor.run(mvd, mvdfn, ttlfn)
32 |
33 | else:
34 | print(sys.executable, "ifcopenshell.mvd", "<.mvdxml>")
35 | print(sys.executable, "ifcopenshell.mvd", "<.mvdxml>", "<.ifc>")
36 |
--------------------------------------------------------------------------------
/mvd.py:
--------------------------------------------------------------------------------
1 | import ifcopenshell
2 | import ifcopenshell.geom
3 |
4 | import os
5 | import itertools
6 |
7 | import csv
8 | import xlsxwriter
9 |
10 |
11 | def is_applicability(concept):
12 | """
13 | Check whether the Concept created has a filtering purpose.
14 | Actually, MvdXML has a specific Applicability node.
15 |
16 | :param concept: mvdXML Concept object
17 | """
18 | return concept.name.startswith("AP")
19 |
20 |
21 | def merge_dictionaries(dicts):
22 | d = {}
23 | for e in dicts:
24 | d.update(e)
25 | return d
26 |
27 |
28 | def extract_data(mvd_node, ifc_data):
29 | """
30 | Recursively traverses mvdXML Concept tree structure.
31 | This tree is made of different mvdXML Rule nodes: AttributesRule
32 | and EntityRule.
33 |
34 | :param mvd_node: an mvdXML Concept
35 | :param ifc_data: an IFC instance or an IFC value
36 |
37 |
38 | """
39 | to_combine = []
40 | return_value = []
41 |
42 | if len(mvd_node.nodes) == 0:
43 | if mvd_node.tag == "AttributeRule":
44 | try:
45 | values_from_attribute = getattr(ifc_data, mvd_node.attribute)
46 | return [{mvd_node: values_from_attribute}]
47 | except:
48 | return [{mvd_node: "Invalid Attribute"}]
49 |
50 | else:
51 | return [{mvd_node: ifc_data}]
52 |
53 | if mvd_node.tag == 'AttributeRule':
54 | data_from_attribute = []
55 | try:
56 | values_from_attribute = getattr(ifc_data, mvd_node.attribute)
57 | if values_from_attribute is None:
58 | return [{mvd_node:"Nonexistent value"}]
59 |
60 | except:
61 | return [{mvd_node:"Invalid attribute rule"}]
62 |
63 |
64 | if isinstance(values_from_attribute, (list, tuple)):
65 | if len(values_from_attribute) == 0:
66 | return [{mvd_node: 'empty data structure'}]
67 | data_from_attribute.extend(values_from_attribute)
68 |
69 | else:
70 | data_from_attribute.append(values_from_attribute)
71 |
72 | for child in mvd_node.nodes:
73 | for data in data_from_attribute:
74 | child_values = extract_data(child, data)
75 | if isinstance(child_values, (list, tuple)):
76 | return_value.extend(child_values)
77 | else:
78 | return_value.append(child_values)
79 | return return_value
80 |
81 | elif mvd_node.tag == 'EntityRule':
82 | # Avoid things like Quantities on Psets
83 | if len(mvd_node.nodes):
84 | if isinstance(ifc_data, ifcopenshell.entity_instance) and not ifc_data.is_a(mvd_node.attribute):
85 | return []
86 |
87 | for child in mvd_node.nodes:
88 | if child.tag == "Constraint":
89 | on_node = child.attribute[0].c
90 | on_node = on_node.replace("'", "")
91 | if isinstance(ifc_data, ifcopenshell.entity_instance):
92 | ifc_type = type(ifc_data[0])
93 | typed_node = (ifc_type)(on_node)
94 |
95 | if ifc_data[0] == typed_node:
96 | return [{mvd_node: ifc_data}]
97 |
98 | elif ifc_data == on_node:
99 | return [{mvd_node: ifc_data}]
100 | else:
101 | to_combine.append(extract_data(child, ifc_data))
102 |
103 | if len(to_combine):
104 | return_value = list(map(merge_dictionaries, itertools.product(*to_combine)))
105 |
106 | return return_value
107 |
108 |
109 | def open_mvd(filename):
110 | """
111 | Open an mvdXML file.
112 |
113 | :param filename: Path of the mvdXML file.
114 | :return: mvdXML Concept instance.
115 | """
116 | my_concept_object = list(ifcopenshell.mvd.concept_root.parse(filename))[0]
117 | return my_concept_object
118 |
119 |
120 | def format_data_from_nodes(recurse_output):
121 | """
122 | Enable to format data collected such that the value to be exported is extracted.
123 |
124 | :param recurse_output: Data extracted from the recursive function
125 |
126 | """
127 | if len(recurse_output) > 1:
128 | output = []
129 | for resulting_dict in recurse_output:
130 | intermediate_storing = []
131 | for value in resulting_dict.values():
132 | intermediate_storing.append(value)
133 | output.extend(intermediate_storing)
134 | return output
135 |
136 | elif len(recurse_output) == 1:
137 | return_list = []
138 | intermediate_list = list(recurse_output[0].values())
139 | if len(intermediate_list) > 1:
140 | returned_value = intermediate_list
141 | for element in intermediate_list:
142 | # In case of a property that comes with all its path
143 | # (like ['PSet_WallCommon, 'IsExternal', IfcBoolean(.F.)
144 | # return only the list element which is not of string type
145 | # todo: check above condition with ifcopenshell type
146 | if not isinstance(element, str):
147 | returned_value = element
148 | if returned_value != intermediate_list:
149 | return returned_value
150 | else:
151 | return intermediate_list
152 | else:
153 | return intermediate_list[0]
154 |
155 | else:
156 | return []
157 |
158 |
159 | def get_data_from_mvd(entities, tree, filtering=False):
160 | """
161 | Apply the recursive function on the entities to return
162 | the values extracted.
163 |
164 | :param entities: IFC instances to be processed.
165 | :param tree: mvdXML Concept instance tree root.
166 | :param filtering: Indicates whether the mvdXML tree is an applicability.
167 |
168 | """
169 | filtered_entities = []
170 | extracted_entities_data = {}
171 |
172 | for entity in entities:
173 | entity_id = entity.GlobalId
174 | combinations = extract_data(tree, entity)
175 | desired_results = []
176 |
177 | for dictionary in combinations:
178 | desired_results.append(dictionary)
179 |
180 | output = format_data_from_nodes(desired_results)
181 |
182 | if filtering:
183 | if len(output):
184 | extracted_entities_data[entity_id] = output
185 | else:
186 | extracted_entities_data[entity_id] = output
187 |
188 | return extracted_entities_data
189 |
190 |
191 | def correct_for_export(all_data):
192 | """
193 | Process the data for spreadsheet export.
194 | """
195 | for d in all_data:
196 | for k, v in d.items():
197 | if isinstance(v, list) or isinstance(v, tuple):
198 | if len(v):
199 | new_list = []
200 | for data in v:
201 | new_list.append(str(data))
202 | d[k] = ','.join(new_list)
203 | if len(v) == 0:
204 | d[k] = 0
205 |
206 | elif isinstance(v, ifcopenshell.entity_instance):
207 | d[k] = v[0]
208 | return all_data
209 |
210 |
211 | def export_to_xlsx(xlsx_name, concepts, all_data):
212 | """
213 | Export data towards XLSX spreadsheet format.
214 |
215 | :param xlsx_name: Name of the outputted file.
216 | :param concepts: List of mvdXML Concept instances.
217 | :param all_data: Data extracted.
218 |
219 | """
220 |
221 | if not os.path.isdir("spreadsheet_output/"):
222 | os.mkdir("spreadsheet_output/")
223 |
224 | workbook = xlsxwriter.Workbook("spreadsheet_output/" + xlsx_name)
225 | worksheet = workbook.add_worksheet()
226 | # Formats
227 | bold_format = workbook.add_format()
228 | bold_format.set_bold()
229 | bold_format.set_center_across()
230 | # Write first row
231 | column_index = 0
232 | for concept in concepts:
233 | worksheet.write(0, column_index, concept.name, bold_format)
234 | column_index += 1
235 |
236 | col = 0
237 | for feature in all_data:
238 | row = 1
239 | for d in feature.values():
240 | worksheet.write(row, col, d)
241 | row += 1
242 | col += 1
243 |
244 | workbook.close()
245 |
246 |
247 | def export_to_csv(csv_name, concepts, all_data):
248 | """
249 | Export data towards CSV spreadsheet format.
250 |
251 | :param csv_name: Name of the file outputted file.
252 | :param concepts: List of mvdXML Concept instances.
253 | :param all_data: Data extracted.
254 | """
255 |
256 | if not os.path.isdir("spreadsheet_output/"):
257 | os.mkdir("spreadsheet_output/")
258 |
259 | with open('spreadsheet_output/' + csv_name, 'w', newline='') as f:
260 | writer = csv.writer(f)
261 | header = [concept.name for concept in concepts]
262 | first_row = writer.writerow(header)
263 |
264 | values_by_row = []
265 | for val in all_data:
266 | values_by_row.append(list(val.values()))
267 | entities_number = len(all_data[0].keys())
268 | for i in range(0, entities_number):
269 | row_to_write = []
270 | for r in values_by_row:
271 | row_to_write.append(r[i])
272 |
273 | f = writer.writerow(row_to_write)
274 |
275 |
276 | def get_data(mvd_concept, ifc_file, spreadsheet_export=True):
277 | """
278 | Use the majority of all the other functions to return the data
279 | queried by the mvdXML file in python format.
280 |
281 | :param mvd_concept: mvdXML Concept instance.
282 | :param ifc_file: IFC file from any schema.
283 | :param spreadsheet_export: The spreadsheet export is carried out when set to True.
284 |
285 |
286 |
287 | """
288 |
289 | # Check if IFC entities have been filtered at least once
290 | filtered = 0
291 |
292 | entities = ifc_file.by_type(mvd_concept.entity)
293 | selected_entities = entities
294 | verification_matrix = {}
295 | for entity in selected_entities:
296 | verification = dict()
297 | verification_matrix[entity.GlobalId] = verification
298 |
299 | # For each Concept(ConceptTemplate) in the ConceptRoot
300 | concepts = sorted(mvd_concept.concepts(), key=is_applicability, reverse=True)
301 | all_data = []
302 | counter = 0
303 | for concept in concepts:
304 | if is_applicability(concept):
305 | filtering = True
306 | else:
307 | filtering = False
308 |
309 | # Access all the Rules of the ConceptTemplate
310 | if len(concept.template().rules) > 1:
311 | attribute_rules = []
312 | for rule in concept.template().rules:
313 | attribute_rules.append(rule)
314 | rules_root = ifcopenshell.mvd.rule("EntityRule", mvd_concept.entity, attribute_rules)
315 | else:
316 | rules_root = concept.template().rules[0]
317 |
318 |
319 | extracted_data = get_data_from_mvd(selected_entities, rules_root, filtering=filtering)
320 | all_data.append(extracted_data)
321 |
322 | if filtering:
323 | filtered = 1
324 | new_entities = []
325 | for entity_id in all_data[counter].keys():
326 | if len(all_data[counter][entity_id]) != 0:
327 | entity = ifc_file.by_id(entity_id)
328 | new_entities.append(entity)
329 |
330 | selected_entities = new_entities
331 | not_respecting_entities = [item for item in entities if item not in selected_entities]
332 | for entity in entities:
333 | val = 0
334 | if entity in not_respecting_entities:
335 | val = 1
336 | verification_matrix[entity.GlobalId].update({concept.name: val})
337 | counter += 1
338 |
339 | all_data = correct_for_export(all_data)
340 |
341 | if spreadsheet_export:
342 | if filtered != 0:
343 | export_name = "output_filtered"
344 | else:
345 | export_name = "output_non_filtered"
346 | export_to_xlsx(export_name + '.xlsx', concepts, all_data)
347 | export_to_csv(export_name + '.csv', concepts, all_data)
348 |
349 |
350 | return all_data, verification_matrix
351 |
352 |
353 | def get_non_respecting_entities(file, verification_matrix):
354 | non_respecting = []
355 | for k, v in verification_matrix.items():
356 | entity = file.by_id(k)
357 | print(list(v.values()))
358 | if sum(v.values()) != 0:
359 | non_respecting.append(entity)
360 |
361 | return non_respecting
362 |
363 |
364 |
365 |
366 | def get_respecting_entities(file, verification_matrix):
367 | respecting = []
368 | for k, v in verification_matrix.items():
369 | entity = file.by_id(k)
370 | print(list(v.values()))
371 | if sum(v.values()) == 0:
372 | respecting.append(entity)
373 |
374 | return respecting
375 |
376 |
377 | def visualize(file, not_respecting_entities):
378 | """
379 | Visualize the instances of the entity type targeted by the mvdXML ConceptRoot.
380 | At display, a color differentiation is made between the entities which comply with
381 | mvdXML requirements and the ones which don't.
382 |
383 | :param file: IFC file from any schema.
384 | :param not_respecting_entities: Entities which don't comply with mvdXML requirements.
385 |
386 | """
387 |
388 | s = ifcopenshell.geom.main.settings()
389 | s.set(s.USE_PYTHON_OPENCASCADE, True)
390 | s.set(s.DISABLE_OPENING_SUBTRACTIONS, False)
391 |
392 | viewer = ifcopenshell.geom.utils.initialize_display()
393 |
394 | entity_type = not_respecting_entities[0].is_a()
395 |
396 | other_entities = [x for x in file.by_type("IfcBuildingElement") if x.is_a() != str(entity_type)]
397 |
398 | set_of_entities = set(not_respecting_entities) | set(file.by_type(entity_type))
399 | set_to_display = set_of_entities.union(set(other_entities))
400 |
401 | for el in set_to_display:
402 | if el in not_respecting_entities:
403 | c = (1, 0, 0, 1)
404 | elif el in other_entities:
405 | c = (1, 1, 1, 0)
406 | else:
407 | c = (0, 1, 0.5, 1)
408 |
409 | try:
410 | shape = ifcopenshell.geom.create_shape(s, el)
411 | # OCC.BRepTools.breptools_Write(shape.geometry, "test.brep")
412 | ds = ifcopenshell.geom.utils.display_shape(shape, clr=c)
413 | except:
414 | pass
415 |
416 | viewer.FitAll()
417 |
418 | ifcopenshell.geom.utils.main_loop()
419 |
420 |
421 | def validate_data(concept, data):
422 | import io
423 | import ast
424 | import operator
425 | from functools import reduce, partial
426 |
427 | rules = [x[0] for x in concept.rules() if not isinstance(x, str)]
428 |
429 | def transform_data(d):
430 | """
431 | Transform dictionary keys from tree nodes to rule ids
432 | """
433 |
434 | return {(k.parent if k.bind is None and (k.parent is not None and k.parent.bind is not None) else k).bind: v for k, v in d.items()}
435 |
436 |
437 | def parse_mvdxml_token(v):
438 | if v.lower() == "true":
439 | return True
440 | if v.lower() == "false":
441 | return False
442 | # @todo make more permissive and tolerant
443 | return ast.literal_eval(v)
444 |
445 |
446 | data = list(map(transform_data, data))
447 |
448 | output = io.StringIO()
449 |
450 | # https://stackoverflow.com/a/70227259
451 | def operation_reduce(x, y):
452 | """
453 | Takes alternating value and function as input and
454 | reduces while applying function
455 | """
456 |
457 | if callable(x):
458 | return x(y)
459 | else:
460 | return partial(y, x)
461 |
462 |
463 | def apply_rules():
464 |
465 | for r in rules:
466 |
467 | def apply_data():
468 |
469 | for d in data:
470 |
471 | def translate(v):
472 | if isinstance(v, str):
473 | return getattr(operator, v.lower() + "_")
474 | else:
475 | if v.b == "Value" or v.b is None:
476 | return d.get(v.a) == parse_mvdxml_token(v.c)
477 | elif v.b == "Type":
478 | return d.get(v.a) is not None and d.get(v.a).is_a(parse_mvdxml_token(v.c))
479 | elif v.b == "Exists":
480 | return (d.get(v.a) is not None) == parse_mvdxml_token(v.c)
481 | else:
482 | raise RuntimeError(f"Invalid rule predicate {v.b}")
483 |
484 | r2 = list(map(translate, r))
485 | yield reduce(operation_reduce, r2)
486 |
487 | v = any(list(apply_data()))
488 | print(("Met:" if v else "Not met:"), r, file=output)
489 | yield v
490 |
491 |
492 | valid = all(list(apply_rules()))
493 | return valid, output.getvalue()
494 |
495 |
496 | if __name__ == '__main__':
497 | print('functions to parse MVD rules and extract IFC data/filter IFC entities from them')
498 |
--------------------------------------------------------------------------------
/mvd_examples/Example-CV100.mvdxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | Any product or product type can have associated materials indicating the physical composition of an object.
17 |
18 | Materials can have representations for surface styles indicating colors, textures, and light reflectance for 3D
19 |
20 | rendering. Materials can have representations for fill styles indicating colors, tiles, and hatch patterns for
21 |
22 | 2D rendering. Materials can have properties such as density, elasticity, thermal resistance, and others as
23 |
24 | defined in this specification. Materials can also be classified according to a referenced industry standard.
25 |
26 |
27 |
28 |
29 |
30 | An object can be comprised of a single material or a set of materials with a particular layout. Several
31 |
32 | examples include:
33 |
34 |
35 |
36 |
37 |
38 | - a slab may have an associated layer of concrete;
39 |
40 |
41 |
42 | - a beam may have an associated I-Shape profile of steel;
43 |
44 |
45 |
46 | - a door may have associated constituents for framing and glazing;
47 |
48 |
49 |
50 | - a port may have an associated profile and/or material flowing through it such as hot water.
51 |
52 |
53 |
54 |
55 | ]]>
56 |
57 |
58 |
59 |
60 |
61 |
62 | Material layer set usage defines layout at occurrences to indicate a direction and offset from the 'Axis' reference curve, and a reference extent such as for a default wall height. ]]>
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
--------------------------------------------------------------------------------
/mvd_examples/Example-CV104.mvdxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Objects may participate in various connectivity
9 | relationships with other objects.
10 |
11 | ]]>
12 |
13 |
14 |
15 |
16 |
17 |
18 | Elements such as doors and windows may be placed inside openings of walls, slabs, or other elements.]]>
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
--------------------------------------------------------------------------------
/mvd_examples/Example-CV106.mvdxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | All files contain a single IfcProject instance indicating overall context and a directory of objects contained within.]]>
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | A project representation context indicates the coordinate system orientation, direction of true north,
17 |
18 | precision, and other values that apply to all geometry within a project or project library.
19 |
20 |
21 | ]]>
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
--------------------------------------------------------------------------------
/mvd_examples/building.mvdxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | The concept templates of Object Definition provides the means to define an object occurrence by its object type and attached property and quantity sets.]]>
8 |
9 |
10 |
11 |
12 |
13 |
14 | Object occurrences can be
15 | defined by a particular object type, using the Object Typing concept. A pair of entities are defined for most semantic objects - an
16 | object occurrence entity and a corresponding object type entity.
17 |
18 |
19 | EXAMPLE The IfcTank
20 | is the object occurrence entity that has a corresponding
21 | IfcTankType being the object type entity.
22 |
23 |
24 |
25 |
26 |
27 | On instance level, an
28 | object occurrence instance may have:
29 |
30 |
31 |
32 | - similar state as its object
33 | type instance by applying all characteristics defined at the type;
34 | - overridden state for particular characteristics;
35 |
36 | - no defined object type instance.
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | Characteristics defined at the object type level may include:
45 |
46 |
47 | - common naming and predefined type;
48 |
49 | - common properties within a type driven property set;
50 |
51 | - common geometry representations, applied as mapped representation to each occurrence;
52 |
53 | - common material assignments (with exception of material set usages);
54 | <
55 | - common definition of a decomposition structure.
56 |
57 |
58 |
59 |
60 |
61 | Many object occurrence and object type entities have an
62 | attribute named PredefinedType consisting of a specific
63 | enumeration. Such predefined
64 | type essentially provides another level of inheritance to
65 | further differentiate objects without the need for
66 | additional entities. Predefined types are not
67 | just informational; various rules apply such as applicable
68 | property
69 | sets, part composition, and distribution ports. If the object is typed by an IfcTypeObject, then the PredefinedType at the IfcObject occurrence shall only be used if the PredefinedType at IfcTypeObject is set to NOTDEFINED.
70 |
71 |
72 |
73 |
74 |
75 | EXAMPLE
76 | For scenarios of object types having part compositions,
77 | such parts may be reflected at object occurrences having
78 | separate state. For example, a wall type may define
79 | a particular arrangement of studs, a wall occurrence
80 | may reflect the same arrangement of studs, and studs within
81 | the wall occurrence may participate in specific
82 | relationships that do not exist at the type such as being
83 | connected to an electrical junction box.
84 |
85 |
86 |
87 |
88 | NOTE If the object type has aggregated elements, such objects are reflected at the object occurrence using the IfcRelDefinesByObject relationship.
]]>
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 | The concept template Property Sets describes how sets of properties (usually defined by a name, value, unit triple) are associated to objects or object types.]]>
111 |
112 |
113 |
114 |
115 |
116 |
117 | The concept template Property Sets for Objects describes how an object occurrence can be related to a single or multiple property sets. A property set
118 | contains a single or multiple properties. The data types of
119 | an individual property are single value, enumerated value,
120 | bounded value, table value, reference value, list value,
121 | and combination of property occurrences.
122 |
123 |
124 |
125 | Property sets can also be related to an object type, see concept Property Sets for Types. They then define the common properties for all occurrences of the same type. If the same property (by name) is provided by the same property set (by name), then the properties directly assigned to the object occurrence override the properties assigned to the object type.
126 | ]]>
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
190 | Any specialization of object can be related to multiple quantity set occurrences. A quantity set
191 | contains multiple quantity occurrences. The data type of
192 | quantity occurrence values are count, length, area, volume, weight, time, or a combination of quantities. Each quantity is defined by its name, value, and optionally a description and a formula.
193 |
194 |
195 | The quantity set is expressed by instances of IfcElementQuantity, where the Name attribute determines the common designator of the quantity set. This specification contains a number of predefined quantity sets, a template definition is provided for each of them. The name of the template has to be used as the value of the Name attribute. The MethodOfMeasurement attribute specifies the method, by which the values of the individual quantities are calculated. For the quantity set templates included in this specification, the value of MethodOfMeasurement shall be "BaseQuantities".
]]>
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 | All entities having semantic significance derive from IfcRoot, where instances are identifiable within a data set using a
274 |
275 | compressed globally unique identifier (IFC-GUID). This identifier must never change during the lifetime of an
276 |
277 | object, which allows data to be merged, versioned, or referenced from other locations.
278 |
279 |
280 |
281 |
282 |
283 |
284 | Resource-level instances (not deriving from IfcRoot) do not have any identity, such that two instances
285 |
286 | having identical state are considered equal. For example, if an object has coordinates described by an
287 |
288 | IfcCartesianPoint instance, another object with the same coordinates may have a separate instance of
289 |
290 | IfcCartesianPoint or share the same instance; such difference is a matter of data storage optimization
291 |
292 | and does not imply any semantic relationship. This also implies that non-rooted instances may only exist if
293 |
294 | referenced by at least one rooted instance through either a direct attribute or inverse attribute, or following
295 |
296 | a chain of attribute references on instances.
297 |
298 |
299 |
300 |
301 |
302 |
303 | The distinction between rooted and non-rooted (resource-level) entities achieves several goals:
304 |
305 |
306 |
307 |
308 |
309 |
310 |
311 | - File size may be reduced by interning (sharing) non-rooted data instances;
312 |
313 |
314 |
315 |
316 | - Database retrieval may be more efficient by storing non-rooted data local to rooted data
317 |
318 | instances;
319 |
320 |
321 |
322 |
323 | - Storage size may be reduced by avoiding IFC-GUID storage for items not requiring direct
324 |
325 | retrieval;
326 |
327 |
328 |
329 |
330 | - Comparisons of differences may be done at a higher level where the context of such change is
331 |
332 | apparent;
333 |
334 |
335 |
336 |
337 | - Implementations may treat non-rooted data instances as immutable for efficiency or simplified
338 |
339 | usage.
340 |
341 |
342 |
343 |
344 |
345 |
346 | ]]>
347 |
348 |
349 |
350 |
351 |
352 |
353 | An object may be labeled for human identification where the Name may indicate a well-known identifier. While there is no restriction on usage of such identifier, it is recommended the Name is unique within it's containing scope. Further guidance on usage is provided at specific entities; for example, for spaces, the Name may reflect a room number. An object may have a description provided via the Description attribute that provides further context in identifying or locating the object.
354 |
355 | Specific subtypes introduce additional attributes for User Identity.
356 |
357 | - Spatial objects may be further identified via the LongName attribute. This value should generally correspond to building signage describing floor levels or rooms. While the Name attribute generally provides a coded or abbreviated identifier, the LongName provides a functional name for the location such as "Reception Area". See concept template Spatial Element Occurrence Attributes
358 | - Physical elements may be further identified via the Tag attribute. This is a human readable identifier such as an element or item number While there is no restriction on usage of such tags, it is recommended the Tag is unique within it's containing scope. See concept template Element Occurrence Attributes
359 |
]]>
360 |
361 |
362 |
363 |
364 |
365 |
366 |
367 |
368 |
369 |
370 |
371 |
372 |
373 |
374 |
375 |
376 |
377 |
378 |
379 |
380 |
381 |
382 |
383 |
384 |
385 |
386 |
387 |
388 |
389 |
390 |
391 |
392 |
393 |
394 |
395 |
396 |
397 |
398 |
399 |
400 |
401 |
402 |
403 |
404 |
405 | Many object occurrences have an
406 | attribute named PredefinedType being a specific
407 | enumeration. Such predefined
408 | type essentially provides another level of "classification by inheritance" to
409 | further differentiate objects without the need for
410 | additional sub types. Predefined types are not
411 | just informational; various rules apply such as applicable
412 | property
413 | sets, part composition, and distribution ports.Such predefined types are added by selecting the correct enumerator for the attribute PredefinedType. If a custom value is needed, the attribute ObjectType has to be used to define such custom type, whereas the PredefinedType is set to USERDEFINED.
414 |
415 | The main attributes to be provided for a Object Occurrence Predefined Type are:
416 |
417 |
418 |
419 | - PredefinedType: holds the entity specific enumeration of predefined types to further classify the entity
420 |
421 |
422 | - ObjectType: allows for a custom value, if no applicable enumerator can be found
423 |
424 |
425 |
426 | If the object is typed by an IfcTypeObject, then the PredefinedType at the IfcObject occurrence shall only be used if the PredefinedType at IfcTypeObject is set to NOTDEFINED.
427 |
428 | ]]>
429 |
430 |
431 |
432 |
433 |
434 |
435 |
436 |
437 |
438 |
439 |
440 |
441 |
442 |
443 |
444 |
445 |
446 |
447 |
448 |
449 |
450 |
451 |
452 |
453 |
454 |
455 | Specific entities have additional attributes defined for describing common characteristics at occurrences.]]>
456 |
457 |
458 |
459 |
460 |
461 |
462 | Spatial objects may be further identified via the LongName attribute. This value should generally correspond to building signage describing floor levels or rooms. While the Name attribute generally provides a coded or abbreviated identifier, the LongName provides a functional name for the location such as "Reception Area". ]]>
463 |
464 |
465 |
466 |
467 |
468 |
469 |
470 |
471 |
472 |
473 |
474 |
475 |
476 |
477 |
478 |
479 |
480 |
481 | A building may be located according to a postal address, and may indicate a baseline elevation and land elevation.]]>
482 |
483 |
484 |
485 |
486 |
487 |
488 |
489 |
490 |
491 |
492 |
493 |
494 |
495 |
496 |
497 |
498 |
499 |
500 |
501 |
502 |
503 |
504 |
505 |
506 |
507 |
508 |
509 |
510 |
511 |
512 |
513 |
514 |
515 |
516 |
517 |
518 |
519 |
520 |
521 |
522 |
523 |
524 |
525 |
526 |
527 |
528 |
529 |
530 |
531 |
532 |
533 |
534 |
535 |
536 |
537 |
538 |
539 |
540 |
541 |
542 |
543 |
544 |
545 |
546 |
547 |
548 |
549 |
550 |
551 |
552 |
553 |
554 |
555 |
556 |
557 |
558 |
559 |
560 |
561 |
562 |
563 |
564 |
565 |
566 |
567 |
568 |
569 |
570 |
571 |
572 |
573 |
574 |
575 |
576 |
577 |
578 |
579 | The concept of an Object Association provides the means to associate sources of information (most notably a
580 | classification, library, document, approval, constraint, or material) to objects definitions. The information associated may reside internally or externally in regard of the project data.
581 | ]]>
582 |
583 |
584 |
585 |
586 |
587 |
588 | The concept Classification Association describes how objects and object types can be further described by associating references to external sources of information.
589 | The source of information can be:
590 |
591 |
592 |
593 |
594 |
595 |
596 | - a classification system;
597 |
598 |
599 | - a dictionary server;
600 |
601 |
602 | - any external catalogue that classifies the object further;
603 |
604 |
605 | - any service that combine the above features.
606 |
607 |
608 |
609 |
610 |
611 |
612 | An individual item within the external source of information can be selected.
613 | It then applies the inherent meaning of the item to the IfcObject or IfcTypeObject.
614 |
615 | NOTE The classification system or dictionary server that is used within the project itself can also be indicated at the level of IfcProject or IfcProjectLibrary either as an external source, or copied with all relevant classification items into the project data. Use the concept Project Classification Information to utilize this functionality.
616 |
617 | The main attributes to be provided for a Classification Association are:
618 |
619 |
620 |
621 | - Identification: holds the key provided for a specific references to classification items (or tables)
622 |
623 |
624 | - Name: allows for a human interpretable designation of a classification notation
625 |
626 |
]]>
627 |
628 |
629 |
630 |
631 |
632 |
633 |
634 |
635 |
636 |
637 |
638 |
639 |
640 |
641 |
642 |
643 |
644 |
645 |
646 |
647 |
648 |
649 |
650 |
651 |
652 |
653 |
654 |
655 |
656 |
657 |
658 |
659 |
660 |
661 |
662 |
663 |
664 |
665 |
666 |
667 |
668 |
669 |
670 |
671 |
672 |
673 |
674 |
675 |
676 |
677 |
678 |
679 |
680 |
681 |
682 |
683 |
684 |
685 |
686 |
687 |
688 |
689 |
690 |
691 |
692 |
693 |
694 |
695 |
696 |
697 |
698 |
699 |
700 |
701 |
702 |
703 |
704 |
705 |
706 |
707 |
708 |
709 |
710 |
711 |
712 |
713 |
714 |
715 |
716 |
717 |
718 |
719 |
720 |
721 | Objects may be composed into parts to indicate levels of
723 | detail, such as a building having multiple storeys, a
724 | framed wall having studs, or a task having subtasks.
725 | Composition may form a hierarchy of multiple levels, where
726 | an object must have a single parent, or if a top-level
727 | object then declared within the single project or a project library.
728 |
729 | ]]>
730 |
731 |
732 |
733 |
734 |
735 |
736 | An aggregation indicates an internal unordered part composition relationship between the whole structure, referred to as the "composite", and the subordinate components, referred to as the "parts". The concept of aggregation is used in various ways. Examples are:
737 |
738 |
739 |
740 |
741 |
742 |
743 | - Aggregation is used on building elements to indicate parts such as studs within a wall;
744 |
745 |
746 |
747 |
748 | - Aggregation is used on spatial elements to indicate a spatial structure such as a story within a building;
749 |
750 |
751 |
752 |
753 | - Aggregation is used on systems to indicate subsystems such as branch circuits.
754 |
755 |
756 |
757 |
758 | Aggregation is a bi-directional relationship, the relationship from the composite to its parts is called Decomposition, and the relationship from the part to its composite is called Composition.
]]>
759 |
760 |
761 |
762 |
763 |
764 |
765 | Provision of a spatial structure of the project by aggregating spatial elements. The spatial structure is a hierarchical tree of spatial elements ultimately assigned to the project. Composition refers to the relationship to a higher level element (e.g. this storey is part of a building).
766 |
767 | NOTE The link between the highest level spatial element and the project is provided by this concept through IfcRelContainedInSpatialStructure and not through declaration using IfcRelDeclares. This is a known anomaly introduced to maintain compatibility with earlier versions of this standard.
768 |
769 | The order of spatial structure elements being included in the concept for builing projects are from high to low level: IfcProject, IfcSite, IfcBuilding, IfcBuildingStorey, and IfcSpace with IfcSite, IfcBuildingStorey and IfcSpace being optional levels. Therefore an spatial structure element can only be part of an element at the same or higher level.
770 |
771 | In addition a more general hierarchical tree of spatial elements can be created by using IfcSpatialZone, from high to low: IfcProject, IfcSite, and IfcSpatialZone with IfcSite being an optional level.
772 |
773 | NOTE The more general hiearchical tree has been introduced as an intermediate solution and stub for further extensions to support infrastructure works.
]]>
774 |
775 |
776 |
777 |
778 |
779 |
780 |
781 |
782 |
783 |
784 |
785 |
786 |
787 |
788 |
789 |
790 |
791 |
792 |
793 |
794 |
795 |
796 |
797 |
798 |
799 |
800 |
801 |
802 |
803 |
804 |
805 |
806 |
807 |
808 |
809 | Provision of a spatial structure of the project by aggregating spatial elements. The spatial structure is a hierarchical tree of spatial elements ultimately assigned to the project. Decomposition refers to the relationship to a lower level element (e.g. this storey has spaces).
810 |
811 | NOTE The link between the project and the highest level spatial element is provided by this concept through IfcRelContainedInSpatialStructure and not through declaration using IfcRelDeclares. This is a known anomaly introduced to maintain compatibility with earlier versions of this standard.
812 |
813 | The order of spatial structure elements being included in the concept for builing projects are from low to high level: IfcSpace, IfcBuildingStorey, IfcBuilding, IfcSite and IfcProject with IfcSite, IfcBuildingStorey and IfcSpace being optional levels. Therefore an spatial structure element can only be part of an element at the same or higher level.
814 |
815 | In addition a more general hierarchical tree of spatial elements can be created by using IfcSpatialZone, from low to high: IfcSpatialZone, IfcSite, and IfcProject, with IfcSite being an optional level.
816 |
817 | NOTE The more general hiearchical tree has been introduced as an intermediate solution and stub for further extensions to support infrastructure works.
]]>
818 |
819 |
820 |
821 |
822 |
823 |
824 |
825 |
826 |
827 |
828 |
829 |
830 |
831 |
832 |
833 |
834 |
835 |
836 |
837 |
838 |
839 |
840 |
841 |
842 |
843 |
844 |
845 |
846 |
847 |
848 |
849 |
850 |
851 |
852 | Objects may participate in various connectivity
853 | relationships with other objects.
854 |
855 | ]]>
856 |
857 |
858 |
859 |
860 |
861 |
862 | Spatial structures, such as site, building, storey, or spaces, may contain physical elements, including building elements, distribution elements, and furnishing elements. The containment relationship between the physical elements and the spatial structures is hierarchical, i.e. a physical element shall only be contained within a single spatial structure.
863 |
864 | EXAMPLE An IfcBeam is placed within the spatial hierarchy using the objectified relationship IfcRelContainedInSpatialStructure, referring to it by its inverse attribute SELF\IfcElement.ContainedInStructure. Subtypes of IfcSpatialStructureElement are valid spatial containers, with IfcBuildingStorey being the default container.
865 |
866 | The spatial containment relationship, together with the Spatial decomposition relationship, being hierarchical as well, establishes the hiearchical project tree structure in a building information model.
867 |
868 | EXAMPLE The IfcBuildingStorey that logically contains the IfcBeam decomposes the IfcBuilding using the IfcRelAggregates relationship. Therefore the IfcBeam is also indirectly contained in the building.
]]>
869 |
870 |
871 |
872 |
873 |
874 |
875 | The Spatial Container concept defines a spatial element as being the spatial container for physical elements, or other elements being directly related to the spatial container, such as annotations or grids.
876 | EXAMPLE A building story is a logical spatial container of building elements, distribution elements, or furnishing elements.
877 | The Spatial Container concept is realized by using the IfcRelContainedInSpatialStructure objectified relationship between subtypes of IfcSpatialElement and the elements contained. The inverse relationship ContainsElements at the subtypes of IfcSpatialElement refers to the contained physical elements.
]]>
878 |
879 |
880 |
881 |
882 |
883 |
884 |
885 |
886 |
887 |
888 |
889 |
890 |
891 |
892 |
893 |
894 |
895 |
896 |
897 |
898 |
899 |
900 |
901 |
902 |
903 | A product is an occurrence of a physical or virtual object with finite spatial extent that may have a shape representation as one of its characteristics. The concepts within the group of Product Shape define how a shape is created to represent different geometric and topological representations.
904 | ]]>
905 |
906 |
907 |
908 |
909 |
910 |
911 |
912 | Product occurrences are placed in a right handed Cartesian coordinate system. The Product Placement is used to establish an object coordinate system that maby be placed relative to a parent object coordinate system, and ultimately to the project coordinate system. ]]>
913 |
914 |
915 |
916 |
917 |
918 |
919 |
920 | Product occurrences can be placed in 3D space relative to
921 | where they are contained. Placement is defined by a
922 | relative position (X, Y, Z coordinates), a horizontal
923 | reference direction, and a vertical axis direction. At the
924 | outermost level, relative directions are defined according
925 | to representation context; for example, +X may point east,
926 | +Y may point north, and +Z may point up.
927 |
928 |
929 |
930 | Placement follows aggregation and containment relationships
931 | as follows:
932 |
933 |
934 |
935 |
936 |
937 | - at the outermost level, a site is globally
938 | positioned according to latitude, longitude, and elevation;
939 |
940 |
941 | - for spatial structures, positioning is
942 | relative to aggregation. For example, a site may aggregate
943 | multiple buildings, each building may aggregate multiple
944 | building storeys, and each building storey may aggregate
945 | multiple spaces;
946 |
947 |
948 | - for building elements, positioning is
949 | relative to the containing spatial structure. For example,
950 | a building storey may contain slabs, walls, columns, and
951 | beams;
952 |
953 |
954 | - for aggregated parts, positioning is
955 | relative to aggregation. For example, a staircase may
956 | aggregate one or more stair flights;
957 | - for feature elements, positioning is
958 | relative to the affected building element. For example, an
959 | opening element is positioned relative to the wall it
960 | voids, which in turn is positioned relative to a building
961 | storey;
962 |
963 | - for fillings, positioning is relative to
964 | the filled opening. For example, a door is positioned
965 | relative to an opening which in turn is positioned relative
966 | to a wall;
967 |
968 |
969 |
970 | - for distribution ports, positioning is
971 | relative to the containing distribution element. For
972 | example, an air terminal may have a port connection for a
973 | duct segment or fitting;
974 |
975 |
976 | - for distribution elements, positioning is
977 | relative to the containing spatial structure, however may be constrained by port connections. For example, a
978 | electrical junction box may fill an opening within a wall,
979 | and the junction box may contain ports for contained
980 | outlets or switches; the placement of such connected
981 | elements is constrained relative to connected port of the
982 | junction box. As another example, an air terminal may fill
983 | a ceiling covering which is placed relative to a space; the
984 | placement of a connecting duct fitting may be constrained
985 | relative to the air terminal.
986 |
987 |
988 |
989 |
990 |
991 |
992 | If a containing spatial structure contains a grid, then
993 | placement may also be based relative to grid coordinates.
994 | In certain use cases, an absolute placement may be used by omitting the IfcObjectPlacement. In this case, the shape representation is defined within the world coordinate system.
995 |
996 | ]]>
997 |
998 |
999 |
1000 |
1001 |
1002 |
1003 |
1004 |
1005 |
1006 |
1007 |
1008 |
1009 |
1010 |
1011 |
1012 |
1013 |
1014 |
1015 |
1016 |
1017 |
1018 |
1019 |
1020 |
1021 |
1022 |
1023 |
1024 |
1025 |
1027 |
1028 | The shape of products may be represented in multiple ways
1029 | for different purposes. Each representation has a
1030 | well-known string identifier and a particular
1031 | representation context. There may be multiple
1032 | representation contexts to describe a shape at various levels of detail. Most building elements have a 'Body'
1033 | representation which defines or approximates the physical shape and volume. In addition to physical building elements, non-physical elements may have representations
1034 | such as spaces and openings.
1035 |
1036 |
1037 |
1038 | ]]>
1039 |
1040 |
1041 |
1042 |
1043 |
1044 |
1045 |
1046 |
1047 |
1048 |
1049 |
1050 |
1051 |
1052 |
1053 |
1054 |
1055 |
1056 |
1057 |
1058 |
1059 |
1060 |
1061 |
1062 |
1063 |
1064 |
1065 |
1066 |
1067 |
1068 |
1069 |
1070 |
1071 |
1072 |
1073 |
1074 |
1075 |
1076 |
1077 |
1078 |
1079 |
1080 |
1081 |
1082 |
1083 |
1084 |
1085 | Elements may have a simplified 'Box' representation
1086 | describing the dimensions of the smallest box bounding the
1087 | object. Such representation may be used for more efficient
1088 | spatial indexing or hit-testing.
1089 |
1090 |
1091 |
1092 |
1093 | The representation identifier and type and the only allowed single representation item of the 'Box' representation
1094 | are:
1095 |
1096 |
1097 |
1098 | - IfcShapeRepresentation.RepresentationIdentifier =
1099 | 'Box'
1100 |
1101 |
1102 | - IfcShapeRepresentation.RepresentationType : 'BoundingBox'
1103 |
1104 | - IfcShapeRepresentation.Items =
1105 | IfcBoundingBox
1106 |
1107 |
1108 |
1109 | NOTE The specification does not determine the method by which the bounding box has to be created. If such a method need to be prescribed the definition has to be established by model view definitions or implementer agreements.
]]>
1110 |
1111 |
1112 |
1113 |
1114 |
1115 |
1116 |
1117 |
1118 |
1119 |
1120 |
1121 |
1122 |
1123 |
1124 |
1125 |
1126 |
1127 |
1128 |
1129 |
1130 |
1131 |
1132 |
1133 |
1134 |
1135 |
1136 |
1137 |
1138 |
1139 |
1140 |
1141 |
1142 |
1143 |
1144 |
1145 |
1146 |
1147 |
1148 |
1149 |
1150 |
1151 |
1152 |
1153 |
1154 |
1155 |
1156 |
1157 |
1158 |
1159 |
1160 |
1161 |
1162 |
1163 |
1164 |
1165 |
1166 |
1167 |
1168 | Partial concept templates are described herein to indicate usage of common data types, which are then incorporated into other templates.]]>
1169 |
1170 |
1171 |
1172 |
1173 |
1174 |
1175 |
1176 |
1177 |
1178 |
1179 |
1180 |
1181 |
1182 |
1183 |
1184 |
1185 |
1186 | Properties may contain user-defined data, where data types are open-ended.]]>
1187 |
1188 |
1189 |
1190 |
1191 |
1192 |
1193 |
1194 |
1195 |
1196 |
1197 |
1198 |
1199 |
1200 |
1201 |
1202 |
1203 |
1204 |
1205 |
1206 |
1207 |
1208 |
1209 |
1210 |
1211 |
1212 |
1213 |
1214 |
1215 |
1216 |
1217 |
1218 |
1219 |
1220 |
1221 |
1222 |
1223 |
1224 |
1225 |
1226 |
1227 |
1228 |
1229 |
1230 |
1231 |
1232 |
1233 |
1234 |
1235 |
1236 |
1237 |
1238 |
1239 |
1240 |
1241 |
1242 |
1243 |
1244 |
1245 |
1246 |
1247 |
1248 |
1249 |
1250 |
1251 |
1252 |
1253 |
1254 |
1255 |
1256 |
1257 |
1258 |
1259 |
1260 |
1261 |
1262 |
1263 |
1264 |
1265 |
1266 |
1267 |
1268 |
1269 |
1270 |
1271 |
1272 |
1273 |
1274 |
1275 |
1276 |
1277 |
1278 |
1279 |
1280 |
1281 |
1282 |
1283 |
1284 |
1285 |
1286 |
1287 |
1288 |
1289 |
1290 |
1291 |
1292 |
1293 |
1294 |
1295 |
1296 |
1297 |
1298 |
1299 |
1300 |
1301 |
1302 |
1303 |
1304 |
1305 |
1306 |
1307 |
1308 |
1309 |
1310 |
1311 |
1312 |
1313 |
1314 |
1315 |
1316 |
1317 |
1318 |
1319 |
1320 |
1321 |
1322 |
1323 |
1324 |
1325 |
1326 |
1327 |
1328 |
1329 |
1330 |
1331 |
1332 |
1333 |
1334 |
1335 |
1336 |
1337 |
1338 |
1339 |
1340 |
1341 |
1342 |
1343 |
1344 |
1345 |
1346 |
1347 |
1348 |
1349 |
1350 |
1351 |
1352 |
1353 |
1354 |
1355 |
1356 |
1357 |
1358 |
1359 |
1360 |
1361 |
1362 |
1363 |
1364 |
1365 |
1366 |
1367 |
1368 |
1369 |
1370 |
1371 |
1372 |
1373 |
1374 |
1375 |
1376 |
1377 |
1378 |
1379 |
1380 |
1381 |
1382 |
1383 |
1384 |
1385 |
1386 |
1387 |
1388 |
1389 |
1390 |
1391 |
1392 |
1393 |
1394 |
1395 |
1396 |
1397 |
1398 |
1399 |
1400 |
1401 |
1402 |
1403 |
1404 |
1405 |
1406 |
1407 |
1408 |
1409 |
1410 |
1411 |
1412 |
1413 |
1414 |
1415 |
1416 |
1417 |
1418 |
1419 |
1420 |
1421 |
1422 |
1423 |
1424 |
1425 |
1426 |
1427 |
1428 |
1429 |
1430 |
1431 |
1432 |
1433 |
1434 |
1435 |
1436 |
1437 |
1438 |
1439 |
1440 |
1441 |
1442 |
1443 |
1444 |
1445 |
1446 |
1447 |
1448 |
1449 |
1450 |
1451 |
1452 |
1453 |
1454 |
1455 |
1456 |
1457 |
1458 |
1459 |
1460 |
1461 |
1462 |
1463 |
1464 |
1465 |
1466 |
1467 |
1468 |
1469 |
1470 |
1471 |
1472 |
1473 |
1474 |
1475 |
1476 |
1477 |
1478 |
1479 |
1480 |
1481 |
1482 |
1483 |
1484 |
1485 |
1486 |
1487 |
1488 |
1489 |
1490 |
1491 |
1492 |
1493 |
1494 |
1495 |
1496 |
1497 |
1498 |
1499 |
1500 |
1501 |
1502 |
1503 |
1504 |
1505 |
1506 |
1507 |
1508 |
1509 |
1510 |
1511 |
1512 |
1513 |
1514 |
1515 |
1516 |
1517 |
1518 |
1519 |
1520 |
1521 |
1522 |
1523 |
1524 |
1525 |
1526 |
1527 |
1528 |
1529 |
1530 |
1531 |
1532 |
1533 |
1534 |
1535 |
1536 |
1537 |
1538 |
1539 |
1540 |
1541 |
1542 |
1543 |
1544 |
1545 |
1546 |
1547 |
1548 |
1549 |
1550 |
1551 |
1552 |
1553 |
1554 |
1555 |
1556 |
1557 |
1558 |
1559 |
1560 |
1561 |
1562 |
1563 |
1564 |
1565 |
1566 |
1567 |
1568 |
1569 |
1570 |
1571 |
1572 |
1573 |
1574 |
1575 |
1576 |
1577 |
1578 |
1579 |
1580 |
1581 |
1582 |
1583 |
1584 |
1585 |
1586 |
1587 |
1588 |
1589 |
1590 |
1591 |
1592 |
1593 |
1594 |
1595 |
1596 |
1597 |
1598 |
1599 |
1600 |
1601 |
1602 |
1603 |
1604 |
1605 |
1606 |
1607 |
1608 |
1609 |
1610 |
1611 |
1612 |
1613 |
1614 |
1615 |
1616 |
1617 |
1618 |
1619 |
--------------------------------------------------------------------------------
/mvd_examples/wall_extraction.mvdxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
--------------------------------------------------------------------------------
/mvd_examples/xset.mvdxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | The concept templates of Object Definition provides the means to define an object occurrence by its object type and attached property and quantity sets.]]>
8 |
9 |
10 |
11 |
12 |
13 |
14 | The concept template Property Sets describes how sets of properties (usually defined by a name, value, unit triple) are associated to objects or object types.]]>
15 |
16 |
17 |
18 |
19 |
20 |
21 | The concept template Property Sets for Objects describes how an object occurrence can be related to a single or multiple property sets. A property set
22 | contains a single or multiple properties. The data types of
23 | an individual property are single value, enumerated value,
24 | bounded value, table value, reference value, list value,
25 | and combination of property occurrences.
26 |
27 |
28 |
29 | Property sets can also be related to an object type, see concept Property Sets for Types. They then define the common properties for all occurrences of the same type. If the same property (by name) is provided by the same property set (by name), then the properties directly assigned to the object occurrence override the properties assigned to the object type.
30 | ]]>
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
90 | Any specialization of object can be related to multiple quantity set occurrences. A quantity set
91 | contains multiple quantity occurrences. The data type of
92 | quantity occurrence values are count, length, area, volume, weight, time, or a combination of quantities. Each quantity is defined by its name, value, and optionally a description and a formula.
93 |
94 |
95 | The quantity set is expressed by instances of IfcElementQuantity, where the Name attribute determines the common designator of the quantity set. This specification contains a number of predefined quantity sets, a template definition is provided for each of them. The name of the template has to be used as the value of the Name attribute. The MethodOfMeasurement attribute specifies the method, by which the values of the individual quantities are calculated. For the quantity set templates included in this specification, the value of MethodOfMeasurement shall be "BaseQuantities".
]]>
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 | Partial concept templates are described herein to indicate usage of common data types, which are then incorporated into other templates.]]>
172 |
173 |
174 |
175 |
176 |
177 |
178 | Properties may contain user-defined data, where data types are open-ended.]]>
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
297 |
298 |
299 |
300 |
301 |
302 |
303 |
304 |
305 |
306 |
307 |
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 |
318 |
319 |
320 |
321 |
322 |
323 |
324 |
325 |
326 |
327 |
328 |
329 |
330 |
331 |
332 |
333 |
334 |
335 |
336 |
337 |
338 |
339 |
340 |
341 |
342 |
343 |
344 |
345 |
346 |
347 |
348 |
349 |
350 |
351 |
352 |
353 |
354 |
355 |
356 |
357 |
358 |
359 |
360 |
361 |
362 |
363 |
364 |
365 |
366 |
367 |
368 |
369 |
370 |
371 |
372 |
373 |
374 |
375 |
376 |
377 |
378 |
379 |
380 |
381 |
382 |
383 |
384 |
385 |
386 |
387 |
388 |
389 |
390 |
391 |
392 |
393 |
394 |
395 |
396 |
397 |
398 |
399 |
400 |
401 |
402 |
403 |
404 |
405 |
406 |
407 |
408 |
409 |
410 |
411 |
412 |
413 |
414 |
415 |
416 |
417 |
418 |
419 |
420 |
421 |
422 |
423 |
424 |
425 |
426 |
427 |
428 |
429 |
430 |
431 |
432 |
433 |
434 |
435 |
436 |
437 |
438 |
439 |
440 |
441 |
442 |
443 |
444 |
445 |
446 |
447 |
448 |
449 |
450 |
451 |
452 |
453 |
454 |
455 |
456 |
457 |
458 |
459 |
460 |
461 |
462 |
463 |
464 |
465 |
466 |
467 |
468 |
469 |
470 |
471 |
472 |
473 |
474 |
475 |
476 |
477 |
478 |
479 |
480 |
481 |
482 |
483 |
484 |
485 |
486 |
487 |
488 |
489 |
490 |
491 |
492 |
493 |
494 |
495 |
496 |
497 |
498 |
499 |
500 |
501 |
502 |
503 |
504 |
505 |
506 |
507 |
508 |
509 |
510 |
511 |
512 |
513 |
--------------------------------------------------------------------------------
/mvdxml_expression.py:
--------------------------------------------------------------------------------
1 | import pyparsing as pp
2 |
3 | class node(object):
4 | def __init__(self, args):
5 | if len(args) == 3 and args[1] == '=':
6 | self.a, self.b, self.c = args[0], None, args[2]
7 | elif (args[1], args[3], args[4]) == ('[', ']', '='):
8 | self.a, self.b, self.c = args[0], args[2], args[5]
9 | else:
10 | self.a, self.b, self.c = None, args[1], args[4]
11 |
12 | def __repr__(self): return "{%s[%s]=%s}" % (self.a, self.b, self.c)
13 |
14 | word = pp.Word(pp.alphanums+"_"+" "+"/"+"#")
15 | quoted = pp.Combine("'" + word + "'")
16 | bool_value = pp.CaselessLiteral("TRUE") | pp.CaselessLiteral("FALSE")
17 | ref_val = word + "[" + word + "]"
18 | rhs = quoted | bool_value | ref_val | word
19 | stmt = (pp.Optional(word) + pp.Optional("[" + word + "]") + "=" + rhs).setParseAction(node)
20 | bool_op = pp.CaselessLiteral("AND") | pp.CaselessLiteral("OR")
21 | grammar = stmt + pp.Optional(pp.OneOrMore(bool_op + stmt))
22 |
23 | def parse(exprs):
24 | def _():
25 | for expr in exprs.split(";"):
26 | expr = "".join(c for c in expr if c not in "\r\n")
27 | if not expr: continue
28 | yield grammar.parseString(expr)
29 | return list(_())
30 |
--------------------------------------------------------------------------------
/sparql.py:
--------------------------------------------------------------------------------
1 | import io
2 | import os
3 | import csv
4 | import platform
5 | import tabulate
6 | import operator
7 | import itertools
8 | import subprocess
9 | import ifcopenshell
10 |
11 | from collections import defaultdict
12 |
13 | import mvdxml_expression
14 |
15 | def camel(s):
16 | """
17 | Camel case conversion function
18 | :param s: str
19 | :return: camel case formatted string
20 | """
21 |
22 | s = s.title().replace(" ", "")
23 | if s.endswith("s"): s = s[:-1]
24 | return s[0].lower() + s[1:]
25 |
26 |
27 | STANDARD_PREFIXES = {
28 | 'rdf': '',
29 | 'owl': '',
30 | 'xsd': '',
31 | 'list': '',
32 | 'ifcowl': '',
33 | 'express': '',
34 | }
35 |
36 | def derive_prefix(ttlfn):
37 | with open(ttlfn, "r") as f:
38 | for ln in f:
39 | ln.strip()
40 | if ln.startswith("@prefix ifcowl"):
41 | uri = ln.split(':', 1)[1].strip()[:-1].strip()
42 | print("Detected ifcowl prefix", uri)
43 | STANDARD_PREFIXES['ifcowl'] = uri
44 | break
45 |
46 | def withschema(fn):
47 | """
48 | Decorator that takes a function and adds an IFC latebound schema definition
49 | in the first parameter. The schema identifier is looked up based on the
50 | global ifcOwl prefix.
51 |
52 | :param fn: input function
53 | :return: decorated function
54 | """
55 |
56 | def _(*args, **kwargs):
57 | schema_name = STANDARD_PREFIXES['ifcowl'].split('/')[-1][:-2]
58 | if "_" in schema_name:
59 | schema_name = schema_name.split('_')[0]
60 | S = ifcopenshell.ifcopenshell_wrapper.schema_by_name(schema_name)
61 | return fn(S, *args, **kwargs)
62 | return _
63 |
64 | noop = lambda *args: None
65 |
66 | class rule_binding(object):
67 | """
68 | Object for mapping rules to generated SPARQL variables
69 | """
70 | def __init__(self):
71 | pass
72 |
73 |
74 | class builder(object):
75 | """
76 | A helper class for dealing with SPARQL query statements
77 | """
78 |
79 | def __init__(self):
80 | self.prefixes = {"express": "", "ifcowl": ""}
81 | self.statements = []
82 |
83 | def append(self, *stmt):
84 | if len(stmt) == 3:
85 | for i, pos in enumerate(stmt[1:]):
86 | for po in pos.split("/"):
87 | if ":" in pos:
88 | a, b = po.split(':')
89 | self.prefixes[a] = ''
90 | self.statements.append(stmt)
91 |
92 | def bind(self, di):
93 | for k in set(self.prefixes.keys()) & set(di.keys()):
94 | self.prefixes[k] = di[k]
95 |
96 | def x(self):
97 | return len(self.statements)
98 |
99 | def __repr__(self):
100 | def f(s):
101 | S = " ".join(s)
102 | if len(s) == 3: S += ' .'
103 | return S
104 |
105 | def g(s):
106 | return "PREFIX %s: %s" % s
107 |
108 | return "\n".join(itertools.chain(
109 | (g(s) for s in self.prefixes.items()),
110 | (f(s) for s in self.statements)
111 | ))
112 |
113 | class ifcOwl(object):
114 | """
115 | Helper class with static function for dealing with ifcOwl attribute names
116 | """
117 |
118 | @staticmethod
119 | @withschema
120 | def supertypes(S, entity):
121 | """
122 | Yields ifcOwl subtypes for the supplied entity name
123 |
124 | :param S: schema definition (from decorator)
125 | :param entity: entity name string
126 | :return:
127 | """
128 |
129 | a, b = entity.split('#')
130 | try:
131 | en = S.declaration_by_name(b)
132 | if en.__class__.__name__ == "entity":
133 | while en.supertype():
134 | yield "%s#%s" % (a, en.supertype().name())
135 | en = en.supertype()
136 | except: pass
137 |
138 | @staticmethod
139 | def get_names(e, c):
140 | """
141 | Returns inverse or forward attribute names for latebound entity definition
142 |
143 | :param e: latebound entity definition
144 | :param c: either 'all_attributes' or 'all_inverse_attributes'
145 | :return: set of attribute names
146 | """
147 |
148 | return set(map(lambda a: a.name(), getattr(e, c)()))
149 |
150 | @staticmethod
151 | @withschema
152 | def is_boxed(S, entity, attribute, predCount=0):
153 | """
154 | Returns whether the entity attribute should be boxed in ifcOwl. Which means
155 | that there is an additional indirection.
156 |
157 | inst:IfcRelDefinesByType_21937
158 | ifcowl:globalId_IfcRoot inst:IfcGloballyUniqueId_117684 ;
159 |
160 | inst:IfcGloballyUniqueId_117684
161 | rdf:type ifcowl:IfcGloballyUniqueId ;
162 | express:hasString "2P9FPkykn0r8rCpmBxZH0w" .
163 |
164 | :param S: schema definition (from decorator)
165 | :param entity: entity name string
166 | :param attribute: attribute name string
167 | :param predCount: numeric identifier to postfix predicate identifier in case of SELECT types
168 | :return: either a predicate from the express namespace or a variable postfixed with predCount
169 | """
170 |
171 | en = S.declaration_by_name(entity)
172 | attr = [a for a in en.all_attributes() if a.name() == attribute][0]
173 | ty = attr.type_of_attribute()
174 | is_boxed = False
175 | while isinstance(ty, ifcopenshell.ifcopenshell_wrapper.named_type):
176 | ty = ty.declared_type()
177 |
178 | if isinstance(ty, ifcopenshell.ifcopenshell_wrapper.select_type):
179 |
180 | # Just assume there is going to be some boxed type in here.
181 | # It could be all instance references, but fact is we don't know at this moment.
182 | # It's likely that mvdXML will only bind to literals?
183 |
184 | return "?pred%d" % predCount
185 |
186 | else:
187 |
188 | while isinstance(ty, ifcopenshell.ifcopenshell_wrapper.type_declaration):
189 | is_boxed = True
190 | ty = ty.declared_type()
191 | if is_boxed and isinstance(ty, ifcopenshell.ifcopenshell_wrapper.simple_type):
192 | ty = ty.declared_type()
193 | return "express:has%s%s" % (ty[0].upper(), ty[1:])
194 |
195 | return False
196 |
197 | @staticmethod
198 | @withschema
199 | def name(S, entity, attribute):
200 | """
201 | Names the entity attribute according to ifcOwl
202 |
203 | :param S:
204 | :param entity:
205 | :param attribute:
206 | :return:
207 | """
208 | en = S.declaration_by_name(entity)
209 |
210 | while True:
211 | st = en.supertype()
212 |
213 | attribute_names = ifcOwl.get_names(en, "all_attributes") | \
214 | ifcOwl.get_names(en, "all_inverse_attributes")
215 |
216 | if st:
217 | attribute_names -= ifcOwl.get_names(st, "all_attributes") | \
218 | ifcOwl.get_names(st, "all_inverse_attributes")
219 |
220 | if attribute in attribute_names:
221 | return "ifcowl:" + attribute[0].lower() + attribute[1:] + "_" + en.name()
222 |
223 | en = st
224 |
225 | if en is None:
226 | raise AttributeError("%s not found on %s" % (attribute, entity))
227 |
228 | @staticmethod
229 | @withschema
230 | def is_select(S, decl_name):
231 | """
232 | Returns True when the declaration is a select type
233 |
234 | :param S:
235 | :param decl_name:
236 | :return:
237 | """
238 | decl = S.declaration_by_name(decl_name)
239 | return isinstance(decl, ifcopenshell.ifcopenshell_wrapper.select_type)
240 |
241 | @staticmethod
242 | @withschema
243 | def is_inverse(S, entity, attribute):
244 | """
245 | When entity attribute is an INVERSE attribute, returns the opposite
246 | forward entity and attribute name. Otherwise returns (False, False)
247 |
248 | :param S:
249 | :param entity:
250 | :param attribute:
251 | :return:
252 | """
253 |
254 | en = S.declaration_by_name(entity)
255 | attrs = [a for a in en.all_inverse_attributes() if a.name() == attribute]
256 | if not attrs: return False, False
257 |
258 | a = attrs[0]
259 | assert a.type_of_aggregation_string() == "set"
260 | entity = a.entity_reference().name()
261 | attr = a.attribute_reference().name()
262 | return "ifcowl:" + entity, ifcOwl.name(entity, attr)
263 |
264 | class convertor(object):
265 |
266 | @staticmethod
267 | def convert(item, *args, **kwargs):
268 | return getattr(convertor, item.__class__.__name__)(item, *args, **kwargs)
269 |
270 | @staticmethod
271 | def concept_or_applicability(concept):
272 | """
273 | Convert the Template (SELECT ... WHERE {}) structure and TemplateRule (FILTER)
274 |
275 | :param qtype: 0 or 1, 0 for a general query matching the applicableRootEntity
276 | :return:
277 | """
278 |
279 | bld = builder()
280 | t = concept.template()
281 | bld.append("# %s" % camel(concept.root.name))
282 | convertor.template(t, bld, concept.root.entity)
283 | bld.bind(STANDARD_PREFIXES)
284 |
285 | return bld
286 |
287 | @staticmethod
288 | def root(rootEntity):
289 | args = ["URI", "GlobalId"]
290 |
291 | b = builder()
292 | b.args = args
293 | b.append("SELECT " + " ".join("?" + a for a in args) + " WHERE {")
294 | b.append("?URI", "rdf:type", "ifcowl:%s" % rootEntity)
295 | b.append("?URI", "ifcowl:globalId_IfcRoot/express:hasString", "?GlobalId")
296 | b.append("}")
297 | b.bind(STANDARD_PREFIXES)
298 |
299 | return b
300 |
301 | @staticmethod
302 | def template(template, bld = None, rootEntity = None):
303 | if bld is None:
304 | bld = builder()
305 |
306 | if rootEntity is None:
307 | rootEntity = template.entity
308 |
309 | args = ["URI", "GlobalId"]
310 |
311 | def enumerate(rule, **kwargs):
312 | if rule.bind:
313 | args.append(rule.bind)
314 |
315 | template.traverse(enumerate)
316 |
317 | bld.args = args
318 |
319 | bld.append("SELECT " + " ".join("?" + a for a in args) + " WHERE {")
320 |
321 | args = set(args)
322 |
323 | bld.append("?URI", "rdf:type", "ifcowl:%s" % rootEntity)
324 | bld.append("?URI", "ifcowl:globalId_IfcRoot/express:hasString", "?GlobalId")
325 |
326 | nm = "?URI"
327 | ROOT = type('_', (), {'attribute': rootEntity})()
328 | # rule_stack = [ROOT]
329 | # name_stack = [nm]
330 | # callback_stack = [noop]
331 | # G = type('_', (object,), dict(indent = 0, nm = nm, next_nm=None, first=True))
332 |
333 | rule_mapping = defaultdict(rule_binding)
334 | rule_mapping[ROOT].name = nm
335 |
336 | def build(rule, parents):
337 | # print "AAA", rule.tag, parent.tag if parent else parent
338 | # print map(id, rule_stack)
339 | # print name_stack
340 | INDENT = " " * (len(parents) * 2)
341 | return_value = None
342 |
343 | if rule.optional:
344 | bld.append(INDENT + "OPTIONAL {")
345 | return_value = lambda: bld.append(INDENT + "}")
346 |
347 | if rule.tag == "EntityRule":
348 | # G.nm = G.next_nm
349 |
350 | if not ifcOwl.is_select(rule.attribute):
351 | # SELECT types should never be qualified as they cannot be inferred
352 | bld.append(INDENT + rule_mapping[parents[-1]].name, "rdf:type", "ifcowl:" + rule.attribute)
353 |
354 | # propagate binding name
355 | rule_mapping[rule].name = rule_mapping[parents[-1]].name
356 | else:
357 |
358 | # if rule_stack[-1] is parent:
359 | # # print "sl", id(parent), id(rule)
360 | # # same level
361 | # pass
362 | # elif parent in rule_stack:
363 | # # print "up", id(parent), id(rule)
364 | # while rule_stack[-1] is not parent:
365 | # # rule_stack.pop()
366 | # name_stack.pop()
367 | # callback_stack.pop()()
368 | # else:
369 | # # print "dn", id(parent), id(rule)
370 | # pass
371 |
372 | indirect = False
373 |
374 | if rule.bind:
375 | if len(rule.nodes) == 1:
376 | indirect = ifcOwl.is_boxed(parents[-1].attribute, rule.attribute, predCount=bld.x())
377 |
378 | if rule.bind and not indirect:
379 | next_nm = "?" + rule.bind
380 | else:
381 | next_nm = "?var%d" % bld.x()
382 |
383 | rule_mapping[rule].name = next_nm
384 |
385 | inventy, invattr = ifcOwl.is_inverse(parents[-1].attribute, rule.attribute)
386 | if invattr:
387 | # This seems not to be necessary, because the entity name is also stated in mvdXML
388 | # q.append(
389 | # next_nm,
390 | # "rdf:type",
391 | # inventy
392 | # )
393 | bld.append(
394 | INDENT + next_nm,
395 | invattr,
396 | rule_mapping[parents[-1]].name
397 | )
398 | else:
399 | bld.append(
400 | INDENT + rule_mapping[parents[-1]].name,
401 | ifcOwl.name(parents[-1].attribute, rule.attribute),
402 | next_nm
403 | )
404 |
405 | if rule.bind and indirect:
406 | # For boxed literals, only strings atm
407 | bld.append(INDENT + next_nm, indirect, "?" + rule.bind)
408 |
409 | # rule_stack.append(rule)
410 | # name_stack.append(next_nm)
411 | # if rule.optional:
412 | # callback_stack.append(lambda: q.append(INDENT+"}"))
413 | # else:
414 | # callback_stack.append(noop)
415 |
416 | # print(q.statements[-1])
417 |
418 | return return_value
419 |
420 | template.traverse(build, root=ROOT, with_parents=True)
421 |
422 | # while callback_stack:
423 | # callback_stack.pop()()
424 |
425 | if template.params:
426 | bld.append(convertor.build_filter(template))
427 |
428 | bld.append("}")
429 |
430 | return bld
431 |
432 | @staticmethod
433 | def build_filter(self):
434 | def v(p):
435 | if isinstance(p, mvdxml_expression.node):
436 | if p.b == "Value":
437 | if p.c.lower() in {'true', 'false'}:
438 | yield "(%s?%s)" % ("!" if p.c.lower() == "false" else "", p.a)
439 | else:
440 | yield "(?%s = %s)" % (p.a, p.c)
441 | elif p.b == "Exists":
442 | yield "(!isBLANK(?%s))" % p.a
443 | else:
444 | raise Exception("Unsupported " + p.b)
445 | elif isinstance(p, str):
446 | yield {
447 | "and": "&&",
448 | "or": "||",
449 | "not": "&& !"
450 | }[p.lower()]
451 | else:
452 | yield "("
453 | for q in p:
454 | yield from v(q)
455 | yield ")"
456 |
457 | return "FILTER(%s)" % " ".join(v(self.params))
458 |
459 | def infer_subtypes(ttlfn):
460 | # Disabled currently
461 | return ttlfn
462 |
463 | if not os.path.exists(ttlfn + ".subclass.nt"):
464 |
465 | print("Inferring supertype relationships")
466 |
467 | import hashlib
468 | import rdflib
469 |
470 | a = rdflib.namespace.RDF.type
471 |
472 | # Hardly possible on Windows
473 | # graph = rdflib.Graph("Sleepycat")
474 | # graph.open("store", create=True)
475 | # graph.parse(ttlfn)
476 |
477 | from sqlalchemy import create_engine
478 | from rdflib_sqlalchemy.store import SQLAlchemy
479 |
480 | if os.path.exists("db.sqlite"):
481 | os.unlink("db.sqlite")
482 |
483 | uri = rdflib.Literal("sqlite:///%(here)s/db.sqlite" % {"here": os.getcwd()})
484 | ident = rdflib.URIRef(hashlib.sha1(ttlfn.encode()).hexdigest())
485 | engine = create_engine(uri)
486 | store = SQLAlchemy(
487 | identifier=ident,
488 | engine=engine,
489 | )
490 | graph = rdflib.Graph(
491 | store,
492 | identifier=ident,
493 | )
494 | graph.open(uri, create=True)
495 | graph.parse(ttlfn, format="ttl")
496 |
497 | # print out all the triples in the graph
498 | def _():
499 | for subject, predicate, object in graph:
500 | if predicate == a:
501 | for sup in ifcOwl.supertypes(object):
502 | yield subject, a, rdflib.URIRef(sup)
503 |
504 | for stmt in list(_()):
505 | graph.add(stmt)
506 |
507 | graph.serialize(destination=ttlfn + ".subclass.nt", format="nt")
508 |
509 | ttlfn += ".subclass.nt"
510 |
511 | if platform.system() == "Windows":
512 | JENA_SPARQL = os.path.join(os.environ.get("JENA_HOME"), "bat", "sparql.bat")
513 | else:
514 | JENA_SPARQL = "sparql"
515 |
516 | class executor(object):
517 | @staticmethod
518 | def run(CR, fn, ttlfn):
519 | """
520 | Generates SPARQL queries for the parsed MVD and executes on the building model
521 |
522 | :param CR: A parsed concept root
523 | :param fn: A filename used as the prefix to store generate SPARQL queries to disk
524 | :param ttlfn: A filename for the LD representation of an IFC model
525 | :return:
526 | """
527 |
528 | def dict_to_list(headers):
529 | return lambda di: [di[h] for h in headers]
530 |
531 | def execute(query, *args):
532 | sparqlfn = ".".join(itertools.chain([fn], map(str, args))) + ".sparql"
533 | with open(sparqlfn, "w") as f:
534 | print(query, file=f)
535 |
536 | proc = subprocess.Popen(
537 | [JENA_SPARQL, "--data=" + ttlfn, "--query=" + sparqlfn, "--results=CSV"],
538 | stdout=subprocess.PIPE,
539 | stderr=subprocess.PIPE)
540 | stdout, stderr = proc.communicate()
541 |
542 | csvf = io.StringIO(stdout.decode('utf-8'))
543 |
544 | return list(csv.DictReader(csvf))
545 |
546 | root_query = convertor.root(CR.entity)
547 | roots = execute(root_query, 0)
548 |
549 | print("\nFile contains %d elements of type %s" % (len(roots), CR.entity))
550 |
551 | passing_all = {}
552 |
553 | # for summary below
554 | num_columns = 0
555 |
556 | try:
557 | # Full MVD with multiple concepts
558 | is_template = False
559 | concept_enumerator = list(itertools.chain([CR.applicability()], CR.concepts()))
560 | except:
561 | is_template = True
562 | concept_enumerator = [CR]
563 |
564 | for ci, C in enumerate(concept_enumerator):
565 |
566 | num_columns += 1
567 |
568 | if is_template or ci > 1:
569 | print("\n%s" % C.name)
570 | else:
571 | print("\nApplicability")
572 |
573 | query = convertor.convert(C)
574 |
575 | print("\nSPARQL query")
576 | print("============")
577 | print(query)
578 |
579 | passing = execute(query, ci, 1)
580 | passing_guids = set(r['GlobalId'] for r in passing)
581 |
582 | print("\nElements passing")
583 | print(tabulate.tabulate(list(map(dict_to_list(query.args), passing)), query.args, tablefmt="grid"))
584 |
585 | print("\nElements failing concept")
586 | hd = ["URI", "GlobalId"]
587 | print(tabulate.tabulate(
588 | list(map(dict_to_list(hd), [r for r in roots if r["GlobalId"] not in passing_guids])), hd,
589 | tablefmt="grid"))
590 |
591 | passing_all[ci] = passing_guids
592 |
593 | print("\nSummary")
594 |
595 | for ci, C in enumerate(concept_enumerator):
596 | print("(%d) %s" % (ci+(0 if is_template else 0), C.name))
597 |
598 | def get_stats(guid):
599 | v = lambda i: guid in passing_all[i]
600 | st = [guid] + ["x" if v(i) else "" for i in range(num_columns)]
601 | if not is_template:
602 | st += ["x" if not v(0) or all(v(i) for i in range(1, num_columns)) else " "]
603 | return st
604 |
605 | hd = ["GlobalId"] + list(map(str, range(num_columns)))
606 | if not is_template:
607 | hd += ["Valid"]
608 |
609 | print(tabulate.tabulate(list(map(get_stats, map(operator.itemgetter("GlobalId"), roots))), hd, tablefmt="grid"))
--------------------------------------------------------------------------------