Skip to content

IDS support in IfcOpenShell #1349

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Moult opened this issue Feb 28, 2021 · 30 comments
Closed

IDS support in IfcOpenShell #1349

Moult opened this issue Feb 28, 2021 · 30 comments

Comments

@Moult
Copy link
Contributor

Moult commented Feb 28, 2021

4e889b7 implements a very basic BIMTester integration. Ping @aothms as well as @rbertucat who this might impact, if IfcRail is testing pset applicability too.

To run, just point to the IDS XML instead of a feature file.

$ bimtester -f ids01.xml -i Duplex_A_20110505.ifc -r report.html

HTML report looks like this:

2021-02-28-144158_1920x1080_scrot

Because it just uses the same argument to specify a feature file, this means that the Blender UI to BIMTester, the FreeCAD UI to BIMTester, as well as the standalone UIs to BIMTester built by @berndhahnebach and @rbertucat all can just work out of the box.

2021-02-28-150528_692x139_scrot

I really like this new IDS approach. I can read the XML and it just makes sense.

BIMTester can provide additional value on top of this IDS XML spec by providing a test running interface, as well as a HTML report generator, and an xUnit compatible output (therefore, will integrated into many continuous integration apps). BIMTester also provides a nice interface for bSI standards development as you can specify a custom IFC schema prior to running tests.

As a separate concept to test running, there is the whole Gherkin approach. The biggest benefit of Gherkin is the ability for humans to understand it, and that it can be included in contracts, and read by non-technical people. At the end of the day, it still needs code to check. Right now, this is the biggest disadvantage of BIMTester. If you want to write your own check, you need to know a bit of Python + the IfcOpenShell API. I see this IDS XML as replacing the Python coding for simple checks that anybody can write who doesn't need / want to know Python or the IfcOpenShell API. And whilst IDS XML is great for general and common specifications, it will never cover everything, so allowing both approaches is great.

Possible Gherkin sentences:

 * IFC data must comply with the IDS provided in "ids01.xml"

... this is easy, but doesn't communicate well to non-technical people - defeats the purpose of Gherkin. Maybe it should exist as a template simply for techies who are prototyping new bSI standards, but definitely deserves the blank looks it'll encounter on real projects :)

 * All entities belonging to "IfcSpace" must have a property set called "PSet_Revit_Identity Data" with a "Name" property filled with one of the following values:
    | Living Room |
    | Kitchen     |

... this can autoconvert XML<->Gherkin, and is highly specific. Kinda midway - readable but contains jargon for non-techies, and extra effort for techies, since it'll probably end up being its own DSL and would need to be translated into different languages ...

 * All spaces must be either a living room or kitchen.

... this is perhaps the best from a Gherkin point of view. This looks like exactly the type of sentence that already exists in contracts. From a UX perspective, there can be a simple UI that a spec writer can use to link this arbitrary user sentence to an XML definition - or even better, reuse <specification name="">. Easy for the manager, easy for the spec writer (can use any tool, IfcOpenShell Python API optional). I personally think this would be the way to go.

@Moult
Copy link
Contributor Author

Moult commented Feb 28, 2021

A couple of minor implementation comments - one of the really valuable things about the way most BIMTester checks are implemented is that it doesn't just say "this failed" or "this passed". It formats it in this way:

 1. We expect foo to be bar. <-- this is the exchange requirement we want. This should always read exactly the same thing.
