mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 17:31:45 +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