티스토리 뷰
사용되는 프로그램
- FDS
- PyroSim
- Houdini
- UE 5.3
0. 개요
FDS 시뮬레이션 결과를 PyroSim 뷰어를 통해 확인한 동영상입니다.
UE에 적용할 FDS 주요 파라미터로는 Physical Dimensions, Grid Dimensions이 있습니다.
Cell size = 0.2 x 0.2 x 0.2 (m)로 설정하였기 때문에 Grid Cells 갯수가 50*165*25 입니다.
UE 5.3에서 볼륨 시뮬레이션 데이터를 표현가능하도록 OpenVDB를 임포트 하는 기능을 실험적으로 도입하였습니다. (https://docs.unrealengine.com/5.3/en-US/unreal-engine-5.3-release-notes/)
포스팅 시점에서는 FDS2OpenVDB는 FDS Road Map에서 잠재적 연구 주제로 선정되었지만 출시가 되지 않은 것 같습니다.. (https://github.com/firemodels/fds/wiki/FDS-Road-Map)
복잡하지만 적용 가능한 방법으로는 FDS -> VTK -> CSV -> OpenVDB 파일을 변환하고 UE에서 머테리얼 설정을 통해 시뮬레이션 결과를 UE에 적용할 수 있습니다.
1. FDS 2 VTK
import numpy as np
import os
import sys
import argparse
def getGridFromSMV(path):
with open(path) as f:
lines = [l.strip() for l in f.readlines()]
lines = [l for l in lines if l!=""]
#Get the begin
begin = [0,0,0]
for i,l in enumerate(lines):
for j,kwd in enumerate(["TRNX", "TRNY", "TRNZ"]):
if kwd in l:
begin[j] = i+2
#Write the X, Y and Z coords
currentInd = 0
X, Y, Z = [], [], []
for i,l in enumerate(lines):
if i>=begin[currentInd]:
try:
if currentInd==0:
X.append(float(l.split()[1]))
if currentInd==1:
Y.append(float(l.split()[1]))
if currentInd==2:
Z.append(float(l.split()[1]))
except:
if currentInd == 2:
break
else:
currentInd+=1
print(len(X), len(Y), len(Z))
return X,Y,Z
def getOutputFilesFromSMV(path):
with open(path) as f:
files = []
lines = [l.strip() for l in f.readlines()]
lines = [l for l in lines if l!=""]
for i in range(len(lines)):
if lines[i].split()[0]=="SMOKF3D":
files.append([lines[i+2].split()[0], lines[i+1].strip()])
if lines[i].split()[0]=="SLCF":
files.append([lines[i+2].strip(), lines[i+1].strip()])
return files
def toInt(byte):
return int(byte.encode('hex'), 16)
def readS3D(s3dfile, frames, start=0, end=None):
if end is None:
end = len(frames)
VALUES = []
with open(s3dfile, "rb") as f:
#First offset
X = 36
f.seek(X,1)
#Skipping until the start
for frame in frames[:start]:
f.seek(72-X, 1)
f.seek(int(frame[2]), 1)
#Reading from start to end
if start!=0 or end!=len(frames):
print ("Reading the frames " + str(start) + " to " + str(end))
for ind, frame in enumerate(frames[start:end]):
f.seek(72-X, 1)
bits = f.read(int(frame[2]))
vals = []
i = 0
while(i<len(bits)):
if toInt(bits[i]) == 255:
val = toInt(bits[i+1])
n = toInt(bits[i+2])
for j in range(n):
vals.append(val)
i+=3
else:
val = toInt(bits[i])
vals.append(val)
i+=1
VALUES.append(vals)
return VALUES
def writeVTKArray(arr, f):
for i in range(len(arr)/6 + 1):
for j in range(6):
if 6*i + j < len(arr):
f.write(str(arr[6*i + j]) + " ")
f.write("\n")
def exportVTK(filepath, name, x,y,z,values):
with open(filepath, "w") as f:
writeVTKArray(values, f)
def arguments():
parser = argparse.ArgumentParser(description='Converts FDS outputs to vtk.')
parser.add_argument('--input', '-i', help='simulation root path', required=False, default="C:\\FDS_6FLOOR")
parser.add_argument('--run', '-r', help='really run', action="store_true", default=True)
parser.add_argument('--output', '-o', help='output folder (default to input folder)', default="C:\\FDS_6FLOOR\\output")
parser.add_argument('--start', '-s', help='frame to start extraction (default to first)', type=int, default=0)
parser.add_argument('--end', '-e', help='frame to end extraction (default to last available)', type=int, default=0)
args = parser.parse_args()
if not os.path.isdir(args.input):
print (args.input + " not a directory")
sys.exit()
if len([f for f in os.listdir(args.input) if f[-4:]==".smv"])==0:
print ("No .smv file in " + args.input)
sys.exit()
args.input = os.path.abspath(args.input)
if args.output is not None:
if not os.path.isdir(args.output):
print (args.output + " does not exist, please create it")
sys.exit()
else:
args.output = args.input
args.output = os.path.abspath(args.output)
return args
if __name__=="__main__":
# 0 - Argument parsing
args = arguments()
case = [f[:-4] for f in os.listdir(args.input) if f[-4:]==".smv"][0]
# 1 - Get the grid info
_x,_y,_z = getGridFromSMV(os.path.join(args.input, case + ".smv"))
# 2 - Get the output data files (.s3d and .sf)
files = getOutputFilesFromSMV(os.path.join(args.input, case + ".smv"))
# 3 - Loop on the data files
if args.run:
for f in files:
#Read the .s3d files
if f[1][-4:] == ".s3d":
folder_path = os.path.join(args.output, f[0])
if not os.path.exists(folder_path):
os.makedirs(folder_path)
with open( os.path.join(args.input, f[1] + ".sz") ) as fsz:
frames = np.array([[float(x) for x in l.split()] for l in fsz.readlines()[1:]])
args.end = len(frames)
values = readS3D( os.path.join(args.input, f[1]) , frames , args.start, args.end)
print (len(values))
for i,v in enumerate(values):
vtkFile = os.path.join(folder_path, str(args.start + i) + ".txt")
print ("Writing " + vtkFile)
exportVTK(vtkFile , f[0], _x, _y, _z, v)
else:
print (args)
print (case)
print (len(_x), len(_y), len(_z))
for f in files:
print (f)
print ("To run the script, add the --run option")
- 참고한 코드 : https://github.com/ISCDdocs/FDS-to-unreal/blob/master/fds2ascii.py
- 파이썬 버전 : 2.7
코드에서 주의해야할 부분은 커맨드로 실행하지 않는 이상 input 디폴트 파일경로를 fds 파일이 존재하는 폴더경로로 설정해주어야 합니다.
코드 실행결과 HRRPUV, SOOT, TEMPERATURE 폴더가 생성됩니다.
HRRPUV -> 단위 부피당 열방출률을 나타내는 지표로써 UE에서 Emissive Color를 나타낼 때 사용됩니다.
SOOT -> 화재시 발생하는 연기를 나타낼 때 사용됩니다.
세 폴더는 공통적으로 0부터 끝 프레임까지의 포인트 데이터가 txt파일 형태로 기록됩니다.
주의해야할 점으로는 각 차원에서 1씩 더해 Grid Cells 갯수가 달라집니다.
해당 파일에서는 51 x 166 x 26으로 총 241,696개 입니다.
2. VTK 2 CSV
import os
import re
from fractions import Fraction
from multiprocessing import Pool, cpu_count
def count_files_in_directory(directory_path):
return sum([1 for item in os.listdir(directory_path) if os.path.isfile(os.path.join(directory_path, item))])
def getNums(file_path: str):
with open(file_path, 'r') as file:
file_contents = file.read()
nums = re.findall(r"\d+", file_contents)
return nums
def process_step(args):
current_step, point_count, x_gap, y_gap, z_gap, x_count, y_count, z_count = args
cur_soot_path = os.path.join("SOOT", f"{current_step}.txt")
cur_temperature_path = os.path.join("HRRPUV", f"{current_step}.txt")
cur_soot = getNums(cur_soot_path)
cur_temperature = getNums(cur_temperature_path)
# 이 프로세스에서의 cur_soot와 cur_temperature의 최대, 최소 값을 찾습니다.
max_soot = max(map(int, cur_soot))
min_soot = min(map(int, cur_soot))
max_temperature = max(map(int, cur_temperature))
min_temperature = min(map(int, cur_temperature))
with open(f"csv/dataset_{current_step:06}.csv", "w") as file:
# first line
file.write("Points:0,Points:1,Points:2,density,temperature\n")
x, y, z = Fraction(0, 1), Fraction(0, 1), Fraction(0, 1)
for point in range(0, point_count):
file.write(f"{float(x)},{float(y)},{float(z)},{cur_soot[point]},{cur_temperature[point]}\n")
x += x_gap
if (x > x_gap * (x_count - 1)):
x = 0
y += y_gap
if (y > y_gap * (y_count - 1)):
y = 0
z += z_gap
print(f"csv/dataset_{current_step:06}.csv finish")
# 최대, 최소 값을 반환합니다.
return max_soot, min_soot, max_temperature, min_temperature
if __name__ == "__main__": # multiprocessing은 이 구문 아래에서 실행해야 합니다.
max_time_step = count_files_in_directory("SOOT")
x_count = 51
y_count = 166
z_count = 26
point_count = x_count * y_count * z_count
x_gap = Fraction(1, 5) # 0.2 정교한 값을 위해 분수로
y_gap = Fraction(1, 5)
z_gap = Fraction(1, 5)
if not os.path.exists("csv"):
os.mkdir("csv")
# 병렬 처리 시작
pool = Pool(processes=cpu_count()) # 사용 가능한 모든 CPU 코어를 사용
args = [(step, point_count, x_gap, y_gap, z_gap, x_count, y_count, z_count) for step in range(max_time_step)]
# 각 프로세스에서 반환된 최대, 최소 값들을 모은다.
results = pool.map(process_step, args)
# 모든 프로세스에서 반환된 최대, 최소 값들 중에서 전체 최대, 최소 값을 찾는다.
overall_max_soot = max(result[0] for result in results)
overall_min_soot = min(result[1] for result in results)
overall_max_temperature = max(result[2] for result in results)
overall_min_temperature = min(result[3] for result in results)
print(f"Overall Max Soot: {overall_max_soot}")
print(f"Overall Min Soot: {overall_min_soot}")
print(f"Overall Max Temperature: {overall_max_temperature}")
print(f"Overall Min Temperature: {overall_min_temperature}")
pool.close()
pool.join()
- 파이썬 버전 : 3.8
해당 코드에서 주의해야 할 점으로는 각 차원의 count를 Physical Dimension 갯수 +1씩 적용하여 수동 입력해주어야 합니다.
또한 vtk2csv.py 파일경로에 vtk 파일폴더를 넣어줍니다. 해당 코드 실행결과 csv폴더안에 csv파일들이 생성됩니다.
csv파일 구성으로는 xyz 데이터, hrr, soot 스칼라 데이터입니다.
3. CSV 2 OpenVDB
OpenVDB 파일로 만들기 위해서 Houdini 프로그램이 사용됩니다. 변환 과정을 동영상을 통해 보여드리겠습니다.
4. 언리얼에 OpenVDB 적용
...
'개인공부 > FDS' 카테고리의 다른 글
기본 데이터 (0) | 2023.09.23 |
---|---|
dxf 파일 구조분석 (0) | 2023.09.18 |
FDS 연산 정지시키는 방법 (0) | 2023.09.17 |
- Total
- Today
- Yesterday
- 초등부
- 정보올림피아드
- 백준 2365
- C++게임개발
- opengl
- 브레젠험 알고리즘
- Codeforces
- 언리얼 프로젝트 재생성
- 언리얼 자동화
- tetris
- UE5.3
- C++게임
- 코드포스
- BOJ 2365
- 홍정모의 게임 만들기 연습 문제 패키지
- BOJ 27469
- 퀸 움직이기
- 언리얼 프로젝트 재생성 자동화
- ndisplay
- DP
- OpenVDB
- ICPC 후기
- unreal enigne
- Python
- 백준 27469
- Unreal Engine
- 숫자판 만들기
- 백준
- pygame
- 테트리스
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |