import clr
import sys
clr.AddReference("RevitServices")
import RevitServices
from RevitServices.Persistence import DocumentManager
clr.AddReference("RevitAPI")
from Autodesk.Revit.DB import *
def replace_ifc_cartesian_point(ifc_text, offset_text):
result_lines = []
found = False
for line in ifc_text.splitlines():
if not found and "IFCCARTESIANPOINT" in line:
parts = line.split("=")
if len(parts) == 2 and "IFCCARTESIANPOINT" in parts[1]:
result_lines.append(f"{parts[0].strip()}= IFCCARTESIANPOINT(({offset_text}));")
found = True
else:
result_lines.append(line)
else:
result_lines.append(line)
return "\n".join(result_lines)
# Example usage:
ifc_text = IN[0]
doc = DocumentManager.Instance.CurrentDBDocument
bp = BasePoint.GetProjectBasePoint(doc).SharedPosition
io = InternalOrigin.Get(doc).SharedPosition
delta_x = round((bp[0] - io[0]) * 0.3048, 3)
delta_y = round((bp[1] - io[1]) * 0.3048, 3)
if delta_x == 0 and delta_y == 0:
result = ifc_text
else:
offset_text = f"{delta_x},{delta_y},0.0"
print(offset_text)
result = replace_ifc_cartesian_point(ifc_text, offset_text)
OUT = result
As of now i think doing a massive search and replace in Notepad is much quicker (Dyn take more than 15 minutes - Notepad takes 30 secondes)
Instead of File Path 1 > FileSystem.ReadText > Python > FileSystem.WriteText, why not just go File Path 1 > Python?
The later is more similar to what your notepad method does. Currently you are opening the entire file and putting it to memory (first node), copying it to memory again in the Python node, moving it to memory again by splitting the single string into lines, copying one line at a time into memory to edit them, copying each of the lines into memory again to put them in the results list, then copying it to memory again as one string, then copying the final string to the Dynamo environment, passing the string into memory for the watch node, passing the display of all that text into memory, and finally moving the string into the written text node.
Instead open the file with Python, read the stream as lines, and edit the line if the magic string is present, and then write the edited stream to disc. Takes the RAM from far more than can be expected to a single action composed using a tool which is built with speedy string manipulation as a primary use instead of one built for low entry computational design.
Wow that update make the graph run like it’s day and night.
Took less than a second to generate :
import clr
import sys
import gc
clr.AddReference("RevitServices")
import RevitServices
from RevitServices.Persistence import DocumentManager
clr.AddReference("RevitAPI")
from Autodesk.Revit.DB import *
def replace_ifc_cartesian_point(ifc_text, offset_text):
result_lines = []
found = False
lines = ifc_text.splitlines()
for i, line in enumerate(lines):
if not found and "IFCCARTESIANPOINT" in line:
parts = line.split("=")
if len(parts) == 2 and "IFCCARTESIANPOINT" in parts[1]:
result_lines.append(f"{parts[0].strip()}= IFCCARTESIANPOINT(({offset_text}));")
found = True
# Append the remaining lines
result_lines.extend(lines[i+1:])
break
else:
result_lines.append(line)
return "\n".join(result_lines)
# Example usage:
input_filepath = IN[0] # Path to the input IFC file
output_filepath = IN[1] # Path to the output IFC file
# Read the IFC file
with open(input_filepath, 'r') as file:
ifc_text = file.read()
# Get document information from Revit
doc = DocumentManager.Instance.CurrentDBDocument
bp = BasePoint.GetProjectBasePoint(doc).SharedPosition
io = InternalOrigin.Get(doc).SharedPosition
delta_x = round((bp[0] - io[0]) * 0.3048, 3)
delta_y = round((bp[1] - io[1]) * 0.3048, 3)
if delta_x == 0 and delta_y == 0:
result = ifc_text
else:
offset_text = f"{delta_x},{delta_y},0.0"
print(offset_text)
result = replace_ifc_cartesian_point(ifc_text, offset_text)
# Write the result to the output file
with open(output_filepath, 'w') as file:
file.write(result)
# Clear cache memory by forcing garbage collection
gc.collect()
OUT = "IFC file processed and saved to " + output_filepath
You could do this by reading into memory with file.readlines() then updating in-place using a regular expression substitution and then writing out.
Since you’re using CPython3 pathlib is a handy way to do file operations.
import re
from pathlib import Path
in_file = Path("C:/temp/Snowdon Towers Sample Architectural.ifc") # or IN[0]
out_file = Path("C:/temp/Snowdon Towers Sample Architectural_update.ifc") # or IN[1]
update = "123.,456.,789." # Example
# Regular expression to find coordinates in IFC format
# \d+ is any number of digits (at least one)
# \. a decimal point
# (\d+)? optional decimals
coords = re.compile("\d+\.(\d+)?,\d+\.(\d+)?,\d+\.(\d+)?")
with in_file.open(mode="r") as f_in:
data = f_in.readlines()
for i, line in enumerate(data):
if "IFCCARTESIANPOINT" in line:
newline = coords.sub(update, line) # Substitute update for matched text
data[i] = newline # Replace in data
break
with out_file.open(mode="w") as f_out:
f_out.writelines(data)
for info, you can use ifcopenshell and this utility class
here is an example of adaptation
# IfcPatch Patcher Modified by Cyril POUPIN
#
# IfcPatch - IFC patching utiliy
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of IfcPatch.
# https://github.com/IfcOpenShell/IfcOpenShell/blob/v0.6.0/src/ifcpatch/ifcpatch/recipes/OffsetObjectPlacements.py
#
# IfcPatch is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# IfcPatch 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
# GNU Lesser General Public License for more details.
import clr
import sys
import System
#
clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import *
import Autodesk.DesignScript.Geometry as DS
clr.AddReference('Python.Included')
import Python.Included as pyInc
path_py3_lib = pyInc.Installer.EmbeddedPythonHome
sys.path.append(path_py3_lib + r'\Lib\site-packages')
import ifcopenshell
import math
class Patcher:
def __init__(self, file, outfile, args=None):
self.outfile = outfile
self.file = ifcopenshell.open(file)
global_unit_assignments = self.file.by_type("IfcUnitAssignment")
global_length_unit_definition = [u for ua in global_unit_assignments for u in ua.Units if u.is_a() in ('IfcSIUnit', 'IfcConversionBasedUnit') and u.UnitType=='LENGTHUNIT'][-1]
self.args = args
if global_length_unit_definition.Prefix == "MILLI":
for i in range(3):
self.args[i] *= 1000
def patch(self):
absolute_placements = []
for product in self.file.by_type('IfcProduct'):
if not product.ObjectPlacement:
continue
absolute_placement = self.get_absolute_placement(product.ObjectPlacement)
if absolute_placement.is_a('IfcLocalPlacement'):
absolute_placements.append(absolute_placement)
absolute_placements = set(absolute_placements)
for placement in absolute_placements:
offset_location = (
placement.RelativePlacement.Location.Coordinates[0] + float(self.args[0]),
placement.RelativePlacement.Location.Coordinates[1] + float(self.args[1]),
placement.RelativePlacement.Location.Coordinates[2] + float(self.args[2])
)
relative_placement = self.file.createIfcAxis2Placement3D(
self.file.createIfcCartesianPoint(offset_location))
if placement.RelativePlacement.Axis:
relative_placement.Axis = placement.RelativePlacement.Axis
if placement.RelativePlacement.RefDirection:
relative_placement.RefDirection = placement.RelativePlacement.RefDirection
angle = float(self.args[3])
if not angle:
placement.RelativePlacement = relative_placement
continue
rotation_matrix = self.z_rotation_matrix(math.radians(angle))
relative_placement.Location.Coordinates = self.multiply_by_matrix(offset_location, rotation_matrix)
if placement.RelativePlacement.Axis:
z_axis = placement.RelativePlacement.Axis.DirectionRatios
relative_placement.Axis = self.file.createIfcDirection(
self.multiply_by_matrix(z_axis, rotation_matrix))
if placement.RelativePlacement.RefDirection:
x_axis = placement.RelativePlacement.RefDirection.DirectionRatios
else:
x_axis = (1., 0., 0.)
relative_placement.RefDirection = self.file.createIfcDirection(
self.multiply_by_matrix(x_axis, rotation_matrix))
placement.RelativePlacement = relative_placement
self.file.write(self.outfile)
def get_absolute_placement(self, object_placement):
if object_placement.PlacementRelTo:
return self.get_absolute_placement(object_placement.PlacementRelTo)
return object_placement
def z_rotation_matrix(self, angle):
return [
[math.cos(angle), -math.sin(angle), 0.],
[math.sin(angle), math.cos(angle), 0.],
[0., 0., 1.]
]
def multiply_by_matrix(self, v, m):
return [
v[0]*m[0][0] + v[1]*m[0][1] + v[2]*m[0][2],
v[0]*m[1][0] + v[1]*m[1][1] + v[2]*m[1][2],
v[0]*m[2][0] + v[1]*m[2][1] + v[2]*m[2][2]
]
input = r"D:\Project Example 2024\IFC\CLOV_LAU_EXE_CME_B1_SOL.ifc"
output = r"D:\Project Example 2024\IFC\CLOV_LAU_EXE_CME_B1_SOLv4.ifc"
# transform coordinates in meters and degre [X, Y, Z, Angle]
arguments = [-10, -10, 0, 0]
o = Patcher(input, output, args=arguments)
o.patch()