mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-19 14:41:25 +00:00
New IFCPatch tool, which can "patch" an IFC, including adjusting coordinates and elevations.
This commit is contained in:
@@ -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.)
|
||||||
@@ -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
|
||||||
Reference in New Issue
Block a user