(Note: if foo is bar, then we move on. No details are shown. Otherwise... read on...)
The element 3621=IfcSpace('2gRXFgjRn2HPE$YoDLX3FC',#33,'B205','',$,#3611,#3620,'Utility',.ELEMENT.,.INTERNAL.,$) has a property foo with the value "baz" instead of "bar".

This way:

  1. There is a consistent, unchanged sentence that clearly explains the expected exchange requirement (right now, the screenshot in the previous comment has a changing sentence, which can create confusion)
  2. Only if there is an error, is complex technical stuff shown (i.e. the STEP code). The concept is that managers are interested in green, whereas techies are interested in red.
  3. The error should not just say "failed", it should give useful information like "it was baz, not bar". This really, really helps with users debugging.

aothms added a commit that referenced this issue Feb 28, 2021
@aothms
Copy link
Member

aothms commented Feb 28, 2021

@Moult very cool! I formatted the messages, feel free to adapt to your wishes. The logger verbosity indicates error or success so you don't need to test the string content.

Given an instance with an entity name 'IfcSpace'
We expect a property 'Name' in 'PSet_Revit_Identity Data' with value 'Living Room or Kitchen'
#349=IfcSpace('0pNy6pOyf7JPmXRLgxs3sW',#1,'R301','',$,#17700,#16934,'Roof',.ELEMENT.,.INTERNAL.,$) has a property 'Name' in 'PSet_Revit_Identity Data' with value 'Roof' so is not compliant

'Living Room or Kitchen' should be 'Living Room' or 'Kitchen' ideally, but that's a bit of work that I'd like to postpone.

@Moult
Copy link
Contributor Author

Moult commented Mar 1, 2021

By the way, I think there would be value in building a UI to generate the IDS XML as not everybody can write XML - this can also help test out the usability and full workflow for the average BIM user. Happy to do this, but probably need some guidance on the rules (e.g. an XSD, or an explanation of what it allows).

@aothms
Copy link
Member

aothms commented Mar 1, 2021 via email

@berndhahnebach
Copy link
Contributor

berndhahnebach commented Mar 10, 2021

guys, thats great stuff :) How do you create ids files? Are there examples around?

@Moult
Copy link
Contributor Author

Moult commented Mar 25, 2021

@berndhahnebach https://github.com/buildingSMART/IDS/blob/master/Development/First%20production%20release/Examples.md

Ping @aothms and @berlotti - I will potentially be digging into the guts of the new FM Handover View MVDs as well as the spreadsheetML conversions. I think this makes for an excellent usecase for writing an IDS that checks whether or not all the appropriate fields are filled out. If this goes ahead, perhaps I can use this usecase to battle test the IDS capabilities?

@atomczak
Copy link
Contributor

atomczak commented Apr 8, 2021

Great feature @Moult!

Following our conversation, I would like to further develop this during GSoC'21 by:

  • developing and making sure IDS checking works against new XSD (is it this one?)
  • creating some IDS samples and wiki to make it easier to start for new people from AEC like myself :)
  • add the BCF output of such report (isn't it linked to #1244?)
  • add IDS authoring and integrate with BIMTester web UI - part of another GSoC project.

My GSoC part in blue:
GSoC IDS BCF - Frame 1

I'm submitting the GSoC proposal, also available here: https://docs.google.com/document/d/15oQDhYrApuHsI1Zee5bDvSqRoUz67xHMr79hdFJUoZ8/edit?usp=sharing

@Moult
Copy link
Contributor Author

Moult commented Apr 9, 2021

Great proposal! I believe the XSD link you have is correct. I have also attached an old version of the XML here.

ids01.xml.txt

Comparing the old XML with the new XML examples may provide some insight on the changes made formal in the XSD.

We should also organise a weekly catchup.

BTW @aothms the BCF lib uses xmlschema to load the XML content and validate against an XSD in one go, and ships with the XSDs. I noticed with minidom validation is done manually. Any thoughts on whether it is worth switching xml libs?

@Moult
Copy link
Contributor Author

Moult commented Apr 9, 2021

@atomczak by the way, the webapp seems to be emerging with a Python backend, so when you get around to developing the webapp to author IDSes, if the XML generation is done server-side, then it would be ideal for it to be a Python thing too. If it is done client-side, no worries.

@Moult
Copy link
Contributor Author

Moult commented Apr 9, 2021

Oh, and one more thing, It would be good to investigate the ability to integrate IDS with MicroMVD's Gherkin syntax, where one would simply "call" the other. This way, it provides a nice human readable description that ties into business objectives, but the implementation still follows strict standards rather than bespoke test definitions.

@aothms
Copy link
Member

aothms commented Apr 9, 2021

BTW @aothms the BCF lib uses xmlschema

This looks really good. By the time I started there wasn't an XSD, I think it might be worth it. At the same time though there might be different dialects and versions (milestones) of IDS and the minInclusive etc aren't really part of the XSD schema, so perhaps the flexibility of using a schemaless approach might be beneficial for the time the IDS schema and implementations between vendors are still maturing.

@aothms
Copy link
Member

aothms commented Apr 10, 2021

IDS and the minInclusive etc aren't really part of the XSD schema

I was just informed that the minInclusive etc, things are going to be part of the XSD schema (or maybe a second imported schema namespace). I think it's worth an experiment to see if xmlschema makes life easier.

@atomczak
Copy link
Contributor

the proposal submitted (link)!

MicroMVD's, Gherkin, XML/XSD/IDS, minInclusive, web UI, IDS authoring, BCF authoring, difference between xml parsers, existing capabilities of IfcOpenShell and BIMTester... there is a lot for me to learn! I'm still confused with some of those, but looking forward to this opportunity and hope to get accepted :)

