mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-24 06:47:57 +00:00
Add documentation for IfcPatch and all recipes
This commit is contained in:
@@ -63,7 +63,7 @@ autoapi_add_toctree_entry = True
|
|||||||
autoapi_type = 'python'
|
autoapi_type = 'python'
|
||||||
|
|
||||||
# autoapi works by reading source code instead of importing modules
|
# autoapi works by reading source code instead of importing modules
|
||||||
autoapi_dirs = ['../ifcopenshell', '../../ifcdiff']
|
autoapi_dirs = ['../ifcopenshell', '../../ifcdiff', '../../ifcpatch/ifcpatch']
|
||||||
|
|
||||||
# These are auto-generated based on the IFC schema, so exclude them
|
# These are auto-generated based on the IFC schema, so exclude them
|
||||||
autoapi_ignore = ['*ifcopenshell/express/rules*']
|
autoapi_ignore = ['*ifcopenshell/express/rules*']
|
||||||
|
|||||||
@@ -75,3 +75,8 @@ Alternatively, you can package it as an executable.
|
|||||||
|
|
||||||
$ python make.py
|
$ python make.py
|
||||||
$ ./dist/ifcpatch
|
$ ./dist/ifcpatch
|
||||||
|
|
||||||
|
Patch recipes
|
||||||
|
-------------
|
||||||
|
|
||||||
|
You can view all built-in patches in IfcPatch here: :doc:`List of IfcPatch recipes <autoapi/ifcpatch/recipes/index>`.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
# IfcPatch - IFC patching utiliy
|
# IfcPatch - IFC patching utiliy
|
||||||
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
|
# Copyright (C) 2020, 2021, 2023 Dion Moult <dion@thinkmoult.com>
|
||||||
#
|
#
|
||||||
# This file is part of IfcPatch.
|
# This file is part of IfcPatch.
|
||||||
#
|
#
|
||||||
@@ -28,6 +28,41 @@ import importlib
|
|||||||
|
|
||||||
|
|
||||||
def execute(args):
|
def execute(args):
|
||||||
|
"""Execute a patch recipe
|
||||||
|
|
||||||
|
The details of how the patch recipe is executed depends on the definition of
|
||||||
|
the recipe, as well as the arguments passed to the recipe. See the
|
||||||
|
documentation for each patch recipe separately to understand more.
|
||||||
|
|
||||||
|
:param args: A dictionary of arguments, corresponding to the parameters
|
||||||
|
listed subsequent to this in this docstring.
|
||||||
|
:type args: dict
|
||||||
|
:param input: An IFC model to apply the patch recipe to.
|
||||||
|
:type input: ifcopenshell.file.file
|
||||||
|
:param recipe: The name of the recipe. This is the same as the filename of
|
||||||
|
the recipe. E.g. "ExtractElements".
|
||||||
|
:type recipe: str
|
||||||
|
:param log: A filepath to a logfile.
|
||||||
|
:type log: str,optional
|
||||||
|
:param arguments: A list of zero or more positional arguments, depending on
|
||||||
|
the patch recipe. Some patch recipes will require you to specify
|
||||||
|
arguments, some won't.
|
||||||
|
:type arguments: list
|
||||||
|
:return: The result of the patch. This is typically a patched model, either
|
||||||
|
as an object or as a string.
|
||||||
|
:rtype: ifcopenshell.file.file,str
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
output = ifcpatch.execute({
|
||||||
|
"input": ifcopenshell.open("input.ifc"),
|
||||||
|
"recipe": "ExtractElements",
|
||||||
|
"arguments": [".IfcWall"],
|
||||||
|
})
|
||||||
|
ifcpatch.write(output, "output.ifc")
|
||||||
|
"""
|
||||||
if "log" in args:
|
if "log" in args:
|
||||||
logging.basicConfig(filename=args["log"], filemode="a", level=logging.DEBUG)
|
logging.basicConfig(filename=args["log"], filemode="a", level=logging.DEBUG)
|
||||||
logger = logging.getLogger("IFCPatch")
|
logger = logging.getLogger("IFCPatch")
|
||||||
@@ -40,13 +75,23 @@ def execute(args):
|
|||||||
patcher = recipe.Patcher(args["input"], ifc_file, logger, args["arguments"])
|
patcher = recipe.Patcher(args["input"], ifc_file, logger, args["arguments"])
|
||||||
patcher.patch()
|
patcher.patch()
|
||||||
output = getattr(patcher, "file_patched", patcher.file)
|
output = getattr(patcher, "file_patched", patcher.file)
|
||||||
if isinstance(output, str):
|
|
||||||
with open(args["output"], "w") as text_file:
|
|
||||||
text_file.write(output)
|
|
||||||
return output
|
return output
|
||||||
|
|
||||||
|
|
||||||
def write(output, filepath):
|
def write(output, filepath):
|
||||||
|
"""Write the output of an IFC patch to a file
|
||||||
|
|
||||||
|
Typically a patch output would be a patched IFC model file object, or as a
|
||||||
|
string. This function lets you agnostically write that output to a filepath.
|
||||||
|
|
||||||
|
:param output: The results from ifcpatch.execute()
|
||||||
|
:type output: ifcopenshell.file.file,str
|
||||||
|
:param filepath: A filepath to where the results of the patched model should
|
||||||
|
be written to.
|
||||||
|
:type filepath: str
|
||||||
|
:return: None
|
||||||
|
:rtype: None
|
||||||
|
"""
|
||||||
if isinstance(output, str):
|
if isinstance(output, str):
|
||||||
with open(filepath, "w") as text_file:
|
with open(filepath, "w") as text_file:
|
||||||
text_file.write(output)
|
text_file.write(output)
|
||||||
|
|||||||
@@ -24,14 +24,32 @@ import ifcopenshell.util.element
|
|||||||
|
|
||||||
|
|
||||||
class Patcher:
|
class Patcher:
|
||||||
def __init__(self, src, file, logger, args=None):
|
def __init__(self, src, file, logger, unit="METERS"):
|
||||||
|
"""Converts the length unit of a model to the specified unit
|
||||||
|
|
||||||
|
Allowed metric units include METERS, MILLIMETERS, CENTIMETERS, etc.
|
||||||
|
Allowed imperial units include INCHES, FEET, MILES.
|
||||||
|
|
||||||
|
:param unit: The name of the desired unit.
|
||||||
|
:type unit: str
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
# Convert to millimeters
|
||||||
|
ifcpatch.execute({"input": model, "recipe": "ExtractElements", "arguments": ["MILLIMETERS"]})
|
||||||
|
|
||||||
|
# Convert to feet
|
||||||
|
ifcpatch.execute({"input": model, "recipe": "ExtractElements", "arguments": ["FEET"]})
|
||||||
|
"""
|
||||||
self.src = src
|
self.src = src
|
||||||
self.file = file
|
self.file = file
|
||||||
self.logger = logger
|
self.logger = logger
|
||||||
self.args = args
|
self.unit = unit
|
||||||
|
|
||||||
def patch(self):
|
def patch(self):
|
||||||
unit = {"is_metric": "METERS" in self.args[0], "raw": self.args[0]}
|
unit = {"is_metric": "METERS" in self.unit, "raw": self.unit}
|
||||||
self.file_patched = ifcopenshell.api.run("project.create_file", version=self.file.schema)
|
self.file_patched = ifcopenshell.api.run("project.create_file", version=self.file.schema)
|
||||||
if self.file.schema == "IFC2X3":
|
if self.file.schema == "IFC2X3":
|
||||||
user = self.file_patched.add(self.file.by_type("IfcProject")[0].OwnerHistory.OwningUser)
|
user = self.file_patched.add(self.file.by_type("IfcProject")[0].OwnerHistory.OwningUser)
|
||||||
|
|||||||
@@ -22,19 +22,51 @@ import ifcopenshell.util.element
|
|||||||
|
|
||||||
|
|
||||||
class Patcher:
|
class Patcher:
|
||||||
def __init__(self, src, file, logger, args=None):
|
def __init__(self, src, file, logger, property_name=None, quantity_name=None):
|
||||||
|
"""Converts a property to a standardised quantity
|
||||||
|
|
||||||
|
IFC can store arbitrary key value metadata associated with a elements
|
||||||
|
known as properties and quantities. The difference between the two is
|
||||||
|
that quantities specifically measure physical dimensions, and can be
|
||||||
|
used parametrically. A collection of standardised quantities have also
|
||||||
|
been published by buildingSMART. Using standardised quantities can help
|
||||||
|
automate BIM workflows, instead of arbitrary properties.
|
||||||
|
|
||||||
|
Sometimes, proprietary BIM software incorrectly stores quantities and
|
||||||
|
properties. This patch lets you convert these incorrect properties to
|
||||||
|
standardised quantities instead.
|
||||||
|
|
||||||
|
The existing property will not be removed.
|
||||||
|
|
||||||
|
:param property_name: The name of the property to convert into a
|
||||||
|
quantity. The name of the property set is not considered.
|
||||||
|
:type property_name: str
|
||||||
|
:param quantity_name: The name of the quantity that this property should
|
||||||
|
be stored in. This should be a standard name that is one of the
|
||||||
|
quantity names of a buildingSMART quantity template. For example, it
|
||||||
|
may be "NetSideArea" for walls, which exists in
|
||||||
|
Qto_WallBaseQuantities. The quantity set name will be based on the
|
||||||
|
standard buildingSMART quantity template.
|
||||||
|
:type quantity_name: str
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
# Any property named "Area" is converted to a quantity named
|
||||||
|
# "NetSideArea", if that standardised quantity exists.
|
||||||
|
ifcpatch.execute({"input": model, "recipe": "ExtractElements", "arguments": ["Area", "NetSideArea"]})
|
||||||
|
"""
|
||||||
self.src = src
|
self.src = src
|
||||||
self.file = file
|
self.file = file
|
||||||
self.logger = logger
|
self.logger = logger
|
||||||
self.args = args
|
self.source_property_name = property_name
|
||||||
|
self.destination_quantity_name = quantity_name
|
||||||
|
|
||||||
def patch(self):
|
def patch(self):
|
||||||
self.qto_template_cache = {}
|
self.qto_template_cache = {}
|
||||||
self.psetqto = ifcopenshell.util.pset.get_template("IFC4")
|
self.psetqto = ifcopenshell.util.pset.get_template("IFC4")
|
||||||
|
|
||||||
self.source_property_name = self.args[0]
|
|
||||||
self.destination_quantity_name = self.args[1]
|
|
||||||
|
|
||||||
for product in self.file.by_type("IfcTypeProduct"):
|
for product in self.file.by_type("IfcTypeProduct"):
|
||||||
self.process_product(product, product.HasPropertySets or [])
|
self.process_product(product, product.HasPropertySets or [])
|
||||||
|
|
||||||
|
|||||||
@@ -21,11 +21,27 @@ import ifcopenshell.util.element
|
|||||||
|
|
||||||
|
|
||||||
class Patcher:
|
class Patcher:
|
||||||
def __init__(self, src, file, logger, args=None):
|
def __init__(self, src, file, logger):
|
||||||
|
"""Downgrade indexed polycurves to simple polylines
|
||||||
|
|
||||||
|
Low quality IFC viewers like Navisworks do not support various IFC4
|
||||||
|
geometry, such as indexed polycurves. These can result in missing
|
||||||
|
geometry or geometric glitches (such as arcs being displayed as full
|
||||||
|
circles). This is pretty common when viewing IFCs from ArchiCAD that
|
||||||
|
include site boundaries (incorrectly drawn using the ArchiCAD grid tool,
|
||||||
|
as ArchiCAD has no site boundary tool).
|
||||||
|
|
||||||
|
This will downgrade specifically the indexed polycurve geometry types in
|
||||||
|
an IFC4 model (IFC2X3 does not have this geometry type) to help
|
||||||
|
compatibility in viewers like Navisworks.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
ifcpatch.execute({"input": model, "recipe": "DowngradeIndexedPolyCurve", "arguments": []})
|
||||||
|
"""
|
||||||
self.src = src
|
self.src = src
|
||||||
self.file = file
|
self.file = file
|
||||||
self.logger = logger
|
self.logger = logger
|
||||||
self.args = args
|
|
||||||
|
|
||||||
def patch(self):
|
def patch(self):
|
||||||
if self.file.schema == "IFC2X3":
|
if self.file.schema == "IFC2X3":
|
||||||
|
|||||||
@@ -23,11 +23,27 @@ import ifcopenshell.util.selector
|
|||||||
|
|
||||||
class Patcher:
|
class Patcher:
|
||||||
def __init__(self, src, file, logger, query: str = ".IfcWall"):
|
def __init__(self, src, file, logger, query: str = ".IfcWall"):
|
||||||
"""Extract Elements
|
"""Extract certain elements into a new model
|
||||||
|
|
||||||
Extract a subset of elements from an existing IFC data set and save it to a new IFC file.
|
Extract a subset of elements from an existing IFC data set and save it
|
||||||
|
to a new IFC file. For example, you might want to extract only the walls
|
||||||
|
in a model and save it as a new model.
|
||||||
|
|
||||||
:param query: A query to select the subset of IFC elements.
|
:param query: A query to select the subset of IFC elements.
|
||||||
|
:type query: str
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
# Extract all walls
|
||||||
|
ifcpatch.execute({"input": model, "recipe": "ExtractElements", "arguments": [".IfcWall"]})
|
||||||
|
|
||||||
|
# Extract all slabs
|
||||||
|
ifcpatch.execute({"input": model, "recipe": "ExtractElements", "arguments": [".IfcSlab"]})
|
||||||
|
|
||||||
|
# Extract all walls and slabs
|
||||||
|
ifcpatch.execute({"input": model, "recipe": "ExtractElements", "arguments": [".IfcWall|.IfcSlab"]})
|
||||||
"""
|
"""
|
||||||
self.src = src
|
self.src = src
|
||||||
self.file = file
|
self.file = file
|
||||||
|
|||||||
@@ -1,28 +1,36 @@
|
|||||||
class Patcher:
|
class Patcher:
|
||||||
def __init__(self, src, file, logger, args=None):
|
def __init__(self, src, file, logger):
|
||||||
|
"""Fix missing or spot-coordinate bugged TINs loading in Revit
|
||||||
|
|
||||||
|
TINs exported from 12D may contain dense or highly obtuse triangles.
|
||||||
|
Although these will load in Revit, you will not be able to use Revit's
|
||||||
|
Spot Coordinate or Spot Elevation tool.
|
||||||
|
|
||||||
|
See bug: https://github.com/Autodesk/revit-ifc/issues/511
|
||||||
|
|
||||||
|
The solution will merge vertices closer than 10mm to prevent dense
|
||||||
|
portions of the TIN at a minor sacrifice of surveying accuracy. It will
|
||||||
|
also triangulate all meshes to prevent non-coplanar surfaces, and delete
|
||||||
|
any obtuse triangles where one of their XY angles is less than 0.3
|
||||||
|
degrees. Therefore the result will contain some minor "holes" in the
|
||||||
|
TIN, but these holes will only be in dense triangles that Revit can't
|
||||||
|
handle anyway and won't affect most coordination tasks.
|
||||||
|
|
||||||
|
This patch is designed to only work on 12D IFC exports. It also requires
|
||||||
|
you to run it using Blender, as the geometric modification uses the
|
||||||
|
Blender geometry engine.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
ifcpatch.execute({"input": model, "recipe": "Fix12DToRevitTINs", "arguments": []})
|
||||||
|
"""
|
||||||
self.src = src
|
self.src = src
|
||||||
self.file = file
|
self.file = file
|
||||||
self.logger = logger
|
self.logger = logger
|
||||||
self.args = args
|
|
||||||
|
|
||||||
def patch(self):
|
def patch(self):
|
||||||
# TINs exported from 12D may contain dense or highly obtuse triangles.
|
|
||||||
# Although these will load in Revit, you will not be able to use Revit's
|
|
||||||
# Spot Coordinate or Spot Elevation tool.
|
|
||||||
#
|
|
||||||
# See bug: https://github.com/Autodesk/revit-ifc/issues/511
|
|
||||||
#
|
|
||||||
# The solution will merge vertices closer than 10mm to prevent dense
|
|
||||||
# portions of the TIN at a minor sacrifice of surveying accuracy. It
|
|
||||||
# will also triangulate all meshes to prevent non-coplanar surfaces, and
|
|
||||||
# delete any obtuse triangles where one of their XY angles is less than
|
|
||||||
# 0.3 degrees. Therefore the result will contain some minor "holes" in
|
|
||||||
# the TIN, but these holes will only be in dense triangles that Revit
|
|
||||||
# can't handle anyway and won't affect most coordination tasks.
|
|
||||||
#
|
|
||||||
# This patch is designed to only work on 12D IFC exports. It also
|
|
||||||
# requires you to run it using Blender, as the geometric modification
|
|
||||||
# uses the Blender geometry engine.
|
|
||||||
import bpy
|
import bpy
|
||||||
import bmesh
|
import bmesh
|
||||||
import blenderbim.tool as tool
|
import blenderbim.tool as tool
|
||||||
|
|||||||
@@ -6,32 +6,40 @@ import ifcopenshell.util.element
|
|||||||
|
|
||||||
|
|
||||||
class Patcher:
|
class Patcher:
|
||||||
def __init__(self, src, file, logger, args=None):
|
def __init__(self, src, file, logger):
|
||||||
|
"""Fix missing door swings in Revit when viewing ArchiCAD IFCs
|
||||||
|
|
||||||
|
ArchiCAD has the ability to store 2D data with objects like doors for
|
||||||
|
door swings. ArchiCAD's implementation is not 100% correct (using
|
||||||
|
footprint instead of annotation contexts), but otherwise not too
|
||||||
|
shabby.
|
||||||
|
|
||||||
|
Revit, however, is incapable of understanding this 2D representation.
|
||||||
|
Revit users linking in IFCs produced by ArchiCAD may experience the
|
||||||
|
following symptoms:
|
||||||
|
|
||||||
|
A. Invisible doors, and difficulty selecting doors
|
||||||
|
B. Invisible door swings, or only visible at particular view ranges
|
||||||
|
C. Weird arc shapes around doors
|
||||||
|
D. Extra lines around doors and walls
|
||||||
|
E. Cannot easily change visibility graphics of 2D vs 3D elements
|
||||||
|
F. Cannot view 2D data in a 3D view
|
||||||
|
|
||||||
|
This is caused by the perfect storm of Revit IFC bugs, which we will
|
||||||
|
work through methodically. For programmers interested in the details of
|
||||||
|
how we fix this, read the comments of the patch function.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
ifcpatch.execute({"input": model, "recipe": "FixArchiCADToRevitDoorSwings", "arguments": []})
|
||||||
|
"""
|
||||||
self.src = src
|
self.src = src
|
||||||
self.file = file
|
self.file = file
|
||||||
self.logger = logger
|
self.logger = logger
|
||||||
self.args = args
|
|
||||||
|
|
||||||
def patch(self):
|
def patch(self):
|
||||||
# ArchiCAD has the ability to store 2D data with objects like doors for
|
|
||||||
# door swings. ArchiCAD's implementation is not 100% correct (using
|
|
||||||
# footprint instead of annotation contexts), but otherwise not too
|
|
||||||
# shabby.
|
|
||||||
#
|
|
||||||
# Revit, however, is incapable of understanding this 2D representation.
|
|
||||||
# Revit users linking in IFCs produced by ArchiCAD may experience the
|
|
||||||
# following symptoms:
|
|
||||||
#
|
|
||||||
# A. Invisible doors, and difficulty selecting doors
|
|
||||||
# B. Invisible door swings, or only visible at particular view ranges
|
|
||||||
# C. Weird arc shapes around doors
|
|
||||||
# D. Extra lines around doors and walls
|
|
||||||
# E. Cannot easily change visibility graphics of 2D vs 3D elements
|
|
||||||
# F. Cannot view 2D data in a 3D view
|
|
||||||
#
|
|
||||||
# This is caused by the perfect storm of Revit IFC bugs, which we will
|
|
||||||
# work through methodically.
|
|
||||||
|
|
||||||
# Revit has the ability to switch between 3D representations and 2D
|
# Revit has the ability to switch between 3D representations and 2D
|
||||||
# representations (e.g. in plan view). It does this by detecting IFC
|
# representations (e.g. in plan view). It does this by detecting IFC
|
||||||
# representations that belong to either the Model Body representation
|
# representations that belong to either the Model Body representation
|
||||||
|
|||||||
@@ -1,35 +1,43 @@
|
|||||||
class Patcher:
|
class Patcher:
|
||||||
def __init__(self, src, file, logger, args=None):
|
def __init__(self, src, file, logger):
|
||||||
|
"""Allow ArchiCAD IFC spaces to open as Revit rooms
|
||||||
|
|
||||||
|
The underlying problem is that Revit does not bring in IFC spaces as
|
||||||
|
spaces / rooms in Revit when you link an IFC in Revit. This has been
|
||||||
|
broken for at least 3 years and counting. This is a problem typically
|
||||||
|
for ArchiCAD architects who want to send rooms to MEP folks using
|
||||||
|
Revit.
|
||||||
|
|
||||||
|
See bug: https://github.com/Autodesk/revit-ifc/issues/15
|
||||||
|
|
||||||
|
The solution is to open an IFC in Revit instead of linking it, which
|
||||||
|
will convert IFC spaces into Revit rooms. However, there are very
|
||||||
|
specific scenarios where Revit will convert these rooms, which have
|
||||||
|
been painstakingly reverse engineered through trial and error.
|
||||||
|
Firstly, the rooms should have a lower bound with a Z value matching
|
||||||
|
the Z value of the storey it is on. Secondly, although faceted breps
|
||||||
|
do work in some scenarios (I assume Revit has an internal topological
|
||||||
|
analysis tool), conversion to an extruded area solid yield much more
|
||||||
|
robust results. Finally, changing the Precision value to an obscene
|
||||||
|
number very strangely seems to cause a lot more rooms to be converted
|
||||||
|
successfully.
|
||||||
|
|
||||||
|
This patch is designed to only work on ArchiCAD IFC exports where the
|
||||||
|
only contents of the IFC is IFC space and `nothing else`. It also
|
||||||
|
requires you to run it using Blender, as the geometric modification
|
||||||
|
uses the Blender geometry engine.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
ifcpatch.execute({"input": model, "recipe": "FixArchiCADToRevitSpaces", "arguments": []})
|
||||||
|
"""
|
||||||
self.src = src
|
self.src = src
|
||||||
self.file = file
|
self.file = file
|
||||||
self.logger = logger
|
self.logger = logger
|
||||||
self.args = args
|
|
||||||
|
|
||||||
def patch(self):
|
def patch(self):
|
||||||
# The underlying problem is that Revit does not bring in IFC spaces as
|
|
||||||
# spaces / rooms in Revit when you link an IFC in Revit. This has been
|
|
||||||
# broken for at least 3 years and counting. This is a problem typically
|
|
||||||
# for ArchiCAD architects who want to send rooms to MEP folks using
|
|
||||||
# Revit.
|
|
||||||
#
|
|
||||||
# See bug: https://github.com/Autodesk/revit-ifc/issues/15
|
|
||||||
#
|
|
||||||
# The solution is to open an IFC in Revit instead of linking it, which
|
|
||||||
# will convert IFC spaces into Revit rooms. However, there are very
|
|
||||||
# specific scenarios where Revit will convert these rooms, which have
|
|
||||||
# been painstakingly reverse engineered through trial and error.
|
|
||||||
# Firstly, the rooms should have a lower bound with a Z value matching
|
|
||||||
# the Z value of the storey it is on. Secondly, although faceted breps
|
|
||||||
# do work in some scenarios (I assume Revit has an internal topological
|
|
||||||
# analysis tool), conversion to an extruded area solid yield much more
|
|
||||||
# robust results. Finally, changing the Precision value to an obscene
|
|
||||||
# number very strangely seems to cause a lot more rooms to be converted
|
|
||||||
# successfully.
|
|
||||||
#
|
|
||||||
# This patch is designed to only work on ArchiCAD IFC exports where the
|
|
||||||
# only contents of the IFC is IFC space and _nothing else_. It also
|
|
||||||
# requires you to run it using Blender, as the geometric modification
|
|
||||||
# uses the Blender geometry engine.
|
|
||||||
import bpy
|
import bpy
|
||||||
from blenderbim.bim.ifc import IfcStore
|
from blenderbim.bim.ifc import IfcStore
|
||||||
from mathutils import Vector, Matrix
|
from mathutils import Vector, Matrix
|
||||||
|
|||||||
@@ -21,17 +21,50 @@ import ifcopenshell.util.element
|
|||||||
|
|
||||||
|
|
||||||
class Patcher:
|
class Patcher:
|
||||||
def __init__(self, src, file, logger, args=None):
|
def __init__(self, src, file, logger, attribute="Tag"):
|
||||||
|
"""Merge duplicate element types via the Tag or another attribute
|
||||||
|
|
||||||
|
Revit is notorious for creating many duplicate element types. Element
|
||||||
|
types may be duplicated by being mirrored, such as doors, columns, etc,
|
||||||
|
or being certain MEP equipment. This means that even though you think
|
||||||
|
you might have only 3 door families and 3 door types in your door
|
||||||
|
schedule, your IFC might actually incorrectly store 6 or more door types.
|
||||||
|
|
||||||
|
Revit stores the Revit Element ID in the "Tag" attribute of all IFC
|
||||||
|
elements, so we can deduce that multiple IFC elements with the same Tag
|
||||||
|
attribute have been duplicated in IFC. This patch will merge them into a
|
||||||
|
single element type.
|
||||||
|
|
||||||
|
You may optionally specify your own attribute if you want to merge using
|
||||||
|
different criteria, such as "Name". For example, may Revit users
|
||||||
|
incorrectly have multiple types with the same name as workarounds to
|
||||||
|
overcome various Revit limitations. This is incorrect and this patch
|
||||||
|
will merge the types into a single type.
|
||||||
|
|
||||||
|
Occurrences of the type will be remapped to the merged type.
|
||||||
|
|
||||||
|
:param attribute: The name of the attribute to merge element types based
|
||||||
|
on. Typically this will be "Tag" as it stores the unique ID from the
|
||||||
|
proprietary BIM software.
|
||||||
|
:type attribute: str
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
# Default behaviour of merging by Tag attribute
|
||||||
|
ifcpatch.execute({"input": model, "recipe": "MergeDuplicateTypes", "arguments": []})
|
||||||
|
|
||||||
|
# Explicitly say we want to merge based on the Name attribute
|
||||||
|
ifcpatch.execute({"input": model, "recipe": "MergeDuplicateTypes", "arguments": ["Name"]})
|
||||||
|
"""
|
||||||
self.src = src
|
self.src = src
|
||||||
self.file = file
|
self.file = file
|
||||||
self.logger = logger
|
self.logger = logger
|
||||||
self.args = args
|
self.attribute = attribute
|
||||||
|
|
||||||
def patch(self):
|
def patch(self):
|
||||||
if self.args:
|
key = self.attribute
|
||||||
key = self.args[0]
|
|
||||||
else:
|
|
||||||
key = "Tag"
|
|
||||||
keys = {}
|
keys = {}
|
||||||
for element_type in self.file.by_type("IfcTypeObject"):
|
for element_type in self.file.by_type("IfcTypeObject"):
|
||||||
original_type = keys.get(getattr(element_type, key), None)
|
original_type = keys.get(getattr(element_type, key), None)
|
||||||
|
|||||||
@@ -21,14 +21,31 @@ import ifcopenshell.util.element
|
|||||||
|
|
||||||
|
|
||||||
class Patcher:
|
class Patcher:
|
||||||
def __init__(self, src, file, logger, args=None):
|
def __init__(self, src, file, logger, filepath=None):
|
||||||
|
"""Merge two IFC models into one
|
||||||
|
|
||||||
|
Note that other than combining the two IfcProject elements into one, no
|
||||||
|
further processing will be done. This means that you may end up with
|
||||||
|
duplicate spatial hierarchies (i.e. 2 sites, 2 buildings, etc).
|
||||||
|
|
||||||
|
:param filepath: The filepath of the second IFC model to merge into the
|
||||||
|
first. The first model is already specified as the input to
|
||||||
|
IfcPatch.
|
||||||
|
:type filepath: str
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
ifcpatch.execute({"input": model, "recipe": "MergeProject", "arguments": ["/path/to/model2.ifc"]})
|
||||||
|
"""
|
||||||
self.src = src
|
self.src = src
|
||||||
self.file = file
|
self.file = file
|
||||||
self.logger = logger
|
self.logger = logger
|
||||||
self.args = args
|
self.filepath = filepath
|
||||||
|
|
||||||
def patch(self):
|
def patch(self):
|
||||||
source = ifcopenshell.open(self.args[0])
|
source = ifcopenshell.open(self.filepath)
|
||||||
original_project = self.file.by_type("IfcProject")[0]
|
original_project = self.file.by_type("IfcProject")[0]
|
||||||
merged_project = self.file.add(source.by_type("IfcProject")[0])
|
merged_project = self.file.add(source.by_type("IfcProject")[0])
|
||||||
for element in source:
|
for element in source:
|
||||||
|
|||||||
@@ -21,14 +21,29 @@ import ifcopenshell.util.schema
|
|||||||
|
|
||||||
|
|
||||||
class Patcher:
|
class Patcher:
|
||||||
def __init__(self, src, file, logger, args=None):
|
def __init__(self, src, file, logger, schema="IFC4"):
|
||||||
|
"""Migrate from one IFC version to another
|
||||||
|
|
||||||
|
Note that this is experimental and will try to preserve as much data as
|
||||||
|
possible. Upgrading to IFC4 is more stable than downgrading to IFC2X3.
|
||||||
|
|
||||||
|
:param schema: The schema identifier of the IFC version to migrate to.
|
||||||
|
:type schema: str
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
# Upgrade an IFC2X3 model to IFC4
|
||||||
|
ifcpatch.execute({"input": model, "recipe": "Migrate", "arguments": ["IFC4"]})
|
||||||
|
"""
|
||||||
self.src = src
|
self.src = src
|
||||||
self.file = file
|
self.file = file
|
||||||
self.logger = logger
|
self.logger = logger
|
||||||
self.args = args
|
self.schema = schema
|
||||||
|
|
||||||
def patch(self):
|
def patch(self):
|
||||||
self.file_patched = ifcopenshell.file(schema=self.args[0])
|
self.file_patched = ifcopenshell.file(schema=self.schema)
|
||||||
migrator = ifcopenshell.util.schema.Migrator()
|
migrator = ifcopenshell.util.schema.Migrator()
|
||||||
for element in self.file:
|
for element in self.file:
|
||||||
migrator.migrate(element, self.file_patched)
|
migrator.migrate(element, self.file_patched)
|
||||||
|
|||||||
@@ -23,11 +23,70 @@ import ifcopenshell.util.placement
|
|||||||
|
|
||||||
|
|
||||||
class Patcher:
|
class Patcher:
|
||||||
def __init__(self, src, file, logger, args=None):
|
def __init__(self, src, file, logger, x=None, y=None, z=None, ax=None, ay=None, az=None):
|
||||||
|
"""Offset and rotate all object placements in a model
|
||||||
|
|
||||||
|
Every physical object in an IFC model has an object placement, a
|
||||||
|
matrix dictating where it is in XYZ space and its rotation.
|
||||||
|
|
||||||
|
Sometimes, models will have their models offset incorrectly into map
|
||||||
|
coordinates (i.e. very large coordinates) when they should be using
|
||||||
|
local coordinates, or vice versa, or simply be using wrong coordinates.
|
||||||
|
|
||||||
|
In some cases, models will even be rotated, especially with mixups where
|
||||||
|
Y is up instead of Z, coming from low quality BIM software.
|
||||||
|
|
||||||
|
This patch lets you translate, and optionally rotate (either rotate 2D
|
||||||
|
in plan view along the Z axis, or rotate in 3D across any axis) the
|
||||||
|
entire IFC model.
|
||||||
|
|
||||||
|
:param x: The X coordinate to offset by in project length units.
|
||||||
|
:type x: float
|
||||||
|
:param y: The Y coordinate to offset by in project length units.
|
||||||
|
:type y: float
|
||||||
|
:param z: The Z coordinate to offset by in project length units.
|
||||||
|
:type z: float
|
||||||
|
:param ax: An optional angle to rotate by. If only this angle is
|
||||||
|
specified, it is treated as the angle to rotate in plan view (i.e.
|
||||||
|
around the Z axis). If all angle parameters are specified, then it
|
||||||
|
is treated as the angle to rotate around the X axis. Angles are in
|
||||||
|
decimal degrees.
|
||||||
|
:type ax: float,optional
|
||||||
|
:param ay: An optional angle to rotate by for 3D rotations along the Y
|
||||||
|
axis. Angles are in decimal degrees.
|
||||||
|
:type ay: float,optional
|
||||||
|
:param az: An optional angle to rotate by for 3D rotations along the Z
|
||||||
|
axis. Angles are in decimal degrees.
|
||||||
|
:type az: float,optional
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
# Offset a model by 100 units in both the X and Y axis.
|
||||||
|
ifcpatch.execute({"input": model, "recipe": "OffsetObjectPlacements", "arguments": [100,100,0]})
|
||||||
|
|
||||||
|
# Rotate by 90 degrees, but don't do any offset
|
||||||
|
ifcpatch.execute({"input": model, "recipe": "OffsetObjectPlacements", "arguments": [0,0,0,90]})
|
||||||
|
|
||||||
|
# Some crazy 3D rotation and offset
|
||||||
|
ifcpatch.execute({"input": model, "recipe": "OffsetObjectPlacements", "arguments": [12.5,5,2,90,90,45]})
|
||||||
|
"""
|
||||||
self.src = src
|
self.src = src
|
||||||
self.file = file
|
self.file = file
|
||||||
self.logger = logger
|
self.logger = logger
|
||||||
self.args = args
|
self.x = x
|
||||||
|
self.y = y
|
||||||
|
self.z = z
|
||||||
|
self.ax = ax
|
||||||
|
self.ay = ay
|
||||||
|
self.az = az
|
||||||
|
if self.ay is not None:
|
||||||
|
self.angle_type = "3D"
|
||||||
|
elif self.ay is not None:
|
||||||
|
self.angle_type = "2D"
|
||||||
|
else:
|
||||||
|
self.angle_type = None
|
||||||
|
|
||||||
def patch(self):
|
def patch(self):
|
||||||
absolute_placements = []
|
absolute_placements = []
|
||||||
@@ -41,17 +100,17 @@ class Patcher:
|
|||||||
absolute_placements = set(absolute_placements)
|
absolute_placements = set(absolute_placements)
|
||||||
|
|
||||||
transformation = self.identity_matrix()
|
transformation = self.identity_matrix()
|
||||||
if len(self.args) == 4:
|
if self.angle_type == "2D":
|
||||||
angle = float(self.args[3])
|
angle = float(self.ax)
|
||||||
if angle:
|
if angle:
|
||||||
transformation = self.z_rotation_matrix(math.radians(angle), transformation)
|
transformation = self.z_rotation_matrix(math.radians(angle), transformation)
|
||||||
elif len(self.args) == 6:
|
elif self.angle_type == "3D":
|
||||||
for arg in (("x", float(self.args[3])), ("y", float(self.args[4])), ("z", float(self.args[5]))):
|
for arg in (("x", float(self.ax)), ("y", float(self.ay)), ("z", float(self.az))):
|
||||||
if arg[1]:
|
if arg[1]:
|
||||||
transformation = getattr(self, f"{arg[0]}_rotation_matrix")(math.radians(arg[1]), transformation)
|
transformation = getattr(self, f"{arg[0]}_rotation_matrix")(math.radians(arg[1]), transformation)
|
||||||
transformation[0][3] += float(self.args[0])
|
transformation[0][3] += float(self.x)
|
||||||
transformation[1][3] += float(self.args[1])
|
transformation[1][3] += float(self.y)
|
||||||
transformation[2][3] += float(self.args[2])
|
transformation[2][3] += float(self.z)
|
||||||
|
|
||||||
for placement in absolute_placements:
|
for placement in absolute_placements:
|
||||||
placement.RelativePlacement = self.get_relative_placement(
|
placement.RelativePlacement = self.get_relative_placement(
|
||||||
|
|||||||
@@ -18,18 +18,32 @@
|
|||||||
|
|
||||||
|
|
||||||
class Patcher:
|
class Patcher:
|
||||||
def __init__(self, src, file, logger, args=None):
|
def __init__(self, src, file, logger, z=None):
|
||||||
|
"""Offset building storeys by a particular Z value
|
||||||
|
|
||||||
|
All objects placed relative to the storeys will also be shifted.
|
||||||
|
|
||||||
|
:param z: The Z value in project length units to offset storeys by.
|
||||||
|
:type z: float
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
# Shift all storeys up by 42 units
|
||||||
|
ifcpatch.execute({"input": model, "recipe": "OffsetStoreyElevations", "arguments": [42]})
|
||||||
|
"""
|
||||||
self.src = src
|
self.src = src
|
||||||
self.file = file
|
self.file = file
|
||||||
self.logger = logger
|
self.logger = logger
|
||||||
self.args = args
|
self.z = z
|
||||||
|
|
||||||
def patch(self):
|
def patch(self):
|
||||||
project = self.file.by_type("IfcProject")[0]
|
project = self.file.by_type("IfcProject")[0]
|
||||||
storeys = self.find_decomposed_ifc_class(project, "IfcBuildingStorey")
|
storeys = self.find_decomposed_ifc_class(project, "IfcBuildingStorey")
|
||||||
for storey in storeys:
|
for storey in storeys:
|
||||||
co = storey.ObjectPlacement.RelativePlacement.Location.Coordinates
|
co = storey.ObjectPlacement.RelativePlacement.Location.Coordinates
|
||||||
storey.ObjectPlacement.RelativePlacement.Location.Coordinates = (co[0], co[1], co[2] + float(self.args[0]))
|
storey.ObjectPlacement.RelativePlacement.Location.Coordinates = (co[0], co[1], co[2] + float(self.z))
|
||||||
co = storey.ObjectPlacement.RelativePlacement.Location.Coordinates
|
co = storey.ObjectPlacement.RelativePlacement.Location.Coordinates
|
||||||
# NOTE If the geometric data is provided (ObjectPlacement is
|
# NOTE If the geometric data is provided (ObjectPlacement is
|
||||||
# specified), the Elevation value shall either not be included, or
|
# specified), the Elevation value shall either not be included, or
|
||||||
|
|||||||
@@ -22,11 +22,34 @@ from toposort import toposort_flatten as toposort
|
|||||||
|
|
||||||
|
|
||||||
class Patcher:
|
class Patcher:
|
||||||
def __init__(self, src, file, logger, args=None):
|
def __init__(self, src, file, logger):
|
||||||
|
"""Optimise the filesize of an IFC model
|
||||||
|
|
||||||
|
It is possible to non-losslessly optimise the filesize of an IFC model.
|
||||||
|
|
||||||
|
Note that this is usually not recommended. Optimising runs a risk of
|
||||||
|
losing some indirect semantic data critical for native IFC authoring.
|
||||||
|
Most parties who recommend optimisation are not aware of these risks.
|
||||||
|
Optimising is only safe in the context of read-only IFCs.
|
||||||
|
|
||||||
|
If filesize is an issue, another approach would be to use IFCZIP
|
||||||
|
instead to compress the model. Optimising the model only typically
|
||||||
|
affects filesize and has minimal impact on load times. Large filesizes
|
||||||
|
can usually be solved through other means. Consult the BlenderBIM Add-on
|
||||||
|
documentation on dealing with large models for more details.
|
||||||
|
|
||||||
|
Warning: this optimise recipe is very, very slow. Please consider using
|
||||||
|
RecycleNonRootedElements instead.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
ifcpatch.execute({"input": model, "recipe": "Optimise", "arguments": []})
|
||||||
|
"""
|
||||||
self.src = src
|
self.src = src
|
||||||
self.file = file
|
self.file = file
|
||||||
self.logger = logger
|
self.logger = logger
|
||||||
self.args = args
|
|
||||||
self.optimized_file = ifcopenshell.file(schema=self.file.schema)
|
self.optimized_file = ifcopenshell.file(schema=self.file.schema)
|
||||||
|
|
||||||
def patch(self):
|
def patch(self):
|
||||||
|
|||||||
@@ -21,11 +21,33 @@ import ifcopenshell.util.element
|
|||||||
|
|
||||||
|
|
||||||
class Patcher:
|
class Patcher:
|
||||||
def __init__(self, src, file, logger, args=None):
|
def __init__(self, src, file, logger):
|
||||||
|
"""Optimise the filesize of an IFC model by reusing non-rooted elements
|
||||||
|
|
||||||
|
It is possible to non-losslessly optimise the filesize of an IFC model.
|
||||||
|
|
||||||
|
Note that this is usually not recommended. Optimising runs a risk of
|
||||||
|
losing some indirect semantic data critical for native IFC authoring.
|
||||||
|
Most parties who recommend optimisation are not aware of these risks.
|
||||||
|
Optimising is only safe in the context of read-only IFCs.
|
||||||
|
|
||||||
|
If filesize is an issue, another approach would be to use IFCZIP
|
||||||
|
instead to compress the model. Optimising the model only typically
|
||||||
|
affects filesize and has minimal impact on load times. Large filesizes
|
||||||
|
can usually be solved through other means. Consult the BlenderBIM Add-on
|
||||||
|
documentation on dealing with large models for more details.
|
||||||
|
|
||||||
|
This patch may be run multiple times with diminishing returns.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
ifcpatch.execute({"input": model, "recipe": "RecycleNonRootedElements", "arguments": []})
|
||||||
|
"""
|
||||||
self.src = src
|
self.src = src
|
||||||
self.file = file
|
self.file = file
|
||||||
self.logger = logger
|
self.logger = logger
|
||||||
self.args = args
|
|
||||||
|
|
||||||
def patch(self):
|
def patch(self):
|
||||||
deleted = []
|
deleted = []
|
||||||
|
|||||||
@@ -20,14 +20,39 @@ import ifcopenshell
|
|||||||
|
|
||||||
|
|
||||||
class Patcher:
|
class Patcher:
|
||||||
def __init__(self, src, file, logger, args=None):
|
def __init__(self, src, file, logger, only_duplicates=False):
|
||||||
|
"""Regenerate GlobalIds in an IFC model
|
||||||
|
|
||||||
|
All root elements in an IFC model must be identified by a unique Global
|
||||||
|
ID (also known as a GUID or UUID). Some proprietary BIM software do this
|
||||||
|
incorrect (I know right), either by generating an invalid ID, creating
|
||||||
|
duplicate IDs, generating IDs in a way that is not universally unique or
|
||||||
|
as random as you might prefer (e.g. non-compliant with UUID v4).
|
||||||
|
|
||||||
|
This will regenerate new GlobalIds for the entire model.
|
||||||
|
|
||||||
|
:param only_duplicates: If set to True, new GlobalIds will only be
|
||||||
|
generated for duplicate IDs. This is a safe thing to run to ensure
|
||||||
|
IFCs are valid. If False, all GlobalIds will be regenerated.
|
||||||
|
:type only_duplicates: bool
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
# Regenerate all GlobalIds
|
||||||
|
ifcpatch.execute({"input": model, "recipe": "RegenerateGlobalIds", "arguments": []})
|
||||||
|
|
||||||
|
# Regenerate only duplicate GlobalIds
|
||||||
|
ifcpatch.execute({"input": model, "recipe": "RegenerateGlobalIds", "arguments": [True]})
|
||||||
|
"""
|
||||||
self.src = src
|
self.src = src
|
||||||
self.file = file
|
self.file = file
|
||||||
self.logger = logger
|
self.logger = logger
|
||||||
self.args = args
|
self.only_duplicates = only_duplicates
|
||||||
|
|
||||||
def patch(self):
|
def patch(self):
|
||||||
if self.args and self.args[0] == "DUPLICATE":
|
if self.only_duplicates:
|
||||||
guids = set()
|
guids = set()
|
||||||
for element in self.file.by_type("IfcRoot"):
|
for element in self.file.by_type("IfcRoot"):
|
||||||
if element.GlobalId in guids:
|
if element.GlobalId in guids:
|
||||||
|
|||||||
@@ -18,11 +18,20 @@
|
|||||||
|
|
||||||
|
|
||||||
class Patcher:
|
class Patcher:
|
||||||
def __init__(self, src, file, logger, args=None):
|
def __init__(self, src, file, logger):
|
||||||
|
"""Removes any 3D geometry associated with a site or multiple sites
|
||||||
|
|
||||||
|
If no sites or no site geometry is present, nothing happens.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
ifcpatch.execute({"input": model, "recipe": "RemoveSiteRepresentation", "arguments": []})
|
||||||
|
"""
|
||||||
self.src = src
|
self.src = src
|
||||||
self.file = file
|
self.file = file
|
||||||
self.logger = logger
|
self.logger = logger
|
||||||
self.args = args
|
|
||||||
|
|
||||||
def patch(self):
|
def patch(self):
|
||||||
project = self.file.by_type("IfcProject")[0]
|
project = self.file.by_type("IfcProject")[0]
|
||||||
|
|||||||
@@ -18,11 +18,76 @@
|
|||||||
|
|
||||||
|
|
||||||
class Patcher:
|
class Patcher:
|
||||||
def __init__(self, src, file, logger, args=None):
|
def __init__(self, src, file, logger, a=None, b=None, c=None, d=None):
|
||||||
|
"""Reset any large coordinates to smaller coordinates based on a threshold
|
||||||
|
|
||||||
|
If you find large coordinates in your model, the large coordinates may
|
||||||
|
either by due to large coordinates in the object placement matrix of
|
||||||
|
each object, or large coordinates in the geometry of each object.
|
||||||
|
|
||||||
|
If it is the former (preferred), consult the OffsetObjectPlacements
|
||||||
|
recipe. If it is the latter, this indicates seriously incorrect
|
||||||
|
coordinates in your IFC model.
|
||||||
|
|
||||||
|
This recipe finds any large coordinates (if either the X, Y, or Z
|
||||||
|
ordinate is larger than a threshold) and offsets it back down to a small
|
||||||
|
number.
|
||||||
|
|
||||||
|
You may either manually specify the offset to apply to any large
|
||||||
|
coordinate, or an offset will be automatically determined arbitrarily
|
||||||
|
based on the first large number we encounter.
|
||||||
|
|
||||||
|
Note that if your model inconsistently mixes coordinates between large
|
||||||
|
and small (such as if your model mixes both local and map coordinates)
|
||||||
|
then the results of this function may be poor. Provide a bug report back
|
||||||
|
to your BIM application to get it fixed.
|
||||||
|
|
||||||
|
You may specify up to 4 arguments, a, b, c, and d.
|
||||||
|
|
||||||
|
If you specify no arguments, then the threshold is set to 1000000. The
|
||||||
|
offset is auto detected.
|
||||||
|
|
||||||
|
If you only specify 1 parameter (i.e. a), then this is treated as the
|
||||||
|
threshold beyond which an ordinate is considered to be large. The offset
|
||||||
|
is auto detected.
|
||||||
|
|
||||||
|
If you specify 3 parameters, (i.e. a, b, c) then your three numbers are
|
||||||
|
treated as the X, Y, Z offset to apply. Typically your numbers will be
|
||||||
|
negative to bring the numbers smaller. The threshold is set to 1000000.
|
||||||
|
|
||||||
|
If you specify 4 parameters (i.e. a, b, c, d), then the first three
|
||||||
|
numbers are treated as the X, Y, Z offset to apply (a, b, c). The fourth
|
||||||
|
(d) will be treated as the threshold.
|
||||||
|
|
||||||
|
:param a: The first parameter
|
||||||
|
:type a: float,optional
|
||||||
|
:param b: The second parameter
|
||||||
|
:type b: float,optional
|
||||||
|
:param c: The third parameter
|
||||||
|
:type c: float,optional
|
||||||
|
:param d: The fourth parameter
|
||||||
|
:type d: float,optional
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
# Reset all coordinates with an ordinate larger than 1000000 arbitrarily
|
||||||
|
ifcpatch.execute({"input": model, "recipe": "ResetAbsoluteCoordinates", "arguments": []})
|
||||||
|
|
||||||
|
# Reset all coordinates with an ordinate larger than 1000 arbitrarily
|
||||||
|
ifcpatch.execute({"input": model, "recipe": "ResetAbsoluteCoordinates", "arguments": [1000]})
|
||||||
|
|
||||||
|
# Reset all coordinates with an ordinate larger than 1000000 by -50000,-20000,0
|
||||||
|
ifcpatch.execute({"input": model, "recipe": "ResetAbsoluteCoordinates", "arguments": [-50000,-20000,0]})
|
||||||
|
|
||||||
|
# Reset all coordinates with an ordinate larger than 1000 by -500,-200,0
|
||||||
|
ifcpatch.execute({"input": model, "recipe": "ResetAbsoluteCoordinates", "arguments": [-500,-200,0,1000]})
|
||||||
|
"""
|
||||||
self.src = src
|
self.src = src
|
||||||
self.file = file
|
self.file = file
|
||||||
self.logger = logger
|
self.logger = logger
|
||||||
self.args = args
|
self.args = [x for x in [a, b, c, d] if x is not None]
|
||||||
|
|
||||||
def patch(self):
|
def patch(self):
|
||||||
placement_coord_ids = set()
|
placement_coord_ids = set()
|
||||||
|
|||||||
@@ -18,15 +18,31 @@
|
|||||||
|
|
||||||
|
|
||||||
class Patcher:
|
class Patcher:
|
||||||
def __init__(self, src, file, logger, args=None):
|
def __init__(self, src, file, logger, ifc_class="IfcSite"):
|
||||||
|
"""Resets the location of a spatial element to 0,0,0
|
||||||
|
|
||||||
|
Another more specialised patch to fix incorrect coordinate usage is to
|
||||||
|
reset the location of spatial elements (sites, buildings, storeys) back
|
||||||
|
to 0,0,0.
|
||||||
|
|
||||||
|
:param ifc_class: The class of spatial element to reset coordinates for.
|
||||||
|
:type ifc_class: str
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
# All IfcSites will shift back to 0,0,0.
|
||||||
|
ifcpatch.execute({"input": model, "recipe": "ResetSpatialElementLocations", "arguments": ["IfcSite"]})
|
||||||
|
"""
|
||||||
self.src = src
|
self.src = src
|
||||||
self.file = file
|
self.file = file
|
||||||
self.logger = logger
|
self.logger = logger
|
||||||
self.args = args
|
self.ifc_class = ifc_class
|
||||||
|
|
||||||
def patch(self):
|
def patch(self):
|
||||||
project = self.file.by_type("IfcProject")[0]
|
project = self.file.by_type("IfcProject")[0]
|
||||||
spatial_elements = self.find_decomposed_ifc_class(project, self.args[0])
|
spatial_elements = self.find_decomposed_ifc_class(project, self.ifc_class)
|
||||||
for spatial_element in spatial_elements:
|
for spatial_element in spatial_elements:
|
||||||
self.patch_placement_to_origin(spatial_element)
|
self.patch_placement_to_origin(spatial_element)
|
||||||
|
|
||||||
|
|||||||
@@ -18,17 +18,37 @@
|
|||||||
|
|
||||||
|
|
||||||
class Patcher:
|
class Patcher:
|
||||||
def __init__(self, src, file, logger, args=None):
|
def __init__(self, src, file, logger, elevation=0):
|
||||||
|
"""Sets the reference elevation of all IfcSites
|
||||||
|
|
||||||
|
To completely reference model coordinates, a reference elevation should
|
||||||
|
be specified on the IfcSite. This is often omitted or not possible by
|
||||||
|
proprietary BIM applications. This patch lets you set the reference
|
||||||
|
elevation attribute explicitly.
|
||||||
|
|
||||||
|
Note that this does not physically shift the Z coordinates of anything.
|
||||||
|
The reference elevation is simply a numerical attribute.
|
||||||
|
|
||||||
|
:param elevation: The elevation to set.
|
||||||
|
:type elevation: float
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
# All IfcSites will have their reference elevation set to 42.
|
||||||
|
ifcpatch.execute({"input": model, "recipe": "SetRefElevation", "arguments": [42]})
|
||||||
|
"""
|
||||||
self.src = src
|
self.src = src
|
||||||
self.file = file
|
self.file = file
|
||||||
self.logger = logger
|
self.logger = logger
|
||||||
self.args = args
|
self.elevation = elevation
|
||||||
|
|
||||||
def patch(self):
|
def patch(self):
|
||||||
project = self.file.by_type("IfcProject")[0]
|
project = self.file.by_type("IfcProject")[0]
|
||||||
sites = self.find_decomposed_ifc_class(project, "IfcSite")
|
sites = self.find_decomposed_ifc_class(project, "IfcSite")
|
||||||
for site in sites:
|
for site in sites:
|
||||||
site.RefElevation = float(self.args[0])
|
site.RefElevation = float(self.elevation)
|
||||||
|
|
||||||
def find_decomposed_ifc_class(self, element, ifc_class):
|
def find_decomposed_ifc_class(self, element, ifc_class):
|
||||||
results = []
|
results = []
|
||||||
|
|||||||
@@ -18,11 +18,22 @@
|
|||||||
|
|
||||||
|
|
||||||
class Patcher:
|
class Patcher:
|
||||||
def __init__(self, src, file, logger, args=None):
|
def __init__(self, src, file, logger):
|
||||||
|
"""Split an IFC model into multiple models based on building storey
|
||||||
|
|
||||||
|
The new IFC model names will be named after the storey name in the
|
||||||
|
format of {i}-{name}.ifc, where {i} is an ascending number starting from
|
||||||
|
0 and {name} is the name of the storey.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
ifcpatch.execute({"input": model, "recipe": "SplitByBuildingStorey", "arguments": []})
|
||||||
|
"""
|
||||||
self.src = src
|
self.src = src
|
||||||
self.file = file
|
self.file = file
|
||||||
self.logger = logger
|
self.logger = logger
|
||||||
self.args = args
|
|
||||||
|
|
||||||
def patch(self):
|
def patch(self):
|
||||||
import ifcopenshell
|
import ifcopenshell
|
||||||
|
|||||||
Reference in New Issue
Block a user