First Commit
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
# Step Physical File Validator
|
||||
|
||||
Pure-python Step Physical File Validator and Parser implemented using Lark (`pip install -r requirements.in`).
|
||||
|
||||
## Example command line usage:
|
||||
|
||||
~~~
|
||||
$ python main.py fixtures\fail_double_comma.ifc
|
||||
On line 8 column 21:
|
||||
Unexpected comma (',')
|
||||
Expecting one of DBLQUOTE DOT HASH INT LPAR NONE QUOTE REAL STAR UPPER
|
||||
00008 | #1=IFCPERSON($,$,'',,$,$,$,$);
|
||||
^
|
||||
|
||||
$ python main.py fixtures\fail_double_semi.ifc
|
||||
On line 27 column 66:
|
||||
Unexpected semicolon (';')
|
||||
Expecting one of ENDSEC HASH
|
||||
00027 | #20=IFCPROJECT('2AyG2X0sb16Bjd4gQc07yZ',#5,'',$,$,$,$,(#11),#19);;
|
||||
^
|
||||
|
||||
$ python main.py fixtures\fail_duplicate_id.ifc
|
||||
On line 27:
|
||||
Duplicate instance name #19
|
||||
00027 | #19=IFCPROJECT('2AyG2X0sb16Bjd4gQc07yZ',#5,'',$,$,$,$,(#11),#19);
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
$ python main.py fixtures\fail_no_header.ifc
|
||||
On line 2 column 1:
|
||||
Unexpected hex ('F')
|
||||
Expecting HEADER
|
||||
00002 | FILE_DESCRIPTION(('ViewDefinition [CoordinationView]'),'2;1');
|
||||
^
|
||||
|
||||
$ python main.py fixtures\pass_1.ifc
|
||||
Valid
|
||||
~~~
|
||||
@@ -0,0 +1,31 @@
|
||||
try:
|
||||
from parser.parse import parse
|
||||
from parser.file import file, open
|
||||
from parser.errors import (
|
||||
_ValidationError,
|
||||
CollectedValidationErrors,
|
||||
DuplicateNameError,
|
||||
HeaderFieldError,
|
||||
InvalidNameError,
|
||||
)
|
||||
except:
|
||||
from .parser.parse import parse
|
||||
from .parser.file import file, open
|
||||
from .parser.errors import (
|
||||
_ValidationError,
|
||||
CollectedValidationErrors,
|
||||
DuplicateNameError,
|
||||
HeaderFieldError,
|
||||
InvalidNameError,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"parse",
|
||||
"open",
|
||||
"file",
|
||||
"_ValidationError",
|
||||
"CollectedValidationErrors",
|
||||
"DuplicateNameError",
|
||||
"HeaderFieldError",
|
||||
"InvalidNameError",
|
||||
] # for testing
|
||||
@@ -0,0 +1,41 @@
|
||||
import sys
|
||||
import json
|
||||
import argparse
|
||||
from . import parse, CollectedValidationErrors
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Parse and validate STEP file.")
|
||||
parser.add_argument("filename", help="The STEP file to validate.")
|
||||
parser.add_argument(
|
||||
"--progress", action="store_true", help="Show progress during validation."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--json", action="store_true", help="Output errors in JSON format."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--only-header", action="store_true", help="Validate only the header section."
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
parse(
|
||||
filename=args.filename,
|
||||
with_progress=args.progress,
|
||||
with_tree=False,
|
||||
only_header=args.only_header,
|
||||
)
|
||||
if not args.json:
|
||||
print("Valid", file=sys.stderr)
|
||||
exit(0)
|
||||
except CollectedValidationErrors as exc:
|
||||
if not args.json:
|
||||
print(exc, file=sys.stderr)
|
||||
else:
|
||||
json.dump([e.asdict() for e in exc.errors], sys.stdout, indent=2)
|
||||
exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
@@ -0,0 +1,29 @@
|
||||
ISO-10303-21;
|
||||
HEADER;
|
||||
FILE_DESCRIPTION(('ViewDefinition [ReferenceView_V1.2]', 'ViewDefinition [QuantityTakeOffAddOnView]', 'Option [ExcludedObjects: Stair, Ramp, Space; SplitLevel: On]', 'ExchangeRequirement [CustomRequirement: Value1, Value2]', 'Remark [SomeKey: SomeValue; AnotherKey: AnotherValue]', 'Comment [This is a free text comment, or a comma-separated list of items]'),'2;1');
|
||||
FILE_NAME('','2022-05-04T08:08:30',(''),(''),'IfcOpenShell-0.7.0','IfcOpenShell-0.7.0','');
|
||||
FILE_SCHEMA(('IFC4'));
|
||||
ENDSEC;
|
||||
DATA;
|
||||
#1=IFCPERSON($,$,'',$,$,$,$);
|
||||
#2=IFCORGANIZATION($,'',$,$,$);
|
||||
#3=IFCPERSONANDORGANIZATION(#1,#2,$);
|
||||
#4=IFCAPPLICATION(#2,'0.7.0','IfcOpenShell-0.7.0','');
|
||||
#5=IFCOWNERHISTORY(#3,#4,$,.ADDED.,$,#3,#4,1651651710);
|
||||
#6=IFCDIRECTION((1.,0.,0.));
|
||||
#7=IFCDIRECTION((0.,0.,1.));
|
||||
#8=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#9=IFCAXIS2PLACEMENT3D(#8,#7,#6);
|
||||
#10=IFCDIRECTION((0.,1.,0.));
|
||||
#11=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.E-05,#9,#10);
|
||||
#12=IFCDIMENSIONALEXPONENTS(0,0,0,0,0,0,0);
|
||||
#13=IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.);
|
||||
#14=IFCSIUNIT(*,.AREAUNIT.,$,.SQUARE_METRE.);
|
||||
#15=IFCSIUNIT(*,.VOLUMEUNIT.,$,.CUBIC_METRE.);
|
||||
#16=IFCSIUNIT(*,.PLANEANGLEUNIT.,$,.RADIAN.);
|
||||
#17=IFCMEASUREWITHUNIT(IFCPLANEANGLEMEASURE(0.017453292519943295),#16);
|
||||
#18=IFCCONVERSIONBASEDUNIT(#12,.PLANEANGLEUNIT.,'DEGREE',#17);
|
||||
#19=IFCUNITASSIGNMENT((#13,#14,#15,#18));
|
||||
#20=IFCPROJECT('2AyG2X0sb16Bjd4gQc07yZ',#5,'',$,$,$,$,(#11),#19);
|
||||
ENDSEC;
|
||||
END-ISO-10303-21;
|
||||
@@ -0,0 +1,29 @@
|
||||
ISO-10303-21;
|
||||
HEADER;
|
||||
FILE_DESCRIPTION(('ViewDefinition [CoordinationView]'),'2;1');
|
||||
FILE_NAME('','2022-05-04T08:08:30',(''),(''),'IfcOpenShell-0.7.0','IfcOpenShell-0.7.0','');
|
||||
FILE_SCHEMA(('IFC4'));
|
||||
ENDSEC;
|
||||
DATA;
|
||||
#1=IFCPERSON($,$,'',,$,$,$,$);
|
||||
#2=IFCORGANIZATION($,'',$,$,$);
|
||||
#3=IFCPERSONANDORGANIZATION(#1,#2,$);
|
||||
#4=IFCAPPLICATION(#2,'0.7.0','IfcOpenShell-0.7.0','');
|
||||
#5=IFCOWNERHISTORY(#3,#4,$,.ADDED.,$,#3,#4,1651651710);
|
||||
#6=IFCDIRECTION((1.,0.,0.));
|
||||
#7=IFCDIRECTION((0.,0.,1.));
|
||||
#8=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#9=IFCAXIS2PLACEMENT3D(#8,#7,#6);
|
||||
#10=IFCDIRECTION((0.,1.,0.));
|
||||
#11=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.E-05,#9,#10);
|
||||
#12=IFCDIMENSIONALEXPONENTS(0,0,0,0,0,0,0);
|
||||
#13=IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.);
|
||||
#14=IFCSIUNIT(*,.AREAUNIT.,$,.SQUARE_METRE.);
|
||||
#15=IFCSIUNIT(*,.VOLUMEUNIT.,$,.CUBIC_METRE.);
|
||||
#16=IFCSIUNIT(*,.PLANEANGLEUNIT.,$,.RADIAN.);
|
||||
#17=IFCMEASUREWITHUNIT(IFCPLANEANGLEMEASURE(0.017453292519943295),#16);
|
||||
#18=IFCCONVERSIONBASEDUNIT(#12,.PLANEANGLEUNIT.,'DEGREE',#17);
|
||||
#19=IFCUNITASSIGNMENT((#13,#14,#15,#18));
|
||||
#20=IFCPROJECT('2AyG2X0sb16Bjd4gQc07yZ',#5,'',$,$,$,$,(#11),#19);
|
||||
ENDSEC;
|
||||
END-ISO-10303-21;
|
||||
@@ -0,0 +1,29 @@
|
||||
ISO-10303-21;
|
||||
HEADER;
|
||||
FILE_DESCRIPTION(('ViewDefinition [CoordinationView]'),'2;1');
|
||||
FILE_NAME('','2022-05-04T08:08:30',(''),(''),'IfcOpenShell-0.7.0','IfcOpenShell-0.7.0','');
|
||||
FILE_SCHEMA(('IFC4'));
|
||||
ENDSEC;
|
||||
DATA;
|
||||
#1=IFCPERSON($,$,'',$,$,$,$,$);
|
||||
#2=IFCORGANIZATION($,'',$,$,$);
|
||||
#3=IFCPERSONANDORGANIZATION(#1,#2,$);
|
||||
#4=IFCAPPLICATION(#2,'0.7.0','IfcOpenShell-0.7.0','');
|
||||
#5=IFCOWNERHISTORY(#3,#4,$,.ADDED.,$,#3,#4,1651651710);
|
||||
#6=IFCDIRECTION((1.,0.,0.));
|
||||
#7=IFCDIRECTION((0.,0.,1.));
|
||||
#8=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#9=IFCAXIS2PLACEMENT3D(#8,#7,#6);
|
||||
#10=IFCDIRECTION((0.,1.,0.));
|
||||
#11=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.E-05,#9,#10);
|
||||
#12=IFCDIMENSIONALEXPONENTS(0,0,0,0,0,0,0);
|
||||
#13=IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.);
|
||||
#14=IFCSIUNIT(*,.AREAUNIT.,$,.SQUARE_METRE.);
|
||||
#15=IFCSIUNIT(*,.VOLUMEUNIT.,$,.CUBIC_METRE.);
|
||||
#16=IFCSIUNIT(*,.PLANEANGLEUNIT.,$,.RADIAN.);
|
||||
#17=IFCMEASUREWITHUNIT(IFCPLANEANGLEMEASURE(0.017453292519943295),#16);
|
||||
#18=IFCCONVERSIONBASEDUNIT(#12,.PLANEANGLEUNIT.,'DEGREE',#17);
|
||||
#19=IFCUNITASSIGNMENT((#13,#14,#15,#18));
|
||||
#20=IFCPROJECT('2AyG2X0sb16Bjd4gQc07yZ',#5,'',$,$,$,$,(#11),#19);;
|
||||
ENDSEC;
|
||||
END-ISO-10303-21;
|
||||
@@ -0,0 +1,29 @@
|
||||
ISO-10303-21;
|
||||
HEADER;
|
||||
FILE_DESCRIPTION(('ViewDefinition [CoordinationView]'),'2;1');
|
||||
FILE_NAME('','2022-05-04T08:08:30',(''),(''),'IfcOpenShell-0.7.0','IfcOpenShell-0.7.0','');
|
||||
FILE_SCHEMA(('IFC4'));
|
||||
ENDSEC;
|
||||
DATA;
|
||||
#1=IFCPERSON($,$,'',$,$,$,$,$);
|
||||
#2=IFCORGANIZATION($,'',$,$,$);
|
||||
#3=IFCPERSONANDORGANIZATION(#1,#2,$);
|
||||
#4=IFCAPPLICATION(#2,'0.7.0','IfcOpenShell-0.7.0','');
|
||||
#5=IFCOWNERHISTORY(#3,#4,$,.ADDED.,$,#3,#4,1651651710);
|
||||
#6=IFCDIRECTION((1.,0.,0.));
|
||||
#7=IFCDIRECTION((0.,0.,1.));
|
||||
#8=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#9=IFCAXIS2PLACEMENT3D(#8,#7,#6);
|
||||
#10=IFCDIRECTION((0.,1.,0.));
|
||||
#11=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.E-05,#9,#10);
|
||||
#12=IFCDIMENSIONALEXPONENTS(0,0,0,0,0,0,0);
|
||||
#13=IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.);
|
||||
#14=IFCSIUNIT(*,.AREAUNIT.,$,.SQUARE_METRE.);
|
||||
#15=IFCSIUNIT(*,.VOLUMEUNIT.,$,.CUBIC_METRE.);
|
||||
#16=IFCSIUNIT(*,.PLANEANGLEUNIT.,$,.RADIAN.);
|
||||
#17=IFCMEASUREWITHUNIT(IFCPLANEANGLEMEASURE(0.017453292519943295),#16);
|
||||
#18=IFCCONVERSIONBASEDUNIT(#12,.PLANEANGLEUNIT.,'DEGREE',#17);
|
||||
#19=IFCUNITASSIGNMENT((#13,#14,#15,#18));
|
||||
#19=IFCPROJECT('2AyG2X0sb16Bjd4gQc07yZ',#5,'',$,$,$,$,(#11),#19);
|
||||
ENDSEC;
|
||||
END-ISO-10303-21;
|
||||
@@ -0,0 +1,10 @@
|
||||
ISO-10303-21;
|
||||
HEADER;
|
||||
FILE_DESCRIPTION(('ViewDefinition [CoordinationView]'),'2;1');
|
||||
FILE_NAME('','2022-05-04T08:08:30',(''),(''),'IfcOpenShell-0.7.0','IfcOpenShell-0.7.0','');
|
||||
FILE_SCHEMA(('IFC4'));
|
||||
ENDSEC;
|
||||
DATA;
|
||||
#0=IFCPERSON($, $,'',$,$,$,$,$);
|
||||
ENDSEC;
|
||||
END-ISO-10303-21;
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
ISO-10303-21;
|
||||
HEADER;
|
||||
FILE_DESCRIPTION(('ViewDefinition [Alignment-basedView]'),'2;1');
|
||||
FILE_NAME('Header example2.ifc', '2022-09-16T10:35:07', ('Evandro Alfieri'), ('buildingSMART Int.'), 'IFC Motor 1.0', 'Company - Application - 26.0.0.0', 'none');
|
||||
FILE_SCHEMA(('IFC4X3_ADD2'));
|
||||
FILE_INFO(
|
||||
'Report Date', '2025-04-21 18:16:37',
|
||||
'IFC Schema', 'IFC2X3',
|
||||
'MVD(s)', 'None',
|
||||
'File Name in Header', 'generated (2).ifc',
|
||||
'File Name', 'generated (2).ifc',
|
||||
'File Size', '0.41 kB',
|
||||
'File Date', '2025-04-21 18:16:37',
|
||||
'Originating System', 'PHP Script',
|
||||
'Preprocessor Version', '1.0.0',
|
||||
'Company Name', 'Your Company Name',
|
||||
'Application Name', 'IFC Generator PHP Script',
|
||||
'Application Version', '1.0.0',
|
||||
'Author', 'Your Name',
|
||||
'Organization', 'Your Company Name'
|
||||
);
|
||||
ENDSEC;
|
||||
DATA;
|
||||
#1=IFCPERSON($,$,'',$,$,$,$,$);
|
||||
#2=IFCORGANIZATION($,'',$,$,$);
|
||||
#3=IFCPERSONANDORGANIZATION(#1,#2,$);
|
||||
#4=IFCAPPLICATION(#2,'v0.7.0-6c9e130ca','IfcOpenShell-v0.7.0-6c9e130ca','');
|
||||
#5=IFCOWNERHISTORY(#3,#4,$,.NOTDEFINED.,$,#3,#4,1700419055);
|
||||
#6=IFCDIRECTION((1.,0.,0.));
|
||||
#7=IFCDIRECTION((0.,0.,1.));
|
||||
#8=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#9=IFCAXIS2PLACEMENT3D(#8,#7,#6);
|
||||
#10=IFCDIRECTION((0.,1.));
|
||||
#11=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.E-05,#9,#10);
|
||||
#12=IFCDIMENSIONALEXPONENTS(0,0,0,0,0,0,0);
|
||||
#13=IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.);
|
||||
#14=IFCSIUNIT(*,.AREAUNIT.,$,.SQUARE_METRE.);
|
||||
#15=IFCSIUNIT(*,.VOLUMEUNIT.,$,.CUBIC_METRE.);
|
||||
#16=IFCSIUNIT(*,.PLANEANGLEUNIT.,$,.RADIAN.);
|
||||
#17=IFCMEASUREWITHUNIT(IFCPLANEANGLEMEASURE(0.017453292519943295),#16);
|
||||
#18=IFCCONVERSIONBASEDUNIT(#12,.PLANEANGLEUNIT.,'DEGREE',#17);
|
||||
#19=IFCUNITASSIGNMENT((#13,#14,#15,#18));
|
||||
#20=IFCPROJECT('0iDmeiiLP3AOllitM2Favn',#5,'',$,$,$,$,(#11),#19);
|
||||
#21=IFCSITE('3rg2jGkIH10RFhrQsGZKRk',#5,$,$,$,$,$,$,$,$,$,$,$,$);
|
||||
ENDSEC;
|
||||
END-ISO-10303-21;
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
ISO-10303-21;
|
||||
HEADER;
|
||||
FILE_DESCRIPTION(('ViewDefinition [CoordinationView]'),'2;1');
|
||||
FILE_NAME('','2022-05-04T08:08:30',(''),(''),'IfcOpenShell-0.7.0','IfcOpenShell-0.7.0','');
|
||||
FILE_SCHEMA(('IFC4'));
|
||||
ENDSEC;
|
||||
DATA;
|
||||
#1=IFCPERSON($,$,'',$,$,$,$,$);
|
||||
#2=IFCORGANIZATION($,'',$,$,$);
|
||||
#3=IFCPERSONANDORGANIZATION(#1,#2,$);
|
||||
#4=IFCAPPLICATION(#2,'0.7.0','IfcOpenShell-0.7.0','');
|
||||
#5=IFCOWNERHISTORY(#3,#4,$,.ADDED.,$,#3,#4,1651651710);
|
||||
#6=IFCDIRECTION((1.,0.,0.));
|
||||
#7=IFCDIRECTION((0.,0.,1.));
|
||||
#8=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#9=IFCAXIS2PLACEMENT3D(#8,#7,#6);
|
||||
#10=IFCDIRECTION((0.,1.,0.));
|
||||
#11=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.E-05,#9,#10);
|
||||
#12=IFCDIMENSIONALEXPONENTS(0,0,0,0,0,0,0);
|
||||
#13=IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.);
|
||||
#14=IFCSIUNIT(*,.AREAUNIT.,$,.SQUARE_METRE.);
|
||||
#15=IFCSIUNIT(*,.VOLUMEUNIT.,$,.CUBIC_METRE.);
|
||||
#16=IFCSIUNIT(*,.PLANEANGLEUNIT.,$,.RADIAN.);
|
||||
#18=IFCMEASUREWITHUNIT(IFCPLANEANGLEMEASURE(0.017453292519943295),#16);
|
||||
#18=IFCCONVERSIONBASEDUNIT(#12,.PLANEANGLEUNIT.,'DEGREE',#17);
|
||||
#19=IFCUNITASSIGNMENT((#13,#14,#15,#18));
|
||||
#19=IFCPROJECT('2AyG2X0sb16Bjd4gQc07yZ',#5,'',$,$,$,$,(#11),#19);
|
||||
ENDSEC;
|
||||
END-ISO-10303-21;
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
ISO-10303-21;
|
||||
HEADER;
|
||||
FILE_DESCRIPTION(('ViewDefinition [ReferenceView_V1.2]', 'ExchangeRequirement [Any]'));
|
||||
FILE_NAME('Header.ifc','2025-02-13T15:58:45',('tricott'),('Trimble Inc.'),'TrimBimToIFC rel. 4.0.2','Example - Example - 2025.0','IFC4 model', '');
|
||||
FILE_SCHEMA(('IFC4'));
|
||||
ENDSEC;
|
||||
DATA;
|
||||
#1=IFCPERSON($,$,'',$,$,$,$,$);
|
||||
#2=IFCORGANIZATION($,'',$,$,$);
|
||||
#3=IFCPERSONANDORGANIZATION(#1,#2,$);
|
||||
#4=IFCAPPLICATION(#2,'v0.7.0-6c9e130ca','IfcOpenShell-v0.7.0-6c9e130ca','');
|
||||
#5=IFCOWNERHISTORY(#3,#4,$,.NOTDEFINED.,$,#3,#4,1700419055);
|
||||
#6=IFCDIRECTION((1.,0.,0.));
|
||||
#7=IFCDIRECTION((0.,0.,1.));
|
||||
#8=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#9=IFCAXIS2PLACEMENT3D(#8,#7,#6);
|
||||
#10=IFCDIRECTION((0.,1.));
|
||||
#11=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.E-05,#9,#10);
|
||||
#12=IFCDIMENSIONALEXPONENTS(0,0,0,0,0,0,0);
|
||||
#13=IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.);
|
||||
#14=IFCSIUNIT(*,.AREAUNIT.,$,.SQUARE_METRE.);
|
||||
#15=IFCSIUNIT(*,.VOLUMEUNIT.,$,.CUBIC_METRE.);
|
||||
#16=IFCSIUNIT(*,.PLANEANGLEUNIT.,$,.RADIAN.);
|
||||
#17=IFCMEASUREWITHUNIT(IFCPLANEANGLEMEASURE(0.017453292519943295),#16);
|
||||
#18=IFCCONVERSIONBASEDUNIT(#12,.PLANEANGLEUNIT.,'DEGREE',#17);
|
||||
#19=IFCUNITASSIGNMENT((#13,#14,#15,#18));
|
||||
#20=IFCPROJECT('0iDmeiiLP3AOllitM2Favn',#5,'',$,$,$,$,(#11),#19);
|
||||
#21=IFCSITE('3rg2jGkIH10RFhrQsGZKRk',#5,$,$,$,$,$,$,$,$,$,$,$,$);
|
||||
ENDSEC;
|
||||
END-ISO-10303-21;
|
||||
@@ -0,0 +1,28 @@
|
||||
ISO-10303-21;
|
||||
FILE_DESCRIPTION(('ViewDefinition [CoordinationView]'),'2;1');
|
||||
FILE_NAME('','2022-05-04T08:08:30',(''),(''),'IfcOpenShell-0.7.0','IfcOpenShell-0.7.0','');
|
||||
FILE_SCHEMA(('IFC4'));
|
||||
ENDSEC;
|
||||
DATA;
|
||||
#1=IFCPERSON($,$,'',$,$,$,$,$);
|
||||
#2=IFCORGANIZATION($,'',$,$,$);
|
||||
#3=IFCPERSONANDORGANIZATION(#1,#2,$);
|
||||
#4=IFCAPPLICATION(#2,'0.7.0','IfcOpenShell-0.7.0','');
|
||||
#5=IFCOWNERHISTORY(#3,#4,$,.ADDED.,$,#3,#4,1651651710);
|
||||
#6=IFCDIRECTION((1.,0.,0.));
|
||||
#7=IFCDIRECTION((0.,0.,1.));
|
||||
#8=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#9=IFCAXIS2PLACEMENT3D(#8,#7,#6);
|
||||
#10=IFCDIRECTION((0.,1.,0.));
|
||||
#11=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.E-05,#9,#10);
|
||||
#12=IFCDIMENSIONALEXPONENTS(0,0,0,0,0,0,0);
|
||||
#13=IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.);
|
||||
#14=IFCSIUNIT(*,.AREAUNIT.,$,.SQUARE_METRE.);
|
||||
#15=IFCSIUNIT(*,.VOLUMEUNIT.,$,.CUBIC_METRE.);
|
||||
#16=IFCSIUNIT(*,.PLANEANGLEUNIT.,$,.RADIAN.);
|
||||
#17=IFCMEASUREWITHUNIT(IFCPLANEANGLEMEASURE(0.017453292519943295),#16);
|
||||
#18=IFCCONVERSIONBASEDUNIT(#12,.PLANEANGLEUNIT.,'DEGREE',#17);
|
||||
#19=IFCUNITASSIGNMENT((#13,#14,#15,#18));
|
||||
#20=IFCPROJECT('2AyG2X0sb16Bjd4gQc07yZ',#5,'',$,$,$,$,(#11),#19);
|
||||
ENDSEC;
|
||||
END-ISO-10303-21;
|
||||
@@ -0,0 +1,29 @@
|
||||
ISO-10303-21;
|
||||
HEADER;
|
||||
FILE_DESCRIPTION(('ViewDefinition [CoordinationView]'),'2;1');
|
||||
FILE_NAME('','2022-05-04T08:08:30',(''),(''),'IfcOpenShell-0.7.0','IfcOpenShell-0.7.0','');
|
||||
FILE_SCHEMA(('IFC4'));
|
||||
ENDSEC;
|
||||
DATA;
|
||||
#1=IFCPERSON($,$,'C:\path',$,$,$,$);
|
||||
#2=IFCORGANIZATION($,'',$,$,$);
|
||||
#3=IFCPERSONANDORGANIZATION(#1,#2,$);
|
||||
#4=IFCAPPLICATION(#2,'0.7.0','IfcOpenShell-0.7.0','');
|
||||
#5=IFCOWNERHISTORY(#3,#4,$,.ADDED.,$,#3,#4,1651651710);
|
||||
#6=IFCDIRECTION((1.,0.,0.));
|
||||
#7=IFCDIRECTION((0.,0.,1.));
|
||||
#8=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#9=IFCAXIS2PLACEMENT3D(#8,#7,#6);
|
||||
#10=IFCDIRECTION((0.,1.,0.));
|
||||
#11=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.E-05,#9,#10);
|
||||
#12=IFCDIMENSIONALEXPONENTS(0,0,0,0,0,0,0);
|
||||
#13=IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.);
|
||||
#14=IFCSIUNIT(*,.AREAUNIT.,$,.SQUARE_METRE.);
|
||||
#15=IFCSIUNIT(*,.VOLUMEUNIT.,$,.CUBIC_METRE.);
|
||||
#16=IFCSIUNIT(*,.PLANEANGLEUNIT.,$,.RADIAN.);
|
||||
#17=IFCMEASUREWITHUNIT(IFCPLANEANGLEMEASURE(0.017453292519943295),#16);
|
||||
#18=IFCCONVERSIONBASEDUNIT(#12,.PLANEANGLEUNIT.,'DEGREE',#17);
|
||||
#19=IFCUNITASSIGNMENT((#13,#14,#15,#18));
|
||||
#20=IFCPROJECT('2AyG2X0sb16Bjd4gQc07yZ',#5,'',$,$,$,$,(#11),#19);
|
||||
ENDSEC;
|
||||
END-ISO-10303-21;
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
ISO-10303-21;
|
||||
HEADER;
|
||||
FILE_DESCRIPTION(('ViewDefinition [ReferenceView_V1.2]', 'ExchangeRequirement [Any]'),'2;1');
|
||||
FILE_NAME('Header.ifc','2025-02-13T15:58:45',('tricott'),('Trimble Inc.'),'TrimBimToIFC rel. 4.0.2','Example - Example - 2025.0','IFC4 model', '');
|
||||
FILE_SCHEMA(('IFC4'));
|
||||
ENDSEC;
|
||||
DATA;
|
||||
#1=IFCPERSON($,$,'',$,$,$,$,$);
|
||||
#2=IFCORGANIZATION($,'',$,$,$);
|
||||
#3=IFCPERSONANDORGANIZATION(#1,#2,$);
|
||||
#4=IFCAPPLICATION(#2,'v0.7.0-6c9e130ca','IfcOpenShell-v0.7.0-6c9e130ca','');
|
||||
#5=IFCOWNERHISTORY(#3,#4,$,.NOTDEFINED.,$,#3,#4,1700419055);
|
||||
#6=IFCDIRECTION((1.,0.,0.));
|
||||
#7=IFCDIRECTION((0.,0.,1.));
|
||||
#8=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#9=IFCAXIS2PLACEMENT3D(#8,#7,#6);
|
||||
#10=IFCDIRECTION((0.,1.));
|
||||
#11=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.E-05,#9,#10);
|
||||
#12=IFCDIMENSIONALEXPONENTS(0,0,0,0,0,0,0);
|
||||
#13=IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.);
|
||||
#14=IFCSIUNIT(*,.AREAUNIT.,$,.SQUARE_METRE.);
|
||||
#15=IFCSIUNIT(*,.VOLUMEUNIT.,$,.CUBIC_METRE.);
|
||||
#16=IFCSIUNIT(*,.PLANEANGLEUNIT.,$,.RADIAN.);
|
||||
#17=IFCMEASUREWITHUNIT(IFCPLANEANGLEMEASURE(0.017453292519943295),#16);
|
||||
#18=IFCCONVERSIONBASEDUNIT(#12,.PLANEANGLEUNIT.,'DEGREE',#17);
|
||||
#19=IFCUNITASSIGNMENT((#13,#14,#15,#18));
|
||||
#20=IFCPROJECT('0iDmeiiLP3AOllitM2Favn',#5,'',$,$,$,$,(#11),#19);
|
||||
#21=IFCSITE('3rg2jGkIH10RFhrQsGZKRk',#5,$,$,$,$,$,$,$,$,$,$,$,$);
|
||||
ENDSEC;
|
||||
END-ISO-10303-21;
|
||||
@@ -0,0 +1,29 @@
|
||||
ISO-10303-21;
|
||||
HEADER;
|
||||
FILE_DESCRIPTION(('ViewDefinition [CoordinationView]'),'2;1');
|
||||
FILE_NAME('','2022-05-04T08:08:30',(''),(''),'IfcOpenShell-0.7.0','IfcOpenShell-0.7.0','');
|
||||
FILE_SCHEMA(('IFC4'));
|
||||
ENDSEC;
|
||||
DATA;
|
||||
#1=IFCPERSON($,$,'W\xc3\xa4nd',$,$,$,$);
|
||||
#2=IFCORGANIZATION($,'',$,$,$);
|
||||
#3=IFCPERSONANDORGANIZATION(#1,#2,$);
|
||||
#4=IFCAPPLICATION(#2,'0.7.0','IfcOpenShell-0.7.0','');
|
||||
#5=IFCOWNERHISTORY(#3,#4,$,.ADDED.,$,#3,#4,1651651710);
|
||||
#6=IFCDIRECTION((1.,0.,0.));
|
||||
#7=IFCDIRECTION((0.,0.,1.));
|
||||
#8=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#9=IFCAXIS2PLACEMENT3D(#8,#7,#6);
|
||||
#10=IFCDIRECTION((0.,1.,0.));
|
||||
#11=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.E-05,#9,#10);
|
||||
#12=IFCDIMENSIONALEXPONENTS(0,0,0,0,0,0,0);
|
||||
#13=IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.);
|
||||
#14=IFCSIUNIT(*,.AREAUNIT.,$,.SQUARE_METRE.);
|
||||
#15=IFCSIUNIT(*,.VOLUMEUNIT.,$,.CUBIC_METRE.);
|
||||
#16=IFCSIUNIT(*,.PLANEANGLEUNIT.,$,.RADIAN.);
|
||||
#17=IFCMEASUREWITHUNIT(IFCPLANEANGLEMEASURE(0.017453292519943295),#16);
|
||||
#18=IFCCONVERSIONBASEDUNIT(#12,.PLANEANGLEUNIT.,'DEGREE',#17);
|
||||
#19=IFCUNITASSIGNMENT((#13,#14,#15,#18));
|
||||
#20=IFCPROJECT('2AyG2X0sb16Bjd4gQc07yZ',#5,'',$,$,$,$,(#11),#19);
|
||||
ENDSEC;
|
||||
END-ISO-10303-21;
|
||||
@@ -0,0 +1,30 @@
|
||||
ISO-10303-21;
|
||||
HEADER;
|
||||
FILE_DESCRIPTION(('ViewDefinition [CoordinationView]'),'2;1');
|
||||
FILE_NAME('','2022-05-04T08:08:30',(''),(''),'IfcOpenShell-0.7.0','IfcOpenShell-0.7.0','');
|
||||
FILE_SCHEMA(('IFC4'));
|
||||
ENDSEC;
|
||||
DATA;
|
||||
#1=IFCPERSON($, $,'',$,$,$,$,$);
|
||||
#2=IFCORGANIZATION($,'',$,$,$);
|
||||
#3=IFCPERSONANDORGANIZATION(#1,#2,$);
|
||||
#4=IFCAPPLICATION(#2,'0.7.0','IfcOpenShell-0.7.0','');
|
||||
#100=IFCAPPLICATION(#2,'0.7.0','Nested '' quotes','');
|
||||
#5=IFCOWNERHISTORY(#3,#4,$,.ADDED.,$,#3,#4,1651651710);
|
||||
#6=IFCDIRECTION((1.,0.,0.));
|
||||
#7=IFCDIRECTION((0.,0.,1.));
|
||||
#8=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#9=IFCAXIS2PLACEMENT3D(#8,#7,#6);
|
||||
#10=IFCDIRECTION((0.,1.,0.));
|
||||
#11=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.E-05,#9,#10);
|
||||
#12=IFCDIMENSIONALEXPONENTS(0,0,0,0,0,0,0);
|
||||
#13=IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.);
|
||||
#14=IFCSIUNIT(*,.AREAUNIT.,$,.SQUARE_METRE.);
|
||||
#15=IFCSIUNIT(*,.VOLUMEUNIT.,$,.CUBIC_METRE.);
|
||||
#16=IFCSIUNIT(*,.PLANEANGLEUNIT.,$,.RADIAN.);
|
||||
#17=IFCMEASUREWITHUNIT(IFCPLANEANGLEMEASURE(0.017453292519943295),#16);
|
||||
#18=IFCCONVERSIONBASEDUNIT(#12,.PLANEANGLEUNIT.,'DEGREE',#17);
|
||||
#19=IFCUNITASSIGNMENT((#13,#14,#15,#18));
|
||||
#20=IFCPROJECT('2AyG2X0sb16Bjd4gQc07yZ',#5,'',$,$,$,$,(#11),#19);
|
||||
ENDSEC;
|
||||
END-ISO-10303-21;
|
||||
@@ -0,0 +1,29 @@
|
||||
ISO-10303-21;
|
||||
HEADER;
|
||||
FILE_DESCRIPTION(('ViewDefinition [CoordinationView]'),'2;1');
|
||||
FILE_NAME('','2022-05-04T08:08:30',(''),(''),'IfcOpenShell-0.7.0','IfcOpenShell-0.7.0','');
|
||||
FILE_SCHEMA(('IFC4'));
|
||||
ENDSEC;
|
||||
DATA;
|
||||
#1=IFCPERSON($,$,'C:\\path',$,$,$,$);
|
||||
#2=IFCORGANIZATION($,'',$,$,$);
|
||||
#3=IFCPERSONANDORGANIZATION(#1,#2,$);
|
||||
#4=IFCAPPLICATION(#2,'0.7.0','IfcOpenShell-0.7.0','');
|
||||
#5=IFCOWNERHISTORY(#3,#4,$,.ADDED.,$,#3,#4,1651651710);
|
||||
#6=IFCDIRECTION((1.,0.,0.));
|
||||
#7=IFCDIRECTION((0.,0.,1.));
|
||||
#8=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#9=IFCAXIS2PLACEMENT3D(#8,#7,#6);
|
||||
#10=IFCDIRECTION((0.,1.,0.));
|
||||
#11=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.E-05,#9,#10);
|
||||
#12=IFCDIMENSIONALEXPONENTS(0,0,0,0,0,0,0);
|
||||
#13=IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.);
|
||||
#14=IFCSIUNIT(*,.AREAUNIT.,$,.SQUARE_METRE.);
|
||||
#15=IFCSIUNIT(*,.VOLUMEUNIT.,$,.CUBIC_METRE.);
|
||||
#16=IFCSIUNIT(*,.PLANEANGLEUNIT.,$,.RADIAN.);
|
||||
#17=IFCMEASUREWITHUNIT(IFCPLANEANGLEMEASURE(0.017453292519943295),#16);
|
||||
#18=IFCCONVERSIONBASEDUNIT(#12,.PLANEANGLEUNIT.,'DEGREE',#17);
|
||||
#19=IFCUNITASSIGNMENT((#13,#14,#15,#18));
|
||||
#20=IFCPROJECT('2AyG2X0sb16Bjd4gQc07yZ',#5,'',$,$,$,$,(#11),#19);
|
||||
ENDSEC;
|
||||
END-ISO-10303-21;
|
||||
@@ -0,0 +1,29 @@
|
||||
ISO-10303-21;
|
||||
HEADER;
|
||||
FILE_DESCRIPTION(('ViewDefinition [CoordinationView]'),'2;1');
|
||||
FILE_NAME('','2022-05-04T08:08:30',(''),(''),'IfcOpenShell-0.7.0','IfcOpenShell-0.7.0','');
|
||||
FILE_SCHEMA(('IFC4'));
|
||||
ENDSEC;
|
||||
DATA;
|
||||
#1=IFCPERSON($,$,'abc\S\'def',$,$,$,$);
|
||||
#2=IFCORGANIZATION($,'',$,$,$);
|
||||
#3=IFCPERSONANDORGANIZATION(#1,#2,$);
|
||||
#4=IFCAPPLICATION(#2,'0.7.0','IfcOpenShell-0.7.0','');
|
||||
#5=IFCOWNERHISTORY(#3,#4,$,.ADDED.,$,#3,#4,1651651710);
|
||||
#6=IFCDIRECTION((1.,0.,0.));
|
||||
#7=IFCDIRECTION((0.,0.,1.));
|
||||
#8=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#9=IFCAXIS2PLACEMENT3D(#8,#7,#6);
|
||||
#10=IFCDIRECTION((0.,1.,0.));
|
||||
#11=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.E-05,#9,#10);
|
||||
#12=IFCDIMENSIONALEXPONENTS(0,0,0,0,0,0,0);
|
||||
#13=IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.);
|
||||
#14=IFCSIUNIT(*,.AREAUNIT.,$,.SQUARE_METRE.);
|
||||
#15=IFCSIUNIT(*,.VOLUMEUNIT.,$,.CUBIC_METRE.);
|
||||
#16=IFCSIUNIT(*,.PLANEANGLEUNIT.,$,.RADIAN.);
|
||||
#17=IFCMEASUREWITHUNIT(IFCPLANEANGLEMEASURE(0.017453292519943295),#16);
|
||||
#18=IFCCONVERSIONBASEDUNIT(#12,.PLANEANGLEUNIT.,'DEGREE',#17);
|
||||
#19=IFCUNITASSIGNMENT((#13,#14,#15,#18));
|
||||
#20=IFCPROJECT('2AyG2X0sb16Bjd4gQc07yZ',#5,'',$,$,$,$,(#11),#19);
|
||||
ENDSEC;
|
||||
END-ISO-10303-21;
|
||||
@@ -0,0 +1,31 @@
|
||||
ISO-10303-21;
|
||||
HEADER;
|
||||
FILE_DESCRIPTION(('ViewDefinition [CoordinationView]'),'2;1');
|
||||
FILE_NAME('','2022-05-04T08:08:30',(''),(''),'IfcOpenShell-0.7.0','IfcOpenShell-0.7.0','');
|
||||
FILE_SCHEMA(('IFC4'));
|
||||
ENDSEC;
|
||||
DATA;
|
||||
/*C:\TEMP\path\...\ifc
|
||||
*/
|
||||
#1=IFCPERSON($,$,'',$,$,$,$);
|
||||
#2=IFCORGANIZATION($,'',$,$,$);
|
||||
#3=IFCPERSONANDORGANIZATION(#1,#2,$);
|
||||
#4=IFCAPPLICATION(#2,'0.7.0','IfcOpenShell-0.7.0','');
|
||||
#5=IFCOWNERHISTORY(#3,#4,$,.ADDED.,$,#3,#4,1651651710);
|
||||
#6=IFCDIRECTION((1.,0.,0.));
|
||||
#7=IFCDIRECTION((0.,0.,1.));
|
||||
#8=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#9=IFCAXIS2PLACEMENT3D(#8,#7,#6);
|
||||
#10=IFCDIRECTION((0.,1.,0.));
|
||||
#11=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.E-05,#9,#10);
|
||||
#12=IFCDIMENSIONALEXPONENTS(0,0,0,0,0,0,0);
|
||||
#13=IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.);
|
||||
#14=IFCSIUNIT(*,.AREAUNIT.,$,.SQUARE_METRE.);
|
||||
#15=IFCSIUNIT(*,.VOLUMEUNIT.,$,.CUBIC_METRE.);
|
||||
#16=IFCSIUNIT(*,.PLANEANGLEUNIT.,$,.RADIAN.);
|
||||
#17=IFCMEASUREWITHUNIT(IFCPLANEANGLEMEASURE(0.017453292519943295),#16);
|
||||
#18=IFCCONVERSIONBASEDUNIT(#12,.PLANEANGLEUNIT.,'DEGREE',#17);
|
||||
#19=IFCUNITASSIGNMENT((#13,#14,#15,#18));
|
||||
#20=IFCPROJECT('2AyG2X0sb16Bjd4gQc07yZ',#5,'',$,$,$,$,(#11),#19);
|
||||
ENDSEC;
|
||||
END-ISO-10303-21;
|
||||
@@ -0,0 +1,30 @@
|
||||
ISO-10303-21;
|
||||
HEADER;
|
||||
FILE_DESCRIPTION(('ViewDefinition [Alignment-basedView]'),'2;1');
|
||||
FILE_NAME('Header example2.ifc', '2022-09-16T10:35:07', ('Evandro Alfieri'), ('buildingSMART Int.'), 'IFC Motor 1.0', 'Company - Application - 26.0.0.0', 'none');
|
||||
FILE_SCHEMA(('IFC4X3_ADD2'));
|
||||
ENDSEC;
|
||||
DATA;
|
||||
#1=IFCPERSON($,$,'',$,$,$,$,$);
|
||||
#2=IFCORGANIZATION($,'',$,$,$);
|
||||
#3=IFCPERSONANDORGANIZATION(#1,#2,$);
|
||||
#4=IFCAPPLICATION(#2,'v0.7.0-6c9e130ca','IfcOpenShell-v0.7.0-6c9e130ca','');
|
||||
#5=IFCOWNERHISTORY(#3,#4,$,.NOTDEFINED.,$,#3,#4,1700419055);
|
||||
#6=IFCDIRECTION((1.,0.,0.));
|
||||
#7=IFCDIRECTION((0.,0.,1.));
|
||||
#8=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#9=IFCAXIS2PLACEMENT3D(#8,#7,#6);
|
||||
#10=IFCDIRECTION((0.,1.));
|
||||
#11=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.E-05,#9,#10);
|
||||
#12=IFCDIMENSIONALEXPONENTS(0,0,0,0,0,0,0);
|
||||
#13=IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.);
|
||||
#14=IFCSIUNIT(*,.AREAUNIT.,$,.SQUARE_METRE.);
|
||||
#15=IFCSIUNIT(*,.VOLUMEUNIT.,$,.CUBIC_METRE.);
|
||||
#16=IFCSIUNIT(*,.PLANEANGLEUNIT.,$,.RADIAN.);
|
||||
#17=IFCMEASUREWITHUNIT(IFCPLANEANGLEMEASURE(0.017453292519943295),#16);
|
||||
#18=IFCCONVERSIONBASEDUNIT(#12,.PLANEANGLEUNIT.,'DEGREE',#17);
|
||||
#19=IFCUNITASSIGNMENT((#13,#14,#15,#18));
|
||||
#20=IFCPROJECT('0iDmeiiLP3AOllitM2Favn',#5,'',$,$,$,$,(#11),#19);
|
||||
#21=IFCSITE('3rg2jGkIH10RFhrQsGZKRk',#5,$,$,$,$,$,$,$,$,$,$,$,$);
|
||||
ENDSEC;
|
||||
END-ISO-10303-21;
|
||||
@@ -0,0 +1,92 @@
|
||||
import matplotlib.pyplot as plt
|
||||
import networkx as nx
|
||||
from itertools import count
|
||||
import matplotlib.patches as mpatches
|
||||
from matplotlib import pylab
|
||||
|
||||
|
||||
class IfcEntity:
|
||||
def __init__(self, data):
|
||||
self.ifctype = data["ifc_type"]
|
||||
self.id = data["id"]
|
||||
|
||||
def __str__(self):
|
||||
return "#" + str(self.id) + "_" + self.ifctype
|
||||
|
||||
__repr__ = __str__
|
||||
|
||||
|
||||
def get_cmap(n, name="hsv"):
|
||||
"""Returns a function that maps each index in 0, 1, ..., n-1 to a distinct
|
||||
RGB color; the keyword argument name must be a standard mpl colormap name."""
|
||||
return plt.cm.get_cmap(name, n)
|
||||
|
||||
|
||||
G = nx.Graph()
|
||||
|
||||
colors = []
|
||||
color_map = []
|
||||
|
||||
ifctypes = set()
|
||||
|
||||
types_color = {}
|
||||
|
||||
for e in ents.values():
|
||||
for r in e["attributes"][1]:
|
||||
G.add_edge(e["id"], r)
|
||||
color_map.append("red")
|
||||
ifctypes.add(e["ifc_type"])
|
||||
ifctypes.add(ents[r]["ifc_type"])
|
||||
|
||||
|
||||
cmap = get_cmap(len(ifctypes), "rainbow")
|
||||
|
||||
colors = [cmap(i) for i in range(len(ifctypes))]
|
||||
|
||||
type_color_mapping = {}
|
||||
|
||||
cols = []
|
||||
|
||||
i = 0
|
||||
for eid in G.nodes():
|
||||
t = ents[eid]["ifc_type"]
|
||||
# print(t)
|
||||
if t in type_color_mapping.keys():
|
||||
cols.append(type_color_mapping[t])
|
||||
else:
|
||||
type_color_mapping[t] = colors[i]
|
||||
i = i + 1
|
||||
cols.append(type_color_mapping[t])
|
||||
|
||||
|
||||
# k controls the distance between the nodes and varies between 0 and 1
|
||||
# iterations is the number of times simulated annealing is run
|
||||
# default k=0.1 and iterations=50
|
||||
pos = nx.spring_layout(G, k=0.8, iterations=60) # positions for all nodes
|
||||
|
||||
nodes = G.nodes()
|
||||
print(get_cmap(len(nodes)))
|
||||
cmap = get_cmap(len(nodes))
|
||||
|
||||
c_map = [cmap(i) for i in range(len(nodes))]
|
||||
|
||||
nc = nx.draw_networkx_nodes(G, pos, nodelist=nodes, node_color=cols, node_size=200)
|
||||
|
||||
# edges
|
||||
elarge = [(u, v) for (u, v, d) in G.edges(data=True)]
|
||||
nx.draw_networkx_edges(G, pos, edgelist=elarge, width=1)
|
||||
|
||||
nx.draw_networkx_labels(G, pos, font_size=8, font_family="sans-serif")
|
||||
|
||||
|
||||
red_patch = mpatches.Patch(color="red", label="The red data")
|
||||
blue_patch = mpatches.Patch(color="blue", label="The blue data")
|
||||
|
||||
patches = []
|
||||
for k, v in type_color_mapping.items():
|
||||
patches.append(mpatches.Patch(color=v, label=k))
|
||||
|
||||
|
||||
plt.legend(handles=patches, fontsize=8)
|
||||
plt.axis("off")
|
||||
plt.show()
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -0,0 +1,140 @@
|
||||
from lark.exceptions import UnexpectedToken
|
||||
|
||||
|
||||
class _ValidationError(Exception):
|
||||
def __init__(self, *args, **kwargs):
|
||||
if self.__class__ is _ValidationError:
|
||||
raise TypeError("Do not raise _ValidationError directly.")
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class ErrorCollector:
|
||||
def __init__(self):
|
||||
self.errors = []
|
||||
|
||||
def add(self, error):
|
||||
self.errors.append(error)
|
||||
|
||||
def raise_if_any(self):
|
||||
if self.errors:
|
||||
raise CollectedValidationErrors(self.errors)
|
||||
|
||||
|
||||
class CollectedValidationErrors(_ValidationError):
|
||||
def __init__(self, errors):
|
||||
self.errors = errors
|
||||
|
||||
def asdict(self, with_message=True):
|
||||
return [e.asdict(with_message=with_message) for e in self.errors]
|
||||
|
||||
def __str__(self):
|
||||
return f"{len(self.errors)} validation error(s) collected:\n" + "\n\n".join(
|
||||
str(e) for e in self.errors
|
||||
)
|
||||
|
||||
|
||||
class SyntaxError(_ValidationError):
|
||||
def __init__(self, filecontent, exception):
|
||||
self.filecontent = filecontent
|
||||
self.exception = exception
|
||||
|
||||
def asdict(self, with_message=True):
|
||||
return {
|
||||
"type": (
|
||||
"unexpected_token"
|
||||
if isinstance(self.exception, UnexpectedToken)
|
||||
else "unexpected_character"
|
||||
),
|
||||
"lineno": self.exception.line,
|
||||
"column": self.exception.column,
|
||||
"found_type": self.exception.token.type.lower(),
|
||||
"found_value": self.exception.token.value,
|
||||
"expected": sorted(x for x in self.exception.accepts if "__ANON" not in x),
|
||||
"line": self.filecontent.split("\n")[self.exception.line - 1],
|
||||
**({"message": str(self)} if with_message else {}),
|
||||
}
|
||||
|
||||
def __str__(self):
|
||||
d = self.asdict(with_message=False)
|
||||
if len(d["expected"]) == 1:
|
||||
exp = d["expected"][0]
|
||||
else:
|
||||
exp = f"one of {' '.join(d['expected'])}"
|
||||
|
||||
sth = "character" if d["type"] == "unexpected_character" else ""
|
||||
|
||||
return f"On line {d['lineno']} column {d['column']}:\nUnexpected {sth}{d['found_type']} ('{d['found_value']}')\nExpecting {exp}\n{d['lineno']:05d} | {d['line']}\n {' ' * (self.exception.column - 1)}^"
|
||||
|
||||
|
||||
class DuplicateNameError(_ValidationError):
|
||||
def __init__(self, filecontent, name, linenumbers):
|
||||
self.name = name
|
||||
self.filecontent = filecontent
|
||||
self.linenumbers = linenumbers
|
||||
|
||||
def asdict(self, with_message=True):
|
||||
return {
|
||||
"type": "duplicate_name",
|
||||
"name": self.name,
|
||||
"lineno": self.linenumbers[0],
|
||||
"line": self.filecontent.split("\n")[self.linenumbers[0] - 1],
|
||||
**({"message": str(self)} if with_message else {}),
|
||||
}
|
||||
|
||||
def __str__(self):
|
||||
d = self.asdict(with_message=False)
|
||||
|
||||
def build():
|
||||
yield f"On line {d['lineno']}:\nDuplicate instance name #{d['name']}"
|
||||
yield f"{d['lineno']:05d} | {d['line']}"
|
||||
yield " " * 8 + "^" * len(d["line"].rstrip())
|
||||
|
||||
return "\n".join(build())
|
||||
|
||||
|
||||
class HeaderFieldError(_ValidationError):
|
||||
def __init__(self, field, found_len, expected_len):
|
||||
self.field = field
|
||||
self.found_len = found_len
|
||||
self.expected_len = expected_len
|
||||
|
||||
def asdict(self, with_message=True):
|
||||
return {
|
||||
"type": "invalid_header_field",
|
||||
"field": self.field,
|
||||
"expected_field_count": self.expected_len,
|
||||
"actual_field_count": self.found_len,
|
||||
**({"message": str(self)} if with_message else {}),
|
||||
}
|
||||
|
||||
def __str__(self):
|
||||
return (
|
||||
f"Invalid number of parameters for HEADER field '{self.field}'. "
|
||||
f"Expected {self.expected_len}, found {self.found_len}."
|
||||
)
|
||||
|
||||
|
||||
class InvalidNameError(_ValidationError):
|
||||
def __init__(self, filecontent, name, linenumbers):
|
||||
self.name = name
|
||||
self.filecontent = filecontent
|
||||
self.linenumbers = linenumbers
|
||||
|
||||
def asdict(self, with_message=True):
|
||||
return {
|
||||
"type": "invalid_name",
|
||||
"name": self.name,
|
||||
"lineno": self.linenumbers[0],
|
||||
"line": self.filecontent.split("\n")[self.linenumbers[0] - 1],
|
||||
**({"message": str(self)} if with_message else {}),
|
||||
}
|
||||
|
||||
def __str__(self):
|
||||
d = self.asdict(with_message=False)
|
||||
|
||||
def build():
|
||||
yield f"On line {d['lineno']}:\nInvalid instance name #{d['name']}"
|
||||
yield f"{d['lineno']:05d} | {d['line']}"
|
||||
yield " " * 8 + "^" * len(d["line"].rstrip())
|
||||
|
||||
return "\n".join(build())
|
||||
@@ -0,0 +1,108 @@
|
||||
import types
|
||||
import re
|
||||
import numbers
|
||||
import itertools
|
||||
|
||||
from .parse import parse, ParseResult
|
||||
from .grammar import HEADER_FIELDS
|
||||
from .transformer import entity_instance
|
||||
|
||||
try:
|
||||
from .mvd_info import MvdInfo, LARK_AVAILABLE
|
||||
except ImportError: # in case of running module locally (e.g. test_parser.py)
|
||||
from mvd_info import MvdInfo, LARK_AVAILABLE
|
||||
|
||||
|
||||
class file:
|
||||
"""
|
||||
A somewhat compatible interface (but very limited) to ifcopenshell.file
|
||||
"""
|
||||
|
||||
def __init__(self, result: ParseResult):
|
||||
self.header_ = result.header
|
||||
self.data_ = result.entities
|
||||
|
||||
@property
|
||||
def schema_identifier(self) -> str:
|
||||
return self.header_["FILE_SCHEMA"][0][0]
|
||||
|
||||
@property
|
||||
def schema(self) -> str:
|
||||
"""General IFC schema version: IFC2X3, IFC4, IFC4X3."""
|
||||
prefixes = ("IFC", "X", "_ADD", "_TC")
|
||||
reg = "".join(f"(?P<{s}>{s}\\d+)?" for s in prefixes)
|
||||
match = re.match(reg, self.schema_identifier)
|
||||
version_tuple = tuple(
|
||||
map(
|
||||
lambda pp: int(pp[1][len(pp[0]) :]) if pp[1] else None,
|
||||
((p, match.group(p)) for p in prefixes),
|
||||
)
|
||||
)
|
||||
return "".join(
|
||||
"".join(map(str, t)) if t[1] else ""
|
||||
for t in zip(prefixes, version_tuple[0:2])
|
||||
)
|
||||
|
||||
@property
|
||||
def schema_version(self) -> tuple[int, int, int, int]:
|
||||
"""Numeric representation of the full IFC schema version.
|
||||
|
||||
E.g. IFC4X3_ADD2 is represented as (4, 3, 2, 0).
|
||||
"""
|
||||
schema = self.schema
|
||||
version = []
|
||||
for prefix in ("IFC", "X", "_ADD", "_TC"):
|
||||
number = re.search(prefix + r"(\d)", schema)
|
||||
version.append(int(number.group(1)) if number else 0)
|
||||
return tuple(version)
|
||||
|
||||
@property
|
||||
def header(self):
|
||||
header = {}
|
||||
for field_name, namedtuple_class in HEADER_FIELDS.items():
|
||||
field_data = self.header_.get(field_name.upper(), [])
|
||||
header[field_name.lower()] = namedtuple_class(*field_data)
|
||||
|
||||
return types.SimpleNamespace(**header)
|
||||
|
||||
@property
|
||||
def mvd(self):
|
||||
if not LARK_AVAILABLE or MvdInfo is None:
|
||||
return None
|
||||
return MvdInfo(self.header)
|
||||
|
||||
def __getitem__(self, key: numbers.Integral) -> entity_instance:
|
||||
return self.by_id(key)
|
||||
|
||||
def by_id(self, id: int) -> entity_instance:
|
||||
"""Return an IFC entity instance filtered by IFC ID.
|
||||
|
||||
:param id: STEP numerical identifier
|
||||
:type id: int
|
||||
|
||||
:raises RuntimeError: If `id` is not found or multiple definitions exist for `id`.
|
||||
|
||||
:rtype: entity_instance
|
||||
"""
|
||||
ns = self.data_.get(id, [])
|
||||
if len(ns) == 0:
|
||||
raise RuntimeError(f"Instance with id {id} not found")
|
||||
elif len(ns) > 1:
|
||||
raise RuntimeError(f"Duplicate definition for id {id}")
|
||||
return ns[0]
|
||||
|
||||
def by_type(self, type: str) -> list[entity_instance]:
|
||||
"""Return IFC objects filtered by IFC Type and wrapped with the entity_instance class.
|
||||
:rtype: list[entity_instance]
|
||||
"""
|
||||
type_lc = type.lower()
|
||||
return list(
|
||||
filter(
|
||||
lambda ent: ent.type.lower() == type_lc,
|
||||
itertools.chain.from_iterable(self.data_.values()),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def open(fn, only_header=False) -> file:
|
||||
return file(parse(filename=fn, only_header=only_header))
|
||||
@@ -0,0 +1,125 @@
|
||||
from collections import namedtuple
|
||||
|
||||
grammar = r"""
|
||||
file: "ISO-10303-21;" header data_section "END-ISO-10303-21;"
|
||||
header: "HEADER" ";" header_entity_list "ENDSEC" ";"
|
||||
header_line: (SPECIAL|DIGIT|LOWER|UPPER)* "*"
|
||||
data_section: "DATA" ";" (entity_instance)* "ENDSEC" ";"
|
||||
entity_instance: simple_entity_instance|complex_entity_instance
|
||||
simple_entity_instance: id "=" simple_record ";"
|
||||
complex_entity_instance: id "=" subsuper_record ";"
|
||||
subsuper_record : "(" simple_record_list ")"
|
||||
simple_record_list:simple_record simple_record*
|
||||
simple_record: keyword "("parameter_list?")"
|
||||
header_entity_list: file_description file_name file_schema
|
||||
file_description: "FILE_DESCRIPTION" "(" parameter_list ")" ";"
|
||||
file_name: "FILE_NAME" "(" parameter_list ")" ";"
|
||||
file_schema: "FILE_SCHEMA" "(" parameter_list ")" ";"
|
||||
id: /#[0-9]+/
|
||||
keyword: /[A-Z][0-9A-Z_]*/
|
||||
parameter: untyped_parameter|typed_parameter|omitted_parameter
|
||||
parameter_list: parameter ("," parameter)*
|
||||
list: "(" parameter ("," parameter)* ")" |"("")"
|
||||
typed_parameter: keyword "(" parameter ")"|"()"
|
||||
untyped_parameter: string| NONE |INT |REAL |enumeration |id |binary |list
|
||||
omitted_parameter:STAR
|
||||
enumeration: "." keyword "."
|
||||
binary: "\"" ("0"|"1"|"2"|"3") (HEX)* "\""
|
||||
string: "'" (REVERSE_SOLIDUS REVERSE_SOLIDUS|SPECIAL|DIGIT|SPACE|LOWER|UPPER|CONTROL_DIRECTIVE|"\\*\\")* "'"
|
||||
|
||||
STAR: "*"
|
||||
SLASH: "/"
|
||||
NONE: "$"
|
||||
SPECIAL : "!"
|
||||
| "*"
|
||||
| "$"
|
||||
| "%"
|
||||
| "&"
|
||||
| "."
|
||||
| "#"
|
||||
| "+"
|
||||
| ","
|
||||
| "-"
|
||||
| "("
|
||||
| ")"
|
||||
| "?"
|
||||
| "/"
|
||||
| ":"
|
||||
| ";"
|
||||
| "<"
|
||||
| "="
|
||||
| ">"
|
||||
| "@"
|
||||
| "["
|
||||
| "]"
|
||||
| "{"
|
||||
| "|"
|
||||
| "}"
|
||||
| "^"
|
||||
| "`"
|
||||
| "~"
|
||||
| "_"
|
||||
| "\""
|
||||
| "\"\""
|
||||
| "''"
|
||||
REAL: SIGN? DIGIT (DIGIT)* "." (DIGIT)* ("E" SIGN? DIGIT (DIGIT)* )?
|
||||
INT: SIGN? DIGIT (DIGIT)*
|
||||
CONTROL_DIRECTIVE: PAGE | ALPHABET | EXTENDED2 | EXTENDED4 | ARBITRARY
|
||||
PAGE : REVERSE_SOLIDUS "S" REVERSE_SOLIDUS LATIN_CODEPOINT
|
||||
LATIN_CODEPOINT : SPACE | DIGIT | LOWER | UPPER | SPECIAL | REVERSE_SOLIDUS | APOSTROPHE
|
||||
ALPHABET : REVERSE_SOLIDUS "P" UPPER REVERSE_SOLIDUS
|
||||
EXTENDED2: REVERSE_SOLIDUS "X2" REVERSE_SOLIDUS (HEX_TWO)* END_EXTENDED
|
||||
EXTENDED4 :REVERSE_SOLIDUS "X4" REVERSE_SOLIDUS (HEX_FOUR)* END_EXTENDED
|
||||
END_EXTENDED: REVERSE_SOLIDUS "X0" REVERSE_SOLIDUS
|
||||
ARBITRARY: REVERSE_SOLIDUS "X" REVERSE_SOLIDUS HEX_ONE
|
||||
HEX_FOUR: HEX_TWO HEX_TWO
|
||||
HEX_TWO: HEX_ONE HEX_ONE
|
||||
HEX_ONE: HEX HEX
|
||||
HEX: "0"
|
||||
| "1"
|
||||
| "2"
|
||||
| "3"
|
||||
| "4"
|
||||
| "5"
|
||||
| "6"
|
||||
| "7"
|
||||
| "8"
|
||||
| "9"
|
||||
| "A"
|
||||
| "B"
|
||||
| "C"
|
||||
| "D"
|
||||
| "E"
|
||||
| "F"
|
||||
APOSTROPHE: "'"
|
||||
REVERSE_SOLIDUS: "\\"
|
||||
DIGIT: "0".."9"
|
||||
SIGN: "+"|"-"
|
||||
LOWER: "a".."z"
|
||||
UPPER: "A".."Z"
|
||||
ESCAPE : "\\" ( "$" | "\"" | CHAR )
|
||||
CHAR : /[^$"\n]/
|
||||
WORD : CHAR+
|
||||
SPACE.10 : " "
|
||||
|
||||
%ignore /[ \t\f\r\n]/+
|
||||
"""
|
||||
|
||||
HEADER_FIELDS = {
|
||||
"file_description": namedtuple(
|
||||
"file_description", ["description", "implementation_level"]
|
||||
),
|
||||
"file_name": namedtuple(
|
||||
"file_name",
|
||||
[
|
||||
"name",
|
||||
"time_stamp",
|
||||
"author",
|
||||
"organization",
|
||||
"preprocessor_version",
|
||||
"originating_system",
|
||||
"authorization",
|
||||
],
|
||||
),
|
||||
"file_schema": namedtuple("file_schema", ["schema_identifiers"]),
|
||||
}
|
||||
@@ -0,0 +1,344 @@
|
||||
try:
|
||||
from lark import Lark, Transformer
|
||||
from lark.exceptions import UnexpectedCharacters, UnexpectedEOF, UnexpectedToken
|
||||
|
||||
LARK_AVAILABLE = True
|
||||
except ImportError:
|
||||
LARK_AVAILABLE = False
|
||||
|
||||
import re
|
||||
|
||||
from typing import Union
|
||||
|
||||
if LARK_AVAILABLE:
|
||||
mvd_grammar = r"""
|
||||
start: entry+
|
||||
|
||||
entry: "ViewDefinition" "[" simple_value_list "]" -> view_definition
|
||||
| "Comment" "[" simple_value_list "]" -> comment
|
||||
| "ExchangeRequirement" "[" other_keyword "]" -> exchangerequirement
|
||||
| "Option" "[" other_keyword "]" -> option
|
||||
| GENERIC_KEYWORD "[" dynamic_option_word "]" -> dynamic_option
|
||||
|
||||
GENERIC_KEYWORD: /[A-Za-z0-9_]+/
|
||||
|
||||
simple_value_list: value ("," value)*
|
||||
|
||||
value_list_set: value_set (";" value_set)*
|
||||
|
||||
value_set: set_name ":" simple_value_list
|
||||
|
||||
set_name: /[A-Za-z0-9_]+/
|
||||
|
||||
value: /[A-Za-z0-9 _\.-]+/
|
||||
|
||||
other_keyword: /[^\[\]]+/
|
||||
|
||||
dynamic_option_word: /[^\[\]]+/
|
||||
|
||||
%import common.WS
|
||||
%ignore WS
|
||||
"""
|
||||
|
||||
parser = Lark(mvd_grammar, parser="lalr")
|
||||
|
||||
class DescriptionTransform(Transformer):
|
||||
def __init__(self):
|
||||
self.view_definitions = []
|
||||
self.keywords = set()
|
||||
self.comments = ""
|
||||
self.exchange_requirements = ""
|
||||
self.options = ""
|
||||
self._dynamic = {}
|
||||
|
||||
def view_definition(self, args):
|
||||
self.keywords.add("view_definitions")
|
||||
self.view_definitions.extend(args[0])
|
||||
|
||||
def store_text_attribute(self, args, keyword):
|
||||
self.keywords.add(keyword)
|
||||
setattr(
|
||||
self,
|
||||
keyword,
|
||||
" ".join(" ".join(str(child) for child in args[0].children).split()),
|
||||
)
|
||||
|
||||
def comment(self, args):
|
||||
self.keywords.add("comments")
|
||||
self.comments = args[0] if len(args[0]) > 1 else args[0][0]
|
||||
|
||||
def exchangerequirement(self, args):
|
||||
self.store_text_attribute(args, "exchange_requirements")
|
||||
|
||||
def option(self, args):
|
||||
if v := parse_semicolon_separated_kv(
|
||||
" ".join(" ".join(str(child) for child in args[0].children).split())
|
||||
):
|
||||
setattr(self, "options", v)
|
||||
else:
|
||||
self.store_text_attribute(args, "options")
|
||||
|
||||
def dynamic_option(self, args):
|
||||
try:
|
||||
original_keyword = str(args[0])
|
||||
key = original_keyword.lower()
|
||||
raw_text = args[1].children[0].value
|
||||
parsed_value = parse_semicolon_separated_kv(raw_text)
|
||||
self._dynamic[key] = (parsed_value, original_keyword)
|
||||
self.keywords.add(key)
|
||||
setattr(self, key, parsed_value)
|
||||
except Exception:
|
||||
setattr(self, key, None)
|
||||
|
||||
def simple_value_list(self, args):
|
||||
return [str(arg) for arg in args]
|
||||
|
||||
def value_list_set(self, args):
|
||||
return args
|
||||
|
||||
def value_set(self, args):
|
||||
return [str(args[0])] + args[1]
|
||||
|
||||
def value(self, args):
|
||||
return str(args[0])
|
||||
|
||||
def set_name(self, args):
|
||||
return str(args[0])
|
||||
|
||||
def parse_mvd(description):
|
||||
text = " ".join(description)
|
||||
parsed_description = DescriptionTransform()
|
||||
try:
|
||||
if not text:
|
||||
parsed_description.view_definitions = None
|
||||
return parsed_description
|
||||
parse_tree = parser.parse(text)
|
||||
parsed_description.transform(parse_tree)
|
||||
except (UnexpectedCharacters, UnexpectedEOF, UnexpectedToken):
|
||||
parsed_description.view_definitions = None
|
||||
return parsed_description
|
||||
|
||||
def parse_semicolon_separated_kv(
|
||||
text: str,
|
||||
) -> Union[dict[str, Union[str, list[str]]], None]:
|
||||
if not re.search(r"\w+\s*:\s*[^:]+", text):
|
||||
return None
|
||||
result = {}
|
||||
try:
|
||||
pairs = text.split(";")
|
||||
for pair in pairs:
|
||||
if ":" in pair:
|
||||
key, value = pair.split(":", 1)
|
||||
key = key.strip()
|
||||
values = [v.strip() for v in value.split(",")]
|
||||
result[key] = values[0] if len(values) == 1 else values
|
||||
return result
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
else:
|
||||
|
||||
def parse_mvd(description):
|
||||
return None
|
||||
|
||||
|
||||
class MvdInfo:
|
||||
def __init__(self, header):
|
||||
self._header = header
|
||||
self._parsed = None
|
||||
|
||||
def _ensure_parsed(self):
|
||||
if not LARK_AVAILABLE:
|
||||
return
|
||||
if self._parsed is None:
|
||||
description = self._header.file_description.description
|
||||
if not description:
|
||||
self._parsed = DescriptionTransform() # avoid AttributeError
|
||||
else:
|
||||
self._parsed = parse_mvd(description)
|
||||
|
||||
@property
|
||||
def description(self) -> list[str]:
|
||||
return self._header.file_description.description
|
||||
|
||||
@description.setter
|
||||
def description(self, new_description: list[str]):
|
||||
self._header.file_description.description = tuple(new_description)
|
||||
self._parsed = None
|
||||
|
||||
@property
|
||||
def view_definitions(self):
|
||||
self._ensure_parsed()
|
||||
if not self._parsed or self._parsed.view_definitions is None:
|
||||
return None #
|
||||
|
||||
vd = self._parsed.view_definitions
|
||||
vd_list = vd if isinstance(vd, list) else [vd] if vd else []
|
||||
return AutoCommitList(
|
||||
vd_list,
|
||||
callback=lambda val: (
|
||||
self._update_keyword("ViewDefinition", val),
|
||||
setattr(self, "_parsed", None),
|
||||
),
|
||||
formatter=lambda lst: ",".join(str(i) for i in lst),
|
||||
)
|
||||
|
||||
@view_definitions.setter
|
||||
def view_definitions(self, new_value: Union[str, list[str]]):
|
||||
if isinstance(new_value, list):
|
||||
value = ", ".join(new_value)
|
||||
else:
|
||||
value = str(new_value)
|
||||
self._update_keyword("ViewDefinition", value)
|
||||
|
||||
@property
|
||||
def comments(self):
|
||||
self._ensure_parsed()
|
||||
comments = self._parsed.comments
|
||||
comment_list = (
|
||||
comments if isinstance(comments, list) else [comments] if comments else []
|
||||
)
|
||||
return AutoCommitList(
|
||||
comment_list,
|
||||
callback=lambda val: self._update_keyword("Comment", val),
|
||||
formatter=lambda lst: ", ".join(str(i) for i in lst),
|
||||
)
|
||||
|
||||
@comments.setter
|
||||
def comments(self, new_value: Union[str, list[str]]):
|
||||
if isinstance(new_value, list):
|
||||
value = ", ".join(new_value)
|
||||
else:
|
||||
value = str(new_value)
|
||||
self._update_keyword("Comment", value)
|
||||
|
||||
@property
|
||||
def exchange_requirements(self):
|
||||
self._ensure_parsed()
|
||||
return self._parsed.exchange_requirements if self._parsed else None
|
||||
|
||||
@exchange_requirements.setter
|
||||
def exchange_requirements(self, new_value: str):
|
||||
self._update_keyword("ExchangeRequirement", new_value)
|
||||
|
||||
@property
|
||||
def options(self):
|
||||
self._ensure_parsed()
|
||||
if isinstance(self._parsed.options, dict):
|
||||
return DictionaryHandler(self._parsed.options, self, "Option")
|
||||
return self._parsed.options if self._parsed else None
|
||||
|
||||
@options.setter
|
||||
def options(self, new_value: str):
|
||||
self._update_keyword("Option", new_value)
|
||||
|
||||
@property
|
||||
def keywords(self):
|
||||
self._ensure_parsed()
|
||||
return self._parsed.keywords if self._parsed else set()
|
||||
|
||||
def _update_keyword(self, keyword: str, new_value: str):
|
||||
updated = False
|
||||
new_line = f"{keyword} [{new_value}]"
|
||||
lines = []
|
||||
for line in self.description:
|
||||
if line.strip().startswith(f"{keyword} ["):
|
||||
lines.append(new_line)
|
||||
updated = True
|
||||
else:
|
||||
lines.append(line)
|
||||
if not updated:
|
||||
lines.append(new_line)
|
||||
self.description = lines
|
||||
|
||||
def __getattr__(self, name):
|
||||
self._ensure_parsed()
|
||||
if hasattr(self._parsed, "_dynamic"):
|
||||
name_lc = name.lower()
|
||||
if name_lc in self._parsed._dynamic:
|
||||
value, original_keyword = self._parsed._dynamic[name_lc]
|
||||
return DictionaryHandler(value, self, original_keyword)
|
||||
raise AttributeError(f"'MvdInfo' object has no attribute '{name}'")
|
||||
|
||||
def __dir__(self):
|
||||
base = super().__dir__()
|
||||
if self._parsed and hasattr(self._parsed, "_dynamic"):
|
||||
return base + [kw for _, kw in self._parsed._dynamic.values()]
|
||||
return base
|
||||
|
||||
|
||||
class DictionaryHandler(dict):
|
||||
def __init__(self, initial_data, mvdinfo, keyword):
|
||||
super().__init__()
|
||||
self._mvdinfo = mvdinfo
|
||||
self._keyword = keyword
|
||||
for k, v in initial_data.items():
|
||||
if isinstance(v, list):
|
||||
super().__setitem__(k, AutoCommitList(v, self._commit))
|
||||
else:
|
||||
super().__setitem__(k, v)
|
||||
|
||||
def _commit(self):
|
||||
new_value = "; ".join(
|
||||
f"{k}: {', '.join(v) if isinstance(v, list) else v}"
|
||||
for k, v in self.items()
|
||||
)
|
||||
self._mvdinfo._update_keyword(self._keyword, new_value)
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
if isinstance(value, list):
|
||||
value = AutoCommitList(value, self._commit)
|
||||
super().__setitem__(key, value)
|
||||
self._commit()
|
||||
|
||||
def __delitem__(self, key):
|
||||
super().__delitem__(key)
|
||||
self._commit()
|
||||
|
||||
|
||||
class AutoCommitList(list):
|
||||
"ensures keyword attributes are written back to ifcopenshell.file.header"
|
||||
|
||||
def __init__(self, iterable, callback, formatter=None):
|
||||
super().__init__(iterable)
|
||||
self._callback = callback
|
||||
self._formatter = formatter
|
||||
|
||||
def _commit(self):
|
||||
if self._formatter:
|
||||
self._callback(self._formatter(self))
|
||||
else:
|
||||
self._callback()
|
||||
|
||||
def append(self, item):
|
||||
super().append(item)
|
||||
self._commit()
|
||||
|
||||
def extend(self, iterable):
|
||||
super().extend(iterable)
|
||||
self._commit()
|
||||
|
||||
def insert(self, index, item):
|
||||
super().insert(index, item)
|
||||
self._commit()
|
||||
|
||||
def remove(self, item):
|
||||
super().remove(item)
|
||||
self._commit()
|
||||
|
||||
def pop(self, index=-1):
|
||||
item = super().pop(index)
|
||||
self._commit()
|
||||
return item
|
||||
|
||||
def clear(self):
|
||||
super().clear()
|
||||
self._commit()
|
||||
|
||||
def __setitem__(self, index, value):
|
||||
super().__setitem__(index, value)
|
||||
self._commit()
|
||||
|
||||
def __delitem__(self, index):
|
||||
super().__delitem__(index)
|
||||
self._commit()
|
||||
@@ -0,0 +1,184 @@
|
||||
from dataclasses import dataclass
|
||||
from collections import defaultdict
|
||||
import re
|
||||
import sys
|
||||
import builtins
|
||||
from lark import Lark, UnexpectedCharacters, UnexpectedToken
|
||||
|
||||
# import transformer
|
||||
from .transformer import (
|
||||
Transformer,
|
||||
entity_instance,
|
||||
make_header_ent,
|
||||
create_step_entity,
|
||||
)
|
||||
from .grammar import grammar, HEADER_FIELDS
|
||||
from .errors import (
|
||||
HeaderFieldError,
|
||||
DuplicateNameError,
|
||||
ErrorCollector,
|
||||
SyntaxError,
|
||||
InvalidNameError,
|
||||
)
|
||||
|
||||
|
||||
def validate_header_fields(header, error_collector, only_header=False):
|
||||
for field in HEADER_FIELDS.keys():
|
||||
observed = header.get(field.upper(), [])
|
||||
expected = HEADER_FIELDS.get(field)._fields
|
||||
if len(observed) != len(expected):
|
||||
error_collector.add(
|
||||
HeaderFieldError(field.upper(), len(observed), len(expected))
|
||||
)
|
||||
if only_header:
|
||||
error_collector.raise_if_any()
|
||||
|
||||
|
||||
@dataclass
|
||||
class ParseResult:
|
||||
header: dict
|
||||
entities: dict[int, list[entity_instance]]
|
||||
|
||||
|
||||
def process_tree(filecontent, file_tree, with_progress, error_collector):
|
||||
ents = defaultdict(list)
|
||||
header, data = file_tree.children
|
||||
|
||||
header = dict(map(make_header_ent, header.children[0].children))
|
||||
validate_header_fields(header, error_collector)
|
||||
|
||||
n = len(data.children)
|
||||
if n:
|
||||
percentages = [i * 100.0 / n for i in range(n + 1)]
|
||||
num_dots = [int(b) - int(a) for a, b in zip(percentages, percentages[1:])]
|
||||
|
||||
for idx, entity_tree in enumerate(data.children):
|
||||
if with_progress:
|
||||
sys.stdout.write(num_dots[idx] * ".")
|
||||
sys.stdout.flush()
|
||||
ent = create_step_entity(entity_tree)
|
||||
id_ = int(ent["id"])
|
||||
if id_ == 0:
|
||||
error_collector.add(InvalidNameError(filecontent, ent["id"], ent["lines"]))
|
||||
if ents[id_]:
|
||||
error_collector.add(
|
||||
DuplicateNameError(filecontent, ent["id"], ent["lines"])
|
||||
)
|
||||
else:
|
||||
ents[id_].append(ent)
|
||||
|
||||
return header, ents
|
||||
|
||||
|
||||
def parse(
|
||||
*,
|
||||
filename=None,
|
||||
filecontent=None,
|
||||
with_progress=False,
|
||||
with_tree=True,
|
||||
only_header=False,
|
||||
) -> ParseResult:
|
||||
error_collector = ErrorCollector()
|
||||
if filename:
|
||||
assert not filecontent
|
||||
filecontent = builtins.open(filename, encoding=None).read()
|
||||
|
||||
# Match and remove the comments
|
||||
p = r"/\*[\s\S]*?\*/"
|
||||
|
||||
def replace_fn(match):
|
||||
return re.sub(r"[^\n]", " ", match.group(), flags=re.M)
|
||||
|
||||
filecontent_wo_comments = re.sub(p, replace_fn, filecontent)
|
||||
|
||||
if only_header:
|
||||
# Extract just the HEADER section using regex
|
||||
header_match = re.search(
|
||||
r"ISO-10303-21;\s*HEADER;(.*?)ENDSEC;",
|
||||
filecontent_wo_comments,
|
||||
flags=re.DOTALL | re.IGNORECASE,
|
||||
)
|
||||
if not header_match:
|
||||
error_collector.add(
|
||||
HeaderFieldError("header", "", "No HEADER section found in file")
|
||||
)
|
||||
error_collector.raise_if_any()
|
||||
|
||||
header_text = f"HEADER;{header_match.group(1)}ENDSEC;"
|
||||
full_header_text = f"ISO-10303-21;{header_text}DATA;ENDSEC;END-ISO-10303-21;"
|
||||
|
||||
parser = Lark(grammar, parser="lalr", start="file")
|
||||
try:
|
||||
ast = parser.parse(full_header_text)
|
||||
except (UnexpectedToken, UnexpectedCharacters) as e:
|
||||
error_collector.add(SyntaxError(filecontent, e))
|
||||
error_collector.raise_if_any() # Immediately abort in case of critical error
|
||||
|
||||
header_tree = ast.children[0] # HEADER section
|
||||
|
||||
header = dict(map(make_header_ent, header_tree.children[0].children))
|
||||
validate_header_fields(header, error_collector, only_header=True)
|
||||
error_collector.raise_if_any()
|
||||
return ParseResult(header=header, entities=defaultdict(list))
|
||||
|
||||
instance_identifiers = []
|
||||
transformer = {}
|
||||
if not with_tree:
|
||||
# If we're not going to return the tree, we also don't need to
|
||||
# keep in memory while parsing. So we build a transformer that
|
||||
# just returns None for every rule. lark creates a dictionary
|
||||
# of callbacks from the transformer type object, so we can't
|
||||
# simply use __getattr__ we need an actual type objects with
|
||||
# callback functions for the rules given in the
|
||||
|
||||
# Create a temporary parser just for analysing the grammar
|
||||
temp = Lark(grammar, parser="lalr", start="file")
|
||||
# Extract the rule names
|
||||
rule_names = filter(
|
||||
lambda s: not s.startswith("_"), set(r.origin.name for r in temp.rules)
|
||||
)
|
||||
null_function = lambda self, *args: None
|
||||
# Create dictionary of methods for type() creation
|
||||
methods = {r: null_function for r in rule_names}
|
||||
|
||||
# Even in this case we do want to report duplicate identifiers
|
||||
# so these need to be captured
|
||||
methods["id"] = lambda self, *args: args
|
||||
methods["simple_entity_instance"] = (
|
||||
lambda self, tree: instance_identifiers.append(
|
||||
(int(tree[0][0][0][1:]), int(tree[0][0][0].line))
|
||||
)
|
||||
)
|
||||
|
||||
NT = type("NullTransformer", (Transformer,), methods)
|
||||
transformer = {"transformer": NT()}
|
||||
|
||||
parser = Lark(grammar, parser="lalr", start="file", **transformer)
|
||||
|
||||
try:
|
||||
ast = parser.parse(filecontent_wo_comments)
|
||||
except (UnexpectedToken, UnexpectedCharacters) as e:
|
||||
error_collector.add(SyntaxError(filecontent, e))
|
||||
error_collector.raise_if_any() # Immediately abort in case of critical error
|
||||
|
||||
if with_tree:
|
||||
header, data = process_tree(filecontent, ast, with_progress, error_collector)
|
||||
error_collector.raise_if_any()
|
||||
return ParseResult(header=header, entities=data)
|
||||
else:
|
||||
# process_tree() would take care of duplicate identifiers,
|
||||
# but we need to do it ourselves now using our rudimentary
|
||||
# transformer
|
||||
seen = set()
|
||||
for iden, lineno in instance_identifiers:
|
||||
if iden == 0:
|
||||
error_collector.add(
|
||||
InvalidNameError(filecontent, iden, [lineno, lineno])
|
||||
)
|
||||
if iden in seen:
|
||||
error_collector.add(
|
||||
DuplicateNameError(filecontent, iden, [lineno, lineno])
|
||||
)
|
||||
else:
|
||||
seen.add(iden)
|
||||
error_collector.raise_if_any()
|
||||
@@ -0,0 +1,107 @@
|
||||
from lark import Transformer
|
||||
from dataclasses import dataclass
|
||||
import numbers
|
||||
from lark import Lark, Transformer, Tree, Token
|
||||
|
||||
|
||||
class IfcType:
|
||||
def __init__(self, ifctype, value):
|
||||
self.ifctype = ifctype
|
||||
self.value = value
|
||||
|
||||
def __str__(self):
|
||||
return self.ifctype + "(" + str(self.value) + ")"
|
||||
|
||||
__repr__ = __str__
|
||||
|
||||
|
||||
@dataclass
|
||||
class entity_instance:
|
||||
id: int
|
||||
type: str
|
||||
attributes: tuple
|
||||
lines: tuple
|
||||
|
||||
def __getitem__(self, k):
|
||||
if isinstance(k, numbers.Integral):
|
||||
return self.attributes[k]
|
||||
else:
|
||||
# compatibility with dict
|
||||
return getattr(self, k)
|
||||
|
||||
def __repr__(self):
|
||||
return f'#{self.id}={self.type}({",".join(map(str, self.attributes))})'
|
||||
|
||||
|
||||
class T(Transformer):
|
||||
def id(self, s):
|
||||
return int(s[0][1:])
|
||||
|
||||
def string(self, s):
|
||||
word = "".join(s).replace("''", "'")
|
||||
return word
|
||||
|
||||
def keyword(self, s):
|
||||
word = "".join(s)
|
||||
return word
|
||||
|
||||
def untyped_parameter(self, s):
|
||||
return s[0]
|
||||
|
||||
def parameter(self, s):
|
||||
return s[0]
|
||||
|
||||
def typed_parameter(self, s):
|
||||
if len(s):
|
||||
return IfcType(s[0], s[1])
|
||||
else:
|
||||
return ()
|
||||
|
||||
def omitted_parameter(self, s):
|
||||
return s[0]
|
||||
|
||||
def enumeration(self, s):
|
||||
return s[0]
|
||||
|
||||
parameter_list = tuple
|
||||
list = tuple
|
||||
subsuper_record = list
|
||||
INT = int
|
||||
REAL = float
|
||||
NONE = lambda *args: None
|
||||
STAR = str
|
||||
|
||||
|
||||
def create_step_entity(entity_tree):
|
||||
t = T(visit_tokens=True).transform(entity_tree)
|
||||
|
||||
def get_line_number(t):
|
||||
if isinstance(t, Token):
|
||||
yield t.line
|
||||
|
||||
def traverse(fn, x):
|
||||
yield from fn(x)
|
||||
if isinstance(x, Tree):
|
||||
for c in x.children:
|
||||
yield from traverse(fn, c)
|
||||
|
||||
lines = list(traverse(get_line_number, entity_tree))
|
||||
|
||||
entity_id = t.children[0].children[0]
|
||||
entity_type = t.children[0].children[1].children[0]
|
||||
|
||||
attributes_tree = t.children[0].children[1].children[1]
|
||||
attributes = list(attributes_tree)
|
||||
|
||||
return entity_instance(
|
||||
entity_id,
|
||||
entity_type,
|
||||
attributes,
|
||||
(min(lines), max(lines)),
|
||||
)
|
||||
|
||||
|
||||
def make_header_ent(ast):
|
||||
rule = ast.data
|
||||
params = T(visit_tokens=True).transform(ast.children[0])
|
||||
return rule.upper(), params
|
||||
@@ -0,0 +1 @@
|
||||
lark-parser
|
||||
@@ -0,0 +1,206 @@
|
||||
import glob
|
||||
import pytest
|
||||
|
||||
try:
|
||||
from .__init__ import (
|
||||
parse,
|
||||
open,
|
||||
_ValidationError,
|
||||
CollectedValidationErrors,
|
||||
DuplicateNameError,
|
||||
HeaderFieldError,
|
||||
)
|
||||
except:
|
||||
from __init__ import (
|
||||
parse,
|
||||
open,
|
||||
_ValidationError,
|
||||
CollectedValidationErrors,
|
||||
DuplicateNameError,
|
||||
HeaderFieldError,
|
||||
)
|
||||
|
||||
from contextlib import nullcontext
|
||||
|
||||
|
||||
def create_context(fn):
|
||||
if "fail_" in fn:
|
||||
return pytest.raises(_ValidationError)
|
||||
else:
|
||||
return nullcontext()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("file", glob.glob("fixtures/*.ifc"))
|
||||
def test_file_with_tree(file):
|
||||
with create_context(file):
|
||||
parse(filename=file, with_tree=True)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("file", glob.glob("fixtures/*.ifc"))
|
||||
def test_file_without_tree(file):
|
||||
if any(
|
||||
sub in file
|
||||
for sub in [
|
||||
"fail_too_many_header_entity_fields.ifc",
|
||||
"fail_multiple_wrong_header_fields",
|
||||
]
|
||||
):
|
||||
pytest.skip(
|
||||
"This file relies on header field validation using the parsed AST, "
|
||||
"but with_tree=False uses a NullTransformer that discards the AST, "
|
||||
"so validating the header field names is not possible in this mode."
|
||||
)
|
||||
with create_context(file):
|
||||
parse(filename=file, with_tree=False)
|
||||
|
||||
|
||||
def test_parse_features():
|
||||
f = open("fixtures/pass_1.ifc")
|
||||
assert f.by_id(1).id == 1
|
||||
assert f.by_id(1).type == "IFCPERSON"
|
||||
assert f.data_[1][0].type == "IFCPERSON"
|
||||
assert f.by_type("ifcperson")[0].id == 1
|
||||
assert f[1][0] is None
|
||||
assert f.header.file_description[0][0] == "ViewDefinition [CoordinationView]"
|
||||
assert f.header_.get("FILE_DESCRIPTION")[0][0]
|
||||
assert f.by_type("ifcapplication")[1][2] == "Nested ' quotes"
|
||||
|
||||
|
||||
def test_parse_valid_header():
|
||||
f = open("fixtures/passing_header.ifc")
|
||||
|
||||
expected_description = {
|
||||
"description": ("ViewDefinition [Alignment-basedView]",),
|
||||
"implementation_level": "2;1",
|
||||
}
|
||||
|
||||
expected_name = {
|
||||
"name": "Header example2.ifc",
|
||||
"time_stamp": "2022-09-16T10:35:07",
|
||||
"author": ("Evandro Alfieri",),
|
||||
"organization": ("buildingSMART Int.",),
|
||||
"preprocessor_version": "IFC Motor 1.0",
|
||||
"originating_system": "Company - Application - 26.0.0.0",
|
||||
"authorization": "none",
|
||||
}
|
||||
|
||||
expected_schema = {
|
||||
"schema_identifiers": ("IFC4X3_ADD2",),
|
||||
}
|
||||
|
||||
for key, val in expected_description.items():
|
||||
assert getattr(f.header.file_description, key) == val, f"{key} mismatch"
|
||||
|
||||
for key, val in expected_name.items():
|
||||
assert getattr(f.header.file_name, key) == val, f"{key} mismatch"
|
||||
|
||||
for key, val in expected_schema.items():
|
||||
assert getattr(f.header.file_schema, key) == val, f"{key} mismatch"
|
||||
|
||||
|
||||
def test_header_only_api():
|
||||
f = open("fixtures/passing_header.ifc", only_header=True)
|
||||
expected_description = {
|
||||
"description": ("ViewDefinition [Alignment-basedView]",),
|
||||
"implementation_level": "2;1",
|
||||
}
|
||||
|
||||
expected_name = {
|
||||
"name": "Header example2.ifc",
|
||||
"time_stamp": "2022-09-16T10:35:07",
|
||||
"author": ("Evandro Alfieri",),
|
||||
"organization": ("buildingSMART Int.",),
|
||||
"preprocessor_version": "IFC Motor 1.0",
|
||||
"originating_system": "Company - Application - 26.0.0.0",
|
||||
"authorization": "none",
|
||||
}
|
||||
|
||||
expected_schema = {
|
||||
"schema_identifiers": ("IFC4X3_ADD2",),
|
||||
}
|
||||
|
||||
for key, val in expected_description.items():
|
||||
assert getattr(f.header.file_description, key) == val, f"{key} mismatch"
|
||||
|
||||
for key, val in expected_name.items():
|
||||
assert getattr(f.header.file_name, key) == val, f"{key} mismatch"
|
||||
|
||||
for key, val in expected_schema.items():
|
||||
assert getattr(f.header.file_schema, key) == val, f"{key} mismatch"
|
||||
|
||||
|
||||
def test_file_mvd_attr():
|
||||
f = open("fixtures/extended_mvd.ifc", only_header=True)
|
||||
|
||||
assert "ReferenceView_V1.2" in f.mvd.view_definitions
|
||||
assert all(
|
||||
i in f.mvd.keywords
|
||||
for i in ["exchange_requirements", "view_definitions", "remark", "comments"]
|
||||
)
|
||||
assert "Ramp" in f.mvd.options["ExcludedObjects"]
|
||||
assert f.mvd.Remark["SomeKey"] == "SomeValue"
|
||||
assert len(f.mvd.comments) == 2
|
||||
assert all(
|
||||
v in vars(f.header).keys()
|
||||
for v in ["file_description", "file_name", "file_schema"]
|
||||
)
|
||||
assert len(f.header.file_name) == 7
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"filename",
|
||||
[
|
||||
"fixtures/fail_invalid_header_entity.ifc",
|
||||
"fixtures/fail_no_header.ifc",
|
||||
],
|
||||
)
|
||||
def test_invalid_headers_(filename):
|
||||
# error in header
|
||||
with pytest.raises(_ValidationError):
|
||||
parse(filename=filename, with_tree=False, only_header=True)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"filename",
|
||||
[
|
||||
"fixtures/fail_duplicate_id.ifc",
|
||||
"fixtures/fail_double_comma.ifc",
|
||||
"fixtures/fail_double_semi.ifc",
|
||||
],
|
||||
)
|
||||
def test_valid_headers(filename):
|
||||
# error in body
|
||||
with nullcontext():
|
||||
parse(filename=filename, with_tree=False, only_header=True)
|
||||
|
||||
|
||||
def test_header_entity_fields():
|
||||
with pytest.raises(_ValidationError):
|
||||
parse(
|
||||
filename="fixtures/fail_too_many_header_entity_fields.ifc", only_header=True
|
||||
)
|
||||
|
||||
|
||||
def test_header_entity_fields_whole_file():
|
||||
with pytest.raises(_ValidationError):
|
||||
parse(filename="fixtures/fail_too_many_header_entity_fields.ifc")
|
||||
|
||||
|
||||
def test_header_entity_fields_whole_file():
|
||||
with pytest.raises(CollectedValidationErrors) as exc_info:
|
||||
parse(filename="fixtures/fail_multiple_duplicate_ids.ifc")
|
||||
|
||||
errors = exc_info.value.errors
|
||||
|
||||
assert len(errors) == 2
|
||||
assert all(isinstance(e, DuplicateNameError) for e in errors)
|
||||
|
||||
|
||||
def test_multiple_wrong_header_fields():
|
||||
with pytest.raises(CollectedValidationErrors) as exc_info:
|
||||
parse(filename="fixtures/fail_multiple_wrong_header_fields.ifc")
|
||||
|
||||
errors = exc_info.value.errors
|
||||
|
||||
assert len(errors) == 2
|
||||
assert all(isinstance(e, HeaderFieldError) for e in errors)
|
||||
Reference in New Issue
Block a user