@Moult, you mentioned to schedule regular meetings, how would you like to arrange them, should all three of us meet? What's your availability? For the next couple of weeks my availability to contribute is limited due to problems around relocation to Norway and academic work, but I will at least try to work on some small aspect of it and learn. Also, which communication channel apart from videocalls do you prefer: email, OSArch Forum, OSArch chat, this GitHub issue, GitHub Project, GitHub Wiki page for IDS sth completely else?

@Moult
Copy link
Contributor Author

Moult commented Apr 13, 2021

Great! There's a bit of a waiting time now until we work out how many Google slots there are. For weekly meetups and live questions, the osarch chat works best. For dev progress, this github issue works. At significant milestones , we should post on the forum so the wider community can keep track.

@atomczak
Copy link
Contributor

atomczak commented Jun 9, 2021

To-do list for the GSoC project:

  • IDS validation:

    • Implement xmlschema for IDS.xml validation against XSD
    • Implement xmlschema for IDS parsing for the IFC validation against IDS (similar to bcfxml.py)
    • Implement all requirements facets:
      • Entity + predefined type
      • Classification
      • Property
      • Material
      • handle more than 1 requirement/applicability of a type
      • handle more than 1 specification per ids file
      • include location = "type" or "instance"
    • Implement all restrictions:
      • Enumeration
      • Length
      • minLength, maxLength
      • minInclusive, maxInclusive, minExclusiv, maxExclusive
      • xs:patterns (~RegEx)
      • outside of scope (fractionDigits, totalDigits, whiteSpace)
    • Human readable messages (sentences) to be declared outside of the code (to be able to change language or structure)
    • Schema differences, e.g. 2x3 vs 4.0 to be declared separately in a dictionary, not hardcoded. E.g IfcClassificationReference
    • code documentation https://ifcopenshell.github.io/docs/python/html/index.html
    • start writing unittests
    • add tests for restrictions
    • prepare and upload test IDS and IFC files (atomczak/Sample-BIM-Files)
    • logging using StreamHandler saving to JSON, CSV, BCF, HTML
  • BCF export

    • Create simple BCF test using bcf module
    • Add BCF export of the IDS validation
    • Add IfcObject reference
    • optional: Add view creation
    • optional: Add image attachment
  • IDS authoring (console)

    • Create basic version that runs in the console (entity and classification, without material, property and restrictions)
    • Create mockup/demo of IDS authoring
    • Add conversion to IDS XML format
    • Add material
    • Add property
    • Add restrictions
    • Merge with IDS.py classes (currently separate)
    • add tests
    • Add authoring and validating Readme tutorial
  • IDS authoring (web-app) link

    • Draw the UI workflow
    • Create basic Flask form page for writting simple case of IDS with one entity, one applicability and one property requirement
    • Add Copy (duplicate) button
    • Add dynamic fields (flask-wtf)
    • session.ids should update on form change (when user types sth)
    • Entity and predefined type should be search fields based on schema, not select/text
    • Property/classification should have different fields
    • Add a few predefined examples/usecases of IDS
    • Should be able to add predefined requirements too
    • Add download IDS.xml functionality
    • Add copy to clipboard functionality (both text; text with hyperlinks and XML code)
    • Add upload IDS.xml functionality
    • Fill the Home page with instructions
    • Add field validation (e.g. entity name should start with 'Ifc')
    • Add regex pattern support
    • Allow for multiple entities per one specification ("All Walls, Beams and Slabs...")

@atomczak
Copy link
Contributor

@Moult, @aothms,

Speaking about the message syntax, I'm not sure if it follows some guidelines, such as this Gherkin syntax or not. Currently we have for example:

