Added test submodule

This commit is contained in:
johltn
2020-10-25 10:45:43 +01:00
80 changed files with 2591 additions and 56463 deletions
+8 -8
View File
@@ -1,7 +1,7 @@
language: cpp
compiler: gcc
os: linux
dist: xenial
dist: bionic
sudo: required
before_install:
@@ -17,11 +17,11 @@ install:
build-essential cmake python2.7 libpython2.7-dev swig zlib1g liblzma5 opencollada-dev wget apt-transport-https
- sudo mkdir -p /usr/include/json/nlohmann/
- sudo wget https://github.com/nlohmann/json/releases/download/v3.6.1/json.hpp -O /usr/include/json/nlohmann/json.hpp
- sudo sh -c 'curl https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add -'
- sudo sh -c 'curl https://storage.googleapis.com/download.dartlang.org/linux/debian/dart_stable.list > /etc/apt/sources.list.d/dart_stable.list'
- sudo apt-get update -qq && sudo apt-get install -y dart
- export PATH=$PATH:/usr/lib/dart/bin:$HOME/.pub-cache/bin
- git clone https://github.com/KhronosGroup/glTF-Validator && pushd glTF-Validator && pub get && pub global activate --source path ./ && popd
# - sudo sh -c 'curl https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add -'
# - sudo sh -c 'curl https://storage.googleapis.com/download.dartlang.org/linux/debian/dart_stable.list > /etc/apt/sources.list.d/dart_stable.list'
# - sudo apt-get update -qq && sudo apt-get install -y dart
# - export PATH=$PATH:/usr/lib/dart/bin:$HOME/.pub-cache/bin
# - git clone https://github.com/KhronosGroup/glTF-Validator && pushd glTF-Validator && pub get && pub global activate --source path ./ && popd
script:
- pwd
@@ -36,7 +36,7 @@ script:
-DPYTHON_INCLUDE_DIR=/usr/include/python2.7 \
-DPYTHON_EXECUTABLE=/usr/bin/python2.7 \
-DLIBXML2_INCLUDE_DIR=/usr/include/libxml2 \
-DLIBXML2_LIBRARIES="/usr/lib/x86_64-linux-gnu/libxml2.so;/lib/x86_64-linux-gnu/libz.so.1;/lib/x86_64-linux-gnu/liblzma.so.5;/usr/lib/x86_64-linux-gnu/libicuuc.so.55;/usr/lib/x86_64-linux-gnu/libicudata.so.55" \
-DLIBXML2_LIBRARIES="/usr/lib/x86_64-linux-gnu/libxml2.so;/lib/x86_64-linux-gnu/libz.so.1;/lib/x86_64-linux-gnu/liblzma.so.5;/usr/lib/x86_64-linux-gnu/libicuuc.so.60;/usr/lib/x86_64-linux-gnu/libicudata.so.60" \
-DGLTF_SUPPORT=On \
-DJSON_INCLUDE_DIR=/usr/include/json \
..
@@ -48,7 +48,7 @@ script:
- cd input
- /usr/local/bin/IfcConvert -yv acad2010_walls.ifc acad2010_walls.glb
- gltf_validator acad2010_walls.glb
# - gltf_validator acad2010_walls.glb
- /usr/bin/python2.7 -c "from __future__ import print_function; from io import open; import ifcopenshell; f = ifcopenshell.open('encoding.ifc'); assert list(map(ord, f[1][0])) == [39, 97, 39, 32, 49, 109, 179, 32, 8804, 32, 53, 109, 179, 32, 8805, 32, 49, 48, 109, 179]"
+1 -1
View File
@@ -240,7 +240,7 @@ ENDIF()
SET(OPENCASCADE_LIBRARY_NAMES
TKernel TKMath TKBRep TKGeomBase TKGeomAlgo TKG3d TKG2d TKShHealing TKTopAlgo TKMesh TKPrim TKBool TKBO
TKFillet TKSTEP TKSTEPBase TKSTEPAttr TKXSBase TKSTEP209 TKIGES TKOffset
TKFillet TKSTEP TKSTEPBase TKSTEPAttr TKXSBase TKSTEP209 TKIGES TKOffset TKHLR
)
IF("${OCC_LIBRARY_DIR}" STREQUAL "")
+57 -33
View File
@@ -78,7 +78,6 @@ class CA2IFC:
ifcElements[i] = self.f.createIfcStructuralCurveMember(self.guid(), ownerHistory, el['name'], None, None, localPlacement, prodDefShape, el['predefinedType'], localZAxis)
if el['geometryType'] == 'surface':
# element
ifcElements[i] = self.f.createIfcStructuralSurfaceMember(self.guid(), ownerHistory, el['name'], None, None, localPlacement, prodDefShape, el['predefinedType'], el['thickness'])
# create structural point connections
@@ -86,19 +85,34 @@ class CA2IFC:
for i,conn in enumerate(self.data['connections']):
# geometry - product definition shape
prodDefShape = self.create_geometry(conn)
# local axes
localAxes = self.create_orientation(conn['orientation'])
# boundary conditions
if conn['appliedCondition']:
bc = self.create_node_applied_conditions(conn['appliedCondition'])
appliedCondition = self.f.createIfcBoundaryNodeCondition(None, bc['dx'], bc['dy'], bc['dz'], bc['drx'], bc['dry'], bc['drz'])
bc = self.create_applied_conditions(conn['appliedCondition'], conn['geometryType'])
if conn['geometryType'] == 'point':
appliedCondition = self.f.createIfcBoundaryNodeCondition(None, bc['dx'], bc['dy'], bc['dz'], bc['drx'], bc['dry'], bc['drz'])
if conn['geometryType'] == 'line':
appliedCondition = self.f.createIfcBoundaryEdgeCondition(None, bc['dx'], bc['dy'], bc['dz'], bc['drx'], bc['dry'], bc['drz'])
if conn['geometryType'] == 'surface':
appliedCondition = self.f.createIfcBoundaryFaceCondition(None, bc['dx'], bc['dy'], bc['dz'])
else:
appliedCondition = None
# connection
if conn['geometryType'] == 'point':
# local axes
localAxes = self.create_orientation(conn['orientation'])
# connection
ifcConnections[i] = self.f.createIfcStructuralPointConnection(self.guid(), ownerHistory, conn['name'], None, None, localPlacement, prodDefShape, appliedCondition, localAxes)
if conn['geometryType'] == 'line':
# z axis TODO: group by elements
localZAxis = self.f.createIfcDirection(tuple(conn['orientation'][2]))
# connection
ifcConnections[i] = self.f.createIfcStructuralCurveConnection(self.guid(), ownerHistory, conn['name'], None, None, localPlacement, prodDefShape, appliedCondition, localZAxis)
if conn['geometryType'] == 'surface':
ifcConnections[i] = self.f.createIfcStructuralSurfaceConnection(self.guid(), ownerHistory, conn['name'], None, None, localPlacement, prodDefShape, appliedCondition)
# assign material-profile-sets
for i,mpSet in enumerate(mpSets):
groupOfElements = []
@@ -121,20 +135,34 @@ class CA2IFC:
# create connections with elements
for i,el in enumerate(self.data['elements']):
for conn in el['connections']:
localAxes = self.create_orientation(conn['orientation'])
j = [c['ifcName'] for c in self.data['connections']].index(conn['relatedConnection'])
geometryType = self.data['connections'][j]['geometryType']
if conn['appliedCondition']:
bc = self.create_node_applied_conditions(conn['appliedCondition'])
appliedCondition = self.f.createIfcBoundaryNodeCondition(None, bc['dx'], bc['dy'], bc['dz'], bc['drx'], bc['dry'], bc['drz'])
bc = self.create_applied_conditions(conn['appliedCondition'], geometryType)
if geometryType == 'point':
appliedCondition = self.f.createIfcBoundaryNodeCondition(None, bc['dx'], bc['dy'], bc['dz'], bc['drx'], bc['dry'], bc['drz'])
if geometryType == 'line':
appliedCondition = self.f.createIfcBoundaryEdgeCondition(None, bc['dx'], bc['dy'], bc['dz'], bc['drx'], bc['dry'], bc['drz'])
if geometryType == 'surface':
appliedCondition = self.f.createIfcBoundaryFaceCondition(None, bc['dx'], bc['dy'], bc['dz'])
else:
appliedCondition = None
j = [c['ifcName'] for c in self.data['connections']].index(conn['relatedConnection'])
if not conn['eccentricity']:
# local axes
localAxes = self.create_orientation(conn['orientation'])
if geometryType == 'point':
if not conn['eccentricity']:
self.f.createIfcRelConnectsStructuralMember(self.guid(), ownerHistory, None, None, ifcElements[i], ifcConnections[j], appliedCondition, None, None, localAxes)
else:
pointOnElement = self.f.createIfcCartesianPoint(tuple(conn['eccentricity']['pointOnElement']))
vector = conn['eccentricity']['vector']
connPointEcc = self.f.createIfcConnectionPointEccentricity(pointOnElement, None, vector[0], vector[1], vector[2])
self.f.createIfcRelConnectsWithEccentricity(self.guid(), ownerHistory, None, None, ifcElements[i], ifcConnections[j], appliedCondition, None, None, localAxes, connPointEcc)
if geometryType in ['line', 'surface']:
self.f.createIfcRelConnectsStructuralMember(self.guid(), ownerHistory, None, None, ifcElements[i], ifcConnections[j], appliedCondition, None, None, localAxes)
else:
pointOnElement = self.f.createIfcCartesianPoint(tuple(conn['eccentricity']['pointOnElement']))
vector = conn['eccentricity']['vector']
connPointEcc = self.f.createIfcConnectionPointEccentricity(pointOnElement, None, vector[0], vector[1], vector[2])
self.f.createIfcRelConnectsWithEccentricity(self.guid(), ownerHistory, None, None, ifcElements[i], ifcConnections[j], appliedCondition, None, None, localAxes, connPointEcc)
# assign elements and connections to group
self.f.createIfcRelAssignsToGroup(self.guid(), ownerHistory, None, None, tuple(ifcElements + ifcConnections), None, model)
@@ -287,38 +315,34 @@ class CA2IFC:
return faceProdDefShape
def create_node_applied_conditions(self, bc):
def create_applied_conditions(self, bc, geometryType):
for dof in ['dx', 'dy', 'dz']:
if isinstance(bc[dof], bool):
bc[dof] = self.f.createIfcBoolean(bc[dof])
else:
bc[dof] = self.f.createIfcLinearStiffnessMeasure(bc[dof])
if geometryType == 'point':
bc[dof] = self.f.createIfcLinearStiffnessMeasure(bc[dof])
if geometryType == 'line':
bc[dof] = self.f.createIfcModulusOfLinearSubgradeReactionMeasure(bc[dof])
if geometryType == 'surface':
bc[dof] = self.f.createIfcModulusOfSubgradeReactionMeasure(bc[dof])
for dof in ['drx', 'dry', 'drz']:
if isinstance(bc[dof], bool):
bc[dof] = self.f.createIfcBoolean(bc[dof])
else:
bc[dof] = self.f.createIfcRotationalStiffnessMeasure(bc[dof])
if geometryType == 'point':
bc[dof] = self.f.createIfcRotationalStiffnessMeasure(bc[dof])
if geometryType == 'line':
bc[dof] = self.f.createIfcModulusOfRotationalSubgradeReactionMeasure(bc[dof])
return bc
if __name__ == '__main__':
inputFilename = 'grid_of_beams.json'
outputFilename = 'grid_of_beams.ifc'
inputFilename = 'structure_01.json'
outputFilename = 'structure_01.ifc'
ca2ifc = CA2IFC(inputFilename, outputFilename)
ca2ifc.convert()
#
# inputFilename = 'portal_01.json'
# outputFilename = 'portal_01.ifc'
#
# ca2ifc = CA2IFC(inputFilename, outputFilename)
# ca2ifc.convert()
#
# inputFilename = 'building_01.json' # json file to read
# outputFilename = 'building_01.ifc' # ifc file to write
#
# ca2ifc = CA2IFC(inputFilename, outputFilename)
# ca2ifc.convert()
+5 -1
View File
@@ -1,10 +1,14 @@
## A change log of the ifc2ca files
##### 14/10/20
- Add curve connections betweeen elements and as supports according to the defined orientation - stiffness is not yet considered
##### 17/08/20
- Add rigid links for point connection to elements
##### 14/07/20
- Add internal springs for point connection to elements and external springs for point connections according to the defined orientation
- Add internal springs for point connection to elements according to the defined orientation
- Add external springs for point connections according to the defined orientation
##### 27/05/20
- Add orientation for point, curve and surface geometries
+65 -13
View File
@@ -98,7 +98,7 @@ class IFC2CA:
for c in connections:
if c['eccentricity']:
if np.linalg.norm(np.array(c['eccentricity']['pointOnElement'])) > length + self.tol:
print((np.linalg.norm(np.array(c['eccentricity']['pointOnElement'])), '>', length))
print(np.linalg.norm(np.array(c['eccentricity']['pointOnElement'])), '>', length)
self.warnings.append('Eccentricity in %s corrected' % (item.is_a() + '|' + str(item.id())))
c['eccentricity']['pointOnElement'][0] = length
# End <--
@@ -181,7 +181,32 @@ class IFC2CA:
'geometryType': 'point',
'geometry': geometry,
'orientation': orientation,
'appliedCondition': self.get_connection_input(item),
'appliedCondition': self.get_connection_input(item, 'point'),
'relatedElements': [con.is_a() + '|' + str(con.id()) for con in item.ConnectsStructuralMembers]
}
elif item.is_a('IfcStructuralCurveConnection'):
representation = self.get_representation(item, 'Edge')
if not representation:
self.warnings.append('No representation defined for %s. Connection excluded' % (item.is_a() + '|' + str(item.id())))
return
geometry = self.get_geometry(representation)
orientation = self.get_1D_orientation(geometry, item.Axis)
if not orientation:
orientation = np.eye(3).tolist()
if transformation:
geometry = self.transform_vectors(geometry, transformation)
orientation = self.transform_vectors(orientation, transformation, include_translation=False)
return {
'ifcName': item.is_a() + '|' + str(item.id()),
'name': item.Name,
'id': item.GlobalId,
'geometryType': 'line',
'geometry': geometry,
'orientation': orientation,
'appliedCondition': self.get_connection_input(item, 'line'),
'relatedElements': [con.is_a() + '|' + str(con.id()) for con in item.ConnectsStructuralMembers]
}
@@ -412,7 +437,7 @@ class IFC2CA:
'relatingElement': rel.RelatingStructuralMember.is_a() + '|' + str(rel.RelatingStructuralMember.id()),
'relatedConnection': rel.RelatedStructuralConnection.is_a() + '|' + str(rel.RelatedStructuralConnection.id()),
'orientation': self.get_0D_orientation(rel.ConditionCoordinateSystem),
'appliedCondition': self.get_connection_input(rel),
'appliedCondition': self.get_connection_input(rel, self.get_geometry_type_from_connection(rel.RelatedStructuralConnection)),
'eccentricity': None if not rel.is_a('IfcRelConnectsWithEccentricity') else {
'vector': [
0.0 if not rel.ConnectionConstraint.EccentricityInX else rel.ConnectionConstraint.EccentricityInX,
@@ -423,16 +448,43 @@ class IFC2CA:
}
} for rel in itemList]
def get_connection_input(self, connection):
def get_geometry_type_from_connection(self, connection):
if connection.is_a('IfcStructuralPointConnection'):
return 'point'
if connection.is_a('IfcStructuralCurveConnection'):
return 'line'
if connection.is_a('IfcStructuralSurfaceConnection'):
return 'surface'
def get_connection_input(self, connection, geometryType):
if connection.AppliedCondition:
return {
'dx': connection.AppliedCondition.TranslationalStiffnessX.wrappedValue,
'dy': connection.AppliedCondition.TranslationalStiffnessY.wrappedValue,
'dz': connection.AppliedCondition.TranslationalStiffnessZ.wrappedValue,
'drx': connection.AppliedCondition.RotationalStiffnessX.wrappedValue,
'dry': connection.AppliedCondition.RotationalStiffnessY.wrappedValue,
'drz': connection.AppliedCondition.RotationalStiffnessZ.wrappedValue
}
if geometryType == 'point':
return {
'dx': connection.AppliedCondition.TranslationalStiffnessX.wrappedValue,
'dy': connection.AppliedCondition.TranslationalStiffnessY.wrappedValue,
'dz': connection.AppliedCondition.TranslationalStiffnessZ.wrappedValue,
'drx': connection.AppliedCondition.RotationalStiffnessX.wrappedValue,
'dry': connection.AppliedCondition.RotationalStiffnessY.wrappedValue,
'drz': connection.AppliedCondition.RotationalStiffnessZ.wrappedValue
}
if geometryType == 'line':
return {
'dx': connection.AppliedCondition.TranslationalStiffnessByLengthX.wrappedValue,
'dy': connection.AppliedCondition.TranslationalStiffnessByLengthY.wrappedValue,
'dz': connection.AppliedCondition.TranslationalStiffnessByLengthZ.wrappedValue,
'drx': connection.AppliedCondition.RotationalStiffnessByLengthX.wrappedValue,
'dry': connection.AppliedCondition.RotationalStiffnessByLengthY.wrappedValue,
'drz': connection.AppliedCondition.RotationalStiffnessByLengthZ.wrappedValue
}
if geometryType == 'surface':
return {
'dx': connection.AppliedCondition.TranslationalStiffnessByAreaX.wrappedValue,
'dy': connection.AppliedCondition.TranslationalStiffnessByAreaY.wrappedValue,
'dz': connection.AppliedCondition.TranslationalStiffnessByAreaZ.wrappedValue
}
return connection.AppliedCondition
def get_i_section_properties(self, profile, profileShape):
@@ -455,7 +507,7 @@ class IFC2CA:
}
if __name__ == '__main__':
fileNames = ['cantilever_01', 'portal_01', 'grid_of_beams']
fileNames = ['cantilever_01', 'portal_01', 'grid_of_beams', 'slab_01', 'structure_01']
files = fileNames
for fileName in files:
+130 -14
View File
@@ -33,16 +33,25 @@ class COMMANDFILE:
for el in elements:
for rel in el['connections']:
conn = [c for c in connections if c['ifcName'] == rel['relatedConnection']][0]
rel['conn_string'] = None
if conn['geometryType'] == 'point':
rel['groupName1'] = self.getGroupName(rel['relatingElement']) + '_0DC_' + self.getGroupName(rel['relatedConnection'])
if rel['eccentricity']:
rel['groupName2'] = self.getGroupName(rel['relatedConnection']) + '_0DC_' + self.getGroupName(rel['relatingElement'])
rel['index'] = len(conn['relatedElements']) + 1
rel['unifiedGroupName'] = self.getGroupName(rel['relatedConnection']) + '_0DC_%g' % rel['index']
else:
rel['groupName2'] = self.getGroupName(rel['relatedConnection'])
rel['conn_string'] = '_0DC_'
rel['springGroupName'] = self.getGroupName(rel['relatingElement']) + '_1DS_' + self.getGroupName(rel['relatedConnection'])
self.calculateConstraints(rel)
if conn['geometryType'] == 'line':
rel['conn_string'] = '_1DC_'
rel['springGroupName'] = None
if conn['geometryType'] == 'surface':
rel['conn_string'] = '_2DC_'
rel['springGroupName'] = None
rel['groupName1'] = self.getGroupName(rel['relatingElement']) + rel['conn_string'] + self.getGroupName(rel['relatedConnection'])
if rel['eccentricity']:
rel['groupName2'] = self.getGroupName(rel['relatedConnection']) + '_0DC_' + self.getGroupName(rel['relatingElement'])
rel['index'] = len(conn['relatedElements']) + 1
rel['unifiedGroupName'] = self.getGroupName(rel['relatedConnection']) + '_0DC_%g' % rel['index']
else:
rel['groupName2'] = self.getGroupName(rel['relatedConnection'])
self.calculateConstraints(rel)
conn['relatedElements'].append(rel)
# End <--
@@ -51,8 +60,9 @@ class COMMANDFILE:
edgeGroupNames = tuple([self.getGroupName(el['ifcName']) for el in elements if el['geometryType'] == 'line'])
faceGroupNames = tuple([self.getGroupName(el['ifcName']) for el in elements if el['geometryType'] == 'surface'])
point0DGroupNames = tuple([self.getGroupName(el['ifcName']) for el in connections if el['geometryType'] == 'point'])
spring1DGroupNames = tuple(flatten([[rel['springGroupName'] for rel in el['connections']] for el in elements]))
point0DGroupNames = tuple([self.getGroupName(el['ifcName']) + '_0D' for el in connections if el['geometryType'] == 'point'])
spring1DGroupNames = tuple(flatten([[rel['springGroupName'] for rel in el['connections'] if rel['springGroupName']] for el in elements]))
point1DGroupNames = tuple([self.getGroupName(el['ifcName']) + '_0D' for el in connections if el['geometryType'] == 'line'])
unifiedConnection = False
rigidLinkGroupNames = []
@@ -156,6 +166,21 @@ model = AFFE_MODELE(
f.write(template.format(**context))
if point1DGroupNames:
template = \
'''
_F(
GROUP_MA = {groupNames},
PHENOMENE = 'MECANIQUE',
MODELISATION = 'DIS_TR'
),'''
context = {
'groupNames': point1DGroupNames
}
f.write(template.format(**context))
if rigidLinkGroupNames:
template = \
'''
@@ -365,7 +390,7 @@ element = AFFE_CARA_ELEM(
),'''
context = {
'groupName': self.getGroupName(conn['ifcName']),
'groupName': self.getGroupName(conn['ifcName']) + '_0D',
'stiffnesses': conn['stiffnesses']
}
@@ -389,6 +414,23 @@ element = AFFE_CARA_ELEM(
f.write(template.format(**context))
for conn in [conn for conn in connections if conn['geometryType'] == 'line']:
template = \
'''
_F(
GROUP_MA = '{groupName}',
CARA = 'K_TR_D_N',
VALE = {stiffnesses},
REPERE = 'LOCAL'
),'''
context = {
'groupName': self.getGroupName(conn['ifcName']) + '_0D',
'stiffnesses': conn['stiffnesses']
}
f.write(template.format(**context))
f.write(
'''
@@ -428,7 +470,7 @@ element = AFFE_CARA_ELEM(
),'''
context = {
'groupName': self.getGroupName(conn['ifcName']),
'groupName': self.getGroupName(conn['ifcName']) + '_0D',
'localAxesXY': tuple(conn['orientation'][0] + conn['orientation'][1])
}
@@ -451,6 +493,23 @@ element = AFFE_CARA_ELEM(
f.write(template.format(**context))
for conn in [conn for conn in connections if conn['geometryType'] == 'line']:
template = \
'''
_F(
GROUP_MA = '{groupName}',
CARA = 'VECT_X_Y',
VALE = {localAxesXY}
),'''
context = {
'groupName': self.getGroupName(conn['ifcName']) + '_0D',
'localAxesXY': tuple(conn['orientation'][0] + conn['orientation'][1])
}
f.write(template.format(**context))
f.write(
'''
),'''
@@ -472,7 +531,7 @@ liaisons = AFFE_CHAR_MECA(
LIAISON_DDL = ('''
)
for conn in connections:
for conn in [conn for conn in connections if conn['geometryType'] == 'point']:
if conn['appliedCondition']:
for i in range(len(conn['liaisons']['coeffs'])):
template = \
@@ -516,6 +575,63 @@ liaisons = AFFE_CHAR_MECA(
),'''
)
f.write(
'''
LIAISON_GROUP = ('''
)
for conn in [conn for conn in connections if conn['geometryType'] == 'line']:
if conn['appliedCondition']:
for i in range(len(conn['liaisons']['coeffs'])):
template = \
'''
_F(
GROUP_NO_1 = {groupName_1},
GROUP_NO_2 = {groupName_1},
DDL_1 = {dofs},
DDL_2 = {dofs},
COEF_MULT_1 = {coeffs},
COEF_MULT_2 = (0.0, 0.0, 0.0),
COEF_IMPO = 0.0
),'''
context = {
'groupName_1': tuple([conn['liaisons']['groupNames'][0]]),
'dofs': conn['liaisons']['dofs'][i],
'coeffs': conn['liaisons']['coeffs'][i]
}
f.write(template.format(**context))
for rel in conn['relatedElements']:
for i in range(len(rel['liaisons']['coeffs'])):
template = \
'''
_F(
GROUP_NO_1 = {groupName_1},
GROUP_NO_2 = {groupName_2},
DDL_1 = {dofs},
DDL_2 = {dofs},
COEF_MULT_1 = {coeffs_1},
COEF_MULT_2 = {coeffs_2},
COEF_IMPO = 0.0
),'''
context = {
'groupName_1': tuple([rel['liaisons']['groupNames'][0]]),
'groupName_2': tuple([rel['liaisons']['groupNames'][3]]),
'dofs': tuple(list(rel['liaisons']['dofs'][i])[:3]),
'coeffs_1': tuple(list(rel['liaisons']['coeffs'][i])[:3]),
'coeffs_2': tuple(list(rel['liaisons']['coeffs'][i])[3:]),
}
f.write(template.format(**context))
f.write(
'''
),'''
)
if unifiedConnection:
f.write(
'''
@@ -825,7 +941,7 @@ FIN()
conn['stiffnesses'] = tuple(stiffnesses)
if __name__ == '__main__':
fileNames = ['cantilever_01', 'portal_01', 'grid_of_beams']
fileNames = ['cantilever_01', 'portal_01', 'grid_of_beams', 'slab_01', 'structure_01']
files = fileNames
for fileName in files:
+39 -24
View File
@@ -160,20 +160,19 @@ class MODEL:
if rel['eccentricity']:
rel['index'] = len(conn['relatedElements']) + 1
conn['relatedElements'].append(rel)
if conn['geometryType'] == 'point':
if not rel['eccentricity']:
el['connObjs'][j] = self.makeObject(conn['geometry'], conn['geometryType'])
else:
if not rel['eccentricity']:
el['connObjs'][j] = self.makeObject(conn['geometry'], conn['geometryType'])
else:
if conn['geometryType'] == 'point':
geometry = self.getLinkGeometry(rel['eccentricity'], el['orientation'], conn['geometry'])
el['connObjs'][j] = self.makeObject(geometry[0], conn['geometryType'])
el['linkPointObjs'][j][0] = self.geompy.MakeVertex(geometry[0][0], geometry[0][1], geometry[0][2])
el['linkPointObjs'][j][1] = self.geompy.MakeVertex(geometry[1][0], geometry[1][1], geometry[1][2])
el['linkObjs'][j] = self.geompy.MakeLineTwoPnt(el['linkPointObjs'][j][0], el['linkPointObjs'][j][1])
elif conn['geometryType'] == 'line':
pass
elif conn['geometryType'] == 'surface':
pass
else:
print('Eccentricity defined for a %s geometryType' %conn['geometryType'])
el['partObj'] = self.makePartition([el['elemObj']] + el['connObjs'], el['geometryType'])
@@ -199,7 +198,15 @@ class MODEL:
# geompy.addToStudy(el['partObj'], self.getGroupName(el['ifcName']))
geompy.addToStudyInFather(el['partObj'], el['elemObj'], self.getGroupName(el['ifcName']))
for j,rel in enumerate(el['connections']):
geompy.addToStudyInFather(el['partObj'], el['connObjs'][j], self.getGroupName(el['ifcName']) + '_0DC_' + self.getGroupName(rel['relatedConnection']))
conn = [c for c in connections if c['ifcName'] == rel['relatedConnection']][0]
rel['conn_string'] = None
if conn['geometryType'] == 'point':
rel['conn_string'] = '_0DC_'
if conn['geometryType'] == 'line':
rel['conn_string'] = '_1DC_'
if conn['geometryType'] == 'surface':
rel['conn_string'] = '_2DC_'
geompy.addToStudyInFather(el['partObj'], el['connObjs'][j], self.getGroupName(el['ifcName']) + rel['conn_string'] + self.getGroupName(rel['relatedConnection']))
if rel['eccentricity']:
pass
# geompy.addToStudy(el['linkObjs'][j], self.getGroupName(el['ifcName']) + '_1DR_' + self.getGroupName(rel['relatedConnection']))
@@ -240,8 +247,8 @@ class MODEL:
geompy.addToStudyInFather(bldComp, el['elemObj'], self.getGroupName(el['ifcName']))
for j,rel in enumerate(el['connections']):
geompy.addToStudyInFather(bldComp, el['connObjs'][j], self.getGroupName(el['ifcName']) + '_0DC_' + self.getGroupName(rel['relatedConnection']))
if rel['eccentricity']:
geompy.addToStudyInFather(bldComp, el['connObjs'][j], self.getGroupName(el['ifcName']) + rel['conn_string'] + self.getGroupName(rel['relatedConnection']))
if rel['eccentricity']: # point geometry
geompy.addToStudyInFather(bldComp, el['linkObjs'][j], self.getGroupName(el['ifcName']) + '_1DR_' + self.getGroupName(rel['relatedConnection']))
geompy.addToStudyInFather(bldComp, el['linkPointObjs'][j][0], self.getGroupName(rel['relatedConnection']) + '_0DC_' + self.getGroupName(el['ifcName']))
geompy.addToStudyInFather(bldComp, el['linkPointObjs'][j][1], self.getGroupName(rel['relatedConnection']) + '_0DC_%g' % rel['index'])
@@ -318,8 +325,8 @@ class MODEL:
smesh.SetName(tempgroup, self.getGroupName(el['ifcName']))
for j,rel in enumerate(el['connections']):
tempgroup = bldMesh.GroupOnGeom(el['connObjs'][j], self.getGroupName(el['ifcName']) + '_0DC_' + self.getGroupName(rel['relatedConnection']), SMESH.NODE)
smesh.SetName(tempgroup, self.getGroupName(el['ifcName']) + '_0DC_' + self.getGroupName(rel['relatedConnection']))
tempgroup = bldMesh.GroupOnGeom(el['connObjs'][j], self.getGroupName(el['ifcName']) + rel['conn_string'] + self.getGroupName(rel['relatedConnection']), SMESH.NODE)
smesh.SetName(tempgroup, self.getGroupName(el['ifcName']) + rel['conn_string'] + self.getGroupName(rel['relatedConnection']))
rel['node'] = (bldMesh.GetIDSource(tempgroup.GetNodeIDs(), SMESH.NODE)).GetIDs()[0]
if rel['eccentricity']:
tempgroup = bldMesh.GroupOnGeom(el['linkObjs'][j], self.getGroupName(el['ifcName']) + '_1DR_' + self.getGroupName(rel['relatedConnection']), SMESH.EDGE)
@@ -334,24 +341,32 @@ class MODEL:
for conn in connections:
# if conn['appliedCondition']:
tempgroup = bldMesh.GroupOnGeom(conn['connObj'], self.getGroupName(conn['ifcName']), SMESH.NODE)
smesh.SetName(tempgroup, self.getGroupName(conn['ifcName']))
nodesId = bldMesh.GetIDSource(tempgroup.GetNodeIDs(), SMESH.NODE)
tempgroup = bldMesh.Add0DElementsToAllNodes(nodesId, self.getGroupName(conn['ifcName']))
smesh.SetName(tempgroup, self.getGroupName(conn['ifcName']))
conn['node'] = nodesId.GetIDs()[0]
smesh.SetName(tempgroup, self.getGroupName(conn['ifcName'] + '_0D'))
if conn['geometryType'] == 'point':
conn['node'] = nodesId.GetIDs()[0]
if conn['geometryType'] == 'line':
tempgroup = bldMesh.GroupOnGeom(conn['connObj'], self.getGroupName(conn['ifcName']), SMESH.EDGE)
smesh.SetName(tempgroup, self.getGroupName(conn['ifcName']))
if conn['geometryType'] == 'surface':
tempgroup = bldMesh.GroupOnGeom(conn['connObj'], self.getGroupName(conn['ifcName']), SMESH.FACE)
smesh.SetName(tempgroup, self.getGroupName(conn['ifcName']))
# create 1D SEG2 spring elements
for el in elements:
for j,rel in enumerate(el['connections']):
grpName = bldMesh.CreateEmptyGroup(SMESH.EDGE, self.getGroupName(el['ifcName']) + '_1DS_' + self.getGroupName(rel['relatedConnection']))
smesh.SetName(grpName, self.getGroupName(el['ifcName']) + '_1DS_' + self.getGroupName(rel['relatedConnection']))
if not rel['eccentricity']:
conn = [conn for conn in connections if conn['ifcName'] == rel['relatedConnection']][0]
grpName.Add([bldMesh.AddEdge([conn['node'], rel['node']])])
else:
grpName.Add([bldMesh.AddEdge([rel['eccNode'], rel['node']])])
conn = [c for c in connections if c['ifcName'] == rel['relatedConnection']][0]
if conn['geometryType'] == 'point':
grpName = bldMesh.CreateEmptyGroup(SMESH.EDGE, self.getGroupName(el['ifcName']) + '_1DS_' + self.getGroupName(rel['relatedConnection']))
smesh.SetName(grpName, self.getGroupName(el['ifcName']) + '_1DS_' + self.getGroupName(rel['relatedConnection']))
if not rel['eccentricity']:
conn = [conn for conn in connections if conn['ifcName'] == rel['relatedConnection']][0]
grpName.Add([bldMesh.AddEdge([conn['node'], rel['node']])])
else:
grpName.Add([bldMesh.AddEdge([rel['eccNode'], rel['node']])])
self.mesh = bldMesh
self.meshNodes = bldMesh.GetNodesId()
@@ -385,7 +400,7 @@ class MODEL:
print('ALL Operations Completed in %g sec' % (elapsed_time))
if __name__ == '__main__':
fileNames = ['cantilever_01']
fileNames = ['structure_01']
files = fileNames
meshSize = 0.1
+36 -28
View File
@@ -1,14 +1,24 @@
from behave import step
from utils import IfcFile, assert_attribute, assert_type
def check_geocode_attribute(guid, ifc_class, name, value):
def get_ifc_class_from_spatial_type(spatial_type):
if spatial_type == 'site':
return 'IfcSite'
elif spatial_type == 'building':
return 'IfcBuilding'
return 'IfcFacility'
def check_geocode_attribute(guid, spatial_type, name, value):
element = IfcFile.by_guid(guid)
assert_type(element, ifc_class)
assert_type(element, get_ifc_class_from_spatial_type(spatial_type))
assert_attribute(element, name, value)
def check_geocode_address(guid, ifc_class, name, value):
def check_geocode_address(guid, spatial_type, name, value):
element = IfcFile.by_guid(guid)
ifc_class = get_ifc_class_from_spatial_type(spatial_type)
assert_type(element, ifc_class)
if ifc_class == 'IfcSite':
address_name = 'SiteAddress'
@@ -20,50 +30,48 @@ def check_geocode_address(guid, ifc_class, name, value):
use_step_matcher('re')
@step('The (site|building|facility) (?P<guid>.*) has a name of (?P<name>.*)')
def step_impl(context, _unused, guid, name):
check_geocode_attribute(guid, 'IfcSite', 'Name', name)
def step_impl(context, spatial_type, guid, name):
check_geocode_attribute(guid, spatial_type, 'Name', name)
@step('The (site|building|facility) (?P<guid>.*) has a description of (?P<description>.*)')
def step_impl(context, _unused, guid, description):
check_geocode_attribute(guid, 'IfcSite', 'Description', description)
@step('The (site|building) (?P<guid>.*) has a land title number of (?P<land_title_number>.*)')
def step_impl(context, _unused, guid, land_title_number):
check_geocode_attribute(guid, 'IfcSite', 'LandTitleNumber', land_title_number)
@step('The (site|building|facility) (?P<guid>.*) has a description of "(?P<description>.*)"')
def step_impl(context, spatial_type, guid, description):
check_geocode_attribute(guid, spatial_type, 'Description', description)
@step('The site (?P<guid>.*) has a land title number of (?P<land_title_number>.*)')
def step_impl(context, guid, land_title_number):
check_geocode_attribute(guid, 'site', 'LandTitleNumber', land_title_number)
@step('The (site|building) (?P<guid>.*) has the address "(?P<address_lines>.*)"')
def step_impl(context, _unused, guid, address_lines):
check_geocode_address(guid, 'IfcSite', 'AddressLines', address_lines.split('\\n'))
def step_impl(context, spatial_type, guid, address_lines):
check_geocode_address(guid, spatial_type, 'AddressLines', address_lines.split('\\n'))
@step('The (site|building) (?P<guid>.*) has a postal box of (?P<postal_box>.*)')
def step_impl(context, _unused, guid, postal_box):
check_geocode_address(guid, 'IfcSite', 'PostalBox', postal_box)
def step_impl(context, spatial_type, guid, postal_box):
check_geocode_address(guid, spatial_type, 'PostalBox', postal_box)
@step('The (site|building) (?P<guid>.*) is in the town (?P<town>.*)')
def step_impl(context, _unused, guid, town):
check_geocode_address(guid, 'IfcSite', 'Town', town)
def step_impl(context, spatial_type, guid, town):
check_geocode_address(guid, spatial_type, 'Town', town)
@step('The (site|building) (?P<guid>.*) is in the region (?P<region>.*)')
def step_impl(context, _unused, guid, region):
check_geocode_address(guid, 'IfcSite', 'Region', region)
def step_impl(context, spatial_type, guid, region):
check_geocode_address(guid, spatial_type, 'Region', region)
@step('The (site|building) (?P<guid>.*) has a post code of (?P<post_code>.*)')
def step_impl(context, _unused, guid, post_code):
check_geocode_address(guid, 'IfcSite', 'PostalCode', post_code)
def step_impl(context, spatial_type, guid, post_code):
check_geocode_address(guid, spatial_type, 'PostalCode', post_code)
@step('The (site|building) (?P<guid>.*) is in the country (?P<country>.*)')
def step_impl(context, _unused, guid, country):
check_geocode_address(guid, 'IfcSite', 'Country', country)
def step_impl(context, spatial_type, guid, country):
check_geocode_address(guid, spatial_type, 'Country', country)
@step('The (site|building) (?P<guid>.*) has an address description of (?P<description>.*)')
def step_impl(context, _unused, guid, description):
check_geocode_address(guid, 'IfcSite', 'Description', description)
@step('The (site|building) (?P<guid>.*) has an address description of "(?P<description>.*)"')
def step_impl(context, spatial_type, guid, description):
check_geocode_address(guid, spatial_type, 'Description', description)
@@ -3,6 +3,7 @@ from utils import IfcFile, assert_number, assert_pset, assert_attribute
import math
import ifcopenshell.util
import ifcopenshell.util.element
import ifcopenshell.util.geolocation
@step(u'There must be at least one {ifc_class} element')
def step_impl(context, ifc_class):
@@ -108,6 +109,8 @@ def step_impl(context, unit):
assert_pset(site, 'EPset_ProjectedCRS', 'MapUnit', unit)
return
actual_value = check_ifc4_geolocation('IfcProjectedCRS', 'MapUnit', should_assert=False)
if not actual_value:
assert False, 'A unit was not provided in the projected CRS'
if actual_value.is_a('IfcSIUnit'):
prefix = actual_value.Prefix if actual_value.Prefix else ''
actual_value = prefix + actual_value.Name
@@ -161,8 +164,7 @@ def step_impl(context, number):
return check_ifc2x3_geolocation('EPset_MapConversion', 'Height', number)
abscissa = check_ifc4_geolocation('IfcMapConversion', 'XAxisAbscissa', should_assert=False)
ordinate = check_ifc4_geolocation('IfcMapConversion', 'XAxisOrdinate', should_assert=False)
# TODO: migrate to geolocation util
actual_value = round(math.degrees(math.atan2(ordinate, abscissa)) - 90, 3)
actual_value = round(ifcopenshell.util.geolocation.xy2angle(abscissa, ordinate), 3)
value = round(number, 3)
assert actual_value == value, 'We expected a value of "{}" but instead got "{}"'.format(value, actual_value)
@@ -183,6 +185,8 @@ def step_impl(context, guid, number):
site = IfcFile.by_guid(guid)
if not site.is_a('IfcSite'):
assert False, 'The element {} is not an IfcSite'.format(site)
ref = assert_attribute(site, 'RefLongitude')
number = ifcopenshell.util.geolocation.dd2dms(number, use_ms=(len(ref) == 4))
assert_attribute(site, 'RefLongitude', number)
@@ -192,6 +196,8 @@ def step_impl(context, guid, number):
site = IfcFile.by_guid(guid)
if not site.is_a('IfcSite'):
assert False, 'The element {} is not an IfcSite'.format(site)
ref = assert_attribute(site, 'RefLatitude')
number = ifcopenshell.util.geolocation.dd2dms(number, use_ms=(len(ref) == 4))
assert_attribute(site, 'RefLatitude', number)
+1 -1
View File
@@ -41,7 +41,7 @@ def assert_attribute(element, name, value=None):
if not value:
if getattr(element, name) is None:
assert False, 'The element {} does not have a value for the attribute {}'.format(element, name)
return
return getattr(element, name)
if value == 'NULL':
value = None
actual_value = getattr(element, name)
+3 -1
View File
@@ -57,7 +57,9 @@
<span class="step-time">{{time}}s</span>
{{^is_success}}
<p class="failure{{#is_unspecified}} unspecified{{/is_unspecified}}">
{{error_message}}
{{#error_message}}
{{.}}<br />
{{/error_message}}
</p>
{{/is_success}}
</li>
+3 -1
View File
@@ -26,7 +26,7 @@ endif
cp -r blenderbim/* dist/blenderbim/
# Provides IfcOpenShell Python functionality
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcblender-python-37-v0.6.0-c15fdc7-$(PLATFORM)64.zip
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcblender-python-37-v0.6.0-ca6adff-$(PLATFORM)64.zip
cd dist/working && unzip ifcblender*
cp -r dist/working/io_import_scene_ifc/ifcopenshell dist/blenderbim/libs/site/packages/
# See bug #812
@@ -243,6 +243,8 @@ endif
cd dist/blenderbim/libs/site/packages/features/steps/ && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/ifcbimtester/features/steps/element_classes.py
cd dist/blenderbim/libs/site/packages/features/steps/ && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/ifcbimtester/features/steps/geocoding.py
cd dist/blenderbim/libs/site/packages/features/steps/ && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/ifcbimtester/features/steps/geolocation.py
cd dist/blenderbim/libs/site/packages/features/steps/ && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/ifcbimtester/features/steps/geometric_detail.py
cd dist/blenderbim/libs/site/packages/features/steps/ && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/ifcbimtester/features/steps/model_federation.py
cd dist/blenderbim/libs/site/packages/features/steps/ && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/ifcbimtester/features/steps/project_setup.py
cd dist/blenderbim/libs/site/packages/features/steps/ && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/ifcbimtester/features/steps/steps.py
cd dist/blenderbim/libs/site/packages/features/steps/ && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/ifcbimtester/features/steps/utils.py
@@ -133,7 +133,6 @@ if bpy is not None:
operator.SelectClashSource,
operator.ExecuteIfcClash,
operator.SelectIfcClashResults,
operator.AssignContext,
operator.SwitchContext,
operator.RemoveContext,
operator.OpenUpstream,
@@ -186,7 +185,6 @@ if bpy is not None:
operator.SaveDrawingStyle,
operator.ActivateDrawingStyle,
operator.EditVectorStyle,
operator.PurgeProjectClassifications,
operator.RemoveSheet,
operator.AddSchedule,
operator.RemoveSchedule,
@@ -196,6 +194,14 @@ if bpy is not None:
operator.SetViewportShadowFromSun,
operator.SetNorthOffset,
operator.GetNorthOffset,
operator.AddDrawingStyleAttribute,
operator.RemoveDrawingStyleAttribute,
operator.CopyPropertyToSelection,
operator.CreateShapeFromStepId,
operator.SelectHighPolygonMeshes,
operator.RefreshDrawingList,
operator.GetRepresentationIfcParameters,
operator.UpdateIfcRepresentation,
prop.StrProperty,
prop.Variable,
prop.Role,
@@ -224,12 +230,14 @@ if bpy is not None:
prop.BcfTopicRelatedTopic,
prop.Subcontext,
prop.BIMProperties,
prop.BIMDebugProperties,
prop.BCFProperties,
prop.DocProperties,
prop.BIMLibrary,
prop.MapConversion,
prop.TargetCRS,
prop.Attribute,
prop.IfcParameter,
prop.BoundaryCondition,
prop.PsetQto,
prop.GlobalId,
@@ -264,6 +272,7 @@ if bpy is not None:
ui.BIM_PT_cobie,
ui.BIM_PT_patch,
ui.BIM_PT_mvd,
ui.BIM_PT_debug,
ui.BIM_PT_material,
ui.BIM_PT_mesh,
ui.BIM_PT_object,
@@ -287,7 +296,6 @@ if bpy is not None:
ui.BIM_UL_document_references,
ui.BIM_UL_topics,
ui.BIM_UL_classifications,
ui.BIM_UL_representation_items,
ui.BIM_ADDON_preferences,
covetool_prop.CoveToolProject,
covetool_prop.CoveToolSimpleAnalysis,
@@ -316,7 +324,7 @@ if bpy is not None:
def on_register(scene):
prop.setDefaultProperties(scene)
bpy.app.handlers.scene_update_post.remove(on_register)
bpy.app.handlers.depsgraph_update_post.remove(on_register)
def register():
for cls in classes:
@@ -326,6 +334,7 @@ if bpy is not None:
bpy.types.TOPBAR_MT_file_export.append(menu_func_export)
bpy.types.TOPBAR_MT_file_import.append(menu_func_import)
bpy.types.Scene.BIMProperties = bpy.props.PointerProperty(type=prop.BIMProperties)
bpy.types.Scene.BIMDebugProperties = bpy.props.PointerProperty(type=prop.BIMDebugProperties)
bpy.types.Scene.BCFProperties = bpy.props.PointerProperty(type=prop.BCFProperties)
bpy.types.Scene.DocProperties = bpy.props.PointerProperty(type=prop.DocProperties)
bpy.types.Scene.BIMLibrary = bpy.props.PointerProperty(type=prop.BIMLibrary)
@@ -355,6 +364,7 @@ if bpy is not None:
bpy.types.TOPBAR_MT_file_export.remove(menu_func_export)
bpy.types.TOPBAR_MT_file_import.remove(menu_func_import)
del(bpy.types.Scene.BIMProperties)
del(bpy.types.Scene.BIMDebugProperties)
del(bpy.types.Scene.BCFProperties)
del(bpy.types.Scene.DocProperties)
del(bpy.types.Scene.MapConversion)
+37 -39
View File
@@ -1,4 +1,5 @@
import os
import re
import math
import time
import numpy
@@ -150,6 +151,7 @@ def do_cut(process_data):
class IfcCutter:
def __init__(self):
self.time = None
self.selector = ifcopenshell.util.selector.Selector()
self.product_shapes = []
self.background_elements = []
@@ -185,52 +187,36 @@ class IfcCutter:
}
def cut(self):
start_time = time.time()
print('# Load files')
self.profile_code('Starting cut process')
self.load_ifc_files()
print('# Timer logged at {:.2f} seconds'.format(time.time() - start_time))
start_time = time.time()
print('# Extract template variables')
self.profile_code('Load IFC files')
self.get_template_variables()
print('# Timer logged at {:.2f} seconds'.format(time.time() - start_time))
start_time = time.time()
print('# Get product shapes')
self.profile_code('Get template variables')
self.get_product_shapes()
print('# Timer logged at {:.2f} seconds'.format(time.time() - start_time))
start_time = time.time()
print('# Create section box')
self.profile_code('Get product shapes')
self.create_section_box()
print('# Timer logged at {:.2f} seconds'.format(time.time() - start_time))
start_time = time.time()
print('# Get cut polygons')
self.profile_code('Create section box')
self.get_cut_polygons()
print('# Get annotation')
self.profile_code('Get cut polygons')
self.get_annotation()
print('# Timer logged at {:.2f} seconds'.format(time.time() - start_time))
start_time = time.time()
print('# Get cut polygon metadata')
self.profile_code('Get annotation')
self.get_cut_polygon_metadata()
print('# Timer logged at {:.2f} seconds'.format(time.time() - start_time))
self.profile_code('Get cut polygon metadata')
# should_get_background is False in production as this is experimental
if not self.should_get_background:
return
start_time = time.time()
print('# Get background elements')
self.get_background_elements()
print('# Timer logged at {:.2f} seconds'.format(time.time() - start_time))
start_time = time.time()
print('# Sort background elements')
self.sort_background_elements(reverse=True)
print('# Timer logged at {:.2f} seconds'.format(time.time() - start_time))
start_time = time.time()
print('# Merge background_elements')
self.merge_background_elements()
print('# Timer logged at {:.2f} seconds'.format(time.time() - start_time))
start_time = time.time()
print('# Sort background elements')
self.sort_background_elements()
print('# Timer logged at {:.2f} seconds'.format(time.time() - start_time))
def profile_code(self, message):
if not self.time:
self.time = time.time()
print('{} :: {:.2f}'.format(message, time.time() - self.time))
self.time = time.time()
def load_ifc_files(self):
if not self.should_recut and not self.should_extract:
@@ -299,7 +285,7 @@ class IfcCutter:
products.extend(self.selector.parse(ifc_file, self.cut_objects))
include_elements = []
selected_elements = []
for i, product in enumerate(products):
if product.is_a('IfcOpeningElement') \
or product.is_a('IfcSite') \
@@ -309,20 +295,20 @@ class IfcCutter:
try:
if self.should_recut_selected \
and product.GlobalId in self.selected_global_ids:
include_elements.append(product)
selected_elements.append(product)
elif product.GlobalId in shape_map:
shape = shape_map[product.GlobalId]
self.product_shapes.append((product, shape))
self.add_product_shape(product, shape)
else:
include_elements.append(product)
selected_elements.append(product)
except:
print('Failed to create shape for {}'.format(product))
if include_elements:
if selected_elements:
total = 0
checkpoint = time.time()
iterator = ifcopenshell.geom.iterator(
settings, ifc_file, multiprocessing.cpu_count(), include=include_elements)
settings, ifc_file, multiprocessing.cpu_count(), include=selected_elements)
valid_file = iterator.initialize()
if valid_file:
while True:
@@ -332,13 +318,16 @@ class IfcCutter:
checkpoint = time.time()
shape = iterator.get()
shape_map[shape.data.guid] = shape.geometry
self.product_shapes.append((ifc_file.by_guid(shape.data.guid), shape.geometry))
self.add_product_shape(ifc_file.by_guid(shape.data.guid), shape.geometry)
if not iterator.next():
break
with open(shape_pickle, 'wb') as shape_file:
pickle.dump(shape_map, shape_file, protocol=pickle.HIGHEST_PROTOCOL)
def add_product_shape(self, product, shape):
self.product_shapes.append((product, shape))
def has_annotation(self, element):
for representation in element.Representation.Representations:
if representation.ContextOfItems.ContextType == 'Plan' \
@@ -807,8 +796,17 @@ class IfcCutter:
classes = [position, element.is_a()]
for association in element.HasAssociations:
if association.is_a('IfcRelAssociatesMaterial'):
classes.append('material-{}'.format(self.get_material_name(association.RelatingMaterial)))
classes.append('material-{}'.format(
re.sub('[^0-9a-zA-Z]+', '', self.get_material_name(association.RelatingMaterial))
))
classes.append('globalid-{}'.format(element.GlobalId))
for attribute in self.attributes:
result = self.selector.get_element_value(element, attribute)
if result:
classes.append('{}-{}'.format(
re.sub('[^0-9a-zA-Z]+', '', attribute),
re.sub('[^0-9a-zA-Z]+', '', result)
))
return classes
def get_material_name(self, element):
@@ -1,3 +1,4 @@
* { stroke-linecap: round; }
.cut { fill: black; stroke: black; stroke-linecap: 'round'; stroke-width: 0.35; }
.background { fill: white; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; }
.annotation { fill: none; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; }
@@ -23,3 +24,4 @@
.material-sand { fill: url(#sand); }
.material-concrete { fill: url(#concrete); stroke-width: 0.5; }
.material-boundary { fill: none; stroke: red; stroke-width: 1; stroke-dasharray: 12,4,3,4,3,4; }
.IfcSpace { fill: none; stroke: none; }
@@ -7,3 +7,4 @@
.stair { marker-start: url(#stair-marker-start); marker-end: url(#stair-marker-end); }
.break { fill: white; }
.breakline { fill: none; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; marker-mid: url(#breakline-marker); }
.IfcSpace { fill: none; stroke: none; }
@@ -1,5 +1,8 @@
<?xml version="1.0" encoding="utf-8" ?>
<svg baseProfile="full" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:ev="http://www.w3.org/2001/xml-events" xmlns:xlink="http://www.w3.org/1999/xlink">
<pattern id="demolish" width="1" height="1" patternTransform="rotate(45 0 0)" patternUnits="userSpaceOnUse">
<line x1="0" y1="0" x2="0" y2="1" style="stroke:red; stroke-width:0.25" />
</pattern>
<pattern id="diagonal1" width="1" height="1" patternTransform="rotate(45 0 0)" patternUnits="userSpaceOnUse">
<line x1="0" y1="0" x2="0" y2="1" style="stroke:black; stroke-width:0.25" />
</pattern>
@@ -9,6 +12,18 @@
<pattern id="diagonal3" width="3" height="3" patternTransform="rotate(45 0 0)" patternUnits="userSpaceOnUse">
<line x1="0" y1="0" x2="0" y2="3" style="stroke:black; stroke-width:0.25" />
</pattern>
<pattern id="square1" width="1" height="1" patternUnits="userSpaceOnUse">
<line x1="0" y1="0" x2="0" y2="1" style="stroke:black; stroke-width:0.25" />
<line x1="0" y1="0" x2="1" y2="0" style="stroke:black; stroke-width:0.25" />
</pattern>
<pattern id="square2" width="2" height="2" patternUnits="userSpaceOnUse">
<line x1="0" y1="0" x2="0" y2="2" style="stroke:black; stroke-width:0.25" />
<line x1="0" y1="0" x2="2" y2="0" style="stroke:black; stroke-width:0.25" />
</pattern>
<pattern id="square3" width="3" height="3" patternUnits="userSpaceOnUse">
<line x1="0" y1="0" x2="0" y2="3" style="stroke:black; stroke-width:0.25" />
<line x1="0" y1="0" x2="3" y2="0" style="stroke:black; stroke-width:0.25" />
</pattern>
<pattern id="crosshatch1" width="1" height="1" patternTransform="rotate(45 0 0)" patternUnits="userSpaceOnUse">
<line x1="0" y1="0" x2="0" y2="1" style="stroke:black; stroke-width:0.25" />
<line x1="0" y1="0" x2="1" y2="0" style="stroke:black; stroke-width:0.25" />
@@ -27,6 +42,7 @@
<line x1="2" y1="0" x2="2" y2="3" style="stroke:black; stroke-width:0.25" />
</pattern>
<pattern id="earth" width="6" height="6" patternUnits="userSpaceOnUse">
<path style="fill: white;" d="M 0 0 6 0 6 6 0 6" />
<line x1="0" y1="0" x2="0" y2="2" style="stroke:black; stroke-width:0.25" />
<line x1="1" y1="0" x2="1" y2="2" style="stroke:black; stroke-width:0.25" />
<line x1="2" y1="0" x2="2" y2="2" style="stroke:black; stroke-width:0.25" />

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 65 KiB

+159 -65
View File
@@ -8,8 +8,9 @@ import zipfile
import tempfile
from pathlib import Path
from mathutils import Vector, Matrix
from .helper import SIUnitHelper
from .helper import SIUnitHelper, get_representation_elements
from . import schema
from . import ifc
import ifcopenshell
import addon_utils
@@ -86,8 +87,9 @@ class IfcParser():
if not self.projects:
self.setup_project()
self.projects = self.get_projects()
self.project = self.projects[0]
if not selected_objects:
selected_objects = self.get_all_objects_in_project(self.project['raw'])
self.units = self.get_units()
self.unit_scale = self.get_unit_scale()
self.people = self.get_people()
@@ -124,25 +126,31 @@ class IfcParser():
self.spatial_structure_elements_tree.extend(self.get_spatial_structure_elements_tree(project))
def get_units(self):
return {
units = {
'length': {
'ifc': None,
'is_metric': bpy.context.scene.unit_settings.system == 'METRIC',
'is_metric': bpy.context.scene.unit_settings.system != 'IMPERIAL',
'raw': bpy.context.scene.unit_settings.length_unit
},
'area': {
'ifc': None,
'is_metric': bpy.context.scene.unit_settings.system == 'METRIC',
'is_metric': bpy.context.scene.unit_settings.system != 'IMPERIAL',
'raw': bpy.context.scene.unit_settings.length_unit
},
'volume': {
'ifc': None,
'is_metric': bpy.context.scene.unit_settings.system == 'METRIC',
'is_metric': bpy.context.scene.unit_settings.system != 'IMPERIAL',
'raw': bpy.context.scene.unit_settings.length_unit
}}
for data in units.values():
if data['raw'] == 'ADAPTIVE':
if data['is_metric']:
data['raw'] = 'METERS'
else:
data['raw'] = 'FEET'
return units
def get_unit_scale(self):
unit_settings = bpy.context.scene.unit_settings
conversions = {
'KILOMETERS': 1e3,
'CENTIMETERS': 1e-2,
@@ -150,12 +158,13 @@ class IfcParser():
'MICROMETERS': 1e-6,
'FEET': 0.3048,
'INCHES': 0.0254}
if unit_settings.system in {'METRIC', 'IMPERIAL'}:
scale = unit_settings.scale_length
if unit_settings.length_unit in conversions.keys():
scale *= conversions[unit_settings.length_unit]
return scale
return 1
if bpy.context.scene.unit_settings.system in {'METRIC', 'IMPERIAL'}:
scale = bpy.context.scene.unit_settings.scale_length
else:
scale = 1
if self.units['length']['raw'] in conversions.keys():
scale *= conversions[self.units['length']['raw']]
return scale
def get_object_attributes(self, obj):
attributes = {'Name': self.get_ifc_name(obj.name)}
@@ -348,10 +357,10 @@ class IfcParser():
'ifc': None,
'raw': obj,
'class': self.get_ifc_class(obj.name),
'attributes': self.get_object_attributes(obj),
'relating_structure': None,
'relating_host': None,
'relating_qtos_key': None,
'attributes': self.get_object_attributes(obj),
'has_boundary_condition': obj.BIMObjectProperties.has_boundary_condition,
'boundary_condition_class': None,
'boundary_condition_attributes': {},
@@ -463,6 +472,8 @@ class IfcParser():
relationships.setdefault(item_key, []).append(product)
def add_automatic_qtos(self, ifc_class, obj):
if not obj.data:
return
qto_names = self.get_applicable_qtos(ifc_class)
for name in qto_names:
if name not in schema.ifc.qtos:
@@ -486,7 +497,7 @@ class IfcParser():
def get_applicable_qtos(self, ifc_class):
results = []
empty = ifcopenshell.file()
empty = ifcopenshell.file(schema=self.ifc_export_settings.schema)
element = empty.create_entity(ifc_class)
for ifc_class, qto_names in schema.ifc.applicable_qtos.items():
if element.is_a(ifc_class):
@@ -626,27 +637,30 @@ class IfcParser():
def get_classifications(self):
results = {}
for classification in bpy.context.scene.BIMProperties.classifications:
schema.ifc.load_classification(classification.filename)
results[classification.filename] = {
if classification.name not in schema.ifc.classification_files:
schema.ifc.classification_files[classification.name] = ifcopenshell.file.from_string(classification.data)
results[classification.name] = {
'ifc': None,
'raw': classification,
'raw_element': schema.ifc.classification_files[classification.filename].by_type('IfcClassification')[0]
'raw_element': schema.ifc.classification_files[classification.name].by_type('IfcClassification')[0]
}
return results
def get_classification_reference_maps(self):
results = {}
for filename, classification in self.classifications.items():
ifc_file = schema.ifc.classification_files[filename]
for name, classification in self.classifications.items():
ifc_file = schema.ifc.classification_files[name]
if ifc_file.schema == 'IFC2X3':
results[filename] = { e.ItemReference: e for e in ifc_file.by_type('IfcClassificationReference')}
results[name] = { e.ItemReference: e for e in ifc_file.by_type('IfcClassificationReference')}
else:
results[filename] = { e.Identification: e for e in ifc_file.by_type('IfcClassificationReference')}
results[name] = { e.Identification: e for e in ifc_file.by_type('IfcClassificationReference')}
return results
def get_classification_references(self):
results = {}
for product in self.selected_products:
for product in self.selected_products \
+ self.selected_types \
+ self.selected_spatial_structure_elements:
for reference in product['raw'].BIMObjectProperties.classifications:
results[reference.name] = {
'ifc': None,
@@ -691,6 +705,11 @@ class IfcParser():
'suffix_titles': 'SuffixTitles',
}
results = []
if self.ifc_export_settings.schema == 'IFC2X3' \
and not bpy.context.scene.BIMProperties.people:
bpy.ops.bim.add_person()
for person in bpy.context.scene.BIMProperties.people:
attributes = {}
for key, value in data_map.items():
@@ -714,6 +733,11 @@ class IfcParser():
'description': 'Description',
}
results = []
if self.ifc_export_settings.schema == 'IFC2X3' \
and not bpy.context.scene.BIMProperties.organisations:
bpy.ops.bim.add_organisation()
for organisation in bpy.context.scene.BIMProperties.organisations:
attributes = {}
for key, value in data_map.items():
@@ -749,6 +773,11 @@ class IfcParser():
def get_addresses(self, addresses):
results = []
for address in addresses:
results.append(self.get_address(address))
return results
def get_address(self, address):
address_data_map = {
'purpose': 'Purpose',
'description': 'Description',
@@ -772,28 +801,26 @@ class IfcParser():
'electronic_mail_addresses': 'ElectronicMailAddresses',
'messaging_ids': 'MessagingIDs',
}
for address in addresses:
attributes = {}
if 'IfcPostalAddress' in address.name:
merged_data_map = {**address_data_map, **postal_data_map}
if address.address_lines:
attributes['AddressLines'] = address.address_lines.split('/')
elif 'IfcTelecomAddress' in address.name:
merged_data_map = {**address_data_map, **telecom_data_map}
for key, value in telecom_list_data_map.items():
if getattr(address, key):
attributes[value] = getattr(address, key).split(',')
for key, value in merged_data_map.items():
attributes = {}
if 'IfcPostalAddress' in address.name:
merged_data_map = {**address_data_map, **postal_data_map}
if address.address_lines:
attributes['AddressLines'] = address.address_lines.split('/')
elif 'IfcTelecomAddress' in address.name:
merged_data_map = {**address_data_map, **telecom_data_map}
for key, value in telecom_list_data_map.items():
if getattr(address, key):
attributes[value] = getattr(address, key)
results.append({
'ifc': None,
'raw': address,
'is_postal': 'IfcPostalAddress' in address.name,
'is_telecom': 'IfcTelecomAddress' in address.name,
'attributes': attributes
})
return results
attributes[value] = getattr(address, key).split(',')
for key, value in merged_data_map.items():
if getattr(address, key):
attributes[value] = getattr(address, key)
return {
'ifc': None,
'raw': address,
'is_postal': 'IfcPostalAddress' in address.name,
'is_telecom': 'IfcTelecomAddress' in address.name,
'attributes': attributes
}
def get_document_references(self):
results = {}
@@ -860,6 +887,13 @@ class IfcParser():
})
return results
def get_all_objects_in_project(self, collection):
results = []
results.extend(list(collection.objects))
for child in collection.children:
results.extend(self.get_all_objects_in_project(child))
return results
def setup_project(self):
bpy.ops.bim.quick_project_setup()
for collection in bpy.data.collections:
@@ -944,10 +978,12 @@ class IfcParser():
'ifc': None,
'raw': obj,
'class': self.get_ifc_class(obj.name),
'attributes': self.get_object_attributes(obj)
'attributes': self.get_object_attributes(obj),
'address': self.get_address(obj.BIMObjectProperties.address)
}
self.append_product_attributes(element, obj)
self.get_product_psets_qtos(element, obj, is_pset=True)
self.get_product_psets_qtos(element, obj, is_qto=True)
elements.append(element)
return elements
@@ -1011,6 +1047,9 @@ class IfcParser():
self.representations['Model/Body/MODEL_VIEW/{}'.format(obj.data.name)] = self.get_representation(
obj.data, obj, 'Model', 'Body', 'MODEL_VIEW')
if 'Model/Box/MODEL_VIEW' in self.generated_subcontexts:
if self.ifc_export_settings.should_roundtrip_native \
and obj.data.BIMMeshProperties.ifc_definition_id:
return
self.representations['Model/Box/MODEL_VIEW/{}'.format(obj.data.name)] = self.get_representation(
obj.data, obj, 'Model', 'Box', 'MODEL_VIEW')
@@ -1048,11 +1087,13 @@ class IfcParser():
self.representations[mesh_name] = self.get_representation(
mesh, obj, context, subcontext, target_view)
if 'Model/Box/MODEL_VIEW' in self.generated_subcontexts \
and context == 'Model' \
and subcontext == 'Body' \
and target_view == 'MODEL_VIEW':
self.representations['Model/Box/MODEL_VIEW/{}'.format(mesh_name.split('/')[3])] = self.get_representation(
obj.data, obj, 'Model', 'Box', 'MODEL_VIEW')
and context_prefix == 'Model/Body/MODEL_VIEW':
if self.ifc_export_settings.should_roundtrip_native \
and obj.data.BIMMeshProperties.ifc_definition_id:
pass
else:
self.representations['Model/Box/MODEL_VIEW/{}'.format(mesh_name.split('/')[3])] = self.get_representation(
obj.data, obj, 'Model', 'Box', 'MODEL_VIEW')
elif context_prefix == 'Model/Body/MODEL_VIEW' \
and obj.data \
and not self.is_mesh_context_sensitive(obj.data.name):
@@ -1081,12 +1122,15 @@ class IfcParser():
'context': context,
'subcontext': subcontext,
'target_view': target_view,
'has_ifc_definition': False if not hasattr(mesh, 'BIMMeshProperties') else (mesh.BIMMeshProperties.ifc_definition or mesh.BIMMeshProperties.ifc_definition_id),
'ifc_definition': mesh.BIMMeshProperties.ifc_definition if hasattr(mesh, 'BIMMeshProperties') else None,
'ifc_definition_id': mesh.BIMMeshProperties.ifc_definition_id if hasattr(mesh, 'BIMMeshProperties') else None,
'is_parametric': mesh.BIMMeshProperties.is_parametric if hasattr(mesh, 'BIMMeshProperties') else False,
'is_curve': isinstance(mesh, bpy.types.Curve),
'is_point_cloud': self.is_point_cloud(obj),
'is_structural': self.is_structural(obj),
'is_text': isinstance(mesh, bpy.types.TextCurve),
'is_wireframe': self.is_wireframe_mesh(mesh),
'is_wireframe': self.is_wireframe_mesh(mesh, obj),
'is_native': mesh.BIMMeshProperties.is_native if hasattr(mesh, 'BIMMeshProperties') else False,
'is_swept_solid': mesh.BIMMeshProperties.is_swept_solid if hasattr(mesh, 'BIMMeshProperties') else False,
'is_generated': False,
@@ -1094,9 +1138,12 @@ class IfcParser():
'attributes': {'Name': mesh.name}
}
def is_wireframe_mesh(self, mesh):
def is_wireframe_mesh(self, mesh, obj):
if isinstance(mesh, bpy.types.Mesh) and not mesh.polygons:
return True
modifiers = [m.type for m in obj.modifiers]
# SCREW and SKIN can create faces, so it is not a wireframe mesh
if 'SCREW' not in modifiers and 'SKIN' not in modifiers:
return True
if isinstance(mesh, bpy.types.Curve) and not mesh.bevel_object and not mesh.bevel_depth:
return True
return False
@@ -1147,7 +1194,9 @@ class IfcParser():
parsed_data_names = []
for product in self.selected_products + self.type_products:
obj = product['raw']
if obj.data is None or obj.data.name in parsed_data_names:
if obj.data is None \
or obj.data.name in parsed_data_names \
or obj.data.BIMMeshProperties.ifc_definition_id:
continue
parsed_data_names.append(obj.data.name)
for slot in obj.material_slots:
@@ -1525,12 +1574,15 @@ class IfcExporter():
def create_addresses(self, addresses):
results = []
for address in addresses:
if self.schema == 'IFC2X3' and 'MessagingIDs' in address['attributes']:
del address['attributes']['MessagingIDs']
results.append(self.file.create_entity('IfcPostalAddress' if
address['is_postal'] else 'IfcTelecomAddress', **address['attributes']))
results.append(self.create_address(address))
return results
def create_address(self, address):
if self.schema == 'IFC2X3' and 'MessagingIDs' in address['attributes']:
del address['attributes']['MessagingIDs']
return self.file.create_entity('IfcPostalAddress' if
address['is_postal'] else 'IfcTelecomAddress', **address['attributes'])
def create_library_information(self):
information = self.ifc_parser.library_information
if not information:
@@ -1642,7 +1694,8 @@ class IfcExporter():
for name, data in templates.items():
if name not in pset['raw']:
continue
if data.TemplateType == 'P_SINGLEVALUE':
if data.TemplateType == 'P_SINGLEVALUE' \
or data.TemplateType == 'P_ENUMERATEDVALUE':
if data.PrimaryMeasureType:
value_type = data.PrimaryMeasureType
else:
@@ -1839,6 +1892,12 @@ class IfcExporter():
'ObjectPlacement': placement,
'Representation': self.get_product_shape(element)
})
if element['class'] == 'IfcSite':
element['attributes'].update({'SiteAddress': self.create_address(element['address'])})
elif element['class'] == 'IfcBuilding':
element['attributes'].update({'BuildingAddress': self.create_address(element['address'])})
element['ifc'] = self.file.create_entity(element['class'], **element['attributes'])
related_objects.append(element['ifc'])
self.create_spatial_structure_elements(node['children'], element['ifc'])
@@ -2144,11 +2203,15 @@ class IfcExporter():
def get_product_shape_representations(self, product):
results = []
for representation_name in product['representations']:
results.append(self.get_product_mapped_geometry(product, representation_name))
representation = self.ifc_parser.representations[representation_name]
if self.ifc_export_settings.should_roundtrip_native and representation['has_ifc_definition']:
results.append(representation['ifc'])
else:
results.append(self.get_product_mapped_geometry(product, representation))
return results
def get_product_mapped_geometry(self, product, representation_name):
mapping_source = self.ifc_parser.representations[representation_name]['ifc']
def get_product_mapped_geometry(self, product, representation):
mapping_source = representation['ifc']
shape_representation = mapping_source.MappedRepresentation
if product['has_scale']:
if not product['has_mirror']:
@@ -2194,6 +2257,8 @@ class IfcExporter():
self.file.createIfcDirection((forward.x, forward.y, forward.z)))
def create_representation(self, representation):
if self.ifc_export_settings.should_roundtrip_native and representation['has_ifc_definition']:
return self.create_representation_from_definition(representation)
self.ifc_vertices = []
self.ifc_edges = []
if representation['context'] == 'Model':
@@ -2203,6 +2268,34 @@ class IfcExporter():
elif representation['context'] == 'NotDefined':
return self.create_variable_representation(representation)
def create_representation_from_definition(self, representation):
if representation['ifc_definition']:
print('Authoring an IFC definition directly is not yet implemented')
return
elif representation['ifc_definition_id']:
entry = self.file.add(ifc.IfcStore.get_file().by_id(representation['ifc_definition_id']))
substitutions = []
for element in get_representation_elements(
ifc.IfcStore.get_file(), representation['ifc_definition_id']):
added_element = self.file.add(element)
if added_element.is_a('IfcGeometricRepresentationContext'):
substitutions.append(added_element)
for element in substitutions:
if element.is_a() == 'IfcGeometricRepresentationContext':
new_element = [e for e in
self.file.by_type('IfcGeometricRepresentationContext')
if e.ContextType == element.ContextType][0]
elif element.is_a() == 'IfcGeometricRepresentationSubContext':
new_element = [e for e in
self.file.by_type('IfcGeometricRepresentationContext')
if e.ContextType == element.ContextType and
e.ContextIdentifier == element.ContextIdentifier][0]
for inverse in self.file.get_inverse(element):
ifcopenshell.util.element.replace_attribute(inverse, element, new_element)
# TODO: Work out how and when to purge this
#self.file.remove(element)
return entry
def create_model_representation(self, representation):
if representation['subcontext'] == 'Annotation':
return self.file.createIfcRepresentationMap(self.origin,
@@ -2290,9 +2383,9 @@ class IfcExporter():
obj.bound_box[0][1],
obj.bound_box[0][2]
),
obj.dimensions[0],
obj.dimensions[1],
obj.dimensions[2]
self.convert_si_to_unit(obj.dimensions[0]),
self.convert_si_to_unit(obj.dimensions[1]),
self.convert_si_to_unit(obj.dimensions[2])
)
return self.file.createIfcShapeRepresentation(
self.ifc_rep_context[representation['context']][representation['subcontext']][
@@ -3035,6 +3128,7 @@ class IfcExportSettings:
settings.should_use_presentation_style_assignment = scene_bim.export_should_use_presentation_style_assignment
settings.should_guess_quantities = scene_bim.export_should_guess_quantities
settings.should_force_faceted_brep = scene_bim.export_should_force_faceted_brep
settings.should_roundtrip_native = scene_bim.import_export_should_roundtrip_native
settings.context_tree = []
for ifc_context in ['model', 'plan']:
if getattr(scene_bim, 'has_{}_context'.format(ifc_context)):
+157 -2
View File
@@ -1,4 +1,18 @@
import math
import bpy
# TODO: figure out where this should go
def get_representation_elements(ifc_file, step_id):
results = []
for child in ifc_file.traverse(ifc_file.by_id(step_id)):
if hasattr(child, 'StyledByItem') and child.StyledByItem:
for styled_by_item in child.StyledByItem:
for style in styled_by_item.Styles:
for style_child in ifc_file.traverse(style):
results.append(style_child)
results.append(child)
return results
# TODO: Deprecate this in favour of ifcopenshell.util.unit
@@ -18,12 +32,12 @@ class SIUnitHelper:
'yard': 0.914,
'mile': 1609,
'square inch': 0.0006452,
'square foot': 0.09290,
'square foot': 0.09290304,
'square yard': 0.83612736,
'acre': 4046.86,
'square mile': 2588881,
'cubic inch': 0.00001639,
'cubic foot': 0.02832,
'cubic foot': 0.02831684671168849,
'cubic yard': 0.7636,
'litre': 0.001,
'fluid ounce UK': 0.0000284130625,
@@ -101,3 +115,144 @@ class SIUnitHelper:
value *= (1 / SIUnitHelper.get_prefix_multiplier(to_prefix))
value *= (1 / SIUnitHelper.get_prefix_multiplier(to_prefix))
return value
# This function stolen from https://github.com/kevancress/MeasureIt_ARCH/blob/dcf607ce0896aa2284463c6b4ae9cd023fc54cbe/measureit_arch_baseclass.py
# MeasureIt-ARCH is GPL-v3
# In the future I will need to rewrite this to allow the user to have custom
# settings for each annotation object, not read from Blender.
def format_distance(value, isArea=False, hide_units=True):
s_code = "\u00b2" # Superscript two THIS IS LEGACY (but being kept for when Area Measurements are re-implimented)
# Get Scene Unit Settings
scaleFactor = bpy.context.scene.unit_settings.scale_length
unit_system = bpy.context.scene.unit_settings.system
unit_length = bpy.context.scene.unit_settings.length_unit
imperial_precision = 32
# (('1', "1\"", "1 Inch"),
# ('2', "1/2\"", "1/2 Inch"),
# ('4', "1/4\"", "1/4 Inch"),
# ('8', "1/8\"", "1/8th Inch"),
# ('16', "1/16\"", "1/16th Inch"),
# ('32', "1/32\"", "1/32th Inch"),
# ('64', "1/64\"", "1/64th Inch")),
toInches = 39.3700787401574887
inPerFoot = 11.999
if isArea:
toInches = 1550
inPerFoot = 143.999
value *= scaleFactor
# Imperial Formating
if unit_system == "IMPERIAL":
base = int(imperial_precision)
decInches = value * toInches
# Seperate ft and inches
# Unless Inches are the specified Length Unit
if unit_length != 'INCHES':
feet = math.floor(decInches/inPerFoot)
decInches -= feet*inPerFoot
else:
feet = 0
#Seperate Fractional Inches
inches = math.floor(decInches)
if inches != 0:
frac = round(base*(decInches-inches))
else:
frac = round(base*(decInches))
#Set proper numerator and denominator
if frac != base:
numcycles = int(math.log2(base))
for i in range(numcycles):
if frac%2 == 0:
frac = int(frac/2)
base = int(base/2)
else:
break
else:
frac = 0
inches += 1
# Check values and compose string
if inches == 12:
feet += 1
inches = 0
if inches !=0:
inchesString = str(inches)
if frac != 0: inchesString += "-"
else: inchesString += "\""
else: inchesString = ""
if feet != 0:
feetString = str(feet) + "' "
else: feetString = ""
if frac != 0:
fracString = str(frac) + "/" + str(base) +"\""
else: fracString = ""
if not isArea:
tx_dist = feetString + inchesString + fracString
else:
tx_dist = str('%1.3f' % (value*toInches/inPerFoot)) + " sq. ft."
# METRIC FORMATING
elif unit_system == "METRIC":
# Meters
if unit_length == 'METERS':
fmt = '%1.3f'
if hide_units is False:
fmt += " m"
tx_dist = fmt % value
# Centimeters
elif unit_length == 'CENTIMETERS':
fmt = '%1.1f'
if hide_units is False:
fmt += " cm"
d_cm = value * (100)
tx_dist = fmt % d_cm
#Millimeters
elif unit_length == 'MILLIMETERS':
fmt = '%1.0f'
if hide_units is False:
fmt += " mm"
d_mm = value * (1000)
tx_dist = fmt % d_mm
# Otherwise Use Adaptive Units
else:
if round(value, 2) >= 1.0:
fmt = '%1.3f'
if hide_units is False:
fmt += " m"
tx_dist = fmt % value
else:
if round(value, 2) >= 0.01:
fmt = '%1.1f'
if hide_units is False:
fmt += " cm"
d_cm = value * (100)
tx_dist = fmt % d_cm
else:
fmt = '%1.0f'
if hide_units is False:
fmt += " mm"
d_mm = value * (1000)
tx_dist = fmt % d_mm
if isArea:
tx_dist += s_code
else:
tx_dist = fmt % value
return tx_dist
@@ -107,7 +107,6 @@ class MaterialCreator():
return
if len(obj.material_slots) == 1:
return
slots = [self.canonicalise_material_name(s.name) for s in obj.material_slots]
material_to_slot = {}
for i, material in enumerate(mesh['ios_materials']):
if material == 'NULLMAT':
@@ -116,13 +115,16 @@ class MaterialCreator():
material = material.split('-')[2]
if len(bytes(material, 'utf-8')) > 63: # Blender material names are up to 63 UTF-8 bytes
material = bytes(material, 'utf-8')[0:63].decode('utf-8')
try:
material_to_slot[i] = slots.index(material)
except:
# If the material name duplicates, a `.001` is added, this
# reduces the maxmium characters for the material name to 59.
slot_index = obj.material_slots.find(material)
if slot_index == -1:
# If we can't find the material, it is possible that the
# material name is duplicated, and so a '.001' is added.
# The maximum characters for the material name is 59 in this
# scenario.
material = material[0:59]
material_to_slot[i] = slots.index(material)
slot_index = [self.canonicalise_material_name(s.name) for s in obj.material_slots].index(material)
material_to_slot[i] = slot_index
if len(mesh.polygons) == len(mesh['ios_material_ids']):
material_index = [(material_to_slot[mat_id] if mat_id != -1
@@ -150,6 +152,10 @@ class MaterialCreator():
self.create_single(material)
elif material.is_a('IfcMaterialLayerSet'):
self.create_layer_set(material)
elif material.is_a('IfcMaterialConstituentSet'):
self.create_constituent_set(material)
elif material.is_a('IfcMaterialList'):
self.create_material_list(material)
def create_single(self, material):
if material.Name not in self.materials:
@@ -164,8 +170,26 @@ class MaterialCreator():
self.create_new_single(layer.Material)
self.assign_material_to_mesh(self.materials[layer.Material.Name])
def create_constituent_set(self, constituent_set):
for constituent in constituent_set.MaterialConstituents:
if constituent.Material:
if constituent.Material.Name not in self.materials:
# TODO import rest of the layer set data
self.create_new_single(constituent.Material)
self.assign_material_to_mesh(self.materials[constituent.Material.Name])
def create_material_list(self, material_list):
for material in material_list.Materials:
if material.Material:
if material.Material.Name not in self.materials:
# TODO import rest of the layer set data
self.create_new_single(material.Material)
self.assign_material_to_mesh(self.materials[material.Material.Name])
def create_new_single(self, material):
self.materials[material.Name] = bpy.data.materials.new(material.Name)
self.materials[material.Name] = obj = bpy.data.materials.new(material.Name)
for pset in getattr(material, "HasProperties", ()):
self.add_pset(pset, obj)
if not material.HasRepresentation \
or not material.HasRepresentation[0].Representations:
return
@@ -175,13 +199,30 @@ class MaterialCreator():
for item in representation.Items:
if not item.is_a('IfcStyledItem'):
continue
self.parse_styled_item(item, self.materials[material.Name])
self.parse_styled_item(item, obj)
def add_pset(self, pset, obj):
new_pset = obj.BIMMaterialProperties.psets.add()
new_pset.name = pset.Name
if new_pset.name in schema.ifc.psets:
for prop_name in schema.ifc.psets[new_pset.name]['HasPropertyTemplates'].keys():
prop = new_pset.properties.add()
prop.name = prop_name
for prop in pset.Properties:
if prop.is_a('IfcPropertySingleValue') and prop.NominalValue:
index = new_pset.properties.find(prop.Name)
if index >= 0:
new_pset.properties[index].string_value = str(prop.NominalValue.wrappedValue)
else:
new_prop = new_pset.properties.add()
new_prop.name = prop.Name
new_prop.string_value = str(prop.NominalValue.wrappedValue)
def get_material_name(self, styled_item):
if styled_item.Name:
return styled_item.Name
styled_item = self.resolve_presentation_style_assignment(styled_item)
for style in styled_item.Styles:
styles = self.get_styled_item_styles(styled_item)
for style in styles:
if not style.is_a('IfcSurfaceStyle'):
continue
if style.Name:
@@ -190,8 +231,8 @@ class MaterialCreator():
return str(styled_item.id())
def parse_styled_item(self, styled_item, material):
styled_item = self.resolve_presentation_style_assignment(styled_item)
for style in styled_item.Styles:
styles = self.get_styled_item_styles(styled_item)
for style in styles:
if not style.is_a('IfcSurfaceStyle'):
continue
external_style = None
@@ -217,11 +258,14 @@ class MaterialCreator():
# IfcPresentationStyleAssignment is deprecated as of IFC4
# However it is still widely used thanks to Revit :(
def resolve_presentation_style_assignment(self, styled_item):
def get_styled_item_styles(self, styled_item):
styles = []
for style in styled_item.Styles:
if style.is_a('IfcPresentationStyleAssignment'):
return style
return styled_item
styles.extend(self.get_styled_item_styles(style))
else:
styles.append(style)
return styles
def resolve_mapped_representation_items(self, representation):
items = []
@@ -246,6 +290,9 @@ class IfcImporter():
self.diff = None
self.file = None
self.settings = ifcopenshell.geom.settings()
self.settings.set_deflection_tolerance(self.ifc_import_settings.deflection_tolerance)
# Uncomment this when the latest IfcOpenBot build is ready
# self.settings.set_angular_tolerance(self.ifc_import_settings.angular_tolerance)
if self.ifc_import_settings.should_import_curves:
self.settings.set(self.settings.INCLUDE_CURVES, True)
self.settings_native = ifcopenshell.geom.settings()
@@ -868,6 +915,7 @@ class IfcImporter():
cumulative_vertex_index += item['total_vertices']
def create_native_mesh(self, element, shape):
# TODO This should be split off into its own module for run-time native mesh conversion
data = self.native_elements[element.GlobalId]
materials = []
items = []
@@ -953,9 +1001,6 @@ class IfcImporter():
mesh['ios_material_ids'] = material_ids
mesh['ios_items'] = representation_items
mesh.BIMMeshProperties.is_native = True
for representation_item in representation_items:
new = mesh.BIMMeshProperties.representation_items.add()
new.name = representation_item['name']
return mesh
def get_representation_item_material_name(self, item):
@@ -1456,13 +1501,8 @@ class IfcImporter():
for entity in entities_to_add:
classification_file.add(entity)
classification_filename = '{}-{}'.format(
Path(os.path.basename(self.ifc_import_settings.input_file)).stem, element.Name)
classification_file.write(os.path.join(
bpy.context.scene.BIMProperties.schema_dir, 'project_classifications',
'{}.ifc'.format(classification_filename)))
classification.filename = classification_filename
self.classifications[classification.filename] = classification
classification.data = classification_file.to_string()
self.classifications[classification.name] = classification
self.schema_dir = bpy.context.scene.BIMProperties.schema_dir
from . import prop
@@ -1550,6 +1590,7 @@ class IfcImporter():
parent.children.link(collection)
obj = self.create_product(element)
if obj:
self.spatial_structure_elements[global_id]['blender_obj'] = obj
collection.objects.link(obj)
del self.added_data[element.GlobalId]
if element.IsDecomposedBy:
@@ -1699,8 +1740,15 @@ class IfcImporter():
if element.Decomposes[0].RelatingObject.is_a('IfcProject'):
collection = self.project['blender']
elif element.Decomposes[0].RelatingObject.is_a('IfcSpatialStructureElement'):
global_id = element.Decomposes[0].RelatingObject.GlobalId
if element.is_a('IfcSpatialStructureElement') and not element.is_a('IfcSpace'):
global_id = element.GlobalId
else:
global_id = element.Decomposes[0].RelatingObject.GlobalId
if global_id in self.spatial_structure_elements:
if element.is_a('IfcSpatialStructureElement') \
and not element.is_a('IfcSpace') \
and 'blender_obj' in self.spatial_structure_elements[global_id]:
bpy.data.objects.remove(self.spatial_structure_elements[global_id]['blender_obj'])
collection = self.spatial_structure_elements[global_id]['blender']
# This may occur if we are nesting an IfcSpace (which is special
# since it does not have a collection within an IfcSpace
@@ -1762,9 +1810,6 @@ class IfcImporter():
def get_referenced_source_name(self, element):
if not hasattr(element, 'ReferencedSource') or not element.ReferencedSource:
if element.is_a('IfcClassification'):
for filename, classification in self.classifications.items():
if classification.name == element.Name:
return filename
return element.Name
else:
return element.Identification
@@ -1821,6 +1866,11 @@ class IfcImporter():
results.append({ 'raw': representation, 'matrix': self.scale_matrix(matrix) })
return results
def get_representation_of_context(self, representations, context):
for representation in representations:
if representation.RepresentationIdentifier == context:
return representation
def scale_matrix(self, matrix):
matrix[0][3] *= self.unit_scale
matrix[1][3] *= self.unit_scale
@@ -1907,10 +1957,23 @@ class IfcImporter():
ios_materials.append(mat.name)
mesh['ios_materials'] = ios_materials
mesh['ios_material_ids'] = geometry.material_ids
mesh.BIMMeshProperties.geometry_type = str(self.get_geometry_type(element))
self.store_representation_source(mesh, element, shape)
return mesh
except:
self.ifc_import_settings.logger.error('Could not create mesh for {}'.format(element))
import traceback
print(traceback.format_exc())
def store_representation_source(self, mesh, element, shape):
# TODO Refactor to specialist class
mesh.BIMMeshProperties.geometry_type = str(self.get_geometry_type(element))
if not self.ifc_import_settings.should_roundtrip_native:
return
if element.is_a('IfcRepresentation'):
representation = element
else:
representation = self.get_representation_of_context(element.Representation.Representations, shape.context)
mesh.BIMMeshProperties.ifc_definition_id = int(representation.id())
def create_curve(self, geometry):
curve = bpy.data.curves.new(geometry.id, type='CURVE')
@@ -2057,6 +2120,7 @@ class IfcImportSettings:
settings.should_use_cpu_multiprocessing = scene_bim.import_should_use_cpu_multiprocessing
settings.should_import_with_profiling = scene_bim.import_should_import_with_profiling
settings.should_import_native = scene_bim.import_should_import_native
settings.should_roundtrip_native = scene_bim.import_export_should_roundtrip_native
settings.should_use_legacy = scene_bim.import_should_use_legacy
settings.should_import_aggregates = scene_bim.import_should_import_aggregates
settings.should_merge_aggregates = scene_bim.import_should_merge_aggregates
@@ -2064,4 +2128,6 @@ class IfcImportSettings:
settings.should_merge_by_material = scene_bim.import_should_merge_by_material
settings.should_merge_materials_by_colour = scene_bim.import_should_merge_materials_by_colour
settings.should_clean_mesh = scene_bim.import_should_clean_mesh
settings.deflection_tolerance = scene_bim.import_deflection_tolerance
settings.angular_tolerance = scene_bim.import_angular_tolerance
return settings
@@ -1,22 +1,39 @@
import bpy
from bpy.types import Operator
from bpy.props import FloatVectorProperty, FloatProperty
from bpy.props import FloatVectorProperty, FloatProperty, BoolProperty
from bpy_extras.object_utils import AddObjectHelper, object_data_add
from mathutils import Vector
def add_object(self, context):
verts = [
Vector((0, 0, 0)),
Vector((0, 0, self.height)),
Vector((self.length, 0, self.height)),
Vector((self.length, 0, 0)),
]
edges = []
faces = [[0, 1, 2, 3]]
if self.use_plane:
verts = [
Vector((0, 0, 0)),
Vector((0, 0, self.height)),
Vector((self.length, 0, self.height)),
Vector((self.length, 0, 0)),
]
edges = []
faces = [[0, 1, 2, 3]]
else:
verts = [
Vector((0, 0, 0)),
Vector((self.length, 0, 0)),
]
edges = [[0, 1]]
faces = []
mesh = bpy.data.meshes.new(name="Dumb Wall")
mesh.from_pydata(verts, edges, faces)
obj = object_data_add(context, mesh, operator=self)
if not self.use_plane:
modifier = obj.modifiers.new('Wall Height', 'SCREW')
modifier.angle = 0
modifier.screw_offset = self.height
modifier.use_smooth_shade = False
modifier.use_normal_calculate = True
modifier.use_normal_flip = True
modifier.steps = 1
modifier.render_steps = 1
modifier = obj.modifiers.new('Wall Width', 'SOLIDIFY')
modifier.use_even_offset = True
modifier.thickness = self.width
@@ -34,6 +51,7 @@ class BIM_OT_add_object(Operator, AddObjectHelper):
height: FloatProperty(name='Height', default=3)
length: FloatProperty(name='Length', default=1)
width: FloatProperty(name='Width', default=.2)
use_plane: BoolProperty(name='Use Plane', default=False)
def execute(self, context):
add_object(self, context)
+350 -83
View File
@@ -5,6 +5,7 @@ import time
import json
import logging
import webbrowser
import subprocess
import ifcopenshell
import ifcopenshell.util.selector
import ifcopenshell.util.geolocation
@@ -50,12 +51,26 @@ def depsgraph_update_pre_handler(scene):
def set_active_camera_resolution(scene):
if not scene.camera \
or '/' not in scene.camera.name:
or '/' not in scene.camera.name \
or not scene.DocProperties.drawings:
return
if scene.render.resolution_x != scene.camera.data.BIMCameraProperties.raster_x \
or scene.render.resolution_y != scene.camera.data.BIMCameraProperties.raster_y:
scene.render.resolution_x = scene.camera.data.BIMCameraProperties.raster_x
scene.render.resolution_y = scene.camera.data.BIMCameraProperties.raster_y
current_drawing = scene.DocProperties.drawings[scene.DocProperties.current_drawing_index]
if scene.camera != current_drawing.camera:
scene.DocProperties.current_drawing_index = scene.DocProperties.drawings.find(scene.camera.name.split('/')[1])
bpy.ops.bim.activate_view(drawing_index=scene.DocProperties.current_drawing_index)
def open_with_user_command(user_command, path):
if user_command:
commands = eval(user_command)
for command in commands:
subprocess.run(command)
else:
webbrowser.open('file://' + path)
class ExportIFC(bpy.types.Operator):
@@ -94,6 +109,8 @@ class ExportIFC(bpy.types.Operator):
if not bpy.context.scene.DocProperties.ifc_files:
new = bpy.context.scene.DocProperties.ifc_files.add()
new.name = output_file
if not bpy.context.scene.BIMProperties.ifc_file:
bpy.context.scene.BIMProperties.ifc_file = output_file
return {'FINISHED'}
class ImportIFC(bpy.types.Operator, ImportHelper):
@@ -1905,11 +1922,18 @@ class ExplodeAggregate(bpy.types.Operator):
class LoadClassification(bpy.types.Operator):
bl_idname = 'bim.load_classification'
bl_label = 'Load Classification'
is_file: bpy.props.BoolProperty()
classification_index: bpy.props.IntProperty()
def execute(self, context):
from . import prop
prop.ClassificationView.raw_data = schema.ifc.load_classification(
context.scene.BIMProperties.classification)
if self.is_file:
prop.ClassificationView.raw_data = schema.ifc.load_classification(
context.scene.BIMProperties.classification)
else:
prop.ClassificationView.raw_data = schema.ifc.load_classification(
context.scene.BIMProperties.classifications[self.classification_index].name,
self.classification_index)
context.scene.BIMProperties.classification_references.root = ''
return {'FINISHED'}
@@ -1932,7 +1956,7 @@ class AddClassification(bpy.types.Operator):
for key, value in data_map.items():
if hasattr(data, value) and getattr(data, value):
setattr(classification, key, str(getattr(data, value)))
classification.filename = context.scene.BIMProperties.classification
classification.data = schema.ifc.classification_files[context.scene.BIMProperties.classification].to_string()
return {'FINISHED'}
@@ -1962,7 +1986,7 @@ class AssignClassification(bpy.types.Operator):
for key in ['location', 'description']:
if data[key]:
setattr(classification, key, data[key])
classification.referenced_source = bpy.context.scene.BIMProperties.classification
classification.referenced_source = bpy.context.scene.BIMProperties.active_classification_name
return {'FINISHED'}
@@ -2089,9 +2113,9 @@ class OpenView(bpy.types.Operator):
view: bpy.props.StringProperty()
def execute(self, context):
webbrowser.open('file://' + os.path.join(
bpy.context.scene.BIMProperties.data_dir, 'diagrams',
self.view + '.svg'))
open_with_user_command(
bpy.context.preferences.addons['blenderbim'].preferences.svg_command,
os.path.join(bpy.context.scene.BIMProperties.data_dir, 'diagrams', self.view + '.svg'))
return {'FINISHED'}
@@ -2103,16 +2127,15 @@ class CutSection(bpy.types.Operator):
camera = bpy.context.scene.camera
if not (camera.type == 'CAMERA' and camera.data.type == 'ORTHO'):
return {'FINISHED'}
bpy.ops.bim.activate_view(drawing_index=bpy.context.scene.DocProperties.drawings.find(camera.name.split('/')[1]))
drawing_style = bpy.context.scene.DocProperties.drawing_styles[camera.data.BIMCameraProperties.active_drawing_style_index]
self.diagram_name = camera.name.split('/')[1]
bpy.context.scene.render.filepath = os.path.join(
bpy.context.scene.BIMProperties.data_dir,
'diagrams',
'{}.png'.format(self.diagram_name)
)
if bpy.context.scene.DocProperties.should_render == 'DEFAULT':
bpy.ops.render.render(write_still=True)
elif bpy.context.scene.DocProperties.should_render == 'VIEWPORT':
bpy.ops.render.opengl(write_still=True)
self.create_raster(camera, drawing_style)
location = camera.location
render = bpy.context.scene.render
if self.is_landscape():
@@ -2130,7 +2153,7 @@ class CutSection(bpy.types.Operator):
import ifccsv
ifc_cutter.ifc_filenames = [i.name for i in bpy.context.scene.DocProperties.ifc_files]
ifc_cutter.data_dir = bpy.context.scene.BIMProperties.data_dir
ifc_cutter.vector_style = bpy.context.scene.DocProperties.drawing_styles[camera.data.BIMCameraProperties.active_drawing_style_index].vector_style
ifc_cutter.vector_style = drawing_style.vector_style
ifc_cutter.diagram_name = self.diagram_name
ifc_cutter.background_image = bpy.context.scene.render.filepath
if camera.data.BIMCameraProperties.cut_objects == 'CUSTOM':
@@ -2148,7 +2171,17 @@ class CutSection(bpy.types.Operator):
ifc_cutter.section_level_obj = None
ifc_cutter.grid_objs = []
ifc_cutter.text_objs = []
ifc_cutter.misc_objs = []
ifc_cutter.attributes = [a.name for a in drawing_style.attributes]
for obj in camera.users_collection[0].objects:
if 'IfcGrid' in obj.name:
ifc_cutter.grid_objs.append(obj)
elif 'IfcGroup' in obj.name and obj.type == 'CAMERA':
ifc_cutter.camera_obj = obj
if 'IfcAnnotation/' not in obj.name:
continue
if 'Leader' in obj.name:
ifc_cutter.leader_obj = (obj, obj.data)
elif 'Stair' in obj.name:
@@ -2163,16 +2196,14 @@ class CutSection(bpy.types.Operator):
ifc_cutter.hidden_objs.append((obj, obj.data))
elif 'Solid' in obj.name:
ifc_cutter.solid_objs.append((obj, obj.data))
elif 'IfcGrid' in obj.name:
ifc_cutter.grid_objs.append(obj)
elif 'Plan Level' in obj.name:
ifc_cutter.plan_level_obj = obj
elif 'Section Level' in obj.name:
ifc_cutter.section_level_obj = obj
elif obj.type == 'CAMERA':
ifc_cutter.camera_obj = obj
elif obj.type == 'FONT':
ifc_cutter.text_objs.append(obj)
else:
ifc_cutter.misc_objs.append(obj)
ifc_cutter.section_box = {
'projection': tuple(projection),
@@ -2216,9 +2247,53 @@ class CutSection(bpy.types.Operator):
bpy.ops.bim.open_view(view=self.diagram_name)
return {'FINISHED'}
def create_raster(self, camera, drawing_style):
if drawing_style.render_type == 'NONE':
return
if drawing_style.render_type == 'DEFAULT':
return bpy.ops.render.render(write_still=True)
previous_visibility = {}
for obj in camera.users_collection[0].objects:
previous_visibility[obj.name] = obj.hide_get()
obj.hide_set(True)
for obj in bpy.context.visible_objects:
if not obj.data \
or isinstance(obj.data, bpy.types.Camera) \
or 'IfcGrid/' in obj.name \
or 'IfcGridAxis/' in obj.name \
or 'IfcOpeningElement/' in obj.name \
or self.does_obj_have_target_view_representation(obj, camera):
previous_visibility[obj.name] = obj.hide_get()
obj.hide_set(True)
space = self.get_view_3d()
previous_shading = space.shading.type
space.shading.type = 'RENDERED'
bpy.ops.render.opengl(write_still=True)
space.shading.type = previous_shading
for name, value in previous_visibility.items():
bpy.data.objects[name].hide_set(value)
def does_obj_have_target_view_representation(self, obj, camera):
return camera.data.BIMCameraProperties.target_view in [c.target_view for c in obj.BIMObjectProperties.representation_contexts]
def is_landscape(self):
return bpy.context.scene.render.resolution_x > bpy.context.scene.render.resolution_y
def get_view_3d(self):
for area in bpy.context.screen.areas:
if area.type != 'VIEW_3D':
continue
for space in area.spaces:
if space.type != 'VIEW_3D':
continue
return space
class AddSheet(bpy.types.Operator):
bl_idname = 'bim.add_sheet'
@@ -2239,9 +2314,11 @@ class OpenSheet(bpy.types.Operator):
def execute(self, context):
props = bpy.context.scene.DocProperties
webbrowser.open('file://' + os.path.join(
bpy.context.scene.BIMProperties.data_dir, 'sheets',
props.sheets[props.active_sheet_index].name + '.svg'))
open_with_user_command(
bpy.context.preferences.addons['blenderbim'].preferences.svg_command,
os.path.join(
bpy.context.scene.BIMProperties.data_dir, 'sheets',
props.sheets[props.active_sheet_index].name + '.svg'))
return {'FINISHED'}
@@ -2253,9 +2330,12 @@ class AddDrawingToSheet(bpy.types.Operator):
props = bpy.context.scene.DocProperties
sheet_builder = sheeter.SheetBuilder()
sheet_builder.data_dir = bpy.context.scene.BIMProperties.data_dir
sheet_builder.add_drawing(
props.drawings[props.active_drawing_index].name,
props.sheets[props.active_sheet_index].name)
try:
sheet_builder.add_drawing(
props.drawings[props.active_drawing_index].name,
props.sheets[props.active_sheet_index].name)
except:
self.report({'ERROR'}, 'Drawings need to be created before being added to a sheet')
return {'FINISHED'}
@@ -2271,8 +2351,9 @@ class CreateSheets(bpy.types.Operator):
sheet_builder.build(name)
svg2pdf_command = bpy.context.preferences.addons['blenderbim'].preferences.svg2pdf_command
svg2dxf_command = bpy.context.preferences.addons['blenderbim'].preferences.svg2dxf_command
if svg2pdf_command:
import subprocess
path = os.path.join(bpy.context.scene.BIMProperties.data_dir, 'build', name)
svg = os.path.join(path, name + '.svg')
pdf = os.path.join(path, name + '.pdf')
@@ -2282,9 +2363,7 @@ class CreateSheets(bpy.types.Operator):
for command in commands:
subprocess.run(command)
svg2dxf_command = bpy.context.preferences.addons['blenderbim'].preferences.svg2dxf_command
if svg2dxf_command:
import subprocess
path = os.path.join(bpy.context.scene.BIMProperties.data_dir, 'build', name)
svg = os.path.join(path, name + '.svg')
eps = os.path.join(path, name + '.eps')
@@ -2298,18 +2377,21 @@ class CreateSheets(bpy.types.Operator):
if svg2pdf_command:
webbrowser.open('file://' + os.path.join(pdf))
open_with_user_command(bpy.context.preferences.addons['blenderbim'].preferences.pdf_command, pdf)
else:
webbrowser.open('file://' + os.path.join(bpy.context.scene.BIMProperties.data_dir, 'build', name, name + '.svg'))
open_with_user_command(
bpy.context.preferences.addons['blenderbim'].preferences.svg_command,
os.path.join(bpy.context.scene.BIMProperties.data_dir, 'build', name, name + '.svg'))
return {'FINISHED'}
class ActivateView(bpy.types.Operator):
bl_idname = 'bim.activate_view'
bl_label = 'Activate View'
drawing_index: bpy.props.IntProperty()
def execute(self, context):
camera = bpy.context.scene.DocProperties.drawings[bpy.context.scene.DocProperties.active_drawing_index].camera
camera = bpy.context.scene.DocProperties.drawings[self.drawing_index].camera
if not camera:
return {'FINISHED'}
bpy.context.scene.camera = camera
@@ -2321,38 +2403,10 @@ class ActivateView(bpy.types.Operator):
bpy.data.collections.get(collection.name).hide_render = True
bpy.context.view_layer.layer_collection.children['Views'].children[camera.users_collection[0].name].hide_viewport = False
bpy.data.collections.get(camera.users_collection[0].name).hide_render = False
bpy.ops.bim.activate_drawing_style()
return {'FINISHED'}
class AssignContext(bpy.types.Operator):
bl_idname = 'bim.assign_context'
bl_label = 'Assign Context'
def execute(self, context):
if not self.is_mesh_context_sensitive(bpy.context.active_object.data.name):
bpy.context.active_object.data.name = '{}/{}/{}/{}'.format(
bpy.context.scene.BIMProperties.available_contexts,
bpy.context.scene.BIMProperties.available_subcontexts,
bpy.context.scene.BIMProperties.available_target_views,
bpy.context.active_object.data.name
)
else:
bpy.context.active_object.data.name = '{}/{}/{}/{}'.format(
bpy.context.scene.BIMProperties.available_contexts,
bpy.context.scene.BIMProperties.available_subcontexts,
bpy.context.scene.BIMProperties.available_target_views,
bpy.context.active_object.data.name.split('/')[3]
)
return {'FINISHED'}
def is_mesh_context_sensitive(self, name):
return '/' in name \
and ( \
name[0:6] == 'Model/' \
or name[0:5] == 'Plan/' \
)
class SwitchContext(bpy.types.Operator):
bl_idname = 'bim.switch_context'
bl_label = 'Switch Context'
@@ -2488,7 +2542,7 @@ class OpenUpstream(bpy.types.Operator):
elif self.page == 'docs':
webbrowser.open('https://blenderbim.org/docs/')
elif self.page == 'wiki':
webbrowser.open('https://wiki.osarch.org/')
webbrowser.open('https://wiki.osarch.org/index.php?title=Category:BlenderBIM_Add-on')
elif self.page == 'community':
webbrowser.open('https://community.osarch.org/')
return {'FINISHED'}
@@ -2557,6 +2611,46 @@ class BIM_OT_CopyAttributesToSelection(bpy.types.Operator):
except: pass
class CopyPropertyToSelection(bpy.types.Operator):
bl_idname = 'bim.copy_property_to_selection'
bl_label = 'Copy Property To Selection'
pset_name: bpy.props.StringProperty()
prop_name: bpy.props.StringProperty()
prop_value: bpy.props.StringProperty()
def execute(self, context):
self.applicable_psets_cache = {}
self.empty = ifcopenshell.file()
for obj in bpy.context.selected_objects:
if '/' not in obj.name:
continue
pset = obj.BIMObjectProperties.psets.get(self.pset_name)
if not pset:
applicable_psets = self.get_applicable_psets(obj.name.split('/')[0])
if self.pset_name not in applicable_psets:
continue
pset = obj.BIMObjectProperties.psets.add()
pset.name = self.pset_name
for template_prop_name in schema.ifc.psets[self.pset_name]['HasPropertyTemplates'].keys():
prop = pset.properties.add()
prop.name = template_prop_name
prop = pset.properties.get(self.prop_name)
if prop:
prop.string_value = self.prop_value
return {'FINISHED'}
# TODO: move into util module. See bug #971
def get_applicable_psets(self, element_class):
if element_class not in self.applicable_psets_cache:
element = self.empty.create_entity(element_class)
applicable_psets = []
for ifc_class, pset_names in schema.ifc.applicable_psets.items():
if element.is_a(ifc_class):
applicable_psets.extend(pset_names)
self.applicable_psets_cache[element_class] = applicable_psets
return self.applicable_psets_cache[element_class]
class BIM_OT_ChangeClassificationLevel(bpy.types.Operator):
bl_idname = "bim.change_classification_level"
bl_label = "Change Classification Level"
@@ -2716,9 +2810,13 @@ class AddSectionPlane(bpy.types.Operator):
section = bpy.data.objects.new('Section', None)
section.empty_display_type = 'SINGLE_ARROW'
section.empty_display_size = 5
section.rotation_euler = Euler((radians(180.0), 0.0, 0.0), 'XYZ')
section.location = bpy.context.scene.cursor.location
section.show_in_front = True
if bpy.context.active_object.select_get() \
and isinstance(bpy.context.active_object.data, bpy.types.Camera):
section.matrix_world = bpy.context.active_object.matrix_world @ Euler((radians(180.0), 0.0, 0.0), 'XYZ').to_matrix().to_4x4()
else:
section.rotation_euler = Euler((radians(180.0), 0.0, 0.0), 'XYZ')
section.location = bpy.context.scene.cursor.location
collection = bpy.data.collections.get('Sections')
if not collection:
collection = bpy.data.collections.new('Sections')
@@ -3401,7 +3499,7 @@ class GuessQuantity(bpy.types.Operator):
def get_prefix_name(self, value):
if '/' in value:
return value.split('/')
return None, bpy.context.scene.BIMProperties.area_unit
return None, value
def get_blender_prefix_name(self):
if bpy.context.scene.unit_settings.system == 'IMPERIAL':
@@ -3428,7 +3526,7 @@ class ExecuteBIMTester(bpy.types.Operator):
os.chdir(bpy.context.scene.BIMProperties.features_dir)
bimtester.run_tests({'feature': filename, 'advanced_arguments': None, 'console': False})
bimtester.generate_report()
webbrowser.open(os.path.join(
webbrowser.open('file://' + os.path.join(
bpy.context.scene.BIMProperties.features_dir,
'report', bpy.context.scene.BIMProperties.features_file + '.feature.html'))
os.chdir(cwd)
@@ -3534,7 +3632,10 @@ class AddOpening(bpy.types.Operator):
bl_label = 'Add Opening'
def execute(self, context):
opening = context.active_object
if context.active_object.children and 'IfcOpeningElement/' in context.active_object.children[0].name:
opening = context.active_object.children[0]
else:
opening = context.active_object
if context.selected_objects[0] != context.active_object:
obj = context.selected_objects[0]
else:
@@ -3584,9 +3685,11 @@ class SaveDrawingStyle(bpy.types.Operator):
index: bpy.props.StringProperty()
def execute(self, context):
space = self.get_view_3d()
style = {
'bpy.data.worlds[0].color': tuple(bpy.data.worlds[0].color),
'bpy.context.scene.render.engine': bpy.context.scene.render.engine,
'bpy.context.scene.render.film_transparent': bpy.context.scene.render.film_transparent,
'bpy.context.scene.display.shading.show_object_outline': bpy.context.scene.display.shading.show_object_outline,
'bpy.context.scene.display.shading.show_cavity': bpy.context.scene.display.shading.show_cavity,
'bpy.context.scene.display.shading.cavity_type': bpy.context.scene.display.shading.cavity_type,
@@ -3596,7 +3699,18 @@ class SaveDrawingStyle(bpy.types.Operator):
'bpy.context.scene.display.shading.light': bpy.context.scene.display.shading.light,
'bpy.context.scene.display.shading.color_type': bpy.context.scene.display.shading.color_type,
'bpy.context.scene.display.shading.single_color': tuple(bpy.context.scene.display.shading.single_color),
'bpy.context.scene.display.shading.show_shadows': bpy.context.scene.display.shading.show_shadows,
'bpy.context.scene.display.shading.shadow_intensity': bpy.context.scene.display.shading.shadow_intensity,
'bpy.context.scene.display.light_direction': tuple(bpy.context.scene.display.light_direction),
'bpy.context.scene.view_settings.use_curve_mapping': bpy.context.scene.view_settings.use_curve_mapping,
'space.overlay.show_wireframes': space.overlay.show_wireframes,
'space.overlay.wireframe_threshold': space.overlay.wireframe_threshold,
'space.overlay.show_floor': space.overlay.show_floor,
'space.overlay.show_axis_x': space.overlay.show_axis_x,
'space.overlay.show_axis_y': space.overlay.show_axis_y,
'space.overlay.show_axis_z': space.overlay.show_axis_z,
'space.overlay.show_object_origins': space.overlay.show_object_origins,
'space.overlay.show_relationship_lines': space.overlay.show_relationship_lines,
}
if self.index:
index = int(self.index)
@@ -3605,21 +3719,32 @@ class SaveDrawingStyle(bpy.types.Operator):
bpy.context.scene.DocProperties.drawing_styles[index].raster_style = json.dumps(style)
return {'FINISHED'}
def get_view_3d(self):
for area in bpy.context.screen.areas:
if area.type != 'VIEW_3D':
continue
for space in area.spaces:
if space.type != 'VIEW_3D':
continue
return space
class ActivateDrawingStyle(bpy.types.Operator):
bl_idname = 'bim.activate_drawing_style'
bl_label = 'Activate Drawing Style'
def execute(self, context):
self.drawing_style = bpy.context.scene.DocProperties.drawing_styles[bpy.context.active_object.data.BIMCameraProperties.active_drawing_style_index]
self.drawing_style = bpy.context.scene.DocProperties.drawing_styles[context.scene.camera.data.BIMCameraProperties.active_drawing_style_index]
self.set_raster_style()
self.set_query()
return {'FINISHED'}
def set_raster_style(self):
space = self.get_view_3d()
style = json.loads(self.drawing_style.raster_style)
bpy.data.worlds[0].color = style['bpy.data.worlds[0].color']
bpy.context.scene.render.engine = style['bpy.context.scene.render.engine']
bpy.context.scene.render.film_transparent = style['bpy.context.scene.render.film_transparent']
bpy.context.scene.display.shading.show_object_outline = style['bpy.context.scene.display.shading.show_object_outline']
bpy.context.scene.display.shading.show_cavity = style['bpy.context.scene.display.shading.show_cavity']
bpy.context.scene.display.shading.cavity_type = style['bpy.context.scene.display.shading.cavity_type']
@@ -3629,14 +3754,30 @@ class ActivateDrawingStyle(bpy.types.Operator):
bpy.context.scene.display.shading.light = style['bpy.context.scene.display.shading.light']
bpy.context.scene.display.shading.color_type = style['bpy.context.scene.display.shading.color_type']
bpy.context.scene.display.shading.single_color = style['bpy.context.scene.display.shading.single_color']
bpy.context.scene.display.shading.show_shadows = style['bpy.context.scene.display.shading.show_shadows']
bpy.context.scene.display.shading.shadow_intensity = style['bpy.context.scene.display.shading.shadow_intensity']
bpy.context.scene.display.light_direction = style['bpy.context.scene.display.light_direction']
bpy.context.scene.view_settings.use_curve_mapping = style['bpy.context.scene.view_settings.use_curve_mapping']
space.overlay.show_wireframes = style['space.overlay.show_wireframes']
space.overlay.wireframe_threshold = style['space.overlay.wireframe_threshold']
space.overlay.show_floor = style['space.overlay.show_floor']
space.overlay.show_axis_x = style['space.overlay.show_axis_x']
space.overlay.show_axis_y = style['space.overlay.show_axis_y']
space.overlay.show_axis_z = style['space.overlay.show_axis_z']
space.overlay.show_object_origins = style['space.overlay.show_object_origins']
space.overlay.show_relationship_lines = style['space.overlay.show_relationship_lines']
space.shading.type = 'RENDERED'
def set_query(self):
self.selector = ifcopenshell.util.selector.Selector()
self.include_global_ids = []
self.exclude_global_ids = []
for ifc_file in bpy.context.scene.DocProperties.ifc_files:
ifc = ifcopenshell.open(ifc_file.name)
try:
ifc = ifcopenshell.open(ifc_file.name)
except:
continue
if self.drawing_style.include_query:
results = self.selector.parse(ifc, self.drawing_style.include_query)
self.include_global_ids.extend([e.GlobalId for e in results])
@@ -3671,6 +3812,16 @@ class ActivateDrawingStyle(bpy.types.Operator):
obj.hide_viewport = True # Note: this breaks alt-H
def get_view_3d(self):
for area in bpy.context.screen.areas:
if area.type != 'VIEW_3D':
continue
for space in area.spaces:
if space.type != 'VIEW_3D':
continue
return space
class AddDrawing(bpy.types.Operator):
bl_idname = 'bim.add_drawing'
bl_label = 'Add Drawing'
@@ -3697,6 +3848,7 @@ class AddDrawing(bpy.types.Operator):
area = next(area for area in bpy.context.screen.areas if area.type == 'VIEW_3D')
area.spaces[0].region_3d.view_perspective = 'CAMERA'
new.camera = camera
bpy.ops.bim.activate_drawing_style()
return {'FINISHED'}
@@ -3727,22 +3879,6 @@ class EditVectorStyle(bpy.types.Operator):
return {'FINISHED'}
class PurgeProjectClassifications(bpy.types.Operator):
bl_idname = 'bim.purge_project_classifications'
bl_label = 'Purge Project Classifications'
def execute(self, context):
self.schema_dir = bpy.context.scene.BIMProperties.schema_dir
path = os.path.join(self.schema_dir, 'project_classifications')
files = os.listdir(path)
for f in files:
os.remove(os.path.join(path, f))
from . import prop
prop.classification_enum.clear()
prop.getClassifications(self, context)
return {'FINISHED'}
class RemoveSheet(bpy.types.Operator):
bl_idname = 'bim.remove_sheet'
bl_label = 'Remove Sheet'
@@ -3804,7 +3940,9 @@ class BuildSchedule(bpy.types.Operator):
bpy.context.scene.BIMProperties.data_dir, 'schedules',
schedule.name + '.svg')
schedule_creator.schedule(schedule.file, outfile)
webbrowser.open('file://' + outfile)
open_with_user_command(
bpy.context.preferences.addons['blenderbim'].preferences.svg_command,
outfile)
return {'FINISHED'}
@@ -3856,3 +3994,132 @@ class GetNorthOffset(bpy.types.Operator):
bpy.context.scene.MapConversion.x_axis_abscissa = str(cos(x_angle))
bpy.context.scene.MapConversion.x_axis_ordinate = str(sin(x_angle))
return {'FINISHED'}
class AddDrawingStyleAttribute(bpy.types.Operator):
bl_idname = 'bim.add_drawing_style_attribute'
bl_label = 'Add Drawing Style Attribute'
def execute(self, context):
props = bpy.context.scene.camera.data.BIMCameraProperties
context.scene.DocProperties.drawing_styles[props.active_drawing_style_index].attributes.add()
return {'FINISHED'}
class RemoveDrawingStyleAttribute(bpy.types.Operator):
bl_idname = 'bim.remove_drawing_style_attribute'
bl_label = 'Remove Drawing Style Attribute'
index: bpy.props.IntProperty()
def execute(self, context):
props = bpy.context.scene.camera.data.BIMCameraProperties
context.scene.DocProperties.drawing_styles[props.active_drawing_style_index].attributes.remove(self.index)
return {'FINISHED'}
class CreateShapeFromStepId(bpy.types.Operator):
bl_idname = 'bim.create_shape_from_step_id'
bl_label = 'Create Shape From STEP ID'
def execute(self, context):
logger = logging.getLogger('ImportIFC')
self.ifc_import_settings = import_ifc.IfcImportSettings.factory(bpy.context, ifc.IfcStore.path, logger)
self.file = ifc.IfcStore.get_file()
element = self.file.by_id(int(bpy.context.scene.BIMDebugProperties.step_id))
settings = ifcopenshell.geom.settings()
#settings.set(settings.INCLUDE_CURVES, True)
shape = ifcopenshell.geom.create_shape(settings, element)
ifc_importer = import_ifc.IfcImporter(self.ifc_import_settings)
ifc_importer.file = self.file
mesh = ifc_importer.create_mesh(element, shape)
obj = bpy.data.objects.new('Debug', mesh)
bpy.context.scene.collection.objects.link(obj)
return {'FINISHED'}
class SelectHighPolygonMeshes(bpy.types.Operator):
bl_idname = 'bim.select_high_polygon_meshes'
bl_label = 'Select High Polygon Meshes'
def execute(self, context):
results = {}
for obj in bpy.data.objects:
if not isinstance(obj.data, bpy.types.Mesh) \
or len(obj.data.polygons) < int(bpy.context.scene.BIMDebugProperties.number_of_polygons):
continue
try:
obj.select_set(True)
except:
# If it is not in the view layer
pass
relating_type = obj.BIMObjectProperties.relating_type
if relating_type:
relating_type.select_set(True)
return {'FINISHED'}
class RefreshDrawingList(bpy.types.Operator):
bl_idname = 'bim.refresh_drawing_list'
bl_label = 'Refresh Drawing List'
def execute(self, context):
while len(bpy.context.scene.DocProperties.drawings) > 0:
bpy.context.scene.DocProperties.drawings.remove(0)
for obj in bpy.context.scene.objects:
if not isinstance(obj.data, bpy.types.Camera):
continue
if 'IfcGroup/' in obj.name and obj.users_collection[0].name == obj.name:
new = bpy.context.scene.DocProperties.drawings.add()
new.name = obj.name.split('/')[1]
new.camera = obj
return {'FINISHED'}
class GetRepresentationIfcParameters(bpy.types.Operator):
bl_idname = 'bim.get_representation_ifc_parameters'
bl_label = 'Get Representation IFC Parameters'
def execute(self, context):
props = bpy.context.active_object.data.BIMMeshProperties
dummy = ifcopenshell.file.from_string(props.ifc_definition)
for element in dummy:
if not element.is_a('IfcRepresentationItem'):
continue
for i in range(0, len(element)):
if element.attribute_type(i) == 'DOUBLE':
new = props.ifc_parameters.add()
new.name = '{}/{}'.format(element.is_a(), element.attribute_name(i))
new.step_id = element.id()
new.type = element.attribute_type(i)
new.index = i
if element[i]:
new.value = element[i]
return {'FINISHED'}
class UpdateIfcRepresentation(bpy.types.Operator):
bl_idname = 'bim.update_ifc_representation'
bl_label = 'Update IFC Representation'
index: bpy.props.IntProperty()
def execute(self, context):
props = bpy.context.active_object.data.BIMMeshProperties
parameter = props.ifc_parameters[self.index]
dummy = ifcopenshell.file.from_string(props.ifc_definition)
element = dummy.by_id(parameter.step_id)[parameter.index] = parameter.value
props.ifc_definition = dummy.to_string()
self.recreate_ifc_representation()
return {'FINISHED'}
def recreate_ifc_representation(self):
props = bpy.context.active_object.data.BIMMeshProperties
dummy = ifcopenshell.file.from_string(props.ifc_definition)
logger = logging.getLogger('ImportIFC')
self.ifc_import_settings = import_ifc.IfcImportSettings.factory(bpy.context, ifc.IfcStore.path, logger)
element = dummy.by_id(props.ifc_definition_id)
settings = ifcopenshell.geom.settings()
shape = ifcopenshell.geom.create_shape(settings, element)
ifc_importer = import_ifc.IfcImporter(self.ifc_import_settings)
ifc_importer.file = dummy
mesh = ifc_importer.create_mesh(element, shape)
bpy.context.active_object.data.user_remap(mesh)
+92 -34
View File
@@ -63,15 +63,48 @@ def setDefaultProperties(scene):
subcontext = bpy.context.scene.BIMProperties.model_subcontexts.add()
subcontext.name = 'Box'
subcontext.target_view = 'MODEL_VIEW'
if bpy.context.scene.BIMProperties.has_plan_context \
and len(bpy.context.scene.BIMProperties.plan_subcontexts) == 0:
subcontext = bpy.context.scene.BIMProperties.plan_subcontexts.add()
subcontext.name = 'Annotation'
subcontext.target_view = 'PLAN_VIEW'
if len(bpy.context.scene.DocProperties.drawing_styles) == 0:
drawing_style = bpy.context.scene.DocProperties.drawing_styles.add()
drawing_style.name = 'Blender Default'
bpy.ops.bim.save_drawing_style(index='0')
drawing_style = bpy.context.scene.DocProperties.drawing_styles.add()
drawing_style.name = 'Shaded'
drawing_style.name = 'Technical'
drawing_style.render_type = 'VIEWPORT'
drawing_style.raster_style = json.dumps({
'bpy.data.worlds[0].color': (1, 1, 1),
'bpy.context.scene.render.engine': 'BLENDER_WORKBENCH',
'bpy.context.scene.render.film_transparent': False,
'bpy.context.scene.display.shading.show_object_outline': True,
'bpy.context.scene.display.shading.show_cavity': False,
'bpy.context.scene.display.shading.cavity_type': 'BOTH',
'bpy.context.scene.display.shading.curvature_ridge_factor': 1,
'bpy.context.scene.display.shading.curvature_valley_factor': 1,
'bpy.context.scene.view_settings.view_transform': 'Standard',
'bpy.context.scene.display.shading.light': 'FLAT',
'bpy.context.scene.display.shading.color_type': 'SINGLE',
'bpy.context.scene.display.shading.single_color': (1, 1, 1),
'bpy.context.scene.display.shading.show_shadows': False,
'bpy.context.scene.display.shading.shadow_intensity': 0.5,
'bpy.context.scene.display.light_direction': (.5, .5, .5),
'bpy.context.scene.view_settings.use_curve_mapping': False,
'space.overlay.show_wireframes': True,
'space.overlay.wireframe_threshold': 0,
'space.overlay.show_floor': False,
'space.overlay.show_axis_x': False,
'space.overlay.show_axis_y': False,
'space.overlay.show_axis_z': False,
'space.overlay.show_object_origins': False,
'space.overlay.show_relationship_lines': False,
})
drawing_style = bpy.context.scene.DocProperties.drawing_styles.add()
drawing_style.name = 'Shaded'
drawing_style.render_type = 'VIEWPORT'
drawing_style.raster_style = json.dumps({
'bpy.data.worlds[0].color': (1, 1, 1),
'bpy.context.scene.render.engine': 'BLENDER_WORKBENCH',
'bpy.context.scene.render.film_transparent': False,
'bpy.context.scene.display.shading.show_object_outline': True,
'bpy.context.scene.display.shading.show_cavity': True,
'bpy.context.scene.display.shading.cavity_type': 'BOTH',
@@ -81,26 +114,23 @@ def setDefaultProperties(scene):
'bpy.context.scene.display.shading.light': 'STUDIO',
'bpy.context.scene.display.shading.color_type': 'MATERIAL',
'bpy.context.scene.display.shading.single_color': (1, 1, 1),
'bpy.context.scene.display.shading.show_shadows': True,
'bpy.context.scene.display.shading.shadow_intensity': 0.5,
'bpy.context.scene.display.light_direction': (.5, .5, .5),
'bpy.context.scene.view_settings.use_curve_mapping': False,
'space.overlay.show_wireframes': True,
'space.overlay.wireframe_threshold': 0,
'space.overlay.show_floor': False,
'space.overlay.show_axis_x': False,
'space.overlay.show_axis_y': False,
'space.overlay.show_axis_z': False,
'space.overlay.show_object_origins': False,
'space.overlay.show_relationship_lines': False,
})
drawing_style = bpy.context.scene.DocProperties.drawing_styles.add()
drawing_style.name = 'Technical'
drawing_style.raster_style = json.dumps({
'bpy.data.worlds[0].color': (1, 1, 1),
'bpy.context.scene.render.engine': 'BLENDER_WORKBENCH',
'bpy.context.scene.display.shading.show_object_outline': True,
'bpy.context.scene.display.shading.show_cavity': True,
'bpy.context.scene.display.shading.cavity_type': 'BOTH',
'bpy.context.scene.display.shading.curvature_ridge_factor': 1,
'bpy.context.scene.display.shading.curvature_valley_factor': 1,
'bpy.context.scene.view_settings.view_transform': 'Standard',
'bpy.context.scene.display.shading.light': 'FLAT',
'bpy.context.scene.display.shading.color_type': 'SINGLE',
'bpy.context.scene.display.shading.single_color': (1, 1, 1),
'bpy.context.scene.view_settings.use_curve_mapping': True,
})
# TODO: This is used for technical styles, but probably should not be hardcoded
bpy.context.scene.view_settings.curve_mapping.curves[3].points.new(.4, 0) # Increase black contrast
drawing_style.name = 'Blender Default'
drawing_style.render_type = 'DEFAULT'
bpy.ops.bim.save_drawing_style(index='2')
def getIfcPredefinedTypes(self, context):
@@ -212,6 +242,10 @@ def refreshBoundaryConditionAttributes(self, context):
new_attribute.name = attribute['name']
def refreshActiveDrawingIndex(self, context):
bpy.ops.bim.activate_view(drawing_index=context.scene.DocProperties.active_drawing_index)
def getIfcProducts(self, context):
global products_enum
if len(products_enum) < 1:
@@ -361,8 +395,6 @@ def getClassifications(self, context):
classification_enum.clear()
files = os.listdir(os.path.join(self.schema_dir, 'classifications'))
classification_enum.extend([(f.replace('.ifc', ''), f.replace('.ifc', ''), '') for f in files])
files = os.listdir(os.path.join(self.schema_dir, 'project_classifications'))
classification_enum.extend([(f.replace('.ifc', ''), f.replace('.ifc', ''), '') for f in files])
return classification_enum
@@ -372,6 +404,7 @@ def refreshReferences(self, context):
context.scene.BIMProperties.classification_references.root = ''
# TODO: move into util module. See bug #971
def getPsetNames(self, context):
global psetnames_enum
psetnames_enum.clear()
@@ -385,6 +418,7 @@ def getPsetNames(self, context):
return psetnames_enum
# TODO: move into util module. See bug #971
def getQtoNames(self, context):
global qtonames_enum
qtonames_enum.clear()
@@ -518,22 +552,24 @@ class Sheet(PropertyGroup):
class DrawingStyle(PropertyGroup):
name: StringProperty(name='Name')
raster_style: StringProperty(name='Raster Style')
render_type: EnumProperty(items=[
('NONE', 'None', ''),
('DEFAULT', 'Default', ''),
('VIEWPORT', 'Viewport', ''),
], name='Render Type', default='VIEWPORT')
vector_style: EnumProperty(items=getVectorStyles, name='Vector Style')
include_query: StringProperty(name='Include Query')
exclude_query: StringProperty(name='Exclude Query')
attributes: CollectionProperty(name='Attributes', type=StrProperty)
class DocProperties(PropertyGroup):
should_recut: BoolProperty(name="Should Recut", default=True)
should_recut_selected: BoolProperty(name="Should Recut Selected Only", default=False)
should_render: EnumProperty(items=[
('NONE', 'None', ''),
('DEFAULT', 'Default', ''),
('VIEWPORT', 'Viewport', ''),
], name='Should Render', default='DEFAULT')
should_extract: BoolProperty(name="Should Extract", default=True)
drawings: CollectionProperty(name='Drawings', type=Drawing)
active_drawing_index: IntProperty(name='Active Drawing Index')
active_drawing_index: IntProperty(name='Active Drawing Index', update=refreshActiveDrawingIndex)
current_drawing_index: IntProperty(name='Current Drawing Index')
schedules: CollectionProperty(name='Schedules', type=Schedule)
active_schedule_index: IntProperty(name='Active Schedule Index')
titleblock: EnumProperty(items=getTitleblocks, name="Titleblock", update=refreshTitleblocks)
@@ -558,7 +594,7 @@ class BIMCameraProperties(PropertyGroup):
raster_y: IntProperty(name='Raster Y', default=1000)
is_nts: BoolProperty(name='Is NTS')
cut_objects: EnumProperty(items=[
('.IfcWall|.IfcSlab|.IfcCurtainWall|.IfcStair|.IfcStairFlight|.IfcColumn|.IfcBeam|.IfcMember|.IfcCovering',
('.IfcWall|.IfcSlab|.IfcCurtainWall|.IfcStair|.IfcStairFlight|.IfcColumn|.IfcBeam|.IfcMember|.IfcCovering|.IfcSpace',
'Overall Plan / Section', ''),
('.IfcElement', 'Detail Drawing', ''),
('CUSTOM', 'Custom', '')
@@ -987,7 +1023,7 @@ class PropertyTemplate(PropertyGroup):
class Address(PropertyGroup):
name: StringProperty(name="Name") # Stores IfcPostalAddress or IfcTelecomAddress
name: StringProperty(name="Name", default='IfcPostalAddress') # Stores IfcPostalAddress or IfcTelecomAddress
purpose: EnumProperty(items=[
('OFFICE', 'OFFICE', 'An office address.'),
('SITE', 'SITE', 'A site address.'),
@@ -1074,7 +1110,7 @@ class Classification(PropertyGroup):
description: StringProperty(name="Description")
location: StringProperty(name="Location")
reference_tokens: StringProperty(name="Reference Tokens")
filename: StringProperty(name="Filename")
data: StringProperty(name="Data")
class ClassificationReference(PropertyGroup):
@@ -1169,6 +1205,7 @@ class BIMProperties(PropertyGroup):
import_should_treat_styled_item_as_material: BoolProperty(name="Import Treating Styled Item as Material", default=False)
import_should_use_legacy: BoolProperty(name="Import with Legacy Importer", default=False)
import_should_import_native: BoolProperty(name="Import Native Representations", default=False)
import_export_should_roundtrip_native: BoolProperty(name="Roundtrip Native Representations", default=False)
import_should_use_cpu_multiprocessing: BoolProperty(name="Import with CPU Multiprocessing", default=True)
import_should_import_with_profiling: BoolProperty(name="Import with Profiling", default=True)
import_should_import_aggregates: BoolProperty(name="Import Aggregates", default=True)
@@ -1177,6 +1214,8 @@ class BIMProperties(PropertyGroup):
import_should_merge_by_material: BoolProperty(name="Import and Merge by Material", default=False)
import_should_merge_materials_by_colour: BoolProperty(name="Import and Merge Materials by Colour", default=False)
import_should_clean_mesh: BoolProperty(name="Import and Clean Mesh", default=True)
import_deflection_tolerance: FloatProperty(name="Import Deflection Tolerance", default=0.001)
import_angular_tolerance: FloatProperty(name="Import Angular Tolerance", default=0.5)
qa_reject_element_reason: StringProperty(name="Element Rejection Reason")
person: EnumProperty(items=getPersons, name="Person")
organisation: EnumProperty(items=getOrganisations, name="Organisation")
@@ -1208,9 +1247,10 @@ class BIMProperties(PropertyGroup):
aggregate_class: EnumProperty(items=getIfcClasses, name="Aggregate Class")
aggregate_name: StringProperty(name="Aggregate Name")
classification: EnumProperty(items=getClassifications, name="Classification", update=refreshReferences)
active_classification_name: StringProperty(name="Active Classification Name")
classifications: CollectionProperty(name="Classifications", type=Classification)
has_model_context: BoolProperty(name="Has Model Context", default=True)
has_plan_context: BoolProperty(name="Has Plan Context", default=False)
has_plan_context: BoolProperty(name="Has Plan Context", default=True)
model_subcontexts: CollectionProperty(name='Model Subcontexts', type=Subcontext)
plan_subcontexts: CollectionProperty(name='Plan Subcontexts', type=Subcontext)
available_contexts: EnumProperty(items=[('Model', 'Model', ''), ('Plan', 'Plan', '')], name="Available Contexts")
@@ -1311,6 +1351,15 @@ class Attribute(PropertyGroup):
int_value: IntProperty(name="Value")
float_value: FloatProperty(name="Value")
class IfcParameter(PropertyGroup):
name: StringProperty(name="Name")
step_id: IntProperty(name="STEP ID")
index: IntProperty(name="Index")
value: FloatProperty(name="Value") # For now, only floats
type: StringProperty(name="Type")
class PsetQto(PropertyGroup):
name: StringProperty(name="Name")
properties: CollectionProperty(name="Properties", type=Attribute)
@@ -1345,6 +1394,13 @@ class BIMObjectProperties(PropertyGroup):
boundary_condition: PointerProperty(name='Boundary Condition', type=BoundaryCondition)
structural_member_connection: PointerProperty(name='Structural Member Connection', type=bpy.types.Object)
representation_contexts: CollectionProperty(name="Representation Contexts", type=Subcontext)
# Address applies to IfcSite's SiteAddress and IfcBuilding's BuildingAddress
address: PointerProperty(name='Address', type=Address)
class BIMDebugProperties(PropertyGroup):
step_id: IntProperty(name="STEP ID")
number_of_polygons: IntProperty(name="Number of Polygons")
class BIMMaterialProperties(PropertyGroup):
@@ -1379,5 +1435,7 @@ class BIMMeshProperties(PropertyGroup):
is_parametric: BoolProperty(name='Is Parametric', default=False)
presentation_layer: StringProperty(name="Presentation Layer")
geometry_type: StringProperty(name="Geometry Type")
representation_items: CollectionProperty(name="Representation Items", type=RepresentationItem)
ifc_definition: StringProperty(name="IFC Definition")
ifc_definition_id: IntProperty(name="IFC Definition ID")
ifc_parameters: CollectionProperty(name="IFC Parameters", type=IfcParameter)
active_representation_item_index: IntProperty(name='Active Representation Item Index')
+11 -8
View File
@@ -62,14 +62,17 @@ class IfcSchema():
entity = prop.ApplicableEntity if prop.ApplicableEntity else 'IfcRoot'
self.applicable_psets.setdefault(entity, []).append(prop.Name)
def load_classification(self, filename):
if filename not in self.classifications:
classification_path = os.path.join(self.schema_dir, 'classifications', '{}.ifc'.format(filename))
if not os.path.isfile(classification_path):
classification_path = os.path.join(self.schema_dir, 'project_classifications', '{}.ifc'.format(filename))
self.classification_files[filename] = ifcopenshell.open(classification_path)
self.classifications[filename] = self.classification_files[filename].by_type('IfcClassification')[0]
classification = self.classifications[filename]
def load_classification(self, name, classification_index=None):
if name not in self.classifications:
if classification_index is not None:
self.classification_files[name] = ifcopenshell.file.from_string(
bpy.context.scene.BIMProperties.classifications[classification_index].data)
else:
classification_path = os.path.join(self.schema_dir, 'classifications', '{}.ifc'.format(name))
self.classification_files[name] = ifcopenshell.open(classification_path)
self.classifications[name] = self.classification_files[name].by_type('IfcClassification')[0]
classification = self.classifications[name]
bpy.context.scene.BIMProperties.active_classification_name = self.classifications[name].Name
return {
'name': '',
'description': '',
@@ -43,6 +43,9 @@ class SheetBuilder:
sheet_path = os.path.join(self.data_dir, 'sheets', sheet_name + '.svg')
view_path = os.path.join(self.data_dir, 'diagrams', view_name + '.svg')
if not os.path.isfile(view_path):
raise FileNotFoundError
ET.register_namespace('', 'http://www.w3.org/2000/svg')
ET.register_namespace('xlink', 'http://www.w3.org/1999/xlink')
+130 -27
View File
@@ -1,10 +1,13 @@
import os
import re
import bpy
import math
import pystache
import xml.etree.ElementTree as ET
import svgwrite
import ifcopenshell
from . import annotation
from . import helper
from mathutils import Vector
from mathutils import geometry
@@ -159,6 +162,9 @@ class SvgWriter():
self.draw_ifc_annotation()
for obj in self.ifc_cutter.misc_objs:
self.draw_misc_annotation(obj, ['IfcAnnotation'])
for obj_data in self.ifc_cutter.hidden_objs:
self.draw_line_annotation(obj_data, ['hidden'])
@@ -169,25 +175,33 @@ class SvgWriter():
self.draw_line_annotation(self.ifc_cutter.leader_obj, ['leader'])
if self.ifc_cutter.plan_level_obj:
matrix_world = self.ifc_cutter.plan_level_obj.matrix_world
for spline in self.ifc_cutter.plan_level_obj.data.splines:
classes = ['annotation', 'plan-level']
points = self.get_spline_points(spline)
d = ' '.join(['L {} {}'.format((x_offset + p.co.x) * self.scale, (y_offset - p.co.y) * self.scale) for p in points])
projected_points = [self.project_point_onto_camera(matrix_world @ p.co.xyz) for p in points]
d = ' '.join(['L {} {}'.format(
(x_offset + p.x) * self.scale, (y_offset - p.y) * self.scale)
for p in projected_points])
d = 'M{}'.format(d[1:])
path = self.svg.add(self.svg.path(d=d, class_=' '.join(classes)))
path['marker-end'] = 'url(#plan-level-marker)'
text_position = Vector((
(x_offset + points[0].co.x) * self.scale,
((y_offset - points[0].co.y) * self.scale) - 2.5
(x_offset + projected_points[0].x) * self.scale,
((y_offset - projected_points[0].y) * self.scale) - 2.5
))
# TODO: unhardcode m unit
rl = ((self.ifc_cutter.plan_level_obj.matrix_world @
# TODO: allow metric to be configurable
rl = ((matrix_world @
points[0].co).xyz + self.ifc_cutter.plan_level_obj.location).z
if points[0].co.x > points[-1].co.x:
if bpy.context.scene.unit_settings.system == 'IMPERIAL':
rl = helper.format_distance(rl)
else:
rl = '{:.3f}m'.format(rl)
if projected_points[0].x > projected_points[-1].x:
text_anchor = 'end'
else:
text_anchor = 'start'
self.svg.add(self.svg.text('RL +{:.3f}m'.format(rl), insert=tuple(text_position), **{
self.svg.add(self.svg.text('RL +{}'.format(rl), insert=tuple(text_position), **{
'font-size': annotation.Annotator.get_svg_text_size(2.5),
'font-family': 'OpenGost Type B TT',
'text-anchor': text_anchor,
@@ -212,9 +226,13 @@ class SvgWriter():
(x_offset + projected_points[0].x) * self.scale,
((y_offset - projected_points[0].y) * self.scale) - 3.5
))
# TODO: unhardcode m unit
# TODO: allow metric to be configurable
rl = (matrix_world @ points[0].co.xyz).z
self.svg.add(self.svg.text('RL +{:.3f}m'.format(rl), insert=tuple(text_position), **{
if bpy.context.scene.unit_settings.system == 'IMPERIAL':
rl = helper.format_distance(rl)
else:
rl = '{:.3f}m'.format(rl)
self.svg.add(self.svg.text('RL +{}'.format(rl), insert=tuple(text_position), **{
'font-size': annotation.Annotator.get_svg_text_size(2.5),
'font-family': 'OpenGost Type B TT',
'text-anchor': 'start',
@@ -243,7 +261,6 @@ class SvgWriter():
'alignment-baseline': 'middle',
'dominant-baseline': 'middle'
}))
self.draw_text_annotations()
def draw_ifc_annotation(self):
@@ -261,6 +278,74 @@ class SvgWriter():
line = self.svg.add(self.svg.line(start=tuple(start * self.scale),
end=tuple(end * self.scale), class_=' '.join(annotation['classes'])))
def draw_misc_annotation(self, obj, classes):
# We have to decide whether this should come from Blender or from IFC.
# For the moment, for convenience of experimenting with ideas, it comes
# from Blender. In the future, it should probably come from IFC.
classes.extend(self.get_attribute_classes(obj))
if len(obj.data.polygons) == 0:
self.draw_edge_annotation(obj, classes)
return
x_offset = self.raw_width / 2
y_offset = self.raw_height / 2
matrix_world = obj.matrix_world
for polygon in obj.data.polygons:
points = [obj.data.vertices[v] for v in polygon.vertices]
projected_points = [self.project_point_onto_camera(matrix_world @ p.co.xyz) for p in points]
d = ' '.join(['L {} {}'.format(
(x_offset + p.x) * self.scale, (y_offset - p.y) * self.scale)
for p in projected_points])
d = 'M{}'.format(d[1:])
path = self.svg.add(self.svg.path(d=d, class_=' '.join(classes)))
def get_attribute_classes(self, obj):
classes = [obj.name.split('/')[0]]
for slot in obj.material_slots:
if slot.material:
classes.append('material-{}'.format(
re.sub('[^0-9a-zA-Z]+', '', slot.material.name)
))
result = obj.BIMObjectProperties.attributes.get('GlobalId')
if not result:
result = obj.BIMObjectProperties.attributes.add()
result.name = 'GlobalId'
result.string_value = ifcopenshell.guid.new()
classes.append('globalid-{}'.format(result.string_value))
for attribute in self.ifc_cutter.attributes:
result = self.get_obj_value(obj, attribute)
if result:
classes.append('{}-{}'.format(
re.sub('[^0-9a-zA-Z]+', '', attribute),
re.sub('[^0-9a-zA-Z]+', '', result)
))
return classes
def get_obj_value(self, obj, key):
# This is a duplicate implementation of the IFC selector key in Blender
# In the future if all this becomes purely IFC based this can be deleted
if '.' in key \
and key.split('.')[0] == 'type':
try:
obj = obj.BIMObjectProperties.relating_type
except:
return
key = '.'.join(key.split('.')[1:])
result = obj.BIMObjectProperties.attributes.get(key)
if result:
return result.string_value
elif key == 'Name':
return obj.name.split('/')[1]
elif '.' in key:
pset_name, prop = key.split('.')
pset = obj.BIMObjectProperties.psets.get(pset_name)
if not pset:
pset = obj.BIMObjectProperties.qtos.get(pset_name)
if not pset:
return
result = pset.properties.get(prop)
if result:
return result.string_value
def draw_line_annotation(self, obj_data, classes):
# TODO: properly scope these offsets
x_offset = self.raw_width / 2
@@ -281,25 +366,41 @@ class SvgWriter():
d = 'M{}'.format(d[1:])
path = self.svg.add(self.svg.path(d=d, class_=' '.join(classes)))
elif isinstance(data, bpy.types.Mesh):
for edge in data.edges:
v0_global = matrix_world @ data.vertices[edge.vertices[0]].co.xyz
v1_global = matrix_world @ data.vertices[edge.vertices[1]].co.xyz
v0 = self.project_point_onto_camera(v0_global)
v1 = self.project_point_onto_camera(v1_global)
start = Vector(((x_offset + v0.x), (y_offset - v0.y)))
end = Vector(((x_offset + v1.x), (y_offset - v1.y)))
vector = end - start
line = self.svg.add(self.svg.line(start=tuple(start * self.scale),
end=tuple(end * self.scale), class_=' '.join(classes)))
self.draw_edge_annotation(obj, classes)
def draw_edge_annotation(self, obj, classes):
x_offset = self.raw_width / 2
y_offset = self.raw_height / 2
matrix_world = obj.matrix_world
for edge in obj.data.edges:
v0_global = matrix_world @ obj.data.vertices[edge.vertices[0]].co.xyz
v1_global = matrix_world @ obj.data.vertices[edge.vertices[1]].co.xyz
v0 = self.project_point_onto_camera(v0_global)
v1 = self.project_point_onto_camera(v1_global)
start = Vector(((x_offset + v0.x), (y_offset - v0.y)))
end = Vector(((x_offset + v1.x), (y_offset - v1.y)))
vector = end - start
line = self.svg.add(self.svg.line(start=tuple(start * self.scale),
end=tuple(end * self.scale), class_=' '.join(classes)))
def draw_text_annotations(self):
x_offset = self.raw_width / 2
y_offset = self.raw_height / 2
for text_obj in self.ifc_cutter.text_objs:
loc, rot, scale = self.ifc_cutter.camera_obj.matrix_world.decompose()
pos = (text_obj.location - self.ifc_cutter.camera_obj.location) @ rot.to_matrix()
text_position = Vector(((x_offset + pos.x), (y_offset - pos.y)))
text_position = self.project_point_onto_camera(text_obj.location)
text_position = Vector(((x_offset + text_position.x), (y_offset - text_position.y)))
local_x_axis = text_obj.matrix_world.to_quaternion() @ Vector((1, 0, 0))
projected_x_axis = self.project_point_onto_camera(text_obj.location + local_x_axis)
angle = math.degrees((Vector((x_offset + projected_x_axis.x, y_offset -
projected_x_axis.y)) - text_position).angle_signed(Vector((1, 0))))
transform = 'rotate({}, {}, {})'.format(
angle,
(text_position * self.scale)[0],
(text_position * self.scale)[1],
)
if text_obj.data.BIMTextProperties.symbol != 'None':
self.svg.add(self.svg.use(
@@ -329,12 +430,14 @@ class SvgWriter():
self.svg.add(self.svg.text(
text_line,
insert=tuple((text_position * self.scale) + Vector((0, 3.5*line_number))),
class_=' '.join(self.get_attribute_classes(text_obj)),
**{
'font-size': annotation.Annotator.get_svg_text_size(text_obj.data.BIMTextProperties.font_size),
'font-family': 'OpenGost Type B TT',
'text-anchor': text_anchor,
'alignment-baseline': alignment_baseline,
'dominant-baseline': alignment_baseline
'dominant-baseline': alignment_baseline,
'transform': transform
}
))
@@ -391,10 +494,10 @@ class SvgWriter():
start = Vector(((x_offset + v0.x), (y_offset - v0.y)))
end = Vector(((x_offset + v1.x), (y_offset - v1.y)))
mid = ((end - start) / 2) + start
# TODO: hardcoded meters to mm conversion, until I properly do units
vector = end - start
perpendicular = Vector((vector.y, -vector.x)).normalized()
dimension = (v1_global - v0_global).length * 1000
dimension = (v1_global - v0_global).length
dimension = helper.format_distance(dimension)
sheet_dimension = ((end*self.scale) - (start*self.scale)).length
if sheet_dimension < 5: # annotation can't fit
# offset text to right of marker
@@ -409,7 +512,7 @@ class SvgWriter():
if text_override is not None:
text = text_override
else:
text = str(round(dimension))
text = str(dimension)
self.svg.add(self.svg.text(text, insert=tuple(text_position), **{
'transform': 'rotate({} {} {})'.format(
rotation,
+125 -33
View File
@@ -63,6 +63,9 @@ class BIM_PT_object(Panel):
row = layout.row()
row.prop(props, 'attributes')
if 'IfcSite/' in context.active_object.name or 'IfcBuilding/' in context.active_object.name:
self.draw_addresses_ui()
row = layout.row(align=True)
row.prop(props, 'relating_type')
row.operator('bim.select_similar_type', icon='RESTRICT_SELECT_OFF', text='')
@@ -72,6 +75,33 @@ class BIM_PT_object(Panel):
row = layout.row()
row.prop(props, 'material_type')
def draw_addresses_ui(self):
layout = self.layout
layout.label(text="Address:")
address = bpy.context.active_object.BIMObjectProperties.address
row = layout.row()
row.prop(address, 'purpose')
if address.purpose == 'USERDEFINED':
row = layout.row()
row.prop(address, 'user_defined_purpose')
row = layout.row()
row.prop(address, 'description')
row = layout.row()
row.prop(address, 'internal_location')
row = layout.row()
row.prop(address, 'address_lines')
row = layout.row()
row.prop(address, 'postal_box')
row = layout.row()
row.prop(address, 'town')
row = layout.row()
row.prop(address, 'region')
row = layout.row()
row.prop(address, 'postal_code')
row = layout.row()
row.prop(address, 'country')
class BIM_PT_object_psets(Panel):
bl_label = 'IFC Object Property Sets'
@@ -101,10 +131,10 @@ class BIM_PT_object_psets(Panel):
row = layout.row(align=True)
row.prop(prop, 'name', text='')
row.prop(prop, 'string_value', text='')
op = row.operator('bim.copy_attributes_to_selection', icon='COPYDOWN', text='')
op.prop_base = 'BIMObjectProperties.psets[\'{}\'].properties'.format(pset.name)
op = row.operator('bim.copy_property_to_selection', icon='COPYDOWN', text='')
op.pset_name = pset.name
op.prop_name = prop.name
op.collection_element = True
op.prop_value = prop.string_value
class BIM_PT_object_qto(Panel):
bl_label = 'IFC Object Quantity Sets'
@@ -411,7 +441,8 @@ class BIM_PT_representations(Panel):
row.prop(bpy.context.scene.BIMProperties, 'available_contexts', text='')
row.prop(bpy.context.scene.BIMProperties, 'available_subcontexts', text='')
row.prop(bpy.context.scene.BIMProperties, 'available_target_views', text='')
row.operator('bim.switch_context', icon='ADD', text='')
op = row.operator('bim.switch_context', icon='ADD', text='')
op.has_target_context = False
for index, subcontext in enumerate(props.representation_contexts):
row = layout.row(align=True)
@@ -501,7 +532,7 @@ class BIM_PT_psets(Panel):
class BIM_PT_classifications(Panel):
bl_label = 'IFC Classifications References'
bl_label = 'IFC Classifications'
bl_idname = 'BIM_PT_classifications'
bl_options = {'DEFAULT_CLOSED'}
bl_space_type = 'PROPERTIES'
@@ -514,7 +545,6 @@ class BIM_PT_classifications(Panel):
row = layout.row(align=True)
row.prop(props, "classification", text='')
row.operator("bim.purge_project_classifications", text='', icon='TRASH')
row.operator("bim.add_classification", text='', icon='ADD')
if context.scene.BIMProperties.classification_references.raw_data:
@@ -524,7 +554,7 @@ class BIM_PT_classifications(Panel):
row.operator("bim.unassign_classification")
else:
row = layout.row(align=True)
row.operator('bim.load_classification')
row.operator('bim.load_classification').is_file = True
if not props.classifications:
return
@@ -534,6 +564,7 @@ class BIM_PT_classifications(Panel):
for index, classification in enumerate(props.classifications):
row = layout.row(align=True)
row.prop(classification, 'name')
row.operator('bim.load_classification', icon='IMPORT', text='').classification_index = index
row.operator('bim.remove_classification', icon='X', text='').classification_index = index
row = layout.row(align=True)
row.prop(classification, 'source')
@@ -569,21 +600,23 @@ class BIM_PT_mesh(Panel):
layout = self.layout
props = context.active_object.data.BIMMeshProperties
row = layout.row(align=True)
row.prop(bpy.context.scene.BIMProperties, 'available_contexts', text='')
row.prop(bpy.context.scene.BIMProperties, 'available_subcontexts', text='')
row.prop(bpy.context.scene.BIMProperties, 'available_target_views', text='')
row = layout.row()
row.operator('bim.assign_context')
row = layout.row(align=True)
row.operator('bim.push_representation')
row = layout.row()
row.prop(props, 'geometry_type')
layout.template_list('BIM_UL_representation_items', '', props, 'representation_items', props, 'active_representation_item_index')
row = layout.row()
row.prop(props, 'ifc_definition')
layout.label(text="IFC Parameters:")
row = layout.row()
row.operator('bim.get_representation_ifc_parameters')
for index, ifc_parameter in enumerate(props.ifc_parameters):
row = layout.row(align=True)
row.prop(ifc_parameter, 'name', text='')
row.prop(ifc_parameter, 'step_id')
row.prop(ifc_parameter, 'index')
row.prop(ifc_parameter, 'value', text='')
row.operator('bim.update_ifc_representation', icon='FILE_REFRESH', text='').index = index
row = layout.row()
row.prop(props, 'presentation_layer')
@@ -667,6 +700,15 @@ class BIM_PT_material(Panel):
row = layout.row(align=True)
row.prop(pset, 'name', text='')
row.operator('bim.remove_material_pset', icon='X', text='').pset_index = index
op = row.operator('bim.copy_property_to_selection', icon='COPYDOWN', text='')
for prop in pset.properties:
row = layout.row(align=True)
row.prop(prop, 'name', text='')
row.prop(prop, 'string_value', text='')
op = row.operator('bim.copy_property_to_selection', icon='COPYDOWN', text='')
op.pset_name = pset.name
op.prop_name = prop.name
op.prop_value = prop.string_value
row = layout.row()
row.prop(props, 'psets', text='')
@@ -736,13 +778,13 @@ class BIM_PT_drawings(Panel):
row = layout.row(align=True)
row.operator('bim.add_drawing')
row.operator('bim.refresh_drawing_list', icon='FILE_REFRESH', text='')
if props.drawings:
op = row.operator('bim.open_view', icon='URL', text='')
op.view = props.drawings[props.active_drawing_index].name
row.operator('bim.activate_view', icon='SCENE', text='')
row.operator('bim.remove_drawing', icon='X', text='').index = props.active_drawing_index
if props.active_drawing_index < len(props.drawings):
op = row.operator('bim.open_view', icon='URL', text='')
op.view = props.drawings[props.active_drawing_index].name
row.operator('bim.remove_drawing', icon='X', text='').index = props.active_drawing_index
layout.template_list('BIM_UL_generic', '', props, 'drawings', props, 'active_drawing_index')
row = layout.row()
@@ -863,8 +905,6 @@ class BIM_PT_camera(Panel):
row = layout.row()
row.prop(dprops, 'should_recut_selected')
row = layout.row()
row.prop(dprops, 'should_render')
row = layout.row()
row.prop(dprops, 'should_extract')
row = layout.row()
@@ -910,6 +950,8 @@ class BIM_PT_camera(Panel):
row.prop(drawing_style, 'name')
row.operator('bim.remove_drawing_style', icon='X', text='').index = props.active_drawing_style_index
row = layout.row()
row.prop(drawing_style, 'render_type')
row = layout.row(align=True)
row.prop(drawing_style, 'vector_style')
row.operator('bim.edit_vector_style', text='', icon='GREASEPENCIL')
@@ -918,6 +960,14 @@ class BIM_PT_camera(Panel):
row = layout.row(align=True)
row.prop(drawing_style, 'exclude_query')
row = layout.row()
row.operator('bim.add_drawing_style_attribute')
for index, attribute in enumerate(drawing_style.attributes):
row = layout.row(align=True)
row.prop(attribute, 'name', text='')
row.operator('bim.remove_drawing_style_attribute', icon='X', text='').index = index
row = layout.row(align=True)
row.operator('bim.save_drawing_style')
row.operator('bim.activate_drawing_style')
@@ -1667,10 +1717,16 @@ class BIM_PT_mvd(Panel):
row = layout.row()
row.prop(bim_properties, 'import_should_import_native')
row = layout.row()
row.prop(bim_properties, 'import_export_should_roundtrip_native')
row = layout.row()
row.prop(bim_properties, 'import_should_use_cpu_multiprocessing')
row = layout.row()
row.prop(bim_properties, 'import_should_import_with_profiling')
row = layout.row()
row.prop(bim_properties, 'import_deflection_tolerance')
row = layout.row()
row.prop(bim_properties, 'import_angular_tolerance')
row = layout.row()
row.prop(bim_properties, 'export_json_compact')
layout.label(text='Simplifications:')
@@ -1792,19 +1848,12 @@ class BIM_UL_classifications(bpy.types.UIList):
layout.label(text=itemdata['name'])
class BIM_UL_representation_items(bpy.types.UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
ob = data
if item:
layout.prop(item, 'name', text='', emboss=False)
else:
layout.label(text="", translate=False)
class BIM_ADDON_preferences(bpy.types.AddonPreferences):
bl_idname = 'blenderbim'
svg2pdf_command: StringProperty(name="SVG to PDF Command")
svg2dxf_command: StringProperty(name="SVG to DXF Command")
svg_command: StringProperty(name="SVG Command")
pdf_command: StringProperty(name="PDF Command")
def draw(self, context):
layout = self.layout
@@ -1818,6 +1867,10 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
row.prop(self, 'svg2pdf_command')
row = layout.row()
row.prop(self, 'svg2dxf_command')
row = layout.row()
row.prop(self, 'svg_command')
row = layout.row()
row.prop(self, 'pdf_command')
class BIM_PT_ifcclash(Panel):
@@ -1952,6 +2005,18 @@ class BIM_PT_annotation_utilities(Panel):
op.obj_name = 'Section Level'
op.data_type = 'curve'
props = bpy.context.scene.DocProperties
row = layout.row(align=True)
row.operator('bim.add_drawing')
row.operator('bim.refresh_drawing_list', icon='FILE_REFRESH', text='')
if props.drawings:
if props.active_drawing_index < len(props.drawings):
op = row.operator('bim.open_view', icon='URL', text='')
op.view = props.drawings[props.active_drawing_index].name
row.operator('bim.remove_drawing', icon='X', text='').index = props.active_drawing_index
layout.template_list('BIM_UL_generic', '', props, 'drawings', props, 'active_drawing_index')
class BIM_PT_qto_utilities(Panel):
@@ -1997,6 +2062,33 @@ class BIM_PT_misc_utilities(Panel):
row.operator("bim.set_viewport_shadow_from_sun")
class BIM_PT_debug(Panel):
bl_label = "IFC Debug"
bl_idname = "BIM_PT_debug"
bl_options = {'DEFAULT_CLOSED'}
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_context = "scene"
def draw(self, context):
layout = self.layout
scene = context.scene
bim_props = scene.BIMProperties
debug_props = scene.BIMDebugProperties
row = layout.row()
row.prop(debug_props, 'step_id', text='')
row = layout.row()
row.operator('bim.create_shape_from_step_id')
row = layout.row()
row.prop(debug_props, 'number_of_polygons', text='')
row = layout.row()
row.operator('bim.select_high_polygon_meshes')
def ifc_units(self, context):
scene = context.scene
props = context.scene.BIMProperties
+5 -1
View File
@@ -25,7 +25,11 @@ import random
import operator
import warnings
from collections import namedtuple, Iterable
from collections import namedtuple
try: # python 3.3+
from collections.abc import Iterable
except ModuleNotFoundError: # python 2
from collections import Iterable
try:
from OCC.Core import TopoDS, gp, Quantity, BRepTools
Executable → Regular
+1 -1
View File
@@ -350,7 +350,7 @@ class CollisionManager(object):
cdata = fcl.CollisionData()
if return_names or return_data:
cdata = fcl.CollisionData(request=fcl.CollisionRequest(
num_max_contacts=100000, enable_contact=True))
num_max_contacts=1000000, enable_contact=True))
self._manager.collide(cdata, fcl.defaultCollisionCallback)
Executable → Regular
+91 -29
View File
@@ -22,7 +22,8 @@ class IfcClasher:
self.settings = settings
self.geom_settings = ifcopenshell.geom.settings()
self.clash_sets = []
#self.tolerance = 0.01
self.clash_data = {'meshes': {}}
self.global_data = {'meshes': {}, 'matrices': {}}
def clash(self):
for clash_set in self.clash_sets:
@@ -36,8 +37,6 @@ class IfcClasher:
for data in clash_set[ab]:
data['ifc'] = ifcopenshell.open(data['file'])
self.patch_ifc(data['ifc'])
self.settings.logger.info(f'Purging unnecessary elements {ab} ...')
self.purge_elements(data)
self.settings.logger.info(f'Creating collision data for {ab} ...')
if len(data['ifc'].by_type('IfcElement')) > 0:
self.add_collision_objects(data, clash_set[f'{ab}_cm'])
@@ -62,10 +61,62 @@ class IfcClasher:
b = self.get_element(clash_set['a'], b_global_id)
if contact.raw.penetration_depth < tolerance:
continue
# fcl returns contact data for faces that aren't actually
# penetrating, but just touching. If our tolerance is zero, then we
# consider these as clashes and we move on. If our tolerance is not
# zero, fcl has a strange behaviour where the penetration depth can
# be a large number even though objects are just touching
# https://github.com/flexible-collision-library/fcl/issues/503 In
# this case, I don't trust the penetration depth and I run my own
# triangle-triangle intersection test. Optimistically, this skips
# the false positives. Conservatively, we let the user manually deal
# with the false positives and we mark it as a clash.
is_optimistic = True # TODO: let user configure this
if is_optimistic and tolerance != 0:
# We'll now check if the contact data's two faces are actually
# intersecting, using this brute force check:
# https://stackoverflow.com/questions/7113344/find-whether-two-triangles-intersect-or-not
# I'm not very good at this kind of code. If you know this stuff
# please help rewrite this.
# Get vertices of clashing tris
p1 = self.global_data['meshes'][contact.names[0]].faces[contact.index(contact.names[0])]
p2 = self.global_data['meshes'][contact.names[1]].faces[contact.index(contact.names[1])]
m1 = self.global_data['matrices'][contact.names[0]]
m2 = self.global_data['matrices'][contact.names[1]]
v1 = []
v2 = []
for v in p1:
v1.append((m1 @ np.array([*self.global_data['meshes'][contact.names[0]].vertices[v], 1]))[0:3].round(2))
for v in p2:
v2.append((m2 @ np.array([*self.global_data['meshes'][contact.names[1]].vertices[v], 1]))[0:3].round(2))
tri1_x = 0
tri2_x = 0
tri1_x += 1 if self.intersect_line_triangle(v1[0], v1[1], v2[0], v2[1], v2[2]) is not None else 0
tri1_x += 1 if self.intersect_line_triangle(v1[1], v1[2], v2[0], v2[1], v2[2]) is not None else 0
tri1_x += 1 if self.intersect_line_triangle(v1[2], v1[0], v2[0], v2[1], v2[2]) is not None else 0
tri2_x += 1 if self.intersect_line_triangle(v2[0], v2[1], v1[0], v1[1], v1[2]) is not None else 0
tri2_x += 1 if self.intersect_line_triangle(v2[1], v2[2], v1[0], v1[1], v1[2]) is not None else 0
tri2_x += 1 if self.intersect_line_triangle(v2[2], v2[0], v1[0], v1[1], v1[2]) is not None else 0
intersections = [tri1_x, tri2_x]
if intersections == [0, 2] or intersections == [2, 0] or intersections == [1, 1]:
# This is a penetrating collision
pass
else:
# This is probably two triangles which just touch
continue
key = f'{a_global_id}-{b_global_id}'
if key in clash_set['clashes'] \
and clash_set['clashes'][key]['penetration_depth'] > contact.raw.penetration_depth:
continue
clash_set['clashes'][key] = {
'a_global_id': a_global_id,
'b_global_id': b_global_id,
@@ -78,6 +129,24 @@ class IfcClasher:
'penetration_depth': contact.raw.penetration_depth
}
# https://stackoverflow.com/questions/42740765/intersection-between-line-and-triangle-in-3d
def intersect_line_triangle(self, q1, q2, p1, p2, p3):
def signed_tetra_volume(a,b,c,d):
return np.sign(np.dot(np.cross(b-a,c-a),d-a)/6.0)
s1 = signed_tetra_volume(q1,p1,p2,p3)
s2 = signed_tetra_volume(q2,p1,p2,p3)
if s1 != s2:
s3 = signed_tetra_volume(q1,q2,p1,p2)
s4 = signed_tetra_volume(q1,q2,p2,p3)
s5 = signed_tetra_volume(q1,q2,p3,p1)
if s3 == s4 and s4 == s5:
n = np.cross(p2-p1,p3-p1)
t = -np.dot(q1,n-p1) / np.dot(q1,q2-q1)
return q1 + t * (q2-q1)
return None
def export(self):
results = self.clash_sets.copy()
for result in results:
@@ -85,9 +154,8 @@ class IfcClasher:
del result['b_cm']
for ab in ['a', 'b']:
for data in result[ab]:
for key in ['ifc', 'meshes']:
if key in data:
del data[key]
if 'ifc' in data:
del data['ifc']
with open(self.settings.output, 'w', encoding='utf-8') as clashes_file:
json.dump(results, clashes_file, indent=4)
@@ -100,27 +168,18 @@ class IfcClasher:
except:
pass
def purge_elements(self, data):
if 'selector' not in data:
for element in data['ifc'].by_type('IfcSpace'):
data['ifc'].remove(element)
return
selector = ifcopenshell.util.selector.Selector()
elements = selector.parse(data['ifc'], data['selector'])
if data['mode'] == 'e':
for element in data['ifc'].by_type('IfcElement'):
if element in elements:
data['ifc'].remove(element)
elif data['mode'] == 'i':
for element in data['ifc'].by_type('IfcElement'):
if element not in elements:
data['ifc'].remove(element)
def add_collision_objects(self, data, cm):
data['meshes'] = {}
iterator = ifcopenshell.geom.iterator(self.geom_settings, data['ifc'], multiprocessing.cpu_count())
self.clash_data['meshes'] = {}
selector = ifcopenshell.util.selector.Selector()
if 'selector' not in data:
iterator = ifcopenshell.geom.iterator(self.geom_settings, data['ifc'], multiprocessing.cpu_count(),
exclude=(data['ifc'].by_type('IfcSpatialStructureElement')))
elif data['mode'] == 'e':
iterator = ifcopenshell.geom.iterator(self.geom_settings, data['ifc'], multiprocessing.cpu_count(),
exclude=selector.parse(data['ifc'], data['selector']))
elif data['mode'] == 'i':
iterator = ifcopenshell.geom.iterator(self.geom_settings, data['ifc'], multiprocessing.cpu_count(),
include=selector.parse(data['ifc'], data['selector']))
valid_file = iterator.initialize()
if not valid_file:
return False
@@ -140,11 +199,12 @@ class IfcClasher:
element = data['ifc'].by_id(shape.guid)
self.settings.logger.info('Creating object {}'.format(element))
mesh_name = f'mesh-{shape.geometry.id}'
if mesh_name in data['meshes']:
mesh = data['meshes'][mesh_name]
if mesh_name in self.clash_data['meshes']:
mesh = self.clash_data['meshes'][mesh_name]
else:
mesh = self.create_mesh(shape)
data['meshes'][mesh_name] = mesh
self.clash_data['meshes'][mesh_name] = mesh
self.global_data['meshes'][shape.guid] = mesh
m = shape.transformation.matrix.data
mat = np.array(
@@ -155,7 +215,9 @@ class IfcClasher:
[0, 0, 0, 1]
]
)
mat.transpose()
self.global_data['matrices'][shape.guid] = mat
cm.add_object(shape.guid, mesh, mat)
def create_mesh(self, shape):
+44 -5
View File
@@ -199,7 +199,7 @@ int main(int argc, char** argv) {
typedef char char_t;
#endif
double deflection_tolerance;
double deflection_tolerance, angular_tolerance, force_space_transparency;
inclusion_filter include_filter;
inclusion_traverse_filter include_traverse_filter;
exclusion_filter exclude_filter;
@@ -328,6 +328,10 @@ int main(int argc, char** argv) {
"model in other modelling application in any case.")
("deflection-tolerance", po::value<double>(&deflection_tolerance)->default_value(1e-3),
"Sets the deflection tolerance of the mesher, 1e-3 by default if not specified.")
("force-space-transparency", po::value<double>(&force_space_transparency),
"Overrides transparency of spaces in geometry output.")
("angular-tolerance", po::value<double>(&angular_tolerance)->default_value(0.5),
"Sets the angular tolerance of the mesher in radians 0.5 by default if not specified.")
("generate-uvs",
"Generates UVs (texture coordinates) by using simple box projection. Requires normals. "
"Not guaranteed to work properly if used with --weld-vertices.")
@@ -342,7 +346,8 @@ int main(int argc, char** argv) {
#endif
short precision;
double section_height;
std::string svg_scale;
std::string svg_scale, svg_center;
std::string section_ref, elevation_ref;
po::options_description serializer_options("Serialization options");
serializer_options.add_options()
@@ -357,6 +362,13 @@ int main(int argc, char** argv) {
("scale", po::value<std::string>(&svg_scale),
"Interprets SVG bounds in mm, centers layout and draw elements to scale. "
"Only used when converting to SVG. Example 1:100.")
("center", po::value<std::string>(&svg_center),
"When using --scale, specifies the location in the range [0 1]x[0 1] around which"
"to center the drawings. Example 0.5x0.5 (default).")
("section-ref", po::value<std::string>(&section_ref),
"Element at which vertical cross sections should be created")
("elevation-ref", po::value<std::string>(&elevation_ref),
"Element at which vertical elevations should be created")
("door-arcs", "Draw door openings arcs for IfcDoor elements")
("section-height", po::value<double>(&section_height),
"Specifies the cut section height for SVG 2D geometry.")
@@ -519,8 +531,8 @@ int main(int argc, char** argv) {
}
}
boost::optional<double> bounding_width;
boost::optional<double> bounding_height;
boost::optional<double> bounding_width, bounding_height, relative_center_x, relative_center_y;
if (vmap.count("bounds") == 1) {
int w, h;
if (sscanf(bounds.c_str(), "%ux%u", &w, &h) == 2 && w > 0 && h > 0) {
@@ -533,6 +545,18 @@ int main(int argc, char** argv) {
}
}
if (vmap.count("center") == 1) {
double cx, cy;
if (sscanf(svg_center.c_str(), "%lfx%lf", &cx, &cy) == 2 && cx >= 0. && cy >= 0. && cx <= 1. && cy <= 1.) {
relative_center_x = cx;
relative_center_y = cy;
} else {
cerr_ << "[Error] Invalid use of --bounds" << std::endl;
print_options(serializer_options);
return EXIT_FAILURE;
}
}
const path_t input_filename = vmap["input-file"].as<path_t>();
if (!file_exists(IfcUtil::path::to_utf8(input_filename))) {
cerr_ << "[Error] Input file '" << input_filename << "' does not exist" << std::endl;
@@ -710,7 +734,13 @@ int main(int argc, char** argv) {
settings.set(SerializerSettings::USE_ELEMENT_TYPES, use_element_types);
settings.set(SerializerSettings::USE_ELEMENT_HIERARCHY, use_element_hierarchy);
settings.set_deflection_tolerance(deflection_tolerance);
settings.precision = precision;
settings.set_angular_tolerance(angular_tolerance);
settings.precision = precision;
if (vmap.count("force-space-transparency")) {
settings.force_space_transparency(force_space_transparency);
IfcGeom::update_default_style("IfcSpace").Transparency().reset(force_space_transparency);
}
boost::shared_ptr<GeometrySerializer> serializer; /**< @todo use std::unique_ptr when possible */
if (output_extension == OBJ) {
@@ -907,6 +937,15 @@ int main(int argc, char** argv) {
return EXIT_FAILURE;
}
}
if (vmap.count("section-ref")) {
static_cast<SvgSerializer*>(serializer.get())->setSectionRef(section_ref);
}
if (vmap.count("elevation-ref")) {
static_cast<SvgSerializer*>(serializer.get())->setElevationRef(elevation_ref);
}
if (relative_center_x && relative_center_y) {
static_cast<SvgSerializer*>(serializer.get())->setDrawingCenter(*relative_center_x, *relative_center_y);
}
}
if (convert_back_units) {
+30 -20
View File
@@ -3,26 +3,50 @@
import ifcopenshell
import ifcopenshell.util.selector
import ifcopenshell.util.element
import csv
import lark
import argparse
class IfcAttributeExtractor():
@staticmethod
def set_element_key(element, key, value):
def set_element_key(ifc_file, element, key, value):
if key == 'type' and element.is_a() != value:
return IfcAttributeExtractor.change_ifc_class(ifc_file, element, value)
if hasattr(element, key):
return setattr(element, key, value)
setattr(element, key, value)
return element
if '.' not in key:
return
return element
if key[0:3] == 'Qto':
qto, prop = key.split('.', 1)
qto = IfcAttributeExtractor.get_element_qto(element, qto_name)
if qto:
return IfcAttributeExtractor.set_qto_property(qto, prop, value)
IfcAttributeExtractor.set_qto_property(qto, prop, value)
return element
pset_name, prop = key.split('.', 1)
pset = IfcAttributeExtractor.get_element_pset(element, pset_name)
if pset:
return IfcAttributeExtractor.set_pset_property(pset, prop, value)
IfcAttributeExtractor.set_pset_property(pset, prop, value)
return element
return element
@staticmethod
def change_ifc_class(ifc_file, element, new_class):
try:
new_element = ifc_file.create_entity(new_class)
except:
return
new_attributes = [new_element.attribute_name(i) for i, attribute in enumerate(new_element)]
for i, attribute in enumerate(element):
try:
new_element[new_attributes.index(element.attribute_name(i))] = attribute
except:
continue
for inverse in ifc_file.get_inverse(element):
ifcopenshell.util.element.replace_attribute(inverse, element, new_element)
ifc_file.remove(element)
return new_element
@staticmethod
def get_element_qto(element, name):
@@ -32,13 +56,6 @@ class IfcAttributeExtractor():
and relationship.RelatingPropertyDefinition.Name == name:
return relationship.RelatingPropertyDefinition
@staticmethod
def get_qto_property(qto, name):
for prop in qto.Quantities:
if prop.Name != name:
continue
return getattr(prop, prop.is_a()[len('IfcQuantity'):] + 'Value')
@staticmethod
def set_qto_property(qto, name, value):
for prop in qto.Quantities:
@@ -61,12 +78,6 @@ class IfcAttributeExtractor():
and relationship.RelatingPropertyDefinition.Name == name:
return relationship.RelatingPropertyDefinition
@staticmethod
def get_pset_property(pset, name):
for property in pset.HasProperties:
if property.Name == name:
return property.NominalValue.wrappedValue
@staticmethod
def set_pset_property(pset, name, value):
for property in pset.HasProperties:
@@ -122,7 +133,6 @@ class IfcCsv():
results = set()
pset_qto_name = attribute.split('.', 1)[0]
for element in self.ifc_file.by_type('IfcPropertySet') + self.ifc_file.by_type('IfcElementQuantity'):
print(element)
if element.Name != pset_qto_name:
continue
if element.is_a('IfcPropertySet'):
@@ -146,7 +156,7 @@ class IfcCsv():
for i, value in enumerate(row):
if i == 0:
continue # Skip GlobalId
IfcAttributeExtractor.set_element_key(element, headers[i], value)
element = IfcAttributeExtractor.set_element_key(ifc_file, element, headers[i], value)
ifc_file.write(ifc)
if __name__ == '__main__':
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
-12
View File
@@ -1,12 +0,0 @@
This folder contains Python code to generate C++ type information based on an
Express schema. In particular is has only been tested using recent version of
the IFC schema and will most likely fail on any other Express schema.
The code can be invoked in the following way and results in several code outputs
named according to the schema name in the Express file. A python 3 interpreter
with the pyparsing [1] library is required.
$ python bootstrap.py express.bnf > express_parser.py
$ python express_parser.py IFC2X3_TC1.exp header implementation schema_class definitions
[1] http://pyparsing.wikispaces.com/Download+and+Installation
-217
View File
@@ -1,217 +0,0 @@
###############################################################################
# #
# This file is part of IfcOpenShell. #
# #
# IfcOpenShell is free software: you can redistribute it and/or modify #
# it under the terms of the Lesser GNU General Public License as published by #
# the Free Software Foundation, either version 3.0 of the License, or #
# (at your option) any later version. #
# #
# IfcOpenShell is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# Lesser GNU General Public License for more details. #
# #
# You should have received a copy of the Lesser GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
###############################################################################
import sys
import string
import operator
import itertools
from pyparsing import *
try: from functools import reduce
except: pass
class Expression:
def __init__(self, contents):
self.contents = contents[0]
def __repr__(self):
if self.op is None: return repr(self.contents)
c = [isinstance(c,str) and c or str(c) for c in self.contents]
if "%s" in self.op: return self.op % (" ".join(c))
else: return "(%s)" % (" %s "%self.op).join(c)
def __iter__(self):
return self.contents.__iter__()
class Union(Expression):
op = "|"
class Concat(Expression):
op = "+"
class Optional(Expression):
op = "Optional(%s)"
class Repeated(Expression):
op = "ZeroOrMore(%s)"
class Term(Expression):
op = None
class Keyword:
def __init__(self, contents):
self.contents = contents[0]
def __repr__(self):
return self.contents
class Terminal:
def __init__(self, contents):
self.contents = contents[0]
s = self.contents
self.is_keyword = len(s) >= 4 and s[0::len(s)-1] == '""' and \
all(c in alphanums+"_" for c in s[1:-1])
def __repr__(self):
ty = "CaselessKeyword" if self.is_keyword else "CaselessLiteral"
return "%s(%s)" % (ty, self.contents)
LPAREN = Suppress("(")
RPAREN = Suppress(")")
LBRACK = Suppress("[")
RBRACK = Suppress("]")
LBRACE = Suppress("{")
RBRACE = Suppress("}")
EQUALS = Suppress("=")
VBAR = Suppress("|")
PERIOD = Suppress(".")
HASH = Suppress("#")
identifier = Word(alphanums+"_")
keyword = Word(alphanums+"_").setParseAction(Keyword)
expression = Forward()
optional = Group(LBRACK + expression + RBRACK).setParseAction(Optional)
repeated = Group(LBRACE + expression + RBRACE).setParseAction(Repeated)
terminal = quotedString.setParseAction(Terminal)
term = (keyword | terminal | optional | repeated | (LPAREN + expression + RPAREN)).setParseAction(Term)
concat = Group(term + OneOrMore(term)).setParseAction(Concat)
factor = concat | term
union = Group(factor + OneOrMore(VBAR + factor)).setParseAction(Union)
rule = identifier + EQUALS + expression + PERIOD
expression << (union | factor)
grammar = OneOrMore(Group(rule))
grammar.ignore(HASH + restOfLine)
express = grammar.parseFile(sys.argv[1])
def find_bytype(expr, ty, li = None):
if li is None: li = []
if isinstance(expr, Term):
expr = expr.contents
if isinstance(expr, ty):
li.append(expr)
return set(li)
elif isinstance(expr, Expression):
for term in expr:
find_bytype(term, ty, li)
return set(li)
actions = {
'type_decl' : "TypeDeclaration",
'entity_decl' : "EntityDeclaration",
'enumeration_type' : "EnumerationType",
'aggregation_types' : "AggregationType",
'general_aggregation_types' : "AggregationType",
'select_type' : "SelectType",
'binary_type' : "BinaryType",
'subtype_declaration' : "SubTypeExpression",
'supertype_constraint' : "SuperTypeExpression",
'derive_clause' : "AttributeList",
'inverse_clause' : "AttributeList",
'inverse_attr' : "InverseAttribute",
'bound_spec' : "BoundSpecification",
'explicit_attr' : "ExplicitAttribute",
'width_spec' : "WidthSpec",
'string_type' : "StringType",
'named_types' : "NamedType",
'simple_types' : "SimpleType",
}
to_emit = set(id for id, expr in express)
emitted = set()
to_combine = set(["simple_id"])
statements = []
terminals = reduce(lambda x,y: x | y, (find_bytype(e, Terminal) for id, e in express))
keywords = list(filter(operator.attrgetter('is_keyword'), terminals))
negated_keywords = map(lambda s: "~%s" % s, keywords)
no_action = {"letter", "digit", "digits", "real_literal", "integer_literal"}
while True:
emitted_in_loop = set()
for id, expr in express:
kws = map(repr, find_bytype(expr, Keyword))
found = [k in emitted for k in kws]
if id in to_emit and all(found):
emitted_in_loop.add(id)
emitted.add(id)
stmt = "(%s)" % expr
if id in to_combine:
stmt = " + ".join(itertools.chain(negated_keywords, ("originalTextFor(Combine%s)" % stmt,)))
if id not in no_action and not isinstance(expr.contents, Keyword) and not id in to_combine:
node_type = "ListNode" if "ZeroOrMore" in stmt else "Node"
action = actions.get(id, "lambda s, loc, t: %s(s, loc, t, rule=\"%s\")" % (node_type, id))
stmt = "%s.setParseAction(%s)" % (stmt, action)
statements.append("%s = %s(\"%s\")" % (id, stmt, id))
to_emit -= emitted_in_loop
if not emitted_in_loop: break
for id in to_emit:
statements.append("%s = Forward()(\"%s\")" % (id, id))
for id in to_emit:
expr = [e for k, e in express if k == id][0]
stmt = "(%s)" % expr
if id in to_combine:
stmt = "Suppress%s" % stmt
if id not in no_action and not isinstance(expr.contents, Keyword):
node_type = "ListNode" if "ZeroOrMore" in stmt else "Node"
action = ".setParseAction(%s)" % (actions[id] if id in actions else "lambda s, loc, t: %s(s, loc, t, rule=\"%s\")" % (node_type, id))
stmt = "(%s)%s" % (stmt, action)
statements.append("%s << %s" % (id, stmt))
print ("""
# This file is generated by IfcOpenShell ifcexpressparser bootstrap.py
import os
import sys
import pickle
import schema
import mapping
from pyparsing import *
from nodes import *
def parse(fn):
cache_file = fn + ".cache.dat"
if os.path.exists(cache_file):
with open(cache_file, "rb") as f:
m = pickle.load(f)
else:
%s
syntax.ignore("--" + restOfLine)
syntax.ignore(Regex(r"\((?:\*(?:[^*]*\*+)+?\))"))
ast = syntax.parseFile(fn)
s = schema.Schema(ast)
m = mapping.Mapping(s)
with open(cache_file, "wb") as f:
pickle.dump(m, f, protocol=0)
return m
if __name__ == "__main__":
m = parse(sys.argv[1])
import importlib
for output in sys.argv[2:]:
mdl = importlib.import_module(output)
mdl.Generator(m).emit()
sys.stdout.write(m.schema.name)
"""%('\n '.join(statements)))
-35
View File
@@ -1,35 +0,0 @@
###############################################################################
# #
# This file is part of IfcOpenShell. #
# #
# IfcOpenShell is free software: you can redistribute it and/or modify #
# it under the terms of the Lesser GNU General Public License as published by #
# the Free Software Foundation, either version 3.0 of the License, or #
# (at your option) any later version. #
# #
# IfcOpenShell is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# Lesser GNU General Public License for more details. #
# #
# You should have received a copy of the Lesser GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
###############################################################################
class Base(object):
"""
A base class for all code generation classes. Currently only working around
some python 2/3 incompatibilities in terms of unicode file handling.
"""
def emit(self):
import platform
if tuple(map(int, platform.python_version_tuple())) < (2, 8):
from io import open as unicode_open
unicode_type = unicode
else:
unicode_open = open
unicode_type = lambda x, *args, **kwargs: x
f = unicode_open(self.file_name, 'w', encoding='utf-8')
f.write(unicode_type(repr(self), encoding='utf-8', errors='ignore'))
f.close()
-68
View File
@@ -1,68 +0,0 @@
###############################################################################
# #
# This file is part of IfcOpenShell. #
# #
# IfcOpenShell is free software: you can redistribute it and/or modify #
# it under the terms of the Lesser GNU General Public License as published by #
# the Free Software Foundation, either version 3.0 of the License, or #
# (at your option) any later version. #
# #
# IfcOpenShell is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# Lesser GNU General Public License for more details. #
# #
# You should have received a copy of the Lesser GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
###############################################################################
import operator
import nodes
import codegen
from collections import defaultdict
class Definitions(codegen.Base):
def __init__(self, mapping):
schema_name = mapping.schema.name
self.schema_name = schema_name_title = schema_name.capitalize()
statements = ['']
def write_entity(schema_name, name, type):
attribute_names = list(map(lambda t: (t.name, t.optional), type.attributes))
for attr, is_optional in attribute_names:
statements.append("#define SCHEMA_%(name)s_HAS_%(attr)s" % locals())
if is_optional:
statements.append("#define SCHEMA_%(name)s_%(attr)s_IS_OPTIONAL" % locals())
inverse_attribute_names = list(map(operator.attrgetter('name'), type.inverse))
for attr in inverse_attribute_names:
statements.append("#define SCHEMA_%(name)s_HAS_%(attr)s" % locals())
def write(name):
statements.append("#define SCHEMA_HAS_%(name)s" % locals())
fn = None
if mapping.schema.is_entity(name):
fn = write_entity
if fn is not None:
decl = mapping.schema[name]
if isinstance(decl, nodes.TypeDeclaration):
decl = decl.type.type
fn(schema_name, name, decl) is not False
for name in mapping.schema:
write(name)
self.str = "\n".join(statements) + "\n"
self.file_name = '%s-definitions.h' % self.schema_name
def __repr__(self):
return self.str
Generator = Definitions
-77
View File
@@ -1,77 +0,0 @@
###############################################################################
# #
# This file is part of IfcOpenShell. #
# #
# IfcOpenShell is free software: you can redistribute it and/or modify #
# it under the terms of the Lesser GNU General Public License as published by #
# the Free Software Foundation, either version 3.0 of the License, or #
# (at your option) any later version. #
# #
# IfcOpenShell is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# Lesser GNU General Public License for more details. #
# #
# You should have received a copy of the Lesser GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
###############################################################################
###############################################################################
# #
# This files uses the documentation files from buildingSMART to generate #
# descriptions from EXPRESS names that are suitable for comments in the C++ #
# code. The .csv files used by this file are generated from the MS Office #
# Access database, which in turn has been generated from the IFC baseline #
# documentation by the IFCDOC utility provided by buildingSMART. #
# #
###############################################################################
import re
import os
import csv
from schema import OrderedCaseInsensitiveDict
try: from html.entities import entitydefs
except: from htmlentitydefs import entitydefs
make_absolute = lambda fn: os.path.join(os.path.dirname(os.path.realpath(__file__)), fn)
name_to_oid = OrderedCaseInsensitiveDict()
oid_to_desc = {}
oid_to_name = {}
oid_to_pid = {}
regices = list(zip([re.compile(s,re.M) for s in [r'<[\w\n=" \-/\.;_\t:%#,\?\(\)]+>',r'(\n[\t ]*){2,}',r'^[\t ]+']],['','\n\n',' ']))
definition_files = ['DocEntity.csv', 'DocEnumeration.csv', 'DocDefined.csv', 'DocSelect.csv']
definition_files = map(make_absolute, definition_files)
for fn in definition_files:
with open(fn, encoding="utf8", errors='ignore') as f:
for oid, name, desc in csv.reader(f, delimiter=';', quotechar='"'):
name_to_oid[name] = oid
oid_to_name[oid] = name
oid_to_desc[oid] = desc
with open(make_absolute('DocEntityAttributes.csv')) as f:
for pid, x, oid in csv.reader(f, delimiter=';', quotechar='"'):
oid_to_pid[oid] = pid
with open(make_absolute('DocAttribute.csv')) as f:
for oid, name, desc in csv.reader(f, delimiter=';', quotechar='"'):
pid = oid_to_pid[oid]
pname = oid_to_name[pid]
name_to_oid[".".join((pname, name))] = oid
oid_to_desc[oid] = desc
def description(item):
global name_to_oid, oid_to_desc, oid_to_name, oid_to_pid
oid = name_to_oid.get(item,0)
desc = oid_to_desc.get(oid, None)
if desc:
for a,b in entitydefs.items(): desc = desc.replace("&%s;"%a,b)
desc = desc.replace("\r","")
for r,s in regices: desc = r.sub(s,desc)
desc = desc.strip()
return desc.split("\n")
else: return []
-342
View File
@@ -1,342 +0,0 @@
ABS = "abs" .
ABSTRACT = "abstract" .
ACOS = "acos" .
AGGREGATE = "aggregate" .
ALIAS = "alias" .
AND = "and" .
ANDOR = "andor" .
ARRAY = "array" .
AS = "as" .
ASIN = "asin" .
ATAN = "atan" .
BAG = "bag" .
BASED_ON = "based_on" .
BEGIN = "begin" .
BINARY = "binary" .
BLENGTH = "blength" .
BOOLEAN = "boolean" .
BY = "by" .
CASE = "case" .
CONSTANT = "constant" .
CONST_E = "const_e" .
COS = "cos" .
DERIVE = "derive" .
DIV = "div" .
ELSE = "else" .
END = "end" .
END_ALIAS = "end_alias" .
END_CASE = "end_case" .
END_CONSTANT = "end_constant" .
END_ENTITY = "end_entity" .
END_FUNCTION = "end_function" .
END_IF = "end_if" .
END_LOCAL = "end_local" .
END_PROCEDURE = "end_procedure" .
END_REPEAT = "end_repeat" .
END_RULE = "end_rule" .
END_SCHEMA = "end_schema" .
END_SUBTYPE_CONSTRAINT = "end_subtype_constraint" .
END_TYPE = "end_type" .
ENTITY = "entity" .
ENUMERATION = "enumeration" .
ESCAPE = "escape" .
EXISTS = "exists" .
EXTENSIBLE = "extensible" .
EXP = "exp" .
FALSE = "false" .
FIXED = "fixed" .
FOR = "for" .
FORMAT = "format" .
FROM = "from" .
FUNCTION = "function" .
GENERIC = "generic" .
GENERIC_ENTITY = "generic_entity" .
HIBOUND = "hibound" .
HIINDEX = "hiindex" .
IF = "if" .
IN = "in" .
INSERT = "insert" .
INTEGER = "integer" .
INVERSE = "inverse" .
LENGTH = "length" .
LIKE = "like" .
LIST = "list" .
LOBOUND = "lobound" .
LOCAL = "local" .
LOG = "log" .
LOG10 = "log10" .
LOG2 = "log2" .
LOGICAL = "logical" .
LOINDEX = "loindex" .
MOD = "mod" .
NOT = "not" .
NUMBER = "number" .
NVL = "nvl" .
ODD = "odd" .
OF = "of" .
ONEOF = "oneof" .
OPTIONAL = "optional" .
OR = "or" .
OTHERWISE = "otherwise" .
PI = "pi" .
PROCEDURE = "procedure" .
QUERY = "query" .
REAL = "real" .
REFERENCE = "reference" .
REMOVE = "remove" .
RENAMED = "renamed" .
REPEAT = "repeat" .
RETURN = "return" .
ROLESOF = "rolesof" .
RULE = "rule" .
SCHEMA = "schema" .
SELECT = "select" .
SELF = "self" .
SET = "set" .
SIN = "sin" .
SIZEOF = "sizeof" .
SKIP = "skip" .
SQRT = "sqrt" .
STRING = "string" .
SUBTYPE = "subtype" .
SUBTYPE_CONSTRAINT = "subtype_constraint" .
SUPERTYPE = "supertype" .
TAN = "tan" .
THEN = "then" .
TO = "to" .
TOTAL_OVER = "total_over" .
TRUE = "true" .
TYPE = "type" .
TYPEOF = "typeof" .
UNIQUE = "unique" .
UNKNOWN = "unknown" .
UNTIL = "until" .
USE = "use" .
USEDIN = "usedin" .
VALUE = "value" .
VALUE_IN = "value_in" .
VALUE_UNIQUE = "value_unique" .
VAR = "var" .
WHERE = "where" .
WHILE = "while" .
WITH = "with" .
XOR = "xor" .
bit = "0" | "1" .
digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" .
digits = digit { digit } .
encoded_character = octet octet octet octet .
hex_digit = digit | "a" | "b" | "c" | "d" | "e" | "f" .
letter = "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" | "m" | "n" | "o" | "p" | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z" .
lparen_then_not_lparen_star = "(" { "(" } not_lparen_star { not_lparen_star } .
not_lparen_star = not_paren_star | ")" .
not_paren_star = letter | digit | not_paren_star_special .
not_paren_star_quote_special = "!" | "#" | "$" | "%" | "&" | "+" | "," | "-" | "." | "/" | ":" | ";" | "<" | "=" | ">" | "?" | "@" | "[" | "\\" | "]" | "^" | "_" | "{" | "|" | "}" | "~" .
not_paren_star_special = not_paren_star_quote_special | "\"\"" .
not_quote = not_paren_star_quote_special | letter | digit | "(" | ")" | "*" .
not_rparen_star = not_paren_star | "(" .
octet = hex_digit hex_digit .
special = not_paren_star_quote_special | "(" | ")" | "*" | "\"\"" .
not_rparen_star_then_rparen = not_rparen_star { not_rparen_star } ")" { ")" } .
binary_literal = "%" bit { bit } .
encoded_string_literal = "\"" encoded_character { encoded_character } "\"" .
integer_literal = digits .
real_literal = ( digits "." [ digits ] [ "e" [ sign ] digits ] ) | integer_literal .
simple_id = letter { letter | digit | "_" } .
simple_string_literal = "'" { ( "'" "'" ) | not_quote } "'" .
embedded_remark = "(*" [ remark_tag ] { ( not_paren_star { not_paren_star } ) | lparen_then_not_lparen_star | ( "*" { "*" } ) | not_rparen_star_then_rparen | embedded_remark } "*)" .
remark = embedded_remark | tail_remark .
remark_tag = "\"" remark_ref { "." remark_ref } "\"" .
remark_ref = attribute_ref | constant_ref | entity_ref | enumeration_ref | function_ref | parameter_ref | procedure_ref | rule_label_ref | rule_ref | schema_ref | subtype_constraint_ref | type_label_ref | type_ref | variable_ref .
tail_remark = "--" [ remark_tag ] .
attribute_ref = attribute_id .
constant_ref = constant_id .
entity_ref = entity_id .
enumeration_ref = enumeration_id .
function_ref = function_id .
parameter_ref = parameter_id .
procedure_ref = procedure_id .
rule_label_ref = rule_label_id .
rule_ref = rule_id .
schema_ref = schema_id .
subtype_constraint_ref = subtype_constraint_id .
type_label_ref = type_label_id .
type_ref = type_id .
variable_ref = variable_id .
abstract_entity_declaration = ABSTRACT .
abstract_supertype = ABSTRACT SUPERTYPE ";" .
abstract_supertype_declaration = ABSTRACT SUPERTYPE [ subtype_constraint ] .
actual_parameter_list = "(" [ parameter ] { "," parameter } ")" .
add_like_op = "+" | "-" | OR | XOR .
aggregate_initializer = "[" [ element { "," element } ] "]" .
aggregate_source = simple_expression .
aggregate_type = AGGREGATE [ ":" type_label ] OF parameter_type .
aggregation_types = array_type | bag_type | list_type | set_type .
algorithm_head = { declaration } [ constant_decl ] [ local_decl ] .
alias_stmt = ALIAS variable_id FOR general_ref { qualifier } ";" stmt { stmt } END_ALIAS ";" .
array_type = ARRAY bound_spec OF [ OPTIONAL ] [ UNIQUE ] instantiable_type .
assignment_stmt = general_ref { qualifier } ":=" expression ";" .
attribute_decl = redeclared_attribute | attribute_id .
attribute_id = simple_id .
attribute_qualifier = "." attribute_ref .
bag_type = BAG [ bound_spec ] OF instantiable_type .
binary_type = BINARY [ width_spec ] .
boolean_type = BOOLEAN .
bound_1 = numeric_expression .
bound_2 = numeric_expression .
bound_spec = "[" bound_1 ":" bound_2 "]" .
built_in_constant = CONST_E | PI | SELF | "?" .
built_in_function = ABS | ACOS | ASIN | ATAN | BLENGTH | COS | EXISTS | EXP | FORMAT | HIBOUND | HIINDEX | LENGTH | LOBOUND | LOINDEX | LOG | LOG2 | LOG10 | NVL | ODD | ROLESOF | SIN | SIZEOF | SQRT | TAN | TYPEOF | USEDIN | VALUE | VALUE_IN | VALUE_UNIQUE .
built_in_procedure = INSERT | REMOVE .
case_action = case_label { "," case_label } ":" stmt .
case_label = expression .
case_stmt = CASE selector OF { case_action } [ OTHERWISE ":" stmt ] END_CASE ";" .
compound_stmt = BEGIN stmt { stmt } END ";" .
concrete_types = aggregation_types | simple_types | type_ref .
constant_body = constant_id ":" instantiable_type ":=" expression ";" .
constant_decl = CONSTANT constant_body { constant_body } END_CONSTANT ";" .
constant_factor = built_in_constant | constant_ref .
constant_id = simple_id .
constructed_types = enumeration_type | select_type .
declaration = entity_decl | function_decl | procedure_decl | subtype_constraint_decl | type_decl .
derived_attr = attribute_decl ":" parameter_type ":=" expression ";" .
derive_clause = DERIVE derived_attr { derived_attr } .
domain_rule = [ rule_label_id ":" ] expression .
element = expression [ ":" repetition ] .
entity_body = { explicit_attr } [ derive_clause ] [ inverse_clause ] [ unique_clause ] [ where_clause ] .
entity_constructor = entity_ref "(" [ expression { "," expression } ] ")" .
entity_decl = entity_head entity_body END_ENTITY ";" .
entity_head = ENTITY entity_id subsuper ";" .
entity_id = simple_id .
enumeration_extension = BASED_ON type_ref [ WITH enumeration_items ] .
enumeration_id = simple_id .
enumeration_items = "(" enumeration_id { "," enumeration_id } ")" .
enumeration_reference = [ type_ref "." ] enumeration_ref .
enumeration_type = [ EXTENSIBLE ] ENUMERATION [ ( OF enumeration_items ) | enumeration_extension ] .
escape_stmt = ESCAPE ";" .
explicit_attr = attribute_decl { "," attribute_decl } ":" [ OPTIONAL ] parameter_type ";" .
expression = simple_expression [ rel_op_extended simple_expression ] .
factor = simple_factor [ "**" simple_factor ] .
formal_parameter = parameter_id { "," parameter_id } ":" parameter_type .
function_call = ( built_in_function | function_ref ) actual_parameter_list .
function_decl = function_head algorithm_head stmt { stmt } END_FUNCTION ";" .
function_head = FUNCTION function_id [ "(" formal_parameter { ";" formal_parameter } ")" ] ":" parameter_type ";" .
function_id = simple_id .
generalized_types = aggregate_type | general_aggregation_types | generic_entity_type | generic_type .
general_aggregation_types = general_array_type | general_bag_type | general_list_type | general_set_type .
general_array_type = ARRAY [ bound_spec ] OF [ OPTIONAL ] [ UNIQUE ] parameter_type .
general_bag_type = BAG [ bound_spec ] OF parameter_type .
general_list_type = LIST [ bound_spec ] OF [ UNIQUE ] parameter_type .
general_ref = parameter_ref | variable_ref .
general_set_type = SET [ bound_spec ] OF parameter_type .
generic_entity_type = GENERIC_ENTITY [ ":" type_label ] .
generic_type = GENERIC [ ":" type_label ] .
group_qualifier = "\\" entity_ref .
if_stmt = IF logical_expression THEN stmt { stmt } [ ELSE stmt { stmt } ] END_IF ";" .
increment = numeric_expression .
increment_control = variable_id ":=" bound_1 TO bound_2 [ BY increment ] .
index = numeric_expression .
index_1 = index .
index_2 = index .
index_qualifier = "[" index_1 [ ":" index_2 ] "]" .
instantiable_type = concrete_types | entity_ref .
integer_type = INTEGER .
interface_specification = reference_clause | use_clause .
interval = "{" interval_low interval_op interval_item interval_op interval_high "}" .
interval_high = simple_expression .
interval_item = simple_expression .
interval_low = simple_expression .
interval_op = "<=" | "<" .
inverse_attr = attribute_decl ":" [ ( SET | BAG ) [ bound_spec ] OF ] entity_ref FOR [ entity_ref "." ] attribute_ref ";" .
inverse_clause = INVERSE inverse_attr { inverse_attr } .
list_type = LIST [ bound_spec ] OF [ UNIQUE ] instantiable_type .
literal = binary_literal | logical_literal | real_literal | string_literal .
local_decl = LOCAL local_variable { local_variable } END_LOCAL ";" .
local_variable = variable_id { "," variable_id } ":" parameter_type [ ":=" expression ] ";" .
logical_expression = expression .
logical_literal = FALSE | TRUE | UNKNOWN .
logical_type = LOGICAL .
multiplication_like_op = "*" | "/" | DIV | MOD | AND | "||" .
named_types = entity_ref | type_ref .
named_type_or_rename = named_types [ AS ( entity_id | type_id ) ] .
null_stmt = ";" .
number_type = NUMBER .
numeric_expression = simple_expression .
one_of = ONEOF "(" supertype_expression { "," supertype_expression } ")" .
parameter = expression .
parameter_id = simple_id .
parameter_type = generalized_types | simple_types | named_types .
population = entity_ref .
precision_spec = numeric_expression .
primary = literal | ( qualifiable_factor { qualifier } ) .
procedure_call_stmt = ( built_in_procedure | procedure_ref ) actual_parameter_list ";" .
procedure_decl = procedure_head algorithm_head { stmt } END_PROCEDURE ";" .
procedure_head = PROCEDURE procedure_id [ "(" [ VAR ] formal_parameter { ";" [ VAR ] formal_parameter } ")" ] ";" .
procedure_id = simple_id .
qualifiable_factor = function_call | attribute_ref | constant_factor | general_ref | population .
qualified_attribute = SELF group_qualifier attribute_qualifier .
qualifier = attribute_qualifier | group_qualifier | index_qualifier .
query_expression = QUERY "(" variable_id "<*" aggregate_source "|" logical_expression ")" .
real_type = REAL [ "(" precision_spec ")" ] .
redeclared_attribute = qualified_attribute [ RENAMED attribute_id ] .
referenced_attribute = attribute_ref | qualified_attribute .
reference_clause = REFERENCE FROM schema_ref [ "(" resource_or_rename { "," resource_or_rename } ")" ] ";" .
rel_op = "<=" | ">=" | "<>" | "=" | ":<>:" | ":=:" | "<" | ">" .
rel_op_extended = rel_op | IN | LIKE .
rename_id = constant_id | entity_id | function_id | procedure_id | type_id .
repeat_control = [ increment_control ] [ while_control ] [ until_control ] .
repeat_stmt = REPEAT repeat_control ";" stmt { stmt } END_REPEAT ";" .
repetition = numeric_expression .
resource_or_rename = resource_ref [ AS rename_id ] .
resource_ref = constant_ref | entity_ref | function_ref | procedure_ref | type_ref .
return_stmt = RETURN [ "(" expression ")" ] ";" .
rule_decl = rule_head algorithm_head { stmt } where_clause END_RULE ";" .
rule_head = RULE rule_id FOR "(" entity_ref { "," entity_ref } ")" ";" .
rule_id = simple_id .
rule_label_id = simple_id .
schema_body = { interface_specification } [ constant_decl ] { declaration | rule_decl } .
schema_decl = SCHEMA schema_id [ schema_version_id ] ";" schema_body END_SCHEMA ";" .
schema_id = simple_id .
schema_version_id = string_literal .
selector = expression .
select_extension = BASED_ON type_ref [ WITH select_list ] .
select_list = "(" named_types { "," named_types } ")" .
select_type = [ EXTENSIBLE [ GENERIC_ENTITY ] ] SELECT [ select_list | select_extension ] .
set_type = SET [ bound_spec ] OF instantiable_type .
sign = "+" | "-" .
simple_expression = term { add_like_op term } .
simple_factor = aggregate_initializer | interval | query_expression | ( [ unary_op ] ( "(" expression ")" | primary ) ) | entity_constructor | enumeration_reference .
simple_types = binary_type | boolean_type | integer_type | logical_type | number_type | real_type | string_type .
skip_stmt = SKIP ";" .
stmt = alias_stmt | assignment_stmt | case_stmt | compound_stmt | escape_stmt | if_stmt | null_stmt | procedure_call_stmt | repeat_stmt | return_stmt | skip_stmt .
string_literal = simple_string_literal | encoded_string_literal .
string_type = STRING [ width_spec ] .
subsuper = [ supertype_constraint ] [ subtype_declaration ] .
subtype_constraint = OF "(" supertype_expression ")" .
subtype_constraint_body = [ abstract_supertype ] [ total_over ] [ supertype_expression ";" ] .
subtype_constraint_decl = subtype_constraint_head subtype_constraint_body END_SUBTYPE_CONSTRAINT ";" .
subtype_constraint_head = SUBTYPE_CONSTRAINT subtype_constraint_id FOR entity_ref ";" .
subtype_constraint_id = simple_id .
subtype_declaration = SUBTYPE OF "(" entity_ref { "," entity_ref } ")" .
supertype_constraint = abstract_supertype_declaration | abstract_entity_declaration | supertype_rule .
supertype_expression = supertype_factor { ANDOR supertype_factor } .
supertype_factor = supertype_term { AND supertype_term } .
supertype_rule = SUPERTYPE subtype_constraint .
supertype_term = one_of | "(" supertype_expression ")" | entity_ref .
syntax = schema_decl { schema_decl } .
term = factor { multiplication_like_op factor } .
total_over = TOTAL_OVER "(" entity_ref { "," entity_ref } ")" ";" .
type_decl = TYPE type_id "=" underlying_type ";" [ where_clause ] END_TYPE ";" .
type_id = simple_id .
type_label = type_label_id | type_label_ref .
type_label_id = simple_id .
unary_op = "+" | "-" | NOT .
underlying_type = constructed_types | concrete_types .
unique_clause = UNIQUE unique_rule ";" { unique_rule ";" } .
unique_rule = [ rule_label_id ":" ] referenced_attribute { "," referenced_attribute } .
until_control = UNTIL logical_expression .
use_clause = USE FROM schema_ref [ "(" named_type_or_rename { "," named_type_or_rename } ")" ] ";" .
variable_id = simple_id .
where_clause = WHERE domain_rule ";" { domain_rule ";" } .
while_control = WHILE logical_expression .
width = numeric_expression .
width_spec = "(" width ")" [ FIXED ] .
-149
View File
@@ -1,149 +0,0 @@
###############################################################################
# #
# This file is part of IfcOpenShell. #
# #
# IfcOpenShell is free software: you can redistribute it and/or modify #
# it under the terms of the Lesser GNU General Public License as published by #
# the Free Software Foundation, either version 3.0 of the License, or #
# (at your option) any later version. #
# #
# IfcOpenShell is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# Lesser GNU General Public License for more details. #
# #
# You should have received a copy of the Lesser GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
###############################################################################
import operator
import codegen
import templates
import documentation
class Header(codegen.Base):
def __init__(self, mapping):
declarations = []
write = lambda str, **kwargs: declarations.append(str%dict({
'documentation': templates.multi_line_comment(documentation.description(kwargs['name']))}, **kwargs))
forward_names = list(mapping.schema.entities.keys()) + list(mapping.schema.simpletypes.keys())
forward_definitions = "".join(["class %s; "%n for n in forward_names])
for name, type in mapping.schema.selects.items():
write(templates.select, name=name)
for name, type in mapping.schema.enumerations.items():
short_name = name[:-4] if name.endswith("Enum") else name
write(templates.enumeration, name=name, values=", ".join(["%s_%s"%(short_name, v) for v in type.values]))
emitted_simpletypes = set()
while len(emitted_simpletypes) < len(mapping.schema.simpletypes):
for name, type in mapping.schema.simpletypes.items():
if name.lower() in emitted_simpletypes: continue
type_str = mapping.make_type_string(mapping.flatten_type_string(type))
attr_type = mapping.make_argument_type(type)
superclass = mapping.simple_type_parent(name)
if superclass is None:
superclass = "IfcUtil::IfcBaseType"
elif superclass.lower() not in emitted_simpletypes:
continue
else:
# Case normalize
superclass = [k for k in mapping.schema.simpletypes.keys() if k.lower() == superclass.lower()][0]
emitted_simpletypes.add(name.lower())
write(templates.simpletype, name=name, type=type_str, attr_type=attr_type, superclass=superclass)
class_definitions = []
write = lambda str, **kwargs: class_definitions.append(str%dict({
'documentation': templates.multi_line_comment(documentation.description(kwargs['name']))}, **kwargs))
emitted_entities = set()
while len(emitted_entities) < len(mapping.schema.entities):
for name, type in mapping.schema.entities.items():
if name.lower() in emitted_entities: continue
if len(type.supertypes) == 0 or set(map(str.lower, type.supertypes)) <= emitted_entities:
attr_lines = []
def write_method(attr):
if attr.optional:
attr_lines.append(templates.optional_attribute_description % (attr.name, name))
attr_lines.append("bool has%s() const;"%(attr.name))
attr_lines.extend(["/// %s"%d for d in documentation.description(".".join((name, attr.name)))])
type_str = mapping.get_parameter_type(attr, allow_optional=False, allow_entities=False)
if mapping.make_argument_type(attr) != "IfcUtil::Argument_UNKNOWN":
attr_lines.append("%s %s() const;"%(type_str, attr.name))
attr_lines.append("void set%s(%s v);"%(attr.name, type_str))
[write_method(attr) for attr in type.attributes]
inv_lines = []
def write_inverse(attr):
inv_lines.append(templates.inverse_attr%{'name':attr.name, 'entity':attr.entity, 'attribute':attr.attribute})
if type.inverse:
[write_inverse(attr) for attr in type.inverse]
attributes = "\n".join(["%s%s"%(' '*4, a) for a in attr_lines])
if len(attributes): attributes += '\n'
inverse = "\n".join(["%s%s"%(' '*4, a) for a in inv_lines])
if len(inverse): inverse += '\n'
def case_norm(n):
n = n.lower()
return [k for k in mapping.schema.entities.keys() if k.lower() == n][0]
supertypes = map(case_norm, type.supertypes) if len(type.supertypes) else ['IfcUtil::IfcBaseEntity']
superclass = ": %s "%(", ".join(["public %s"%c for c in supertypes]))
argument_count = mapping.argument_count(type)
argument_start = argument_count - len(type.attributes)
argument_name_function_body_switch_stmt = " switch (i) {%s}"%("".join(['case %d: return "%s"; '%(i+argument_start, attr.name) for i, attr in enumerate(type.attributes)])) if len(type.attributes) else ""
argument_name_function_body_tail = (" return %s::getArgumentName(i); "%type.supertypes[0]) if len(type.supertypes) == 1 else ' (void)i; throw IfcParse::IfcAttributeOutOfRangeException("Argument index out of range"); '
argument_name_function_body = argument_name_function_body_switch_stmt + argument_name_function_body_tail
derived = mapping.derived_in_supertype(type)
attribute_names = list(map(operator.attrgetter('name'), mapping.arguments(type)))
derived_in_supertype = set(derived) & set(attribute_names)
derived_in_supertype_indices = sorted(attribute_names.index(nm) for nm in derived_in_supertype)
attribute_type_cases = ['case %d: return IfcUtil::Argument_DERIVED; ' % idx for idx in derived_in_supertype_indices]
attribute_type_cases += ['case %d: return %s; '%(i+argument_start, mapping.make_argument_type(attr)) for i, attr in enumerate(type.attributes)]
argument_type_function_body_switch_stmt = " switch (i) {%s}"%("".join(attribute_type_cases)) if len(type.attributes) else ""
argument_type_function_body_tail = (" return %s::getArgumentType(i); "%type.supertypes[0]) if len(type.supertypes) == 1 else ' (void)i; throw IfcParse::IfcAttributeOutOfRangeException("Argument index out of range"); '
argument_type_function_body = argument_type_function_body_switch_stmt + argument_type_function_body_tail
argument_entity_function_body_switch_stmt = " switch (i) {%s}"%("".join(['case %d: return %s; '%(i+argument_start, mapping.make_argument_entity(attr)) for i, attr in enumerate(type.attributes)])) if len(type.attributes) else ""
argument_entity_function_body_tail = (" return %s::getArgumentEntity(i); "%type.supertypes[0]) if len(type.supertypes) == 1 else ' (void)i; throw IfcParse::IfcAttributeOutOfRangeException("Argument index out of range"); '
argument_entity_function_body = argument_entity_function_body_switch_stmt + argument_entity_function_body_tail
constructor_arguments = ", ".join("%(full_type)s v%(index)d_%(name)s"%a for a in mapping.get_assignable_arguments(type))
write(templates.entity, **locals())
emitted_entities.add(name)
self.str = templates.header % {
'schema_name_upper' : mapping.schema.name.upper(),
'schema_name' : mapping.schema.name.capitalize(),
'declarations' : ''.join(declarations),
'forward_definitions' : forward_definitions,
'class_definitions' : ''.join(class_definitions)
}
self.schema_name = mapping.schema.name.capitalize()
self.file_name = '%s.h'%self.schema_name
def __repr__(self):
return self.str
Generator = Header
-262
View File
@@ -1,262 +0,0 @@
###############################################################################
# #
# This file is part of IfcOpenShell. #
# #
# IfcOpenShell is free software: you can redistribute it and/or modify #
# it under the terms of the Lesser GNU General Public License as published by #
# the Free Software Foundation, either version 3.0 of the License, or #
# (at your option) any later version. #
# #
# IfcOpenShell is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# Lesser GNU General Public License for more details. #
# #
# You should have received a copy of the Lesser GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
###############################################################################
import codegen
import templates
from schema import OrderedCaseInsensitiveDict
class Implementation(codegen.Base):
def __init__(self, mapping):
enumeration_functions = []
entity_implementations = []
schema_entity_statements = []
schema_name = mapping.schema.name.capitalize()
schema_name_upper = mapping.schema.name.upper()
stringify = lambda s: '"%s"'%s
cat = lambda vs: "".join(vs)
catc = lambda vs: ", ".join(vs)
catnl = lambda vs: "\n".join(vs)
cator = lambda vs: " || ".join(vs)
nl = lambda s: "%s\n"%s if len(s) else s
write = lambda str, **kwargs: enumeration_functions.append(str%kwargs)
for name, enum in mapping.schema.enumerations.items():
short_name = name[:-4] if name.endswith("Enum") else name
context = locals()
write(
templates.enumeration_function,
max_id = len(enum.values),
name = name,
schema_name = schema_name,
schema_name_upper = schema_name_upper,
values = catc(map(stringify, enum.values)),
from_string_statements = catnl(templates.enum_from_string_stmt%dict(context,**locals()) for value in enum.values)
)
write = lambda str, **kwargs: entity_implementations.append(str%kwargs)
for name, type in mapping.schema.entities.items():
parent_type_test = "" if not type.supertypes or len(type.supertypes) != 1 \
else templates.parent_type_test%(type.supertypes[0])
constructor_arguments = mapping.get_assignable_arguments(type, include_derived = True)
constructor_arguments_str = catc("%(full_type)s v%(index)d_%(name)s"%a for a in constructor_arguments if not a['is_derived'])
attributes = []
constructor_implementations = []
write_attr = lambda str, **kwargs: attributes.append(str%kwargs)
for arg in constructor_arguments:
if not arg['is_inherited'] and not arg['is_derived']:
if arg['is_optional']:
write_attr(
templates.const_function,
class_name = name,
schema_name = schema_name,
schema_name_upper = schema_name_upper,
name = 'has%s'%arg['name'],
arguments = '',
return_type = 'bool',
body = templates.optional_attr_stmt % {'index':arg['index']-1}
)
def find_template(arg):
simple = mapping.schema.is_simpletype(arg['list_instance_type'])
select = arg['list_instance_type'] == "IfcUtil::IfcBaseClass"
express = mapping.flatten_type_string(arg['list_instance_type']) in mapping.express_to_cpp_typemapping
if arg['is_enum']: return templates.get_attr_stmt_enum
elif arg['is_nested'] and arg['is_templated_list']: return templates.get_attr_stmt_nested_array
elif arg['is_templated_list'] and not (select or simple or express): return templates.get_attr_stmt_array
elif arg['non_optional_type'].endswith('*'): return templates.get_attr_stmt_entity
else: return templates.get_attr_stmt
tmpl = find_template(arg)
write_attr(
templates.const_function,
class_name = name,
name = arg['name'],
arguments = '',
schema_name = schema_name,
schema_name_upper = schema_name_upper,
return_type = arg['non_optional_type'],
body = tmpl % {'index': arg['index']-1,
'type' : arg['non_optional_type'].replace('::Value', ''),
'list_instance_type' : arg['list_instance_type']}
)
def find_template(arg):
simple = mapping.schema.is_simpletype(arg['list_instance_type'])
select = arg['list_instance_type'] == "IfcUtil::IfcBaseClass"
express = arg['list_instance_type'] in mapping.express_to_cpp_typemapping
if arg['is_enum']: return templates.set_attr_stmt_enum
elif arg['is_templated_list'] and not (select or simple or express): return templates.set_attr_stmt_array
else: return templates.set_attr_stmt
tmpl = find_template(arg)
write_attr(
templates.function,
class_name = name,
name = 'set%s'%arg['name'],
arguments = '%s v'%arg['non_optional_type'],
return_type = 'void',
schema_name = schema_name,
schema_name_upper = schema_name_upper,
body = tmpl % {'index': arg['index']-1,
'type' : arg['non_optional_type'].replace('::Value', '')}
)
if arg['is_derived']:
constructor_implementations.append(templates.constructor_stmt_derived % {'index' : arg['index']-1})
else:
is_optional_non_naked_ptr = arg['is_optional'] and not arg['non_optional_type'].endswith('*')
arg_name = "v%(index)d_%(name)s"%arg
deref_name = ("*%s"%arg_name) if is_optional_non_naked_ptr else arg_name
tmpl = templates.constructor_stmt_array if arg['is_templated_list'] \
else templates.constructor_stmt_enum if arg['is_enum'] \
else templates.constructor_stmt
impl = tmpl % {'name' : deref_name,
'index' : arg['index']-1,
'type' : arg['non_optional_type'].replace('::Value', '')}
if is_optional_non_naked_ptr:
impl = templates.constructor_stmt_optional%{'name' : arg_name,
'index' : arg['index']-1,
'stmt' : impl}
constructor_implementations.append(impl)
def get_attribute_index(entity, attr_name):
related_entity = mapping.schema.entities[entity]
return [a['name'].lower() for a in mapping.get_assignable_arguments(related_entity, include_derived=True)].index(attr_name.lower())
inverse = [templates.const_function % {
'class_name' : name,
'schema_name' : schema_name,
'schema_name_upper' : schema_name_upper,
'name' : i.name,
'arguments' : '',
'return_type' : '::%s::%s::list::ptr' % (schema_name, i.entity),
'body' : templates.get_inverse % {'type': i.entity, 'index':get_attribute_index(i.entity, i.attribute), 'schema_name' : schema_name, 'schema_name_upper': schema_name_upper}
} for i in type.inverse]
superclass = "%s((IfcEntityInstanceData*)0)" % type.supertypes[0] if len(type.supertypes) == 1 else 'IfcUtil::IfcBaseEntity()'
write(
templates.entity_implementation,
name = name,
parent_type_test = parent_type_test,
constructor_arguments = constructor_arguments_str,
constructor_implementation = cat(constructor_implementations),
attributes = nl(catnl(attributes)),
inverse = nl(catnl(inverse)),
superclass = superclass,
schema_name = schema_name,
schema_name_upper = schema_name_upper
)
selectable_simple_types = sorted(set(sum([b.values for a,b in mapping.schema.selects.items()], [])) & set(map(str, mapping.schema.types.keys())))
schema_entity_statements += [templates.schema_entity_stmt%locals() for name, type in mapping.schema.simpletypes.items()]
schema_entity_statements += [templates.schema_entity_stmt%locals() for name, type in mapping.schema.entities.items()]
enumerable_types = sorted(set([name for name, type in mapping.schema.types.items()] + [name for name, type in mapping.schema.entities.items()]))
max_len = max(map(len, enumerable_types))
type_name_strings = catc(map(stringify, enumerable_types))
string_map_statements = [templates.string_map_statement % {
'uppercase_name' : name.upper(),
'name' : name,
'padding' : ' ' * (max_len - len(name))
} for name in enumerable_types]
enumeration_index_by_str = OrderedCaseInsensitiveDict((j,i) for i,j in enumerate(enumerable_types))
def get_parent_id(s):
e = mapping.schema.entities.get(s)
if e and e.supertypes:
return enumeration_index_by_str[e.supertypes[0]]
else: return -1
parent_type_statements = ",".join(map(str, map(get_parent_id, enumerable_types)))
max_id = len(enumerable_types)
simple_type_statements = cator("v == Type::%s"%name for name in selectable_simple_types)
simple_type_impl = []
for class_name, type in mapping.schema.simpletypes.items():
type_str = mapping.make_type_string(mapping.flatten_type_string(type))
attr_type = mapping.make_argument_type(type)
superclass = mapping.simple_type_parent(class_name)
simpletype_impl_is = templates.simpletype_impl_is_with_supertype if superclass \
else templates.simpletype_impl_is_without_supertype
constructor = templates.constructor_single_initlist if superclass \
else templates.constructor
simpletype_impl_cast = templates.simpletype_impl_cast_templated if mapping.is_templated_list(type) \
else templates.simpletype_impl_cast
simpletype_impl_constructor = templates.simpletype_impl_constructor_templated if mapping.is_templated_list(type) \
else templates.simpletype_impl_constructor
def compose(params, schema_name=schema_name, schema_name_upper=schema_name_upper):
class_name, attr_type, superclass, superclass_init, name, tmpl, return_type, args, body = params
underlying_type = mapping.list_instance_type(type)
arguments = ",".join(args)
body = body % locals()
return tmpl % locals()
simple_type_impl.append(templates.simpletype_impl_comment % {'name': class_name})
simple_type_impl.extend(map(compose, map(lambda x: (class_name, attr_type, superclass, "(IfcEntityInstanceData*)0")+x, (
('Class', templates.function, 'const IfcParse::type_declaration&', (), templates.simpletype_impl_class ),
('declaration', templates.const_function, 'const IfcParse::type_declaration&', (), templates.simpletype_impl_declaration ),
('', constructor, '', ('IfcEntityInstanceData* e',), templates.simpletype_impl_explicit_constructor),
('', constructor, '', ("%s v" % type_str,), simpletype_impl_constructor ),
('', templates.cast_function, type_str, (), simpletype_impl_cast )
))))
simple_type_impl.append('')
external_definitions = [("extern entity* %s_%%s_type;" % schema_name_upper) % n for n in mapping.schema.entities.keys() ] + \
[("extern type_declaration* %s_%%s_type;" % schema_name_upper) % n for n in mapping.schema.simpletypes.keys()]
self.str = templates.implementation % {
'schema_name_upper' : schema_name_upper,
'schema_name' : schema_name,
'max_id' : max_id,
'enumeration_functions' : cat(enumeration_functions),
'schema_entity_statements' : catnl(schema_entity_statements),
'type_name_strings' : type_name_strings,
'string_map_statements' : catnl(string_map_statements),
'simple_type_statement' : simple_type_statements,
'parent_type_statements' : parent_type_statements,
'entity_implementations' : catnl(entity_implementations),
'simple_type_impl' : catnl(simple_type_impl),
'external_definitions' : catnl(external_definitions)
}
self.schema_name = mapping.schema.name.capitalize()
self.file_name = '%s.cpp'%self.schema_name
def __repr__(self):
return self.str
Generator = Implementation
-245
View File
@@ -1,245 +0,0 @@
###############################################################################
# #
# This file is part of IfcOpenShell. #
# #
# IfcOpenShell is free software: you can redistribute it and/or modify #
# it under the terms of the Lesser GNU General Public License as published by #
# the Free Software Foundation, either version 3.0 of the License, or #
# (at your option) any later version. #
# #
# IfcOpenShell is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# Lesser GNU General Public License for more details. #
# #
# You should have received a copy of the Lesser GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
###############################################################################
from __future__ import print_function
import sys
import nodes
import templates
class Mapping:
express_to_cpp_typemapping = {
'boolean' : 'bool',
'logical' : 'bool',
'integer' : 'int',
'real' : 'double',
'number' : 'double',
'string' : 'std::string',
'binary' : 'boost::dynamic_bitset<>'
}
supported_argument_types = set([
'INT', 'BOOL', 'DOUBLE', 'STRING', 'BINARY', 'ENUMERATION', 'ENTITY_INSTANCE',
'AGGREGATE_OF_INT', 'AGGREGATE_OF_DOUBLE', 'AGGREGATE_OF_STRING', 'AGGREGATE_OF_BINARY', 'AGGREGATE_OF_ENTITY_INSTANCE',
'AGGREGATE_OF_AGGREGATE_OF_INT', 'AGGREGATE_OF_AGGREGATE_OF_DOUBLE', 'AGGREGATE_OF_AGGREGATE_OF_ENTITY_INSTANCE',
])
def __init__(self, schema):
self.schema = schema
def flatten_type_string(self, type):
return self.flatten_type_string(self.schema.types[type].type) if self.schema.is_simpletype(type) else type
def flatten_type(self, type):
res = self.flatten_type(self.schema.types[type].type) if self.schema.is_simpletype(type) else type
return res
def simple_type_parent(self, type):
parent = self.schema.types[type].type
if isinstance(parent, (nodes.AggregationType, nodes.StringType)) or (isinstance(parent, nodes.SimpleType) and isinstance(parent.type, nodes.StringType)):
return None
if str(parent) in self.express_to_cpp_typemapping:
return None
return str(parent)
def make_type_string(self, type):
if isinstance(type, nodes.StringType) or (isinstance(type, nodes.SimpleType) and isinstance(type.type, nodes.StringType)):
type = "string"
if isinstance(type, (str, nodes.BinaryType, nodes.SimpleType, nodes.NamedType)):
return self.express_to_cpp_typemapping.get(str(type), "::%s::%s" % (self.schema.name.capitalize(), type))
else:
if type.bounds is None:
import pdb; pdb.set_trace()
is_list = self.schema.is_entity(type.type)
is_nested_list = isinstance(type.type, nodes.AggregationType)
tmpl = templates.list_list_type if is_nested_list else templates.list_type if is_list else templates.array_type
return tmpl % {
'instance_type' : self.make_type_string(self.flatten_type_string(type.type)),
'lower' : type.bounds.lower,
'upper' : type.bounds.upper,
}
def is_array(self, type):
if isinstance(type, nodes.AggregationType):
return True
elif isinstance(type, str) and self.schema.is_type(type):
return self.is_array(self.schema.types[type].type)
else:
return False
def make_argument_entity(self, attr):
type = attr.type if hasattr(attr, 'type') else attr
while isinstance(type, nodes.AggregationType): type = type.type
if str(type) in self.express_to_cpp_typemapping: return "Type::UNDEFINED"
else: return "Type::%s" % type
def make_argument_type(self, attr):
def _make_argument_type(type):
if isinstance(type, nodes.SimpleType):
type = type.type
if self.schema.is_entity(type) or isinstance(type, nodes.SelectType):
return "ENTITY_INSTANCE"
elif isinstance(type, nodes.BinaryType):
return "BINARY"
elif isinstance(type, nodes.StringType):
return "STRING"
elif isinstance(type, nodes.EnumerationType):
return "ENUMERATION"
elif isinstance(type, nodes.AggregationType):
ty = _make_argument_type(type.type)
if ty == "UNKNOWN": return "UNKNOWN"
return "AGGREGATE_OF_" + ty
elif str(type) in self.express_to_cpp_typemapping:
return self.express_to_cpp_typemapping.get(str(type), type).split('::')[-1].upper()
elif self.schema.is_type(type):
return _make_argument_type(self.schema.types[type].type)
else:
raise ValueError("Unable to map type %r for attribute %r" % (type, attr))
ty = _make_argument_type(attr.type if hasattr(attr, 'type') else attr)
if ty not in self.supported_argument_types:
print("Attribute %r mapped as 'unknown'" % (attr), file=sys.stderr)
ty = 'UNKNOWN'
return "IfcUtil::Argument_%s" % ty
def get_type_dep(self, type):
if isinstance(type, str):
return self.express_to_cpp_typemapping.get(str(type), type)
else:
return self.get_type_dep(type.type)
def get_parameter_type(self, attr, allow_optional, allow_entities, allow_pointer = True):
attr_type = self.flatten_type(attr.type)
if (isinstance(attr_type, nodes.SimpleType) and isinstance(attr_type.type, nodes.StringType)) or isinstance(attr_type, nodes.StringType):
type_str = self.express_to_cpp_typemapping["string"]
else:
type_str = self.express_to_cpp_typemapping.get(str(attr_type), attr_type)
is_ptr = False
if self.schema.is_enumeration(attr_type):
type_str = '::%s::%s::Value' % (self.schema.name.capitalize(), attr_type)
elif isinstance(type_str, nodes.AggregationType):
is_nested_list = isinstance(attr_type.type, nodes.AggregationType)
ty = self.get_parameter_type(attr_type.type if is_nested_list else attr_type, False, allow_entities, False)
if self.schema.is_select(attr_type.type):
type_str = templates.untyped_list
elif self.schema.is_simpletype(ty) or str(ty) in self.express_to_cpp_typemapping.values():
tmpl = templates.nested_array_type if is_nested_list else templates.array_type
bounds = (attr_type.bounds.lower, attr_type.bounds.upper) if attr_type.bounds else (-1, -1)
type_str = tmpl % {
'instance_type' : ty,
'lower' : bounds[0],
'upper' : bounds[1]
}
else:
tmpl = templates.list_list_type if is_nested_list else templates.list_type
type_str = tmpl % {
'instance_type': ty
}
elif (self.schema.is_entity(type_str) or self.schema.is_select(type_str)):
type_str = '::%s::%s' % (self.schema.name.capitalize(), attr_type)
if allow_pointer:
type_str += "*"
is_ptr = True
elif not allow_pointer and self.schema.is_select(type_str):
type_str = "IfcUtil::IfcBaseClass*"
is_ptr = True
if allow_optional and attr.optional and not is_ptr:
type_str = "boost::optional< %s >"%type_str
return type_str
def argument_count(self, t):
c = sum([self.argument_count(self.schema.entities[s]) for s in t.supertypes])
return c + len(t.attributes)
def arguments(self, t):
c = sum([self.arguments(self.schema.entities[s]) for s in t.supertypes], [])
return c + t.attributes
def derived_in_supertype(self, t):
c = sum([self.derived_in_supertype(self.schema.entities[s]) for s in t.supertypes], [])
derived = c + t.derive
return [d[0][1] for d in derived if isinstance(d[0], tuple)]
def list_instance_type(self, attr):
attr_type = attr.type if isinstance(attr, nodes.ExplicitAttribute) else attr
if isinstance(attr_type, str): return None
def f(v):
v = self.flatten_type(v)
if isinstance(v, (nodes.AggregationType, nodes.StringType)) or (isinstance(v, nodes.SimpleType) and isinstance(v.type, nodes.StringType)):
return "string"
if self.schema.is_select(v):
return 'IfcUtil::IfcBaseClass'
elif str(v) in self.schema.types or str(v) in self.schema.entities:
return "::%s::%s" % (self.schema.name.capitalize(), v)
else: return str(v)
if self.is_array(attr_type):
if not isinstance(attr_type, str) and self.is_array(attr_type.type):
if isinstance(attr_type.type, str):
return f(attr_type.type)
else: return f(attr_type.type.type)
else:
if isinstance(attr_type, str):
return f(attr_type)
else: return f(attr_type.type)
return None
def is_templated_list(self, attr):
attr_type = attr.type if isinstance(attr, nodes.ExplicitAttribute) else attr
if isinstance(attr, str): return False
ty = self.list_instance_type(attr)
if ty is None: return False
arr = self.is_array(attr_type)
simple = self.schema.is_simpletype(ty)
express = self.flatten_type_string(ty) in self.express_to_cpp_typemapping
select = ty == 'IfcUtil::IfcBaseClass'
return arr and not simple and not express and not select
def get_assignable_arguments(self, t, include_derived = False):
count = self.argument_count(t)
num_inherited = count - len(t.attributes)
derived = set(self.derived_in_supertype(t))
attrs = enumerate(self.arguments(t))
def include(attr):
not_derived = include_derived or (attr.name not in derived)
supported = self.make_argument_type(attr) != "IfcUtil::Argument_UNKNOWN"
return not_derived and supported
return [{
'index' : i+1,
'name' : attr.name,
'full_type' : self.get_parameter_type(attr, allow_optional=True, allow_entities=True),
'specialized_type' : self.get_parameter_type(attr, allow_optional=True, allow_entities=False),
'non_optional_type' : self.get_parameter_type(attr, allow_optional=False, allow_entities=False),
'list_instance_type' : self.list_instance_type(attr),
'is_optional' : attr.optional,
'is_inherited' : i < num_inherited,
'is_enum' : attr.type in self.schema.enumerations,
'is_array' : self.is_array(attr.type),
'is_nested' : self.is_array(attr.type) and not isinstance(attr.type, str) and self.is_array(attr.type.type),
'is_derived' : attr.name in derived,
'is_templated_list' : self.is_templated_list(attr),
'argument_type_enum' : self.make_argument_type(attr),
'argument_entity' : self.make_argument_entity(attr),
'argument_type' : attr.type
} for i, attr in attrs if include(attr)]
-358
View File
@@ -1,358 +0,0 @@
###############################################################################
# #
# This file is part of IfcOpenShell. #
# #
# IfcOpenShell is free software: you can redistribute it and/or modify #
# it under the terms of the Lesser GNU General Public License as published by #
# the Free Software Foundation, either version 3.0 of the License, or #
# (at your option) any later version. #
# #
# IfcOpenShell is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# Lesser GNU General Public License for more details. #
# #
# You should have received a copy of the Lesser GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
###############################################################################
from __future__ import print_function
import io
import string
import collections
class Node:
def __init__(self, s, loc, tokens, rule=None):
self.rule = rule or (type(self).__name__)
self.tokens = tokens.asDict()
self.flat = sum([getattr(t, 'flat', [t]) for t in tokens.asList()], [])
if rule is None:
self.init()
def __repr__(self):
return "%s(%s)" % (self.rule, ",".join("%s:%s" % i for i in self.tokens.items()))
def __getattr__(self, k):
return self.tokens.get(k)
def __getstate__(self): return self.__dict__
def __setstate__(self, d): self.__dict__.update(d)
def init(self): pass
def any(self):
return next(iter(self.tokens.values()))
class ListNode:
def __init__(self, s, loc, tokens, rule=None):
self.rule = rule or (type(self).__name__)
self.tokens = tokens.asList()
self.flat = sum([getattr(t, 'flat', [t]) for t in self.tokens], [])
def __repr__(self):
return "%s[%s]" % (self.rule, ",".join("%s" % i for i in self.tokens))
def __iter__(self):
return iter(self.tokens)
def __getitem__(self, i):
return self.tokens[i]
def init(self): pass
class SimpleType(Node):
def get_type(self):
t = self.any()
if (type(t) == Node):
return t.any()
else:
t = t[0]
if (type(t) == Node):
return t.any().any()
else:
return t
type = property(get_type)
def __repr__(self):
return str(self.type)
def format_clause(exp):
def whitespace(t):
if t in {'=', '|', '<*', 'or', 'in', '<>', 'and'}:
return ' %s ' % t
return t
return "".join(whitespace(term) for term in exp.flat)
class TypeDeclaration(Node):
name = property(lambda self: self.type_id[0])
type = property(lambda self: self.underlying_type.any().any())
def init(self):
assert hasattr(self, "TYPE")
self.where = []
clause = self.where_clause
if clause:
clause = clause[0]
self.where = [(r.simple_id, format_clause(r.expression[0])) for r in clause[1::2]]
def __repr__(self):
s = "TYPE %s = %s;\n" % (self.name, self.type)
if self.where:
s += " WHERE\n"
for nm_exp in self.where:
s += " %s : %s;\n" % nm_exp
s += "END_TYPE;"
return s
class EntityDeclaration(Node):
name = property(lambda self: self.entity_head[0].entity_id[0])
supertype = property(lambda self: self.entity_head[0].subsuper[0].supertype_constraint)
subtype = property(lambda self: self.entity_head[0].subsuper[0].subtype_declaration)
supertypes = property(lambda self: [self.subtype.super_type] if self.subtype else [])
def get_abstract(self):
if self.entity_head[0].subsuper[0].supertype_constraint:
return self.entity_head[0].subsuper[0].supertype_constraint.abstract
else:
return False
abstract = property(get_abstract)
def init(self):
def redeclared_attribute(a):
try:
return (
a.attribute_decl.redeclared_attribute.qualified_attribute.group_qualifier.simple_id,
a.attribute_decl.redeclared_attribute.qualified_attribute.attribute_qualifier.simple_id
)
except:
return a.attribute_decl.simple_id
assert self.flat[0] == 'entity'
self.attributes = [a for a in self.entity_body[0] if isinstance(a, ExplicitAttribute)]
self.inverse = []
alist = [x for x in self.entity_body[0] if isinstance(x, AttributeList) and x.type == 'inverse']
if alist:
self.inverse = alist[0]
self.derive = []
alist = [x for x in self.entity_body[0] if isinstance(x, AttributeList) and x.type == 'derive']
if alist:
alist = alist[0]
self.derive = [(redeclared_attribute(a), format_clause(a.expression[0])) for a in alist]
self.where = []
clause = [r for r in self.entity_body[0] if r.rule == "where_clause"]
if clause:
clause = clause[0]
self.where = [(r.simple_id, format_clause(r.expression[0])) for r in clause[1::2]]
self.unique = []
clause = [r for r in self.entity_body[0] if r.rule == "unique_clause"]
if clause:
clause = clause[0]
self.unique = [(r[0], r[2].simple_id) for r in clause[1::2]]
def __repr__(self):
strm = io.StringIO()
print("ENTITY %s" % self.name, file=strm)
if self.supertype:
print("", self.supertype, file=strm)
if self.subtype:
print("", self.subtype, file=strm)
strm.seek(strm.tell() - 1)
print(";", file=strm)
for a in self.attributes:
print(" ", a, ";", file=strm, sep='')
if self.derive:
print(" DERIVE", file=strm)
for nm, exp in self.derive:
if isinstance(nm, tuple):
nm = "SELF\\%s.%s" % nm
print(" %s : %s;" % (nm, exp), file=strm)
if self.inverse:
print(" INVERSE", file=strm)
print(self.inverse, file=strm)
if self.where:
print(" WHERE", file=strm)
for nm_exp in self.where:
print(" %s : %s;" % nm_exp, file=strm)
if self.unique:
print(" UNIQUE", file=strm)
for nm_exp in self.unique:
print(" %s : %s;" % nm_exp, file=strm)
print("END_ENTITY;", file=strm)
return strm.getvalue()
class EnumerationType(Node):
values = property(lambda self: self.enumeration_type[2][1::2])
def __repr__(self):
return "ENUMERATION OF (" + ",".join(self.values) + ")"
class NamedType(Node):
type = property(lambda self: self.simple_id)
def __repr__(self):
return self.type
class AggregationType(Node):
aggregate_type = property(lambda self: self.flat[0])
bounds = property(lambda self: (list(self.tokens.values())[0][0].bound_spec or [None])[0])
unique = property(lambda self: list(self.tokens.values())[0][0].UNIQUE is not None)
def get_type(self):
v = list(self.tokens.values())[0][0]
if v.instantiable_type:
try:
return v.instantiable_type.concrete_types.simple_id or v.instantiable_type.concrete_types.simple_types
except:
return v.instantiable_type
elif v.parameter_type.simple_types:
return v.parameter_type.simple_types
elif v.parameter_type.named_types:
return v.parameter_type.named_types
elif v.parameter_type.generalized_types.general_aggregation_types:
return v.parameter_type.generalized_types.general_aggregation_types
else:
import pdb; pdb.set_trace()
raise ValueError()
type = property(get_type)
def init(self):
assert self.bounds is None or isinstance(self.bounds, BoundSpecification)
def __repr__(self):
return "%s%s of %s%s"%(self.aggregate_type, self.bounds, "unique " if self.unique else "", self.type)
class SelectType(Node):
values = property(lambda self: self.select_type[1][1::2])
def __repr__(self):
return "SELECT (" + ",".join(map(str, self.values)) + ")"
class SuperTypeExpression(Node):
abstract = property(lambda self: self.abstract_supertype_declaration is not None)
def get_sub_types(self):
if self.abstract:
constraint = self.abstract_supertype_declaration[0]
else:
constraint = self.supertype_rule[0]
return [s[0][0].simple_id for s in constraint.subtype_constraint[0].supertype_expression[0][0][0].one_of[0][2::2]]
sub_types = property(get_sub_types)
def __repr__(self):
return "%sSUPERTYPE OF(ONEOF(%s))" % ("ABSTRACT " if self.abstract else "",",".join(self.sub_types))
class SubTypeExpression(Node):
super_type = property(lambda self: self.entity_ref[0])
def __repr__(self):
return "SUBTYPE OF(%s)" % self.super_type
class AttributeList(ListNode):
type = property(lambda self: self.flat[0] if self.flat[0] in {'inverse', 'derive'} else 'explicit')
def __repr__(self):
return "\n".join([" %s;"%s for s in self.tokens[1:]])
def __iter__(self):
return iter(self.tokens[1:])
def __len__(self):
return len(self.tokens[1:])
class InverseAttribute(Node):
name = property(lambda self: self.attribute_decl.simple_id)
type = property(lambda self: self.flat[2] if self.flat[2] != self.flat[-4] else None)
bounds = property(lambda self: self.bound_spec[0] if self.bound_spec else None)
entity = property(lambda self: self.entity_ref[0])
attribute = property(lambda self: self.attribute_ref[0])
def __repr__(self):
def _():
yield self.name
yield ":"
if self.type:
yield self.type.upper()
yield "OF"
if self.bounds:
yield self.bounds
yield self.entity
yield "FOR"
yield self.attribute
return " ".join(map(str, _()))
"""
class DerivedAttribute(Node):
def init(self):
return
name_index = list(self.tokens).index(':') - 1
self.name = self.tokens[name_index]
def __repr__(self):
return str(self.name)
"""
class BinaryType(Node):
def __repr__(self):
return "binary"
class BoundSpecification(Node):
lower = property(lambda self: self.flat[1])
upper = property(lambda self: self.flat[3])
def __repr__(self):
return "[%s:%s]"%(self.lower, self.upper)
class ExplicitAttribute(Node):
name = property(lambda self: self.attribute_decl.simple_id)
optional = property(lambda self: self.OPTIONAL is not None)
def get_type(self):
v = next(iter(self.parameter_type.tokens.values()))
if v.general_aggregation_types:
return v.general_aggregation_types
else:
return v
type = property(get_type)
def __repr__(self):
return "%s : %s%s" % (self.name, "optional " if self.optional else "", self.type)
class WidthSpec(Node):
fixed = property(lambda self: self.FIXED is not None)
def init(self):
self.width = int(''.join(self.width[0].flat))
def __repr__(self):
return "(%d)%s" % (self.width, " fixed" if self.fixed else "")
class StringType(Node):
width = property(lambda self: self.width_spec[0] if self.width_spec else None)
def __repr__(self):
s = "string"
if self.width:
s += " " + repr(self.width)
return s
-91
View File
@@ -1,91 +0,0 @@
###############################################################################
# #
# This file is part of IfcOpenShell. #
# #
# IfcOpenShell is free software: you can redistribute it and/or modify #
# it under the terms of the Lesser GNU General Public License as published by #
# the Free Software Foundation, either version 3.0 of the License, or #
# (at your option) any later version. #
# #
# IfcOpenShell is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# Lesser GNU General Public License for more details. #
# #
# You should have received a copy of the Lesser GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
###############################################################################
import nodes
import platform
import collections
if tuple(map(int, platform.python_version_tuple())) < (2, 7):
import ordereddict
collections.OrderedDict = ordereddict.OrderedDict
# According to ISO 10303-11 7.1.2: Letters: "... The case of
# letters is significant only within explicit string literals."
class OrderedCaseInsensitiveDict_KeyObject(str):
def __eq__(self, other):
return self.lower() == other.lower()
def __hash__(self):
return hash(self.lower())
class OrderedCaseInsensitiveDict(collections.OrderedDict):
def __init__(self, *args, **kwargs):
collections.OrderedDict.__init__(self)
for key, value in collections.OrderedDict(*args, **kwargs).items():
self[OrderedCaseInsensitiveDict_KeyObject(key)] = value
def __setitem__(self, key, value):
return collections.OrderedDict.__setitem__(self, OrderedCaseInsensitiveDict_KeyObject(key), value)
def __getitem__(self, key):
return collections.OrderedDict.__getitem__(self, OrderedCaseInsensitiveDict_KeyObject(key))
def get(self, key, *args, **kwargs):
return collections.OrderedDict.get(self, OrderedCaseInsensitiveDict_KeyObject(key), *args, **kwargs)
def __contains__(self, key):
return collections.OrderedDict.__contains__(self, OrderedCaseInsensitiveDict_KeyObject(key))
def __delitem__(self, key):
return collections.OrderedDict.__delitem__(self, OrderedCaseInsensitiveDict_KeyObject(key))
class Schema:
def is_enumeration(self, v):
return str(v) in self.enumerations
def is_select(self, v):
return str(v) in self.selects
def is_simpletype(self, v):
return str(v) in self.simpletypes
def is_type(self, v):
return str(v) in self.types
def is_entity(self, v):
return str(v) in self.entities
def __len__(self):
return len(self.types) + len(self.entities)
def __iter__(self):
return iter(self.keys)
def __getitem__(self, key):
return self.types_entities[key]
def __init__(self, parsetree):
self.name = parsetree.syntax[0][0].simple_id
sort = lambda d: OrderedCaseInsensitiveDict(sorted(d))
declarations = [d.any()[0] for d in parsetree.syntax[0][0].schema_body[0] if d.rule == 'declaration' and d.any()[0].rule != 'function_decl']
self.types = sort([(t.name,t) for t in declarations if isinstance(t, nodes.TypeDeclaration)])
self.entities = sort([(t.name,t) for t in declarations if isinstance(t, nodes.EntityDeclaration)])
self.keys = list(self.types.keys()) + list(self.entities.keys())
self.types_entities = {k: v for d in (self.types, self.entities) for k, v in d.items()}
of_type = lambda *types: sort([(a, b.type) for a,b in self.types.items() if any(isinstance(b.type, ty) for ty in types)])
self.enumerations = of_type(nodes.EnumerationType)
self.selects = of_type(nodes.SelectType)
self.simpletypes = of_type(str, nodes.AggregationType, nodes.BinaryType, nodes.StringType, nodes.SimpleType, nodes.NamedType)
assert len(self.enumerations) + len(self.selects) + len(self.simpletypes) == len(self.types)
-277
View File
@@ -1,277 +0,0 @@
###############################################################################
# #
# This file is part of IfcOpenShell. #
# #
# IfcOpenShell is free software: you can redistribute it and/or modify #
# it under the terms of the Lesser GNU General Public License as published by #
# the Free Software Foundation, either version 3.0 of the License, or #
# (at your option) any later version. #
# #
# IfcOpenShell is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# Lesser GNU General Public License for more details. #
# #
# You should have received a copy of the Lesser GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
###############################################################################
import operator
import nodes
import codegen
import templates
from collections import defaultdict
class SchemaClass(codegen.Base):
def __init__(self, mapping):
class UnmetDependenciesException(Exception): pass
schema_name = mapping.schema.name
self.schema_name = schema_name_title = schema_name.capitalize()
declared_types = []
def get_declared_type(type, emitted_names=None):
if isinstance(type, nodes.SimpleType):
type = type.type
if isinstance(type, nodes.NamedType):
type = str(type)
if isinstance(type, nodes.AggregationType):
aggr_type = type.aggregate_type
make_bound = lambda b: -1 if b == '?' else int(b)
bound1, bound2 = map(make_bound, (type.bounds.lower, type.bounds.upper))
decl_type = get_declared_type(type.type, emitted_names)
return "new aggregation_type(aggregation_type::%(aggr_type)s_type, %(bound1)d, %(bound2)d, %(decl_type)s)" % locals()
elif isinstance(type, nodes.BinaryType):
return "new simple_type(simple_type::binary_type)"
elif isinstance(type, nodes.StringType):
return "new simple_type(simple_type::string_type)"
elif isinstance(type, str):
if mapping.schema.is_type(type) or mapping.schema.is_entity(type):
if emitted_names is None or type.lower() in emitted_names:
return "new named_type(%s_%s_type)" % (schema_name, type)
else:
raise UnmetDependenciesException(type)
else:
return "new simple_type(simple_type::%s_type)" % type
else:
raise ValueError("No mapping for '%s'" % type)
def find_inverse_name_and_index(entity_name, attribute_name):
attributes_per_subtype = []
while True:
entity = mapping.schema.entities[entity_name]
attr_names = list(map(operator.attrgetter('name'), entity.attributes))
if len(attr_names):
attributes_per_subtype.append((entity_name, attr_names))
if len(entity.supertypes) != 1: break
entity_name = entity.supertypes[0]
index = 0
for et, attrs in attributes_per_subtype[::-1]:
try: return et, attrs.index(attribute_name)
except: pass
else:
raise Exception("No declared type for <%r>" % type)
statements = ['',
'#include "../ifcparse/IfcSchema.h"',
'#include "../ifcparse/%(schema_name_title)s.h"' % locals(),
'',
'using namespace IfcParse;',
'']
collections_by_type = (('entity', mapping.schema.entities ),
('type_declaration', mapping.schema.simpletypes ),
('select_type', mapping.schema.selects ),
('enumeration_type', mapping.schema.enumerations))
for cpp_type, collection in collections_by_type:
for name in collection.keys():
statements.append('%(cpp_type)s* %(schema_name)s_%(name)s_type = 0;' % locals())
declarations_by_index = []
statements.append("{factory_placeholder}")
statements.append("""
#if defined(__clang__)
__attribute__((optnone))
#elif defined(__GNUC__) || defined(__GNUG__)
#pragma GCC push_options
#pragma GCC optimize ("O0")
#elif defined(_MSC_VER)
#pragma optimize("", off)
#endif
""")
statements.append('IfcParse::schema_definition* %(schema_name)s_populate_schema() {' % locals())
emitted = set()
len_to_emit = len(mapping.schema)
def write_simpletype(schema_name, name, type):
try:
declared_type = get_declared_type(type, emitted)
except UnmetDependenciesException:
# @todo?
# print("Unmet", repr(name))
return False
statements.append(' %(schema_name)s_%(name)s_type = new type_declaration("%(name)s", %%(index_in_schema_%(name)s)d, %(declared_type)s);' % locals())
def write_enumeration(schema_name, name, enum):
statements.append(' {')
statements.append(' std::vector<std::string> items; items.reserve(%d);' % len(enum.values))
statements.extend(map(lambda v: ' items.push_back("%s");' % v, sorted(enum.values)))
statements.append(' %(schema_name)s_%(name)s_type = new enumeration_type("%(name)s", %%(index_in_schema_%(name)s)d, items);' % locals())
statements.append(' }')
def write_entity(schema_name, name, type):
if len(type.supertypes) == 0 or set(map(lambda s: s.lower(), type.supertypes)) < emitted:
supertype = '0' if len(type.supertypes) == 0 else '%s_%s_type' % (schema_name, type.supertypes[0])
is_abstract = "true" if type.abstract else "false"
statements.append(' %(schema_name)s_%(name)s_type = new entity("%(name)s", %(is_abstract)s, %%(index_in_schema_%(name)s)d, %(supertype)s);' % locals())
else: return False
def write_select(schema_name, name, type):
if set(map(lambda s: str(s).lower(), type.values)) < emitted:
statements.append(' {')
statements.append(' std::vector<const declaration*> items; items.reserve(%d);' % len(type.values))
statements.extend(map(lambda v: ' items.push_back(%s_%s_type);' % (schema_name, v), sorted(map(str, type.values))))
statements.append(' %(schema_name)s_%(name)s_type = new select_type("%(name)s", %%(index_in_schema_%(name)s)d, items);' % locals())
statements.append(' }')
else: return False
def write(name):
if mapping.schema.is_simpletype(name):
fn = write_simpletype
elif mapping.schema.is_enumeration(name):
fn = write_enumeration
elif mapping.schema.is_entity(name):
fn = write_entity
elif mapping.schema.is_select(name):
fn = write_select
decl = mapping.schema[name]
if isinstance(decl, nodes.TypeDeclaration):
decl = decl.type
return fn(schema_name, name, decl) is not False
while len(emitted) < len_to_emit:
for name in mapping.schema:
if name.lower() in emitted: continue
if write(name):
emitted.add(name.lower())
declarations_by_index.append(name)
declared_types.append('%(schema_name)s_%(name)s_type' % locals())
num_declarations = len(declared_types)
for name, type in mapping.schema.entities.items():
derived = set(mapping.derived_in_supertype(type))
attribute_names = list(map(operator.attrgetter('name'), mapping.arguments(type)))
statements.append(' {')
statements.append(' std::vector<const attribute*> attributes; attributes.reserve(%d);' % len(type.attributes))
for attr in type.attributes:
attr_name, optional = attr.name, str(attr.optional).lower()
decl_type = get_declared_type(attr.type)
statements.append(' attributes.push_back(new attribute("%(attr_name)s", %(decl_type)s, %(optional)s));' % locals())
statements.append(' std::vector<bool> derived; derived.reserve(%d);' % len(attribute_names))
statements.append(' ' + " ".join(map(lambda b: 'derived.push_back(%s);' % str(b in derived).lower(), attribute_names)))
statements.append(' %(schema_name)s_%(name)s_type->set_attributes(attributes, derived);' % locals())
statements.append(' }')
for name, type in mapping.schema.entities.items():
if type.inverse:
statements.append(' {')
statements.append(' std::vector<const inverse_attribute*> attributes; attributes.reserve(%d);' % len(type.inverse))
for attr in type.inverse:
if attr.bounds:
make_bound = lambda b: -1 if b == '?' else int(b)
bound1, bound2 = map(make_bound, (attr.bounds.lower, attr.bounds.upper))
else:
bound1, bound2 = -1, -1
attr_name, aggr_type, entity_ref = attr.name, attr.type, attr.entity
if aggr_type is None: aggr_type = 'unspecified'
attribute_entity, attribute_entity_index = find_inverse_name_and_index(entity_ref, attr.attribute)
statements.append(' attributes.push_back(new inverse_attribute("%(attr_name)s", inverse_attribute::%(aggr_type)s_type, %(bound1)d, %(bound2)d, %(schema_name)s_%(entity_ref)s_type, %(schema_name)s_%(attribute_entity)s_type->attributes()[%(attribute_entity_index)d]));' % locals())
statements.append(' %(schema_name)s_%(name)s_type->set_inverse_attributes(attributes);' % locals())
statements.append(' }')
subtypes = defaultdict(list)
for name, type in mapping.schema.entities.items():
for ty in type.supertypes:
subtypes[ty].append(name)
for name, tys in subtypes.items():
statements.append(' {')
statements.append(' std::vector<const entity*> defs; defs.reserve(%d);' % len(tys))
statements.append((' ' + "".join(map(lambda t: ("defs.push_back(%%(schema_name)s_%s_type);" % t), tys))) % locals())
statements.append(' %(schema_name)s_%(name)s_type->set_subtypes(defs);' % locals())
statements.append(' }')
statements.append('')
statements.append(' std::vector<const declaration*> declarations; declarations.reserve(%(num_declarations)d);' % locals())
for type_name in declared_types:
statements.append(' declarations.push_back(%(type_name)s);' % locals())
statements.append(' return new schema_definition("%(schema_name)s", declarations, new %(schema_name)s_instance_factory());' % locals())
statements.extend(('}',''))
statements.append("""
#if defined(__clang__)
#elif defined(__GNUC__) || defined(__GNUG__)
#pragma GCC pop_options
#elif defined(_MSC_VER)
#pragma optimize("", on)
#endif
""")
statements.extend(('const schema_definition& %s::get_schema() {' % schema_name_title,
'',
' static const schema_definition* s = %(schema_name)s_populate_schema();' % locals(),
' return *s;',
'}','',''))
declarations_by_index.sort(key=str.lower)
declarations_by_index_map = dict(("index_in_schema_%s" % j,i) for i,j in enumerate(declarations_by_index))
def bind(s):
if "%" in s: return s % declarations_by_index_map
else: return s
can_be_instantiated_set = set(list(mapping.schema.entities.keys()) + list(mapping.schema.simpletypes.keys()))
def can_be_instantiated(idx_name):
name = idx_name[1]
return name in can_be_instantiated_set
instance_mapping = """switch(data->type()->index_in_schema()) {
%s
default: throw IfcParse::IfcException(data->type()->name() + " cannot be instantiated");
}
""" % "\n ".join(map(lambda tup: ("case %%d: return new ::%s::%%s(data);" % schema_name_title) % tup, filter(can_be_instantiated, enumerate(declarations_by_index))))
statements[statements.index("{factory_placeholder}")] = """
class %(schema_name)s_instance_factory : public IfcParse::instance_factory {
virtual IfcUtil::IfcBaseClass* operator()(IfcEntityInstanceData* data) const {
%(instance_mapping)s
}
};
""" % locals()
self.str = "\n".join(map(bind, statements))
self.file_name = '%s-schema.cpp'%self.schema_name
def __repr__(self):
return self.str
Generator = SchemaClass
-227
View File
@@ -1,227 +0,0 @@
###############################################################################
# #
# This file is part of IfcOpenShell. #
# #
# IfcOpenShell is free software: you can redistribute it and/or modify #
# it under the terms of the Lesser GNU General Public License as published by #
# the Free Software Foundation, either version 3.0 of the License, or #
# (at your option) any later version. #
# #
# IfcOpenShell is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# Lesser GNU General Public License for more details. #
# #
# You should have received a copy of the Lesser GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
###############################################################################
header = """
#ifndef %(schema_name_upper)s_H
#define %(schema_name_upper)s_H
#include <string>
#include <vector>
#include <boost/optional.hpp>
#include "../ifcparse/ifc_parse_api.h"
#include "../ifcparse/IfcEntityList.h"
#include "../ifcparse/IfcBaseClass.h"
#include "../ifcparse/IfcSchema.h"
#include "../ifcparse/IfcException.h"
#include "../ifcparse/Argument.h"
struct %(schema_name)s {
static const IfcParse::schema_definition& get_schema();
static const char* const Identifier;
// Forward definitions
%(forward_definitions)s
%(declarations)s
%(class_definitions)s
};
#endif
"""
enum_header = """
#ifndef %(schema_name_upper)sENUM_H
#define %(schema_name_upper)sENUM_H
#include "../ifcparse/ifc_parse_api.h"
#include <string>
#include <boost/optional.hpp>
#endif
"""
lb_header = """"""
implementation= """
#include "../ifcparse/%(schema_name)s.h"
#include "../ifcparse/IfcSchema.h"
#include "../ifcparse/IfcException.h"
#include "../ifcparse/IfcWrite.h"
#include <map>
const char* const %(schema_name)s::Identifier = "%(schema_name_upper)s";
using namespace IfcParse;
using namespace IfcWrite;
// External definitions
%(external_definitions)s
%(enumeration_functions)s
%(simple_type_impl)s
%(entity_implementations)s
"""
lb_implementation = """"""
entity_descriptor = """ current = entity_descriptor_map[Type::%(type)s] = new IfcEntityDescriptor(Type::%(type)s,%(parent_statement)s);
%(entity_descriptor_attributes)s"""
entity_descriptor_parent = "entity_descriptor_map.find(Type::%(type)s)->second"
entity_descriptor_attribute_without_entity = ' current->add("%(name)s",%(optional)s,%(type)s);'
entity_descriptor_attribute_with_entity = ' current->add("%(name)s",%(optional)s,%(type)s,Type::%(entity_name)s);'
enumeration_descriptor = """ values.clear(); values.reserve(128);
%(enumeration_descriptor_values)s
enumeration_descriptor_map[Type::%(type)s] = new IfcEnumerationDescriptor(Type::%(type)s, values);"""
enumeration_descriptor_value = ' values.push_back("%(name)s");'
derived_field_statement = ' {std::set<int> idxs; %(statements)sderived_map[Type::%(type)s] = idxs;}';
derived_field_statement_attrs = 'idxs.insert(%d); '
simpletype = """%(documentation)s
class IFC_PARSE_API %(name)s : public %(superclass)s {
public:
virtual const IfcParse::type_declaration& declaration() const;
static const IfcParse::type_declaration& Class();
explicit %(name)s (IfcEntityInstanceData* e);
%(name)s (%(type)s v);
operator %(type)s() const;
};
"""
simpletype_impl_comment = "// Function implementations for %(name)s"
simpletype_impl_argument_type = "if (i == 0) { return %(attr_type)s; } else { throw IfcParse::IfcAttributeOutOfRangeException(\"Argument index out of range\"); }"
simpletype_impl_argument = "return data_->getArgument(i);"
simpletype_impl_is_with_supertype = "return v == %(class_name)s_type || %(superclass)s::is(v);"
simpletype_impl_is_without_supertype = "return v == %(class_name)s_type;"
simpletype_impl_type = "return *%(schema_name_upper)s_%(class_name)s_type;"
simpletype_impl_class = "return *%(schema_name_upper)s_%(class_name)s_type;"
simpletype_impl_explicit_constructor = "data_ = e;"
simpletype_impl_constructor = "data_ = new IfcEntityInstanceData(%(schema_name_upper)s_%(class_name)s_type); {IfcWrite::IfcWriteArgument* attr = new IfcWrite::IfcWriteArgument(); attr->set(v" +"); data_->setArgument(0, attr);}"
simpletype_impl_constructor_templated = "data_ = new IfcEntityInstanceData(%(schema_name_upper)s_%(class_name)s_type); {IfcWrite::IfcWriteArgument* attr = new IfcWrite::IfcWriteArgument(); attr->set(v->generalize()); data_->setArgument(0, attr);}"
simpletype_impl_cast = "return *data_->getArgument(0);"
simpletype_impl_cast_templated = "IfcEntityList::ptr es = *data_->getArgument(0); return es->as< %(underlying_type)s >();"
simpletype_impl_declaration = "return *%(schema_name_upper)s_%(class_name)s_type;"
select = """%(documentation)s
typedef IfcUtil::IfcBaseClass %(name)s;
"""
enumeration = """struct %(name)s {
%(documentation)s
typedef enum {%(values)s} Value;
IFC_PARSE_API static const char* ToString(Value v);
IFC_PARSE_API static Value FromString(const std::string& s);
};
"""
entity = """%(documentation)s
class IFC_PARSE_API %(name)s %(superclass)s{
public:
%(attributes)s %(inverse)s virtual const IfcParse::entity& declaration() const;
static const IfcParse::entity& Class();
%(name)s (IfcEntityInstanceData* e);
%(name)s (%(constructor_arguments)s);
typedef IfcTemplatedEntityList< %(name)s > list;
};
"""
enumeration_function="""
const char* %(schema_name)s::%(name)s::ToString(Value v) {
if ( v < 0 || v >= %(max_id)d ) throw IfcException("Unable to find find keyword in schema");
const char* names[] = { %(values)s };
return names[v];
}
%(schema_name)s::%(name)s::Value %(schema_name)s::%(name)s::FromString(const std::string& s) {
%(from_string_statements)s
throw IfcException("Unable to find find keyword in schema");
}
"""
entity_implementation = """// Function implementations for %(name)s
%(attributes)s
%(inverse)s
const IfcParse::entity& %(schema_name)s::%(name)s::declaration() const { return *%(schema_name_upper)s_%(name)s_type; }
const IfcParse::entity& %(schema_name)s::%(name)s::Class() { return *%(schema_name_upper)s_%(name)s_type; }
%(schema_name)s::%(name)s::%(name)s(IfcEntityInstanceData* e) : %(superclass)s { if (!e) return; if (e->type() != %(schema_name_upper)s_%(name)s_type) throw IfcException("Unable to find find keyword in schema"); data_ = e; }
%(schema_name)s::%(name)s::%(name)s(%(constructor_arguments)s) : %(superclass)s {data_ = new IfcEntityInstanceData(%(schema_name_upper)s_%(name)s_type); %(constructor_implementation)s }
"""
optional_attribute_description = "/// Whether the optional attribute %s is defined for this %s"
function = "%(return_type)s %(schema_name)s::%(class_name)s::%(name)s(%(arguments)s) { %(body)s }"
const_function = "%(return_type)s %(schema_name)s::%(class_name)s::%(name)s(%(arguments)s) const { %(body)s }"
constructor = "%(schema_name)s::%(class_name)s::%(class_name)s(%(arguments)s) { %(body)s }"
constructor_single_initlist = "%(schema_name)s::%(class_name)s::%(class_name)s(%(arguments)s) : %(superclass)s(%(superclass_init)s) { %(body)s }"
cast_function = "%(schema_name)s::%(class_name)s::operator %(return_type)s() const { %(body)s }"
array_type = "std::vector< %(instance_type)s > /*[%(lower)s:%(upper)s]*/"
nested_array_type = "std::vector< std::vector< %(instance_type)s > >"
list_type = "IfcTemplatedEntityList< %(instance_type)s >::ptr"
list_list_type = "IfcTemplatedEntityListList< %(instance_type)s >::ptr"
untyped_list = "IfcEntityList::ptr"
inverse_attr = "IfcTemplatedEntityList< %(entity)s >::ptr %(name)s() const; // INVERSE %(entity)s::%(attribute)s"
enum_from_string_stmt = ' if (s == "%(value)s") return ::%(schema_name)s::%(name)s::%(short_name)s_%(value)s;'
schema_entity_stmt = ' case Type::%(name)s: return new %(name)s(e); break;'
string_map_statement = ' string_map["%(uppercase_name)s"%(padding)s] = Type::%(name)s;'
parent_type_stmt = ' if(v==%(name)s%(padding)s) { return %(parent)s; }'
parent_type_test = " || %s::is(v)"
optional_attr_stmt = "return !data_->getArgument(%(index)d)->isNull();"
get_attr_stmt = "return *data_->getArgument(%(index)d);"
get_attr_stmt_enum = "return %(type)s::FromString(*data_->getArgument(%(index)d));"
get_attr_stmt_entity = "return (%(type)s)((IfcUtil::IfcBaseClass*)(*data_->getArgument(%(index)d)));"
get_attr_stmt_array = "IfcEntityList::ptr es = *data_->getArgument(%(index)d); return es->as< %(list_instance_type)s >();"
get_attr_stmt_nested_array = "IfcEntityListList::ptr es = *data_->getArgument(%(index)d); return es->as< %(list_instance_type)s >();"
get_inverse = "return data_->getInverse(%(schema_name_upper)s_%(type)s_type, %(index)d)->as<%(type)s>();"
set_attr_stmt = "{IfcWrite::IfcWriteArgument* attr = new IfcWrite::IfcWriteArgument();attr->set(v" +");data_->setArgument(%(index)d,attr);}"
set_attr_stmt_enum = "{IfcWrite::IfcWriteArgument* attr = new IfcWrite::IfcWriteArgument();attr->set(IfcWrite::IfcWriteArgument::EnumerationReference(v,%(type)s::ToString(v)));data_->setArgument(%(index)d,attr);}"
set_attr_stmt_array = "{IfcWrite::IfcWriteArgument* attr = new IfcWrite::IfcWriteArgument();attr->set(v->generalize()" +");data_->setArgument(%(index)d,attr);}"
constructor_stmt = "{IfcWrite::IfcWriteArgument* attr = new IfcWrite::IfcWriteArgument();attr->set((%(name)s)" +");data_->setArgument(%(index)d,attr);}"
constructor_stmt_enum = "{IfcWrite::IfcWriteArgument* attr = new IfcWrite::IfcWriteArgument();attr->set((IfcWrite::IfcWriteArgument::EnumerationReference(%(name)s,%(type)s::ToString(%(name)s)))" +");data_->setArgument(%(index)d,attr);}"
constructor_stmt_array = "{IfcWrite::IfcWriteArgument* attr = new IfcWrite::IfcWriteArgument();attr->set((%(name)s)->generalize()" +");data_->setArgument(%(index)d,attr);}"
constructor_stmt_derived = "{IfcWrite::IfcWriteArgument* attr = new IfcWrite::IfcWriteArgument();attr->set(IfcWrite::IfcWriteArgument::Derived()" +");data_->setArgument(%(index)d,attr);}"
constructor_stmt_optional = " if (%(name)s) {%(stmt)s } else { IfcWrite::IfcWriteArgument* attr = new IfcWrite::IfcWriteArgument(); attr->set(boost::blank()); data_->setArgument(%(index)d, attr); }"
inverse_implementation = " inverse_map[Type::%(type)s].insert(std::make_pair(\"%(name)s\", std::make_pair(Type::%(related_type)s, %(index)d)));"
def multi_line_comment(li):
return ("/// %s"%("\n/// ".join(li))) if len(li) else ""
+12 -3
View File
@@ -584,10 +584,14 @@ bool IfcGeom::Kernel::convert(const IfcSchema::IfcRectangleHollowProfileDef* l,
}
bool IfcGeom::Kernel::convert(const IfcSchema::IfcTrapeziumProfileDef* l, TopoDS_Shape& face) {
const double x1 = l->BottomXDim() / 2.0f * getValue(GV_LENGTH_UNIT);
const double x1 = l->BottomXDim() / 2. * getValue(GV_LENGTH_UNIT);
const double w = l->TopXDim() * getValue(GV_LENGTH_UNIT);
const double dx = l->TopXOffset() * getValue(GV_LENGTH_UNIT);
const double y = l->YDim() / 2.0f * getValue(GV_LENGTH_UNIT);
const double y = l->YDim() / 2. * getValue(GV_LENGTH_UNIT);
// See: https://forums.buildingsmart.org/t/how-are-the-sides-of-ifctrapeziumprofiledefs-bounding-box-calculated-in-most-implementations/2945/8
// The trapezium x center should not be midway of BottomXDim but rather at the center of the overall bounding box.
const double x_offset = ((std::min(dx, 0.) + std::max(w + dx, x1 * 2.)) / 2.) - x1;
if ( x1 < ALMOST_ZERO || w < ALMOST_ZERO || y < ALMOST_ZERO ) {
Logger::Message(Logger::LOG_NOTICE,"Skipping zero sized profile:",l);
@@ -603,7 +607,12 @@ bool IfcGeom::Kernel::convert(const IfcSchema::IfcTrapeziumProfileDef* l, TopoDS
IfcGeom::Kernel::convert(l->Position(), trsf2d);
}
double coords[8] = {-x1,-y, x1,-y, dx+w-x1,y, dx-x1,y};
double coords[8] = {
-x1 - x_offset, -y,
+x1 - x_offset, -y,
-x1 + dx + w - x_offset, y,
-x1 + dx - x_offset,y
};
return profile_helper(4,coords,0,0,0,trsf2d,face);
}
+31 -11
View File
@@ -1854,6 +1854,18 @@ IfcGeom::BRepElement<P, PP>* IfcGeom::Kernel::create_brep_for_representation_and
representation_id_builder << "-material-" << single_material->data().id();
}
if (settings.force_space_transparency() >= 0. && product->declaration().is("IfcSpace")) {
for (auto& s : shapes) {
if (s.hasStyle()) {
for (auto& p : style_cache) {
if (&p.second == &s.Style()) {
p.second.Transparency() = settings.force_space_transparency();
}
}
}
}
}
int parent_id = -1;
try {
IfcUtil::IfcBaseEntity* parent_object = get_decomposing_entity(product);
@@ -2712,7 +2724,7 @@ bool IfcGeom::Kernel::fold_layers(const IfcSchema::IfcWall* wall, const IfcRepre
double layer_offset = 0;
const double total_thickness = std::accumulate(thicknesses.begin(), thicknesses.end(), 0);
const double total_thickness = std::accumulate(thicknesses.begin(), thicknesses.end(), 0.);
std::vector<double>::const_iterator thickness = thicknesses.begin();
result_t::iterator result_vector = result.begin() + 1;
@@ -2849,6 +2861,13 @@ bool IfcGeom::Kernel::fold_layers(const IfcSchema::IfcWall* wall, const IfcRepre
namespace {
void subshapes(const TopoDS_Shape& in, std::list<TopoDS_Shape>& out) {
TopoDS_Iterator sit(in);
for (; sit.More(); sit.Next()) {
out.push_back(sit.Value());
}
}
#if OCC_VERSION_HEX >= 0x70200
bool split(IfcGeom::Kernel&, const TopoDS_Shape& input, const TopTools_ListOfShape& operands, double eps, std::vector<TopoDS_Shape>& slices) {
if (operands.Extent() < 2) {
@@ -2880,18 +2899,19 @@ namespace {
}
}
// Count subshapes
size_t n = 0;
TopoDS_Iterator sit(split.Shape());
for (; sit.More(); sit.Next()) {
++n;
auto result_shape = split.Shape();
std::list<TopoDS_Shape> subs;
subshapes(result_shape, subs);
if (subs.size() == 1 && operands.Size() - 2 > subs.size() && (subs.front().ShapeType() == TopAbs_COMPSOLID || subs.front().ShapeType() == TopAbs_COMPOUND)) {
auto s = subs.front();
subs.clear();
subshapes(s, subs);
}
// Initialize storage
slices.resize(n);
slices.resize(subs.size());
sit.Initialize(split.Shape());
for (; sit.More(); sit.Next()) {
for (auto& s : subs) {
// Iterate over the faces of solid to find correspondence to original
// splitting surfaces. For the outmost slices, there will be a single
@@ -2900,7 +2920,7 @@ namespace {
// slices, two surface indices should be find that should be next to
// each other in the array of input surfaces.
TopExp_Explorer exp(sit.Value(), TopAbs_FACE);
TopExp_Explorer exp(s, TopAbs_FACE);
int min = std::numeric_limits<int>::max();
int max = std::numeric_limits<int>::min();
for (; exp.More(); exp.Next()) {
@@ -2928,7 +2948,7 @@ namespace {
if (idx < (int) slices.size()) {
if (slices[idx].IsNull()) {
slices[idx] = sit.Value();
slices[idx] = s;
continue;
}
}
+12 -1
View File
@@ -101,11 +101,14 @@ namespace IfcGeom
IteratorSettings()
: settings_(WELD_VERTICES) // OR options that default to true here
, deflection_tolerance_(1.e-3)
, angular_tolerance_(0.5)
{
}
/// Note that this is independent of the IFC length unit, one millimeter by default.
double deflection_tolerance() const { return deflection_tolerance_; }
double angular_tolerance() const { return angular_tolerance_; }
double force_space_transparency() const { return force_space_transparency_; }
void set_deflection_tolerance(double value)
{
@@ -118,6 +121,14 @@ namespace IfcGeom
}
}
void set_angular_tolerance(double value) {
angular_tolerance_ = value;
}
void force_space_transparency(double value) {
force_space_transparency_ = value;
}
/// Get boolean value for a single settings or for a combination of settings.
bool get(SettingField setting) const
{
@@ -143,7 +154,7 @@ namespace IfcGeom
protected:
SettingField settings_;
double deflection_tolerance_;
double deflection_tolerance_, angular_tolerance_, force_space_transparency_;
};
class IFC_GEOM_API ElementSettings : public IteratorSettings
+1 -1
View File
@@ -163,7 +163,7 @@ namespace IfcGeom {
// Triangulate the shape
try {
BRepMesh_IncrementalMesh(s, settings().deflection_tolerance());
BRepMesh_IncrementalMesh(s, settings().deflection_tolerance(), false, settings().angular_tolerance());
} catch(...) {
Logger::Message(Logger::LOG_ERROR, "Failed to triangulate shape");
continue;
+51 -3
View File
@@ -13,6 +13,7 @@
#include <TColStd_Array1OfReal.hxx>
#include <TColStd_Array2OfReal.hxx>
#include <TColStd_Array1OfInteger.hxx>
#include <Geom_BezierCurve.hxx>
#include "IfcGeom.h"
@@ -129,6 +130,52 @@ int convert_to_ifc(const Handle_Geom_Curve& c, IfcSchema::IfcCurve*& curve, bool
return 1;
}
#ifdef SCHEMA_HAS_IfcRationalBSplineSurfaceWithKnots
else if (c->DynamicType() == STANDARD_TYPE(Geom_BezierCurve)) {
Handle_Geom_BezierCurve bezier = Handle_Geom_BezierCurve::DownCast(c);
std::vector<int> mults;
std::vector<double> knots;
std::vector<double> weights;
IfcSchema::IfcKnotType::Value knot_spec = IfcSchema::IfcKnotType::IfcKnotType_QUASI_UNIFORM_KNOTS;
IfcSchema::IfcCartesianPoint::list::ptr points(new IfcSchema::IfcCartesianPoint::list);
TColgp_Array1OfPnt poles(1, bezier->NbPoles());
bezier->Poles(poles);
for (int i = 1; i <= bezier->NbPoles(); ++i) {
IfcSchema::IfcCartesianPoint* p;
if (!convert_to_ifc(poles.Value(i), p, advanced)) {
return 0;
}
points->push(p);
if (i == 1 || i == bezier->NbPoles()) {
mults.push_back(bezier->Degree() + 1);
} else {
mults.push_back(bezier->Degree());
}
knots.push_back((double) i - 1);
}
TColStd_Array1OfReal bspline_weights(1, bezier->NbPoles());
bezier->Weights(bspline_weights);
opencascade_array_to_vector(bspline_weights, weights);
curve = new IfcSchema::IfcRationalBSplineCurveWithKnots(
bezier->Degree(),
points,
IfcSchema::IfcBSplineCurveForm::IfcBSplineCurveForm_UNSPECIFIED,
bezier->IsClosed() != 0,
false,
mults,
knots,
knot_spec,
weights
);
return 1;
}
else if (c->DynamicType() == STANDARD_TYPE(Geom_BSplineCurve)) {
Handle_Geom_BSplineCurve bspline = Handle_Geom_BSplineCurve::DownCast(c);
@@ -460,7 +507,7 @@ int convert_to_ifc(const TopoDS_Face& f, IfcSchema::IfcFace*& face, bool advance
face = new IfcSchema::IfcFace(bounds);
return 1;
} else {
#ifdef USE_IFC4
#ifdef SCHEMA_HAS_IfcAdvancedFace
IfcSchema::IfcSurface* surface;
if (!convert_to_ifc(surf, surface, advanced)) {
return 0;
@@ -499,7 +546,8 @@ int convert_to_ifc(const TopoDS_Shape& s, U*& item, bool advanced) {
}
IfcUtil::IfcBaseClass* IfcGeom::MAKE_TYPE_NAME(serialise_)(const TopoDS_Shape& shape, bool advanced) {
#ifndef USE_IFC4
#ifndef SCHEMA_HAS_IfcAdvancedBrep
advanced = false;
#endif
@@ -528,7 +576,7 @@ IfcUtil::IfcBaseClass* IfcGeom::MAKE_TYPE_NAME(serialise_)(const TopoDS_Shape& s
}
}
#ifdef USE_IFC4
#ifdef SCHEMA_HAS_IfcAdvancedBrep
if (advanced) {
if (inner->size()) {
items->push(new IfcSchema::IfcAdvancedBrepWithVoids(outer, inner));
+64 -2
View File
@@ -355,7 +355,7 @@ bool IfcGeom::Kernel::convert(const IfcSchema::IfcSurfaceOfRevolution* l, TopoDS
bool IfcGeom::Kernel::convert(const IfcSchema::IfcRevolvedAreaSolid* l, TopoDS_Shape& shape) {
const double ang = l->Angle() * getValue(GV_PLANEANGLE_UNIT);
TopoDS_Face face;
TopoDS_Shape face;
if ( ! convert_face(l->SweptArea(),face) ) return false;
gp_Ax1 ax1;
@@ -370,6 +370,45 @@ bool IfcGeom::Kernel::convert(const IfcSchema::IfcRevolvedAreaSolid* l, TopoDS_S
IfcGeom::Kernel::convert(l->Position(), trsf);
}
{
// https://github.com/IfcOpenShell/IfcOpenShell/issues/1030
// Check whether Axis does not intersect SweptArea
double min_dot = +std::numeric_limits<double>::infinity();
double max_dot = -std::numeric_limits<double>::infinity();
gp_Ax2 ax(ax1.Location(), gp::DZ(), ax1.Direction());
TopExp_Explorer exp(face, TopAbs_EDGE);
for (; exp.More(); exp.Next()) {
BRepAdaptor_Curve crv(TopoDS::Edge(exp.Current()));
GCPnts_QuasiUniformDeflection tessellater(crv, getValue(GV_PRECISION));
int n = tessellater.NbPoints();
for (int i = 1; i <= n; ++i) {
double d = ax.YDirection().XYZ().Dot(tessellater.Value(i).XYZ());
if (d < min_dot) {
min_dot = d;
}
if (d > max_dot) {
max_dot = d;
}
}
}
bool intersecting;
if (std::abs(min_dot) > std::abs(max_dot)) {
intersecting = max_dot > + getValue(GV_PRECISION);
} else {
intersecting = min_dot < - getValue(GV_PRECISION);
}
if (intersecting) {
Logger::Warning("Warning Axis and SweptArea intersecting", l);
}
}
if (ang >= M_PI * 2. - ALMOST_ZERO) {
shape = BRepPrimAPI_MakeRevol(face, ax1);
} else {
@@ -1188,7 +1227,7 @@ namespace {
// @todo we could be extruding the wire only when we know this is an intermediate edge.
const double depth = std::abs(u - v);
TopoDS_Face face = BRepBuilderAPI_MakeFace(section).Face();
result = BRepPrimAPI_MakeRevol(section, circ->Axis(), depth).Shape();
result = BRepPrimAPI_MakeRevol(face, circ->Axis(), depth).Shape();
}
void process_sweep_as_pipe(const TopoDS_Wire& wire, const TopoDS_Wire& section, TopoDS_Shape& result, bool force_transformed=false) {
@@ -1408,6 +1447,29 @@ bool IfcGeom::Kernel::convert(const IfcSchema::IfcSweptDiskSolid* l, TopoDS_Shap
return false;
}
if (count(wire, TopAbs_EDGE) == 1) {
TopoDS_Vertex v0, v1;
TopExp::Vertices(wire, v0, v1);
if (v0.IsSame(v1)) {
TopExp_Explorer exp(wire, TopAbs_EDGE);
auto& e = TopoDS::Edge(exp.Current());
double a, b;
auto crv = BRep_Tool::Curve(e, a, b);
if ((crv->DynamicType() == STANDARD_TYPE(Geom_Circle)) ||
(crv->DynamicType() == STANDARD_TYPE(Geom_Ellipse)))
{
BRepBuilderAPI_MakeEdge me(crv, l->StartParam(), l->EndParam());
if (me.IsDone()) {
auto e2 = me.Edge();
BRep_Builder B;
wire.Nullify();
B.MakeWire(wire);
B.Add(wire, e2);
}
}
}
}
// NB: Note that StartParam and EndParam param are ignored and the assumption is
// made that the parametric range over which to be swept matches the IfcCurve in
// its entirety.
+21 -4
View File
@@ -581,17 +581,34 @@ bool IfcGeom::Kernel::convert(const IfcSchema::IfcTrimmedCurve* l, TopoDS_Wire&
// Fix from @sanderboer to compare using model tolerance, see #744
// Made dependent on radius, see #928
// @todo another good critereon for determining whether to take full curve
// A good critereon for determining whether to take full curve
// or trimmed segment would be whether there are other curve segments or this
// is the only one. But it does not really match the bottom-up construction
// mechanism of IfcOpenShell.
// is the only one.
boost::optional<size_t> num_segments;
auto segment = l->data().getInverse(&IfcSchema::IfcCompositeCurveSegment::Class(), -1);
if (segment->size() == 1) {
auto comp = (*segment->begin())->data().getInverse(&IfcSchema::IfcCompositeCurve::Class(), -1);
if (comp->size() == 1) {
num_segments = (*comp->begin())->as<IfcSchema::IfcCompositeCurve>()->Segments()->size();
}
}
if (isConic && ALMOST_THE_SAME(fmod(flts[1]-flts[0],M_PI*2.), 0., 100 * getValue(GV_PRECISION) / (2 * M_PI * radius))) {
e = BRepBuilderAPI_MakeEdge(curve).Edge();
} else {
BRepBuilderAPI_MakeEdge me (curve,flts[0],flts[1]);
e = me.Edge();
}
}
if (num_segments && *num_segments > 1) {
TopoDS_Vertex v0, v1;
TopExp::Vertices(e, v0, v1);
if (v0.IsSame(v1)) {
Logger::Warning("Skipping degenerate segment", l);
return false;
}
}
} else if ( trim_cartesian_failed && (has_pnts[0] && has_pnts[1]) ) {
e = BRepBuilderAPI_MakeEdge(pnts[0], pnts[1]).Edge();
}
@@ -96,7 +96,10 @@ namespace IfcGeom {
boost::optional<double>& Specularity() { return specularity; }
};
IFC_GEOM_API const SurfaceStyle* get_default_style(const std::string& ifc_type);
IFC_GEOM_API const SurfaceStyle* get_default_style(const std::string& ifc_type);
IFC_GEOM_API SurfaceStyle& update_default_style(const std::string& ifc_type);
IFC_GEOM_API void set_default_style_file(const std::string& json_file);
}
@@ -43,6 +43,10 @@ void InitDefaultMaterials() {
default_materials.insert(std::make_pair("IfcPlate", IfcGeom::SurfaceStyle("IfcPlate")));
default_materials["IfcPlate"].Diffuse().reset(IfcGeom::SurfaceStyle::ColorComponent(0.8, 0.8, 0.8));
default_materials.insert(std::make_pair("IfcSpace", IfcGeom::SurfaceStyle("IfcSpace")));
default_materials["IfcWindow"].Diffuse().reset(IfcGeom::SurfaceStyle::ColorComponent(0.65, 0.75, 0.8));
default_materials["IfcWindow"].Transparency().reset(0.8);
default_material = IfcGeom::SurfaceStyle("DefaultMaterial");
default_material.Diffuse().reset(IfcGeom::SurfaceStyle::ColorComponent(0.7, 0.7, 0.7));
@@ -121,3 +125,12 @@ const IfcGeom::SurfaceStyle* IfcGeom::get_default_style(const std::string& s) {
const IfcGeom::SurfaceStyle& surface_style = it->second;
return &surface_style;
}
IfcGeom::SurfaceStyle& IfcGeom::update_default_style(const std::string& s) {
if (!default_materials_initialized) InitDefaultMaterials();
std::map<std::string, IfcGeom::SurfaceStyle>::iterator it = default_materials.find(s);
if (it == default_materials.end()) {
throw std::runtime_error("No style registered for " + s);
}
return it->second;
}
@@ -73,6 +73,10 @@ def create_entity(type, *args, **kwargs):
for idx, arg in attrs:
e[idx] = arg
return e
gcroot = []
def register_schema(schema):
gcroot.append(schema)
ifcopenshell_wrapper.register_schema(schema.schema)
from .main import *
@@ -11,7 +11,11 @@ import multiprocessing
import OCC.AIS
from collections import defaultdict, Iterable, OrderedDict
from collections import defaultdict, OrderedDict
try: # python 3.3+
from collections.abc import Iterable
except ModuleNotFoundError: # python 2
from collections import Iterable
try:
QString = unicode
@@ -25,7 +25,11 @@ import random
import operator
import warnings
from collections import namedtuple, Iterable
from collections import namedtuple
try: # python 3.3+
from collections.abc import Iterable
except ModuleNotFoundError: # python 2
from collections import Iterable
try:
from OCC.Core import V3d, TopoDS, gp, AIS, Quantity, BRepTools, Graphic3d
@@ -44,7 +44,7 @@ def get_properties(properties):
results = {}
for prop in properties:
if prop.is_a('IfcPropertySingleValue'):
results[prop.Name] = prop.NominalValue
results[prop.Name] = prop.NominalValue.wrappedValue
elif prop.is_a('IfcComplexProperty'):
data = prop.get_info()
data['properties'] = get_properties(prop.HasProperties)
@@ -1,17 +1,21 @@
import math
def dms2dd(degrees, minutes, seconds, milliseconds=0):
dd = float(degrees) + float(minutes)/60.0 + float(seconds)/(3600.0) + float(milliseconds/3600000.0)
def dms2dd(degrees, minutes, seconds, ms=0):
dd = float(degrees) + float(minutes)/60.0 + float(seconds)/(3600.0) + float(ms/3600000000.0)
return dd
def dd2dms(dd):
def dd2dms(dd, use_ms=False):
dd = float(dd)
sign = 1 if dd >= 0 else -1
dd = abs(dd)
minutes, seconds = divmod(dd*3600, 60)
if use_ms:
seconds, ms = divmod(dd*60*60*1000000, 1000000)
minutes, seconds = divmod(dd*60*60, 60)
degrees, minutes = divmod(minutes, 60)
if dd < 0:
degrees = -degrees
if use_ms:
return (int(degrees) * sign, int(minutes) * sign, int(seconds) * sign, int(ms) * sign)
return (int(degrees) * sign, int(minutes) * sign, int(seconds) * sign)
def xyz2enh(x, y, z, eastings, northings, orthogonal_height, x_axis_abscissa, x_axis_ordinate, scale=None):
@@ -27,4 +31,4 @@ def xyz2enh(x, y, z, eastings, northings, orthogonal_height, x_axis_abscissa, x_
# Used for converting the X and Y vectors of the X Axis in IFC geolocation
def xy2angle(x, y):
return math.degrees(math.atan2(y, x)) - 90
return math.degrees(math.atan2(y, x))
@@ -215,6 +215,8 @@ class Selector():
and key.split('.')[0] == 'type':
try:
element = ifcopenshell.util.element.get_type(element)
if not element:
return None
except:
return
key = '.'.join(key.split('.')[1:])
@@ -17,12 +17,12 @@ si_conversions = {
'yard': 0.914,
'mile': 1609,
'square inch': 0.0006452,
'square foot': 0.09290,
'square foot': 0.09290304,
'square yard': 0.83612736,
'acre': 4046.86,
'square mile': 2588881,
'cubic inch': 0.00001639,
'cubic foot': 0.02832,
'cubic foot': 0.02831684671168849,
'cubic yard': 0.7636,
'litre': 0.001,
'fluid ounce UK': 0.0000284130625,
+12
View File
@@ -74,6 +74,18 @@ namespace IfcUtil {
}
};
class IFC_PARSE_API IfcLateBoundEntity : public IfcBaseClass {
private:
const IfcParse::declaration* decl_;
public:
IfcLateBoundEntity(const IfcParse::declaration* decl, IfcEntityInstanceData* data) : IfcBaseClass(data), decl_(decl) {}
virtual const IfcParse::declaration& declaration() const {
return *decl_;
}
};
class IFC_PARSE_API IfcBaseEntity : public IfcBaseClass {
public:
IfcBaseEntity() : IfcBaseClass() {}
+9
View File
@@ -195,6 +195,15 @@ public:
IfcUtil::IfcBaseClass* addEntity(IfcUtil::IfcBaseClass* entity);
void addEntities(IfcEntityList::ptr es);
/// Removes entity instance from file and unsets references.
///
/// Attention when running removeEntity inside a loop over a list of entities to be removed.
/// This invalidates the iterator. A workaround is to reverse the loop:
/// boost::shared_ptr<IfcEntityList> entities = ...;
/// for (auto it = entities->end() - 1; it >= entities->begin(); --it) {
/// IfcUtil::IfcBaseClass *const inst = *it;
/// model->removeEntity(inst);
/// }
void removeEntity(IfcUtil::IfcBaseClass* entity);
const IfcSpfHeader& header() const { return _header; }
+9 -1
View File
@@ -1763,6 +1763,14 @@ IfcUtil::IfcBaseClass* IfcFile::addEntity(IfcUtil::IfcBaseClass* entity) {
void IfcFile::removeEntity(IfcUtil::IfcBaseClass* entity) {
const unsigned id = entity->data().id();
IfcUtil::IfcBaseClass* file_entity = instance_by_id(id);
// Attention when running removeEntity inside a loop over a list of entities to be removed.
// This invalidates the iterator. A workaround is to reverse the loop:
// boost::shared_ptr<IfcEntityList> entities = ...;
// for (auto it = entities->end() - 1; it >= entities->begin(); --it) {
// IfcUtil::IfcBaseClass *const inst = *it;
// model->removeEntity(inst);
// }
// TODO: Create a set of weak relations. Inverse relations that do not dictate an
// instance to be retained. For example: when deleting an IfcRepresentation, the
@@ -2173,4 +2181,4 @@ void IfcParse::IfcFile::build_inverses() {
for (auto& pair : *this) {
build_inverses_(pair.second);
}
}
}
+14
View File
@@ -1,4 +1,5 @@
#include "IfcSchema.h"
#include "../ifcparse/IfcBaseClass.h"
#include <map>
@@ -62,6 +63,19 @@ IfcParse::schema_definition::~schema_definition() {
}
}
IfcUtil::IfcBaseClass* IfcParse::schema_definition::instantiate(IfcEntityInstanceData * data) const {
if (factory_) {
return (*factory_)(data);
} else {
return new IfcUtil::IfcLateBoundEntity(data->type(), data);
}
}
void IfcParse::register_schema(schema_definition* s) {
schemas.insert({ s->name(), s });
}
#include "../ifcparse/Ifc2x3.h"
#include "../ifcparse/Ifc4.h"
#include "../ifcparse/Ifc4x1.h"
+3 -1
View File
@@ -443,10 +443,12 @@ namespace IfcParse {
const std::string& name() const { return name_; }
IfcUtil::IfcBaseClass* instantiate(IfcEntityInstanceData* data) const { return (*factory_)(data); }
IfcUtil::IfcBaseClass* instantiate(IfcEntityInstanceData* data) const;
};
const schema_definition* schema_by_name(const std::string&);
void register_schema(schema_definition*);
}
#endif
+4 -1
View File
@@ -83,7 +83,10 @@ IF(PYTHONINTERP_FOUND AND NOT "${PYTHON_EXECUTABLE}" STREQUAL "")
IF("${python_package_dir}" STREQUAL "")
MESSAGE(WARNING "Unable to locate Python site-package directory, unable to install the Python wrapper")
ELSE()
FILE(GLOB_RECURSE sourcefiles "${CMAKE_CURRENT_SOURCE_DIR}/../ifcopenshell-python/ifcopenshell/*.py")
FILE(GLOB_RECURSE sourcefiles
"${CMAKE_CURRENT_SOURCE_DIR}/../ifcopenshell-python/ifcopenshell/*.py"
"${CMAKE_CURRENT_SOURCE_DIR}/../ifcopenshell-python/ifcopenshell/*.bnf"
)
FOREACH(file ${sourcefiles})
FILE(RELATIVE_PATH relative "${CMAKE_CURRENT_SOURCE_DIR}/../ifcopenshell-python/ifcopenshell/" "${file}")
GET_FILENAME_COMPONENT(dir "${relative}" DIRECTORY)
+22 -3
View File
@@ -154,9 +154,28 @@ struct ShapeRTTI : public boost::static_visitor<PyObject*>
%extend IfcGeom::IteratorSettings {
%pythoncode %{
attrs = ("convert_back_units", "deflection_tolerance", "disable_opening_subtractions", "disable_triangulation", "faster_booleans", "sew_shells", "use_brep_data", "use_world_coords", "weld_vertices")
def __repr__(self):
return "%s(%s)"%(self.__class__.__name__, ",".join(tuple("%s=%r"%(a, getattr(self, a)()) for a in self.attrs)))
old_init = __init__
def __init__(self, **kwargs):
self.old_init()
for k, v in kwargs.items():
self.set(getattr(self, k), v)
def __repr__(self):
def d():
import numbers
for x in dir(self):
if x.isupper() and x not in {"NUM_SETTINGS", "USE_PYTHON_OPENCASCADE"}:
v = getattr(self, x)
if isinstance(v, numbers.Integral):
yield x
return "%s(%s)" % (
type(self).__name__,
(", ".join(map(lambda x: "%s = %r" % (x, self.get(getattr(self, x))), d())))
)
%}
}
+2
View File
@@ -115,6 +115,8 @@
return SWIGTYPE_p_IfcParse__select_type;
} else if (t->as_enumeration_type()) {
return SWIGTYPE_p_IfcParse__enumeration_type;
} else {
throw std::runtime_error("Unexpected declaration type");
}
}
+90
View File
@@ -65,6 +65,96 @@ CREATE_VECTOR_TYPEMAP_IN(int, INTEGER, int)
CREATE_VECTOR_TYPEMAP_IN(double, REAL, float)
CREATE_VECTOR_TYPEMAP_IN(std::string, STRING, str)
// @todo use macros.
%typemap(in) const std::vector<const IfcParse::declaration*>& {
if (PySequence_Check($input)) {
$1 = new std::vector<const IfcParse::declaration*>;
for(Py_ssize_t i = 0; i < PySequence_Size($input); ++i) {
PyObject* element = PySequence_GetItem($input, i);
void *arg = 0;
int res = SWIG_ConvertPtr(element, &arg, SWIGTYPE_p_IfcParse__declaration, 0);
auto decl = static_cast<const IfcParse::declaration*>(SWIG_IsOK(res) ? arg : 0);
if (decl) {
$1->push_back(decl);
} else {
SWIG_exception(SWIG_TypeError, "Expected a schema declaration");
}
}
} else {
SWIG_exception(SWIG_TypeError, "Expected an sequence type");
}
}
%typemap(in) const std::vector<const IfcParse::entity*>& {
if (PySequence_Check($input)) {
$1 = new std::vector<const IfcParse::entity*>;
for(Py_ssize_t i = 0; i < PySequence_Size($input); ++i) {
PyObject* element = PySequence_GetItem($input, i);
void *arg = 0;
int res = SWIG_ConvertPtr(element, &arg, SWIGTYPE_p_IfcParse__entity, 0);
auto decl = static_cast<const IfcParse::entity*>(SWIG_IsOK(res) ? arg : 0);
if (decl) {
$1->push_back(decl);
} else {
SWIG_exception(SWIG_TypeError, "Expected a schema entity");
}
}
} else {
SWIG_exception(SWIG_TypeError, "Expected an sequence type");
}
}
%typemap(in) const std::vector<const IfcParse::attribute*>& {
if (PySequence_Check($input)) {
$1 = new std::vector<const IfcParse::attribute*>;
for(Py_ssize_t i = 0; i < PySequence_Size($input); ++i) {
PyObject* element = PySequence_GetItem($input, i);
void *arg = 0;
int res = SWIG_ConvertPtr(element, &arg, SWIGTYPE_p_IfcParse__attribute, 0);
auto decl = static_cast<const IfcParse::attribute*>(SWIG_IsOK(res) ? arg : 0);
if (decl) {
$1->push_back(decl);
} else {
SWIG_exception(SWIG_TypeError, "Expected a schema attribute");
}
}
} else {
SWIG_exception(SWIG_TypeError, "Expected an sequence type");
}
}
%typemap(in) const std::vector<const IfcParse::inverse_attribute*>& {
if (PySequence_Check($input)) {
$1 = new std::vector<const IfcParse::inverse_attribute*>;
for(Py_ssize_t i = 0; i < PySequence_Size($input); ++i) {
PyObject* element = PySequence_GetItem($input, i);
void *arg = 0;
int res = SWIG_ConvertPtr(element, &arg, SWIGTYPE_p_IfcParse__inverse_attribute, 0);
auto decl = static_cast<const IfcParse::inverse_attribute*>(SWIG_IsOK(res) ? arg : 0);
if (decl) {
$1->push_back(decl);
} else {
SWIG_exception(SWIG_TypeError, "Expected a schema inverse attribute");
}
}
} else {
SWIG_exception(SWIG_TypeError, "Expected an sequence type");
}
}
%typemap(in) const std::vector<bool>& {
if (PySequence_Check($input)) {
$1 = new std::vector<bool>;
for(Py_ssize_t i = 0; i < PySequence_Size($input); ++i) {
PyObject* element = PySequence_GetItem($input, i);
$1->push_back(PyObject_IsTrue(element));
}
} else {
SWIG_exception(SWIG_TypeError, "Expected an sequence type");
}
}
%typemap(in) IfcEntityList::ptr {
if (PySequence_Check($input)) {
$1 = IfcEntityList::ptr(new IfcEntityList());
+2
View File
@@ -21,6 +21,8 @@
$result = SWIG_NewPointerObj(SWIG_as_voidptr($1->as_simple_type()), SWIGTYPE_p_IfcParse__simple_type, 0);
} else if ($1->as_aggregation_type()) {
$result = SWIG_NewPointerObj(SWIG_as_voidptr($1->as_aggregation_type()), SWIGTYPE_p_IfcParse__aggregation_type, 0);
} else {
throw std::runtime_error("unexpected parameter type");
}
}
+353 -91
View File
@@ -65,6 +65,8 @@
#include <BRepBndLib.hxx>
#include <BRepBuilderAPI_MakeEdge.hxx>
#include <ShapeFix_Edge.hxx>
#include "../ifcparse/IfcGlobalId.h"
#include "SvgSerializer.h"
@@ -128,7 +130,7 @@ void SvgSerializer::write(path_object& p, const TopoDS_Wire& wire) {
double r = circle->Radius();
gp_Circ c = circle->Circ();
gp_Pnt center = c.Location();
path.add(" <circle style=\"stroke:black; fill:none;\" r=\"");
path.add(" <circle r=\"");
radii.push_back(path.add(r));
path.add("\" cx=\"");
xcoords.push_back(path.add(center.X()));
@@ -146,7 +148,7 @@ void SvgSerializer::write(path_object& p, const TopoDS_Wire& wire) {
gp_Pnt center = e.Location();
// Write the ellipse with major radius along X axis:
path.add(" <ellipse style=\"stroke:black; fill:none;\" rx=\"");
path.add(" <ellipse rx=\"");
radii.push_back(path.add(e.MajorRadius()));
path.add("\" ry=\"");
radii.push_back(path.add(e.MinorRadius()));
@@ -193,7 +195,7 @@ void SvgSerializer::write(path_object& p, const TopoDS_Wire& wire) {
}
if (first) {
path.add(" <path style=\"stroke:black; fill:none;\" d=\"");
path.add(" <path d=\"");
path.add("M");
addXCoordinate(path.add(p1.X()));
path.add(",");
@@ -291,68 +293,159 @@ void SvgSerializer::write(path_object& p, const TopoDS_Wire& wire) {
}
SvgSerializer::path_object& SvgSerializer::start_path(IfcUtil::IfcBaseEntity* storey, const std::string& id) {
SvgSerializer::path_object& p = paths.insert(std::make_pair(storey, path_object()))->second;
auto key = std::make_pair(std::make_pair(storey, ""), path_object());
SvgSerializer::path_object& p = paths.insert(key)->second;
p.first = id;
return p;
}
void SvgSerializer::write(const IfcGeom::BRepElement<real_t>* o)
{
std::vector<std::pair<std::pair<double, double>, IfcUtil::IfcBaseEntity*>> section_heights_storage;
const std::vector<std::pair<std::pair<double, double>, IfcUtil::IfcBaseEntity*>>* section_heights_used = &section_heights_storage;
SvgSerializer::path_object& SvgSerializer::start_path(const std::string& drawing_name, const std::string& id) {
auto key = std::make_pair(std::make_pair(nullptr, drawing_name), path_object());
SvgSerializer::path_object& p = paths.insert(key)->second;
p.first = id;
return p;
}
if (section_heights) {
section_heights_used = section_heights.get_ptr();
} else {
namespace {
boost::optional<std::pair<IfcUtil::IfcBaseEntity*, double>> storey_elevation_from_element(const IfcGeom::BRepElement<real_t>* o) {
for (const auto& p : o->parents()) {
if (p->type() == "IfcBuildingStorey") {
try {
const IfcGeom::ElementSettings& settings = o->geometry().settings();
double e = *p->product()->get("Elevation");
double storey_elevation = e * settings.unit_magnitude();
section_heights_storage.push_back({ {storey_elevation, +1.} , p->product() });
return std::make_pair(p->product(), storey_elevation);
} catch (...) {
continue;
}
break;
}
}
return boost::none;
}
if (section_heights_storage.empty()) {
Logger::Warning("No global section height and unable to determine building storey for:", o->product());
boost::optional<TopoDS_Edge> edge_from_compound(TopoDS_Shape& compound) {
TopoDS_Iterator it(compound);
if (it.More()) {
TopoDS_Shape wire = it.Value();
it.Next();
if (!it.More() && wire.ShapeType() == TopAbs_WIRE) {
TopoDS_Iterator jt(wire);
if (jt.More()) {
TopoDS_Shape edge = jt.Value();
jt.Next();
if (!jt.More() && edge.ShapeType() == TopAbs_EDGE) {
return TopoDS::Edge(edge);
}
}
}
}
return boost::none;
}
}
void SvgSerializer::write(const IfcGeom::BRepElement<real_t>* brep_obj) {
boost::optional<std::string> object_type;
if (!brep_obj->product()->get("ObjectType")->isNull()) {
object_type = static_cast<std::string>(*brep_obj->product()->get("ObjectType"));
}
TopoDS_Shape compound_local = brep_obj->geometry().as_compound();
const gp_Trsf& trsf = brep_obj->transformation().data();
const bool is_section = (section_ref_ && object_type && *section_ref_ == *object_type);
const bool is_elevation = (elevation_ref_ && object_type && *elevation_ref_ == *object_type);
if (is_section || is_elevation) {
BRepBuilderAPI_Transform make_transform_global(compound_local, trsf, true);
make_transform_global.Build();
// (When determinant < 0, copy is implied and the input is not mutated.)
auto compound_unmirrored = make_transform_global.Shape();
auto e = edge_from_compound(compound_unmirrored);
if (e) {
TopoDS_Edge global_edge = TopoDS::Edge(e->Moved(trsf));
double u0, u1;
auto crv = BRep_Tool::Curve(global_edge, u0, u1);
if (crv->DynamicType() == STANDARD_TYPE(Geom_Line)) {
gp_Pnt P;
gp_Vec V;
crv->D1((u0 + u1) / 2., P, V);
auto N = V.Crossed(gp::DZ());
gp_Pln pln(gp_Ax3(P, N, V));
if (!deferred_section_data_) {
deferred_section_data_.emplace();
}
std::string name = brep_obj->name();
if (name.empty()) {
name = boost::lexical_cast<std::string>(brep_obj->id());
}
if (is_section) {
deferred_section_data_->push_back(vertical_section{ pln , "Section " + name, false });
}
if (is_elevation) {
deferred_section_data_->push_back(vertical_section{ pln , "Elevation " + name, true });
}
}
}
return;
}
auto p = storey_elevation_from_element(brep_obj);
IfcUtil::IfcBaseEntity* storey = p ? p->first : nullptr;
double elev = p ? p->second : std::numeric_limits<double>::quiet_NaN();
geometry_data data{ compound_local, trsf, brep_obj->product(), storey, elev, brep_obj->name(), nameElement(storey, brep_obj) };
if (buffer_elements_) {
element_buffer_.push_back(data);
}
write(data);
}
void SvgSerializer::write(const geometry_data& data) {
std::vector<section_data> section_heights_storage;
const std::vector<section_data>* section_heights_used = &section_heights_storage;
if (section_data_) {
section_heights_used = section_data_.get_ptr();
} else {
if (data.storey) {
section_heights_storage.push_back(horizontal_plan{ data.storey, data.storey_elevation, +1. });
} else {
Logger::Warning("No global section height and unable to determine building storey for:", data.product);
return;
}
}
TopoDS_Shape compound_local = o->geometry().as_compound();
const gp_Trsf& trsf = o->transformation().data();
BRepBuilderAPI_Transform make_transform_global(compound_local, trsf, true);
BRepBuilderAPI_Transform make_transform_global(data.compound_local, data.trsf, true);
make_transform_global.Build();
// (When determinant < 0, copy is implied and the input is not mutated.)
auto compound = make_transform_global.Shape();
auto compound_unmirrored = make_transform_global.Shape();
// SVG has a coordinate system with the origin in the *upper*-left corner
// therefore we mirror the shape along the XZ-plane.
gp_Trsf trsf_mirror;
trsf_mirror.SetMirror(gp_Ax2(gp::Origin(), gp::DY()));
BRepBuilderAPI_Transform make_transform_mirror(compound, trsf_mirror, true);
BRepBuilderAPI_Transform make_transform_mirror(compound_unmirrored, trsf_mirror, true);
make_transform_mirror.Build();
// (When determinant < 0, copy is implied and the input is not mutated.)
compound = make_transform_mirror.Shape();
auto compound = make_transform_mirror.Shape();
TopoDS_Wire annotation;
if (draw_door_arcs_ && o->product()->declaration().is("IfcDoor")) {
if (is_floor_plan_ && draw_door_arcs_ && data.product->declaration().is("IfcDoor")) {
boost::optional<std::string> operation_type;
try {
IfcEntityList::ptr rels;
if (o->product()->declaration().schema()->name() == "IFC2X3") {
rels = o->product()->get_inverse("IsDefinedBy");
if (data.product->declaration().schema()->name() == "IFC2X3") {
rels = data.product->get_inverse("IsDefinedBy");
} else {
// Damn you, IFC
rels = o->product()->get_inverse("IsTypedBy");
rels = data.product->get_inverse("IsTypedBy");
}
for (auto& rel : *rels) {
if (rel->declaration().name() == "IfcRelDefinesByType") {
@@ -372,7 +465,7 @@ void SvgSerializer::write(const IfcGeom::BRepElement<real_t>* o)
const bool is_left = *operation_type == "SINGLE_SWING_LEFT";
Bnd_Box bb;
BRepBndLib::Add(compound_local, bb);
BRepBndLib::Add(data.compound_local, bb);
if (bb.IsVoid()) {
return;
@@ -405,9 +498,9 @@ void SvgSerializer::write(const IfcGeom::BRepElement<real_t>* o)
make_transform_mirror.Perform(edge_global, true);
auto edge_global_mirrored = make_transform_mirror.Shape();
center.Transform(trsf);
p1.Transform(trsf);
p2.Transform(trsf);
center.Transform(data.trsf);
p1.Transform(data.trsf);
p2.Transform(data.trsf);
center.Transform(trsf_mirror);
p1.Transform(trsf_mirror);
p2.Transform(trsf_mirror);
@@ -431,22 +524,48 @@ void SvgSerializer::write(const IfcGeom::BRepElement<real_t>* o)
bool emitted = false;
for (auto sit = section_heights_used->begin(); sit != section_heights_used->end(); ++sit) {
const auto& pair = *sit;
const auto& variant = *sit;
// Elev + offset
auto cut_z = pair.first.first + pair.first.second;
double cut_z = std::numeric_limits<double>::infinity();
// Elev .. Elev(next)
std::pair<double, double> range{ pair.first.first, std::numeric_limits<double>::infinity() };
if (sit == section_heights_used->begin()) {
range.first = -range.second;
}
if (sit + 1 != section_heights_used->end()) {
range.second = (sit + 1)->first.first;
}
auto storey = pair.second;
std::pair<double, double> range;
TopoDS_Iterator it(compound);
gp_Vec projection_direction;
IfcUtil::IfcBaseEntity* storey = nullptr;
std::string drawing_name;
bool use_hlr = false;
// @todo use visitor
// horizontal_plan, horizontal_plan_at_element, vertical_section
if (variant.which() == 0) {
const auto& plan = boost::get<horizontal_plan>(variant);
storey = plan.storey;
cut_z = plan.elevation + plan.offset;
range = { plan.elevation, plan.next_elevation };
if (sit == section_heights_used->begin()) {
range.first = -std::numeric_limits<double>::infinity();
}
projection_direction = gp::DZ();
} else if (variant.which() == 1) {
projection_direction = gp::DZ();
} else if (variant.which() == 2) {
const auto& section = boost::get<vertical_section>(variant);
projection_direction = section.plane.Axis().Direction();
drawing_name = section.name;
use_hlr = section.with_projection;
}
auto& compound_to_use = is_floor_plan_ ? compound : compound_unmirrored;
if (use_hlr && hlr) {
hlr->Add(compound_to_use);
}
TopoDS_Iterator it(compound_to_use);
TopoDS_Face largest_closed_wire_face;
double largest_closed_wire_area = 0.;
@@ -456,7 +575,7 @@ void SvgSerializer::write(const IfcGeom::BRepElement<real_t>* o)
for (; it.More(); it.Next()) {
const TopoDS_Shape& subshape = it.Value();
Bnd_Box bb;
try {
BRepBndLib::Add(it.Value(), bb);
@@ -469,19 +588,28 @@ void SvgSerializer::write(const IfcGeom::BRepElement<real_t>* o)
double x1, y1, zmin, x2, y2, zmax;
bb.Get(x1, y1, zmin, x2, y2, zmax);
// Determine slicing plane z coordinate, priority:
// 1) explicitly set global section height
// 2) containing building storey elevation + 1m
// 3) zmin (from geometry bounding box) + 1m
if (std::isnan(cut_z)) {
if (variant.which() == 1) {
cut_z = zmin + 1.;
}
if (o->type() == "IfcAnnotation" && ((zmax - zmin) < 1.e-5) && zmin >= range.first && zmin <= range.second) {
gp_Vec bbmin(x1, y1, zmin);
gp_Vec bbmax(x2, y2, zmax);
auto bbdif = bbmax - bbmin;
auto proj = projection_direction ^ bbdif ^ projection_direction;
if (data.product->declaration().is("IfcAnnotation") && (proj.Magnitude() > 1.e-5) && zmin >= range.first && zmin <= range.second) {
if (po == nullptr) {
po = &start_path(storey, nameElement(storey, o));
if (storey) {
po = &start_path(storey, data.svg_name);
} else {
po = &start_path(drawing_name, data.svg_name);
}
}
TopExp_Explorer exp(subshape, TopAbs_EDGE, TopAbs_FACE);
@@ -499,6 +627,9 @@ void SvgSerializer::write(const IfcGeom::BRepElement<real_t>* o)
B.Add(W, e);
write(*po, W);
util::string_buffer path;
// dominant-baseline="central" is not well supported in IE.
// so we add a 0.35 offset to the dy of the tspans
@@ -543,18 +674,42 @@ void SvgSerializer::write(const IfcGeom::BRepElement<real_t>* o)
}
// No intersection with bounding box, fail early
if (zmin > cut_z || zmax < cut_z) continue;
if (variant.which() < 2) {
if (zmin > cut_z || zmax < cut_z) continue;
}
emitted = true;
if (po == nullptr) {
po = &start_path(storey, nameElement(storey, o));
if (storey) {
po = &start_path(storey, data.svg_name);
} else {
po = &start_path(drawing_name, data.svg_name);
}
}
// Create a horizontal cross section 1 meter above the bottom point of the shape
const gp_Pln pln(gp_Pnt(0, 0, cut_z), gp::DZ());
gp_Pln pln;
if (variant.which() < 2) {
pln = gp_Pln(gp_Pnt(0, 0, cut_z), gp::DZ());
} else {
const auto& section = boost::get<vertical_section>(variant);
pln = section.plane;
}
TopoDS_Shape result = BRepAlgoAPI_Section(subshape, pln);
if (variant.which() == 2) {
gp_Trsf trsf;
trsf.SetTransformation(gp::XOY(), pln.Position());
result.Move(trsf);
gp_Trsf trsf_mirror;
trsf_mirror.SetMirror(gp_Ax2(gp::Origin(), gp::DY()));
BRepBuilderAPI_Transform make_transform_mirror(result, trsf_mirror, true);
make_transform_mirror.Build();
result = make_transform_mirror.Shape();
}
Handle(TopTools_HSequenceOfShape) edges = new TopTools_HSequenceOfShape();
Handle(TopTools_HSequenceOfShape) wires = new TopTools_HSequenceOfShape();
{
@@ -569,7 +724,7 @@ void SvgSerializer::write(const IfcGeom::BRepElement<real_t>* o)
for (int i = 1; i <= wires->Length(); ++i) {
const TopoDS_Wire& wire = TopoDS::Wire(wires->Value(i));
if (wire.Closed() && (print_space_names_ || print_space_areas_) && o->type() == "IfcSpace") {
if (wire.Closed() && (print_space_names_ || print_space_areas_) && data.product->declaration().is("IfcSpace")) {
// we explicitly specify the surface here, to later on
// simplify the projection from {x,y,z} to {u, v} because
// we know we can simply discard z.
@@ -636,10 +791,10 @@ void SvgSerializer::write(const IfcGeom::BRepElement<real_t>* o)
if (center_point) {
std::vector<std::string> labels;
if (print_space_names_) {
labels.push_back(o->name());
labels.push_back(data.ifc_name);
}
if (print_space_names_ && o->type() == "IfcSpace") {
auto attr = o->product()->get("LongName");
if (print_space_names_ && data.product->declaration().is("IfcSpace")) {
auto attr = data.product->get("LongName");
if (!attr->isNull()) {
std::string long_name = *attr;
if (!long_name.empty()) {
@@ -689,7 +844,7 @@ void SvgSerializer::write(const IfcGeom::BRepElement<real_t>* o)
}
if (!emitted) {
Logger::Warning("Element not written to SVG due to section heights", o->product());
Logger::Warning("Element not written to SVG due to section heights", data.product);
}
}
@@ -699,8 +854,7 @@ void SvgSerializer::setBoundingRectangle(double width, double height) {
this->rescale = true;
}
void SvgSerializer::finalize() {
void SvgSerializer::resize() {
if (rescale) {
// Scale the resulting image to a bounding rectangle specified by command line arguments
const double dx = xmax - xmin;
@@ -709,43 +863,132 @@ void SvgSerializer::finalize() {
double sc, cx, cy;
if (scale_) {
sc = (*scale_) * 1000;
cx = (xmax + xmin) / 2. * sc - width / 2.;
cy = (ymax + ymin) / 2. * sc - height / 2.;
cx = (xmax + xmin) / 2. * sc - width * center_x_.get_value_or(0.5);
cy = (ymax + ymin) / 2. * sc - height * center_y_.get_value_or(0.5);
} else {
if (dx / width > dy / height) {
sc = width / dx;
if (calculated_scale_) {
sc = *calculated_scale_;
} else {
sc = height / dy;
if (dx / width > dy / height) {
sc = width / dx;
} else {
sc = height / dy;
}
calculated_scale_ = sc;
}
cx = xmin * sc;
cy = ymin * sc;
}
{std::vector< boost::shared_ptr<util::string_buffer::float_item> >::const_iterator it;
for (it = xcoords.begin(); it != xcoords.end(); ++it) {
float_item_list::const_iterator it;
for (it = xcoords.begin() + xcoords_begin; it != xcoords.end(); ++it, ++xcoords_begin) {
double& v = (*it)->value();
v = v * sc - cx;
}
for (it = ycoords.begin(); it != ycoords.end(); ++it) {
for (it = ycoords.begin() + ycoords_begin; it != ycoords.end(); ++it, ++ycoords_begin) {
double& v = (*it)->value();
v = v * sc - cy;
}
for (it = radii.begin(); it != radii.end(); ++it) {
for (it = radii.begin() + radii_begin; it != radii.end(); ++it, ++radii_begin) {
(*it)->value() *= sc;
}}
}
}
std::multimap<IfcUtil::IfcBaseEntity*, path_object>::const_iterator it;
// reset the bounding box, as a subsequent drawing (elevation, section) will be centered, but use the same scale.
xmin = +std::numeric_limits<double>::infinity();
ymin = +std::numeric_limits<double>::infinity();
xmax = -std::numeric_limits<double>::infinity();
ymax = -std::numeric_limits<double>::infinity();
}
IfcUtil::IfcBaseEntity* previous = 0;
bool first = true;
void SvgSerializer::finalize() {
resize();
if (deferred_section_data_ && deferred_section_data_->size() && element_buffer_.size()) {
// Draw door arcs only on floor plans.
is_floor_plan_ = false;
for (auto& sd : *deferred_section_data_) {
bool use_hlr = false;
std::string drawing_name;
if (sd.which() == 2) {
const auto& section = boost::get<vertical_section>(sd);
use_hlr = section.with_projection;
drawing_name = section.name;
}
if (use_hlr) {
hlr = new HLRBRep_Algo;
}
*section_data_ = { sd };
for (auto& e : element_buffer_) {
write(e);
}
if (use_hlr) {
const auto& section = boost::get<vertical_section>(sd);
gp_Ax2 transform = section.plane.Position().Ax2();
HLRAlgo_Projector projector(transform);
hlr->Projector(projector);
hlr->Update();
hlr->Hide();
HLRBRep_HLRToShape hlr_shapes(hlr);
auto hlr_compound_unmirrored = hlr_shapes.VCompound();
// Compound 3D curves for mirroring to work
ShapeFix_Edge sfe;
TopExp_Explorer exp(hlr_compound_unmirrored, TopAbs_EDGE);
for (; exp.More(); exp.Next()) {
sfe.FixAddCurve3d(TopoDS::Edge(exp.Current()));
}
// Mirror to match SVG coord system.
// @todo this is very wasteful. We better do the Y-mirror in the SVG writing and
// not on the TopoDS_Shape input.
gp_Trsf trsf_mirror;
trsf_mirror.SetMirror(gp_Ax2(gp::Origin(), gp::DY()));
BRepBuilderAPI_Transform make_transform_mirror(hlr_compound_unmirrored, trsf_mirror, true);
make_transform_mirror.Build();
auto hlr_compound = make_transform_mirror.Shape();
exp.Init(hlr_compound, TopAbs_EDGE);
BRep_Builder B;
auto& po = start_path(drawing_name, "class=\"projection\"");
for (; exp.More(); exp.Next()) {
TopoDS_Wire w;
B.MakeWire(w);
B.Add(w, exp.Current());
write(po, w);
}
}
resize();
if (use_hlr) {
hlr.Nullify();
}
}
}
std::multimap<drawing_key, path_object, storey_sorter>::const_iterator it;
boost::optional<drawing_key> previous;
for (it = paths.begin(); it != paths.end(); ++it) {
if (it->first != previous || first) {
if (!first) {
if (!previous || it->first != *previous) {
if (previous) {
svg_file << " </g>\n";
}
std::ostringstream oss;
svg_file << " <g " << nameElement(it->first) << ">\n";
if (it->first.first) {
svg_file << " <g " << nameElement(it->first.first) << ">\n";
} else {
svg_file << " <g data-name=\"" << it->first.second << "\" class=\"section\">\n";
}
}
svg_file << " <g " << it->second.first << ">\n";
std::vector<util::string_buffer>::const_iterator jt;
@@ -754,10 +997,9 @@ void SvgSerializer::finalize() {
}
svg_file << " </g>\n";
previous = it->first;
first = false;
}
if (!first) {
if (previous) {
svg_file << " </g>\n";
}
svg_file << "</svg>" << std::endl;
@@ -783,15 +1025,32 @@ void SvgSerializer::writeHeader() {
" </defs>\n"
" <style type=\"text/css\" >\n"
" <![CDATA[\n"
" path {\n"
" stroke: #222222;\n"
" fill: #444444;\n"
" }\n"
" .IfcDoor path {\n"
" fill: none;\n"
" }\n"
" .IfcSpace path {\n"
" fill-opacity: .2;\n"
" }\n"
" .IfcAnnotation path {\n"
" marker-end: url(#arrowend);\n"
" marker-start: url(#arrowstart);\n"
" }\n";
if (scale_) {
// previously:
// (pt) (px) (in) (mm)
// approx 12 / 0.75 / 96 * 25.4
svg_file <<
" text {\n" // (pt) (px) (in) (mm)
" font-size: 4;\n" // approx 12 / 0.75 / 96 * 25.4
" text {\n"
" font-size: 2;\n" // (reduced to two).
" }\n"
" path {\n"
" stroke-width: 0.3;\n"
" }\n";
}
@@ -803,11 +1062,11 @@ void SvgSerializer::writeHeader() {
namespace {
std::string nameElement_(const std::vector<std::pair<std::string, std::string> >& attrs) {
std::ostringstream oss;
for (auto& a : attrs) {
// @todo while we're at it might as well implement escaping
oss << a.first << "=\"" << a.second << "\" ";
}
return oss.str();
for (auto& a : attrs) {
// @todo while we're at it might as well implement escaping
oss << a.first << "=\"" << a.second << "\" ";
}
return oss.str();
}
}
@@ -817,7 +1076,7 @@ std::string SvgSerializer::nameElement(const IfcUtil::IfcBaseEntity* storey, con
{"class", elem->type()},
{"data-name", elem->name()},
{"data-guid", elem->guid()}
});
});
}
std::string SvgSerializer::idElement(const IfcUtil::IfcBaseEntity* elem) {
@@ -843,11 +1102,11 @@ std::string SvgSerializer::nameElement(const IfcUtil::IfcBaseEntity* elem) {
}
return nameElement_({
{"id", idElement(elem)},
{"class", entity},
{"id", idElement(elem)},
{"class", entity},
{"data-name", ifc_name},
{"data-guid", *elem->get("GlobalId")}
});
});
}
void SvgSerializer::setFile(IfcParse::IfcFile* f) {
@@ -855,9 +1114,9 @@ void SvgSerializer::setFile(IfcParse::IfcFile* f) {
auto storeys = f->instances_by_type("IfcBuildingStorey");
if (!storeys || storeys->size() == 0) {
IfcGeom::Kernel kernel(f);
std::vector<const IfcParse::declaration*> to_derive_from;
to_derive_from.push_back(f->schema()->declaration_by_name("IfcBuilding"));
to_derive_from.push_back(f->schema()->declaration_by_name("IfcSite"));
@@ -883,13 +1142,13 @@ void SvgSerializer::setFile(IfcParse::IfcFile* f) {
}
void SvgSerializer::setSectionHeight(double h, IfcUtil::IfcBaseEntity* storey) {
section_heights.emplace();
section_heights->push_back({ {h, 0.}, storey });
section_data_.emplace();
section_data_->push_back(horizontal_plan{ storey, h, 0., std::numeric_limits<double>::infinity() });
}
void SvgSerializer::setSectionHeightsFromStoreys(double offset) {
with_section_heights_from_storey_ = true;
section_heights.emplace();
section_data_.emplace();
auto storeys = file->instances_by_type("IfcBuildingStorey");
const double lu = file->getUnit("LENGTHUNIT").second;
if (storeys && storeys->size() > 0) {
@@ -903,10 +1162,13 @@ void SvgSerializer::setSectionHeightsFromStoreys(double offset) {
Logger::Error(e);
continue;
}
section_heights->push_back({ {elev * lu, offset} , (IfcUtil::IfcBaseEntity*)s });
if (!section_data_->empty()) {
boost::get<horizontal_plan>(section_data_->back()).next_elevation = elev * lu;
}
section_data_->push_back(horizontal_plan{ (IfcUtil::IfcBaseEntity*)s, elev * lu, offset, std::numeric_limits<double>::infinity() });
}
}
} else {
section_heights->push_back({ {std::numeric_limits<double>::quiet_NaN(), 0.}, nullptr });
section_data_->push_back(horizontal_plan_at_element{});
}
}
+85 -10
View File
@@ -27,12 +27,29 @@
#include "../ifcparse/utils.h"
#include <HLRBRep_Algo.hxx>
#include <HLRBRep_HLRToShape.hxx>
#include <gp_Pln.hxx>
#include <sstream>
#include <string>
#include <limits>
typedef std::pair<IfcUtil::IfcBaseEntity*, std::string> drawing_key;
struct storey_sorter {
bool operator()(IfcUtil::IfcBaseEntity* a, IfcUtil::IfcBaseEntity* b) const {
bool operator()(const drawing_key& ad, const drawing_key& bd) const {
if (ad.first == nullptr && bd.first != nullptr) {
return false;
} else if (bd.first == nullptr && ad.first != nullptr) {
return true;
} else if (ad.first == nullptr && bd.first == nullptr) {
return std::less<std::string>()(ad.second, bd.second);
}
auto a = ad.first;
auto b = bd.first;
const bool a_is_storey = a->declaration().is("IfcBuildingStorey");
const bool b_is_storey = b->declaration().is("IfcBuildingStorey");
if (a_is_storey && b_is_storey) {
@@ -66,21 +83,56 @@ struct storey_sorter {
}
};
struct horizontal_plan {
IfcUtil::IfcBaseEntity* storey;
double elevation, offset, next_elevation;
};
struct horizontal_plan_at_element {};
struct vertical_section {
gp_Pln plane;
std::string name;
bool with_projection;
};
typedef boost::variant<horizontal_plan, horizontal_plan_at_element, vertical_section> section_data;
struct geometry_data {
TopoDS_Shape compound_local;
gp_Trsf trsf;
IfcUtil::IfcBaseEntity* product;
IfcUtil::IfcBaseEntity* storey;
double storey_elevation;
std::string ifc_name, svg_name;
};
class SvgSerializer : public GeometrySerializer {
public:
typedef std::pair<std::string, std::vector<util::string_buffer> > path_object;
typedef std::vector< boost::shared_ptr<util::string_buffer::float_item> > float_item_list;
protected:
std::ofstream svg_file;
double xmin, ymin, xmax, ymax, width, height;
boost::optional<std::vector<std::pair<std::pair<double, double>, IfcUtil::IfcBaseEntity*>>> section_heights;
boost::optional<double> scale_;
bool rescale, print_space_names_, print_space_areas_, draw_door_arcs_, with_section_heights_from_storey_;
std::multimap<IfcUtil::IfcBaseEntity*, path_object, storey_sorter> paths;
std::vector< boost::shared_ptr<util::string_buffer::float_item> > xcoords;
std::vector< boost::shared_ptr<util::string_buffer::float_item> > ycoords;
std::vector< boost::shared_ptr<util::string_buffer::float_item> > radii;
boost::optional<std::vector<section_data>> section_data_;
boost::optional<std::vector<section_data>> deferred_section_data_;
boost::optional<double> scale_, calculated_scale_, center_x_, center_y_;
bool rescale, print_space_names_, print_space_areas_, draw_door_arcs_;
bool with_section_heights_from_storey_, buffer_elements_;
bool is_floor_plan_;
std::multimap<drawing_key, path_object, storey_sorter> paths;
float_item_list xcoords, ycoords, radii;
size_t xcoords_begin, ycoords_begin, radii_begin;
boost::optional<std::string> section_ref_, elevation_ref_;
IfcParse::IfcFile* file;
IfcUtil::IfcBaseEntity* storey_;
std::list<geometry_data> element_buffer_;
Handle(HLRBRep_Algo) hlr;
public:
SvgSerializer(const std::string& out_filename, const SerializerSettings& settings)
: GeometrySerializer(settings)
@@ -94,8 +146,13 @@ public:
, print_space_names_(false)
, print_space_areas_(false)
, draw_door_arcs_(false)
, buffer_elements_(false)
, is_floor_plan_(true)
, file(0)
, storey_(0)
, xcoords_begin(0)
, ycoords_begin(0)
, radii_begin(0)
{}
void addXCoordinate(const boost::shared_ptr<util::string_buffer::float_item>& fi) { xcoords.push_back(fi); }
void addYCoordinate(const boost::shared_ptr<util::string_buffer::float_item>& fi) { ycoords.push_back(fi); }
@@ -106,8 +163,10 @@ public:
void write(const IfcGeom::TriangulationElement<real_t>* /*o*/) {}
void write(const IfcGeom::BRepElement<real_t>* o);
void write(path_object& p, const TopoDS_Wire& wire);
void write(const geometry_data& data);
path_object& start_path(IfcUtil::IfcBaseEntity* storey, const std::string& id);
bool isTesselated() const { return false; }
path_object& start_path(const std::string& drawing_name, const std::string& id);
bool isTesselated() const { return false; }
void finalize();
void setUnitNameAndMagnitude(const std::string& /*name*/, float /*magnitude*/) {}
void setFile(IfcParse::IfcFile* f);
@@ -117,12 +176,28 @@ public:
void setPrintSpaceNames(bool b) { print_space_names_ = b; }
void setPrintSpaceAreas(bool b) { print_space_areas_ = b; }
void setDrawDoorArcs(bool b) { draw_door_arcs_ = b; }
void resize();
void setSectionRef(const boost::optional<std::string>& s) {
section_ref_ = s;
buffer_elements_ = true;
}
void setElevationRef(const boost::optional<std::string>& s) {
elevation_ref_ = s;
buffer_elements_ = true;
}
void setScale(double s) { scale_ = s; }
void setDrawingCenter(double x, double y) {
center_x_ = x; center_y_ = y;
}
std::string nameElement(const IfcUtil::IfcBaseEntity* storey, const IfcGeom::Element<real_t>* elem);
std::string nameElement(const IfcUtil::IfcBaseEntity* elem);
std::string idElement(const IfcUtil::IfcBaseEntity* elem);
std::string object_id(const IfcUtil::IfcBaseEntity* storey, const IfcGeom::Element<real_t>* o) {
return idElement(storey) + "-" + GeometrySerializer::object_id(o);
if (storey) {
return idElement(storey) + "-" + GeometrySerializer::object_id(o);
} else {
return GeometrySerializer::object_id(o);
}
}
};