New IFCPatch tool, which can "patch" an IFC, including adjusting coordinates and elevations.

This commit is contained in:
Dion Moult
2020-02-06 16:20:15 +11:00
parent bf55214e1a
commit 7c97ad05fd
7 changed files with 185 additions and 0 deletions
+54
View File
@@ -0,0 +1,54 @@
import ifcopenshell
import logging
import argparse
def execute(args):
logging.basicConfig(filename=args.log, filemode='a', level=logging.DEBUG)
logger = logging.getLogger('IFCPatch')
print('# Loading IFC file ...')
ifc_file = ifcopenshell.open(args.input)
print('# Loading patch recipe ...')
patcher = getattr(__import__(f'recipes.{args.recipe}'), args.recipe).Patcher(
ifc_file, logger, args.arguments)
print('# Patching ...')
patcher.patch()
print('# Writing patched file ...')
if not args.output:
args.output = args.input
ifc_file.write(args.output)
parser = argparse.ArgumentParser(
description='Patches IFC files to fix badly formatted data')
parser.add_argument(
'-i',
'--input',
type=str,
required=True,
help='The IFC file to patch')
parser.add_argument(
'-o',
'--output',
type=str,
help='The output file to save the patched IFC')
parser.add_argument(
'-r',
'--recipe',
type=str,
required=True,
help='Name of the recipe to use when patching')
parser.add_argument(
'-l',
'--log',
type=str,
help='Specify a log file',
default='ifcpatch.log')
parser.add_argument(
'-a',
'--arguments',
nargs='+',
help='Specify custom arguments to the patch recipe')
args = parser.parse_args()
execute(args)
print('# All tasks are complete :-)')
@@ -0,0 +1,29 @@
class Patcher:
def __init__(self, file, logger, args=None):
self.file = file
self.logger = logger
self.args = args
def patch(self):
project = self.file.by_type('IfcProject')[0]
storeys = self.find_decomposed_ifc_class(project, 'IfcBuildingStorey')
for storey in storeys:
co = storey.ObjectPlacement.RelativePlacement.Location.Coordinates
storey.ObjectPlacement.RelativePlacement.Location.Coordinates = (co[0], co[1], co[2]+float(self.args[0]))
co = storey.ObjectPlacement.RelativePlacement.Location.Coordinates
# NOTE If the geometric data is provided (ObjectPlacement is
# specified), the Elevation value shall either not be included, or
# be equal to the local placement Z value.
storey.Elevation = co[2]
def find_decomposed_ifc_class(self, element, ifc_class):
results = []
rel_aggregates = element.IsDecomposedBy
if not rel_aggregates:
return results
for rel_aggregate in rel_aggregates:
for part in rel_aggregate.RelatedObjects:
if part.is_a(ifc_class):
results.append(part)
results.extend(self.find_decomposed_ifc_class(part, ifc_class))
return results
@@ -0,0 +1,23 @@
class Patcher:
def __init__(self, file, logger, args=None):
self.file = file
self.logger = logger
self.args = args
def patch(self):
project = self.file.by_type('IfcProject')[0]
sites = self.find_decomposed_ifc_class(project, 'IfcSite')
for site in sites:
site.Representation = None
def find_decomposed_ifc_class(self, element, ifc_class):
results = []
rel_aggregates = element.IsDecomposedBy
if not rel_aggregates:
return results
for rel_aggregate in rel_aggregates:
for part in rel_aggregate.RelatedObjects:
if part.is_a(ifc_class):
results.append(part)
results.extend(self.find_decomposed_ifc_class(part, ifc_class))
return results
@@ -0,0 +1,26 @@
class Patcher:
def __init__(self, file, logger):
self.file = file
self.logger = logger
def patch(self):
# 12D can have some funky coordinates out of any sensible range. This
# method will not work all the time, but will catch most issues.
offset_point = None
for point in self.file.by_type('IfcCartesianPoint'):
if len(point.Coordinates) == 2 or not self.is_point_far_away(point):
continue
if not offset_point:
offset_point = (point.Coordinates[0], point.Coordinates[1], point.Coordinates[2])
self.logger.info(f'Resetting absolute coordinates by {point}')
point.Coordinates = (
point.Coordinates[0] - offset_point[0],
point.Coordinates[1] - offset_point[1],
point.Coordinates[2] - offset_point[2]
)
def is_point_far_away(self, point):
# Arbitrary threshold based on experience
return abs(point.Coordinates[0]) > 1000000 \
or abs(point.Coordinates[1]) > 1000000 \
or abs(point.Coordinates[2]) > 1000000
@@ -0,0 +1,30 @@
class Patcher:
def __init__(self, file, logger, args=None):
self.file = file
self.logger = logger
self.args = args
def patch(self):
project = self.file.by_type('IfcProject')[0]
spatial_elements = self.find_decomposed_ifc_class(project, self.args[0])
for spatial_element in spatial_elements:
self.patch_placement_to_origin(spatial_element)
def find_decomposed_ifc_class(self, element, ifc_class):
results = []
rel_aggregates = element.IsDecomposedBy
if not rel_aggregates:
return results
for rel_aggregate in rel_aggregates:
for part in rel_aggregate.RelatedObjects:
if part.is_a(ifc_class):
results.append(part)
results.extend(self.find_decomposed_ifc_class(part, ifc_class))
return results
def patch_placement_to_origin(self, element):
element.ObjectPlacement.RelativePlacement.Location.Coordinates = (0., 0., 0.)
if element.ObjectPlacement.RelativePlacement.Axis:
element.ObjectPlacement.RelativePlacement.Axis.DirectionRatios = (0., 0., 1.)
if element.ObjectPlacement.RelativePlacement.RefDirection:
element.ObjectPlacement.RelativePlacement.RefDirection.DirectionRatios = (1., 0., 0.)
+23
View File
@@ -0,0 +1,23 @@
class Patcher:
def __init__(self, file, logger, args=None):
self.file = file
self.logger = logger
self.args = args
def patch(self):
project = self.file.by_type('IfcProject')[0]
sites = self.find_decomposed_ifc_class(project, 'IfcSite')
for site in sites:
site.RefElevation = float(self.args[0])
def find_decomposed_ifc_class(self, element, ifc_class):
results = []
rel_aggregates = element.IsDecomposedBy
if not rel_aggregates:
return results
for rel_aggregate in rel_aggregates:
for part in rel_aggregate.RelatedObjects:
if part.is_a(ifc_class):
results.append(part)
results.extend(self.find_decomposed_ifc_class(part, ifc_class))
return results
View File