"We expect an entity name 'IfcWall' and a property 'IsExternal' in 'Pset_WallCommon' with value 'not specified'
#13740=IfcWall('1nZz9768j4pAFS7rcljTUA',#25,'TEST wall name',$,$,#13668,#13735,'718FD247-188B-44CC-A3DC-1F59AFB5D78A') has an entity name 'IfcWall'
and a property 'IsExternal' in 'Pset_WallCommon' with value 'True' so is not compliant"

which is a bit long and mixes both failing and passing sentences, sometimes confusing. Can we reduce it to:

'IfcWall' #13740 is not compliant, because property 'IsExternal' of 'Pset_WallCommon' has value 'True' instead of 'not specified'.

similarly for passing ones:

'IfcWall' of id '1nZz9768j4pAFS7rcljTUA' is compliant, because property 'IsExternal' of 'Pset_WallCommon' has value 'True' as expected.

What do you think about grouping messages together?

  1. IDS expects 'IfcSpace' parameter 'foo' to be equal to 'bar'.

1.1 'IfcSpace' #3621 has 'foo' equal to 'baz' instead of 'bar'.
1.2 'IfcSpace' #3622 has 'foo' equal to 'dar' instead of 'bar'.

  1. IDS expects 'IfcSpace' parameter 'foo2' to be equal to 'bar'.

2.1 'IfcSpace' #3621 has 'foo2' equal to 'baz' instead of 'bar'.

@atomczak
Copy link
Contributor

I've found two bugs in our code that I'm trying to resolve:

  1. Currently it's only validating one requirement of a type, so if somebody wants an entity two have 2+ properties it will only validate first from the list.
  2. If there are multiple IDS defined in a single file, it treats them separately, so one entity can pass one IDS but fail the other. As a result we get in the report two opposite results and I assume ideally we would like to get one (?):
    {'guid': '1nZz9768j4pAFS7rcljTU5', 'result': True, 'sentence': "Given an instance with..."}
    {'guid': '1nZz9768j4pAFS7rcljTU5', 'result': False, 'sentence': "Given an instance with..."}

Added to the To-do list

@atomczak
Copy link
Contributor

  1. Currently it's only validating one requirement of a type, so if somebody wants an entity two have 2+ properties it will only validate first from the list.

My mistake when switching to xmlschema (from DOM to dictionaries). Fixed it.

@aothms
Copy link
Member

aothms commented Jun 12, 2021

Wonderful overview @atomczak

Enumeration
Length
minLength, maxLength
minInclusive, maxInclusive, minExclusiv, maxExclusive
fractionDigits
totalDigits
whiteSpace
xs:patterns (~RegEx)

I don't think all of these are intended to be used in the IDS spec. Maybe @berlotti can comment?

What do you think about grouping messages together?

  1. IDS expects 'IfcSpace' parameter 'foo' to be equal to 'bar'.

1.1 'IfcSpace' #3621 has 'foo' equal to 'baz' instead of 'bar'.
1.2 'IfcSpace' #3622 has 'foo' equal to 'dar' instead of 'bar'.

  1. IDS expects 'IfcSpace' parameter 'foo2' to be equal to 'bar'.

2.1 'IfcSpace' #3621 has 'foo2' equal to 'baz' instead of 'bar'.

I think it's a good idea. It'll reduce some of the wall of text we currently have. It's a bit like a matrix transpose of the information. It probably just shows we need a better data structure for capturing the results so that this kind of grouping and sorting is easy.

@atomczak
Copy link
Contributor

I listed those restrictions based on IDS.xsd:
Can have (multiple) XSD Restrictions/Facets (enumeration, fractionDigits, length, maxExclusive, maxInclusive, maxLength, minExclusive, minInclusive, minLength, pattern, totalDigits, whiteSpace)

@atomczak
Copy link
Contributor

atomczak commented Jun 12, 2021

Regarding the grouping of messages, currently we have a list:

