Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# This file was generated with the assistance of an AI coding tool.

name: Tests

on:
push:
pull_request:
workflow_dispatch:

permissions:
contents: read

jobs:
pytest:
runs-on: ubuntu-latest
timeout-minutes: 10

steps:
- name: Check out repository
uses: actions/checkout@v6

- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.10"

- name: Install test dependencies
run: python -m pip install "ifcopenshell[advanced]" pytest pyparsing

- name: Install the checked-out MVD modules
shell: bash
run: |
package_dir="$(python -c 'import ifcopenshell, pathlib; print(pathlib.Path(ifcopenshell.__file__).parent / "mvd")')"
cp ./*.py "$package_dir/"

- name: Run tests
working-directory: tests
run: python -m pytest .
216 changes: 139 additions & 77 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,100 +1,162 @@
## python-mvdxml

A mvdXML checker and w3c SPARQL converter, as an IfcOpenShell submodule or stand-alone.
An mvdXML parser, checker, and W3C SPARQL converter provided as an
IfcOpenShell submodule.

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.
> [!WARNING]
> While this package has useful building blocks for mvdXML and IFC, there are
> many mvdXML dialects and not all variants are fully supported.

### Quickstart

#### Extraction
### Parsing

Parsed documents are immutable dataclasses. Templates and template references
are resolved while parsing, so repeated access returns the same parsed object.

```python
import ifcopenshell
from ifcopenshell.mvd import mvd
from ifcopenshell.mvd import parse

concept_roots = parse("mvd_examples/wall_extraction.mvdxml")
concept_root = concept_roots[0]

print(concept_root.name)
print(concept_root.entity)
print([concept.name for concept in concept_root.concepts()])
```

`parse()` returns a tuple of `concept_root` objects. A document containing only
concept templates returns a tuple of `template` objects instead. Invalid XML
and missing template references raise `ValueError` with the relevant document
or template identifier. Recursive template branches are expanded once.

mvd_concept = mvd.open_mvd("examples/wall_extraction.mvdxml")
file = ifcopenshell.open("Duplex_A_20110505.ifc")
### Graphviz concept templates

all_data = mvd.get_data(mvd_concept, file, spreadsheet_export=True)
The lightweight concept graphs used by the buildingSMART IFC4.x documentation
can be read directly into the same immutable `template` representation:

non_respecting_entities = mvd.get_non_respecting_entities(file, all_data[1])
respecting_entities = mvd.get_respecting_entities(file, all_data[1])
~~~python
from ifcopenshell.mvd import template

source = """
This prose is ignored.

```
concept {
IfcObject:ObjectType -> IfcLabel
IfcObject:ObjectType[binding="UserDefinedType"]
}
```
"""

parsed_template = template.from_graphviz(
source,
name="Object Predefined Type",
)
~~~

Only `concept {}` declarations inside triple-backtick fences are read; the
surrounding Markdown is not parsed. Edges, attribute bindings, constraint nodes,
and named template references follow the syntax used by buildingSMART's
`templates_to_mvdxml.py`. Referenced templates must already be parsed and
provided by name:

~~~python
parent = template.from_graphviz(
parent_source,
references={"Surface Color Style": surface_color_style},
)
~~~

Reference names are matched without spaces or underscores. Graph parsing uses
`networkx`, available through IfcOpenShell's `advanced` optional dependencies.

### Extraction

Extraction returns native Python containers and IFC values.

```python
# Create a new file
new_file = ifcopenshell.file(schema=file.schema)
proj = file.by_type("IfcProject")[0]
new_file.add(proj)
import ifcopenshell

from ifcopenshell.mvd import concept_root, parse

for e in respecting_entities:
new_file.add(e)
parsed = parse("mvd_examples/wall_extraction.mvdxml")
root = parsed[0]
assert isinstance(root, concept_root)
ifc_file = ifcopenshell.open("Duplex_A_20110505.ifc")

new_file.write("new_file.ifc")
all_data, verification = root.get_data(ifc_file)
non_respecting = root.get_non_respecting_entities(ifc_file, verification)
respecting = root.get_respecting_entities(ifc_file, verification)
```

The result is:

```python
# Visualize results
mvd.visualize(file, non_respecting_entities)
tuple[
list[dict[str, object]], # one GlobalId-to-value mapping per concept
dict[str, dict[str, int]], # GlobalId-to-concept verification matrix
]
```

##### Validation
Individual structures also expose their own behavior:

~~~py
import ifcopenshell
```python
concept = next(root.concepts())
parsed_template = concept.template()
entity = ifc_file.by_type(root.entity)[0]

from ifcopenshell.mvd import mvd
from colorama import Fore
from colorama import Style

concept_roots = list(ifcopenshell.mvd.concept_root.parse(MVDXML_FILENAME))
file = ifcopenshell.open(IFC_FILENAME)

tt = 0 # total number of tests
ts = 0 # total number of successful tests

for concept_root in concept_roots:
print("ConceptRoot: ", concept_root.entity)
for concept in concept_root.concepts():
tt = tt + 1
print("Concept: ", concept.name)
try:

if len(concept.template().rules) > 1:
attribute_rules = []
for rule in concept.template().rules:
attribute_rules.append(rule)
rules_root = ifcopenshell.mvd.rule("EntityRule", concept_root.entity, attribute_rules)
else:
rules_root = concept.template().rules[0]
ts = ts + 1
finst = 0 #failed instances

for inst in file.by_type(concept_root.entity):
try:
data = mvd.extract_data(rules_root, inst)
valid, output = mvd.validate_data(concept, data)
if not valid:
finst = finst + 1
print("[VALID]" if valid else Fore.RED +"[failure]"+Style.RESET_ALL, inst)
print(output)
except Exception as e:
print(Fore.RED+"EXCEPTION: ", e, Style.RESET_ALL,inst)
print ()
print (int(finst), "out of", int(len(file.by_type(concept_root.entity))), "instances failed the check")
print ("---------------------------------")
except Exception as e:
print("EXCEPTION: "+Fore.RED,e,Style.RESET_ALL)
print("---------------------------------")
print("---------------------------------")
print("---------------------------------")

tf = tt-ts # total number of failed tests

print ("\nRESULTS OVERVIEW")
print ("Total number of tests: ",tt)
print ("Total number of executed tests: ", ts)
print ("Total number of failed tests: ", tf)
~~~
extracted = parsed_template.extract(entity)
valid, report = concept.validate(extracted)
```

`extracted` is a list of dictionaries mapping immutable `rule` objects to IFC
values. `valid` is a boolean and `report` is a string; validation itself does
not print.

### Visualization and export

Visualization and spreadsheet generation are deliberately outside this
package. Use the returned GlobalIds to select or colour entities in the caller's
viewer. For CSV, JSON, dataframe, or spreadsheet output, transform `all_data`
and `verification` with the corresponding Python library. Keeping those
operations at the application boundary means importing this package does not
initialize a geometry backend or require a spreadsheet dependency.

### Command line

Inspect a document:

```console
python -m ifcopenshell.mvd inspect mvd_examples/wall_extraction.mvdxml
```

Validate an IFC file against an mvdXML document:

```console
python -m ifcopenshell.mvd validate model.mvdxml model.ifc
```

Generate and execute SPARQL against an IFC-OWL Turtle file:

```console
python -m ifcopenshell.mvd convert model.mvdxml model.ttl
```

Extract a ConceptTemplate, ConceptRoot, or Concept within a ConceptRoot:

```console
python -m ifcopenshell.mvd extract source.mvdxml "Project Context" project-context.mvdxml
python -m ifcopenshell.mvd extract source.mvdxml IfcZone zone.mvdxml
python -m ifcopenshell.mvd extract source.mvdxml "IfcZone/Property Sets for Objects" zone-psets.mvdxml
```

Combine two or more documents (the final argument is the output path):

```console
python -m ifcopenshell.mvd combine first.mvdxml second.mvdxml combined.mvdxml
```

Both commands operate on the immutable objects returned by `parse()`, then pass
the selected or concatenated objects to `serialize()`. Output contains one
flattened definition per ConceptTemplate UUID.

Use `python -m ifcopenshell.mvd --help` for argument details.
Loading