{'guid': '1nZz9768j4pAFS7rcljTU1', 'result': False, 'sentence': "Given an instance with an entity name 'IfcWall'\nWe expect an entity name 'IfcWall' of predefined type 'CLADDING' and a property 'Codice WBS' in 'Anas' with value 'Whatever1 or TestTest or Whatever2'\nIfcWall 'TEST wall name 1' (#13740) has an entity name 'IfcWall' of predefined type 'NOTCLADDING' and a property 'Codice WBS' in 'Anas' with value 'TestTest' so is not compliant"}
{'guid': '1nZz9768j4pAFS7rcljTU2', 'result': False, 'sentence': "Given an instance with an entity name 'IfcWall'\nWe expect an entity name 'IfcWall' of predefined type 'CLADDING' and a property 'Codice WBS' in 'Anas' with value 'Whatever1 or TestTest or Whatever2'\nIfcWall 'TEST wall name 2' (#137402) has an entity name 'IfcWall' of predefined type 'None' and no set 'Anas' so is not compliant"}
{'guid': '1nZz9768j4pAFS7rcljTU1', 'result': False, 'sentence': "Given an instance with an entity name 'IfcWall'\nWe expect an entity name 'IfcWall' of predefined type 'CLADDING' and a property 'Codice WBS' in 'Anas' with value 'Whatever1 or TestTest or Whatever2'\nIfcWall 'TEST wall name 1' (#13740) has an entity name 'IfcWall' of predefined type 'NOTCLADDING' and a property 'Codice WBS' in 'Anas' with value 'TestTest' so is not compliant"}
{'guid': '1nZz9768j4pAFS7rcljTU2', 'result': False, 'sentence': "Given an instance with an entity name 'IfcWall'\nWe expect an entity name 'IfcWall' of predefined type 'CLADDING' and a property 'Codice WBS' in 'Anas' with value 'Whatever1 or TestTest or Whatever2'\nIfcWall 'TEST wall name 2' (#137402) has an entity name 'IfcWall' of predefined type 'None' and no set 'Anas' so is not compliant"}

If we group it by specifications, the output would become a hierarchy not a list, but also benefit from less text - simpler to understand. I suggest:

[{'specification': 'First IDS', 
 'sentence': 'Given an instance with an entity name 'IfcWall'\nWe expect an entity name 'IfcWall' of predefined type 'CLADDING' and a property 'Codice WBS' in 'Anas' with value 'Whatever1 or TestTest or Whatever2'',
 'results': [{'guid': '1nZz9768j4pAFS7rcljTU1', 'result': False, 'sentence': "IfcWall 'TEST wall name 1' (#13740) has an entity name 'IfcWall' of predefined type 'NOTCLADDING' and a property 'Codice WBS' in 'Anas' with value 'TestTest' so is not compliant"}
             {'guid': '1nZz9768j4pAFS7rcljTU2', 'result': False, 'sentence': "IfcWall 'TEST wall name 2' (#137402) has an entity name 'IfcWall' of predefined type 'None' and no set 'Anas' so is not compliant"}]
},
{'specification': 'Second IDS', 
 'sentence': 'Given an instance with an entity name 'IfcWall'\nWe expect an entity name 'IfcWall' of predefined type 'CLADDING' and a property 'Codice WBS' in 'Anas' with value 'Whatever1 or TestTest or Whatever2'',
 'results': [{'guid': '1nZz9768j4pAFS7rcljTU1', 'result': False, 'sentence': "IfcWall 'TEST wall name 1' (#13740) has an entity name 'IfcWall' of predefined type 'NOTCLADDING' and a property 'Codice WBS' in 'Anas' with value 'TestTest' so is not compliant"}
             {'guid': '1nZz9768j4pAFS7rcljTU2', 'result': False, 'sentence': "IfcWall 'TEST wall name 2' (#137402) has an entity name 'IfcWall' of predefined type 'None' and no set 'Anas' so is not compliant"}]
}]

I realise it doesn't solve the initial problem: one element can be flagged multiple times, once as True, once as False. For this we could add another level of the hierarchy, but I'm not sure if it's helpful 🤔 For example:

[{'guid': '1nZz9768j4pAFS7rcljTU1', 'entity': 'IfcWall', 'result': 'False', specifications: [
    {'specification': 'First IDS', 
     'sentence': 'Given an instance with an entity name 'IfcWall'\nWe expect an entity name 'IfcWall' of predefined type 'CLADDING' and a property 'Codice WBS' in 'Anas' with value 'Whatever1 or TestTest or Whatever2'',
     'explanation': "IfcWall 'TEST wall name 1' (#13740) has an entity name 'IfcWall' of predefined type 'NOTCLADDING' and a property 'Codice WBS' in 'Anas' with value 'TestTest' so is not compliant"},
    {'specification': 'Second IDS', 
     'sentence': 'Given an instance with an entity name 'IfcWall'\nWe expect an entity name 'IfcWall' of predefined type 'CLADDING' and a property 'Codice WBS' in 'Anas' with value 'Whatever1 or TestTest or Whatever2'',
     'explanation': "IfcWall 'TEST wall name 1' (#13740) has an entity name 'IfcWall' of predefined type 'NOTCLADDING' and a property 'Codice WBS' in 'Anas' with value 'TestTest' so is not compliant"}]},
{'guid': '1nZz9768j4pAFS7rcljTU2', 'entity': 'IfcWall', 'result': 'False', specifications: [
    {'specification': 'First IDS', 
     'sentence': 'Given an instance with an entity name 'IfcWall'\nWe expect an entity name 'IfcWall' of predefined type 'CLADDING' and a property 'Codice WBS' in 'Anas' with value 'Whatever1 or TestTest or Whatever2'',
     'explanation': "IfcWall 'TEST wall name 2' (#137402) has an entity name 'IfcWall' of predefined type 'NOTCLADDING' and a property 'Codice WBS' in 'Anas' with value 'TestTest' so is not compliant"},
    {'specification': 'Second IDS', 
     'sentence': 'Given an instance with an entity name 'IfcWall'\nWe expect an entity name 'IfcWall' of predefined type 'CLADDING' and a property 'Codice WBS' in 'Anas' with value 'Whatever1 or TestTest or Whatever2'',
     'explanation': "IfcWall 'TEST wall name 2' (#137402) has an entity name 'IfcWall' of predefined type 'NOTCLADDING' and a property 'Codice WBS' in 'Anas' with value 'TestTest' so is not compliant"}]}
]

@atomczak
Copy link
Contributor

  • Draw the UI workflow
  • Create mockup/demo of IDS authoring
  • Create basic Flask form page for writting simple case of IDS with one entity, one applicability and one property requirement
  • Add dynamic fields (flask-wtf or angular/react)

Sharing the work-in-progress authoring web-app: http://artomczak.pythonanywhere.com/create. Bare in mind it only looks like creating IDS, there is no save or validate functionality. Source-code repo: https://github.com/atomczak/IDS-web-app

@atomczak
Copy link
Contributor

atomczak commented Jul 5, 2021

basic tests written in test_ids.py using unittest module. Validate using: Python test_ids.py
from the ifcopenshell location and should result in 6 successful results. More tests to be implemented on: invalid IDS, IDS authoring, IFC validation and result output.

Test files were created and can be found in this separate repo: https://github.com/atomczak/Sample-BIM-Files/tree/main/IDS

@atomczak
Copy link
Contributor

atomczak commented Aug 3, 2021

Recent development: support for the new IDS 0.4.1 schema, full restrictions support including authoring, implemented asdict() functionality for all classes to easily convert to dictionaries and IDS.xml.

@atomczak
Copy link
Contributor

atomczak commented Sep 3, 2021

Should we close this issue @Moult @aothms?

@aothms
Copy link
Member

aothms commented Sep 5, 2021

Yes, let's do that. @atomczak but maybe first scroll through the text and see if there is something interesting we need to carry over to a new issue. After that feel free to close.

@atomczak
Copy link
Contributor

atomczak commented Sep 7, 2021

I'm not able to close it. I also couldn't add labels to issues. Can you do it? I have checked the text and didn't see anything that we might still need.

@Moult
Copy link
Contributor Author

Moult commented Oct 20, 2021

Sorry, missed this message. Closing :)

@Moult Moult closed this as completed Oct 20, 2021
@MarkusSteinbrecher
Copy link

@atomczak I have just stumbled across this discussion ... I am playing around again with Ifcopenshell and IDS and I was wondering if you took this idea any further: https://github.com/atomczak/IDS-web-app ... the repo seems to have vanished. I would be very interested in having an open source solution around IDS. Thanks!

@Moult
Copy link
Contributor Author

Moult commented Apr 11, 2023

@MarkusSteinbrecher there is https://blenderbim.org/ifctester which is half built.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants