├── .gitignore
├── LICENSE
├── README.md
├── TODO.md
├── __init__.py
├── bl
├── __init__.py
├── handlers.py
├── menus.py
├── operators
│ ├── __init__.py
│ ├── check_geom.py
│ ├── choose_namelist_id.py
│ ├── clean_ma_slots.py
│ ├── copy_params.py
│ ├── gis.py
│ ├── load_bf_settings.py
│ ├── mesh_tools.py
│ ├── run_external.py
│ ├── scene_export.py
│ ├── scene_import.py
│ ├── show_fds_code.py
│ ├── show_fds_geometry.py
│ ├── show_ui.py
│ └── update_addon.py
├── panels.py
├── preferences.py
├── ui
│ ├── __init__.py
│ ├── bf_ui
│ │ ├── __init__.py
│ │ ├── properties.py
│ │ ├── topbar.py
│ │ └── view3d.py
│ └── simplify_ui.py
└── ui_lists.py
├── config.py
├── dev
├── doxygen
│ ├── .gitignore
│ ├── doxyfile
│ └── update-doxygen.sh
└── make_release.py
├── docs
├── CNAME
├── _config.yml
└── index.md
├── lang
├── MN_SURF.py
├── ON_DEVC.py
├── ON_GEOM
│ ├── ON_GEOM.py
│ ├── __init__.py
│ ├── bingeom.py
│ ├── geom_to_ob.py
│ └── ob_to_geom.py
├── ON_HOLE.py
├── ON_INIT.py
├── ON_MESH
│ ├── ON_MESH.py
│ ├── __init__.py
│ ├── align_meshes.py
│ ├── calc_meshes.py
│ └── split_mesh.py
├── ON_MOVE
│ ├── ON_MOVE.py
│ ├── __init__.py
│ └── t34.py
├── ON_MULT
│ ├── ON_MULT.py
│ ├── __init__.py
│ └── multiply.py
├── ON_OBST.py
├── ON_PROF.py
├── ON_SLCF.py
├── ON_VENT.py
├── ON_ZONE.py
├── ON_other.py
├── OP_PB
│ ├── OP_PB.py
│ ├── __init__.py
│ ├── ob_to_pbs.py
│ └── pbs_to_ob.py
├── OP_SURF_ID.py
├── OP_XB
│ ├── OP_XB.py
│ ├── __init__.py
│ ├── calc_pixels.py
│ ├── calc_voxels.py
│ ├── ob_to_xbs.py
│ └── xbs_to_ob.py
├── OP_XYZ
│ ├── OP_XYZ.py
│ ├── __init__.py
│ ├── ob_to_xyzs.py
│ └── xyzs_to_ob.py
├── SN_CATF.py
├── SN_DUMP
│ ├── SN_DUMP.py
│ ├── __init__.py
│ └── sc_to_ge1.py
├── SN_HEAD.py
├── SN_MISC.py
├── SN_MOVE.py
├── SN_MULT.py
├── SN_PRES.py
├── SN_RADI.py
├── SN_REAC.py
├── SN_TIME.py
├── SN_config.py
├── __init__.py
├── bf_collection.py
├── bf_material.py
├── bf_object.py
└── bf_scene
│ ├── __init__.py
│ ├── bf_scene.py
│ ├── export_helper.py
│ └── import_helper.py
├── startup.blend
├── types
├── __init__.py
├── bf_exception.py
├── bf_namelist.py
├── bf_param.py
└── fds_list.py
└── utils
├── __init__.py
├── binpacking.py
├── geometry.py
├── gis.py
├── io.py
├── text.py
├── ui.py
└── updater.py
/.gitignore:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | .trash
4 |
5 | # Blender
6 | *.blend1
7 |
8 | # Byte-compiled / optimized / DLL files
9 | __pycache__/
10 | *.py[cod]
11 | *$py.class
12 |
13 | # C extensions
14 | *.so
15 |
16 | # Jupyter Notebook
17 | .ipynb_checkpoints
18 |
19 | # pyenv
20 | .python-version
21 |
22 | # Environments
23 | .env
24 | .venv
25 | env/
26 | venv/
27 | ENV/
28 | env.bak/
29 | venv.bak/
30 |
31 | # Spyder project settings
32 | .spyderproject
33 | .spyproject
34 |
35 | # Visual Studio Code
36 | .vscode
37 |
38 | # html Doxygen
39 | doxyfile/html
40 |
41 | # verification settings
42 | verification/verification.sh
43 | *.log
44 |
45 | # FDS calculations
46 | *.s3d
47 | *.s3d.sz
48 | *_cpu.csv
49 | *_git.txt
50 | *_hrr.csv
51 | *_steps.csv
52 | *_devc.csv
53 | *.binfo
54 | *.end
55 | *.out
56 | *.sinfo
57 | *.smv
58 | *.ge
59 | *.ge2
60 | *.bf
61 | *.bf.bnd
62 | *.sf
63 | *.sf.bnd
64 | *.prt5
65 | *.prt5.bnd
66 |
67 | *.zip
68 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # BlenderFDS repository
2 |
3 | 
4 |
5 | This is the main repository of *BlenderFDS*, the open user interface for
6 | the [NIST Fire Dynamics Simulator (FDS)](https://pages.nist.gov/fds-smv/).
7 |
8 | | [Read the
wiki doc](https://github.com/firetools/blenderfds/wiki) | [Ask a question on the
discussion group](https://groups.google.com/g/blenderfds) | [Submit an
issue](https://github.com/firetools/blenderfds/issues) |
9 | | :---: | :---: | :---: |
10 |
11 | ***
12 |
13 | The development of *BlenderFDS* was funded by a grant from
14 | the Italian Ministry of Foreign Affairs and International Cooperation.
15 |
16 | 
17 |
--------------------------------------------------------------------------------
/TODO.md:
--------------------------------------------------------------------------------
1 | # TODO
2 |
3 | Op calc area of selected bc
4 | HRR Ramp
5 | Auto mesh cutter (https://fireworkbench.com/fds_prepro_mesh)
6 |
7 | check SN_config also when exporting, something could be wrong!
8 |
9 | check mat_slots preview bug corrected, and no need for use_node=False
10 |
11 | Investigate use of bf_dialog:
12 | WM_OT_bf_dialog(Operator): # FIXME move to ui? # FIXME Use WM_OT_bf_dialog?
13 |
14 | collection instance?
15 |
16 | Manage HVAC
17 | Split and coarser MESHes
18 |
19 | release notes
20 |
21 | test cell_size
22 | test geolocation
23 |
24 | user doc
25 |
26 | progress_begin(min, max)
27 |
28 | progress_update(value)
29 |
30 | progress_end()
31 |
--------------------------------------------------------------------------------
/__init__.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | # BlenderFDS, an open tool for the NIST Fire Dynamics Simulator
4 | # Copyright (C) 2013 Emanuele Gissi, http://www.blenderfds.org
5 | #
6 | # This program is free software; you can redistribute it and/or modify
7 | # it under the terms of the GNU General Public License as published by
8 | # the Free Software Foundation; either version 3 of the License, or
9 | # (at your option) any later version.
10 | #
11 | # This program is distributed in the hope that it will be useful, but
12 | # WITHOUT ANY WARRANTY; without even the implied warranty of
13 | # MERCHANTIBILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 | # General Public License for more details.
15 | #
16 | # You should have received a copy of the GNU General Public License
17 | # along with this program. If not, see .
18 |
19 | bl_info = {
20 | "name": "BlenderFDS",
21 | "author": "Emanuele Gissi",
22 | "description": "BlenderFDS, an open graphical editor for the NIST Fire Dynamics Simulator",
23 | "blender": (3, 2, 0),
24 | "version": (6, 0, 0),
25 | "location": "File > Export > FDS Case (.fds)",
26 | "warning": "",
27 | "category": "Import-Export",
28 | "doc_url": "http://www.blenderfds.org/",
29 | "tracker_url": "https://github.com/firetools/blenderfds/issues",
30 | "support": "COMMUNITY",
31 | }
32 |
33 | import logging
34 |
35 | # Reading class definitions,
36 | # bl should be imported before lang,
37 | # because it imports ui_lists
38 | from . import bl, lang
39 |
40 | logging.basicConfig(level=logging.INFO) # INFO or DEBUG
41 | log = logging.getLogger(__name__)
42 |
43 |
44 | # Automatic registering/deregistering
45 | # of Blender entities
46 | def register():
47 | log.info("Register BlenderFDS...")
48 | bl.register()
49 | lang.register()
50 |
51 |
52 | def unregister():
53 | log.info("Unregister BlenderFDS...")
54 | lang.unregister()
55 | bl.unregister()
56 |
--------------------------------------------------------------------------------
/bl/__init__.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | """!
4 | BlenderFDS, Blender related extensions.
5 | """
6 |
7 | from . import handlers, menus, operators, panels, preferences, ui_lists, ui
8 |
9 | ms_to_register = (
10 | handlers,
11 | menus,
12 | operators,
13 | panels,
14 | preferences,
15 | ui_lists,
16 | ui,
17 | )
18 |
19 |
20 | def register():
21 | for m in ms_to_register:
22 | m.register()
23 |
24 |
25 | def unregister():
26 | for m in ms_to_register:
27 | m.unregister()
28 |
--------------------------------------------------------------------------------
/bl/menus.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | """!
4 | BlenderFDS, import/export menus.
5 | """
6 |
7 | import bpy
8 |
9 |
10 | def menu_func_import_FDS(self, context):
11 | self.layout.operator("import_to_scene.fds", text="NIST FDS Case (.fds)")
12 |
13 |
14 | def menu_func_export_to_fds(self, context):
15 | self.layout.operator("export_scene.fds", text="NIST FDS Case (.fds)")
16 |
17 |
18 | def register():
19 | bpy.types.TOPBAR_MT_file_import.append(menu_func_import_FDS)
20 | bpy.types.TOPBAR_MT_file_export.append(menu_func_export_to_fds)
21 |
22 |
23 | def unregister():
24 | bpy.types.TOPBAR_MT_file_export.remove(menu_func_export_to_fds)
25 | bpy.types.TOPBAR_MT_file_import.remove(menu_func_import_FDS)
26 |
--------------------------------------------------------------------------------
/bl/operators/__init__.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | """!
4 | BlenderFDS, Blender Operators.
5 | """
6 |
7 | from . import (
8 | check_geom,
9 | choose_namelist_id,
10 | copy_params,
11 | gis,
12 | load_bf_settings,
13 | mesh_tools,
14 | scene_export,
15 | scene_import,
16 | show_fds_code,
17 | show_fds_geometry,
18 | show_ui,
19 | run_external,
20 | clean_ma_slots,
21 | update_addon,
22 | )
23 |
24 | ms_to_register = (
25 | scene_import,
26 | scene_export,
27 | check_geom,
28 | choose_namelist_id,
29 | copy_params,
30 | gis,
31 | load_bf_settings,
32 | mesh_tools,
33 | show_fds_code,
34 | show_fds_geometry,
35 | show_ui,
36 | run_external,
37 | clean_ma_slots,
38 | update_addon,
39 | )
40 |
41 |
42 | def register():
43 | for m in ms_to_register:
44 | m.register()
45 |
46 |
47 | def unregister():
48 | for m in reversed(ms_to_register):
49 | m.unregister()
50 |
--------------------------------------------------------------------------------
/bl/operators/check_geom.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | """!
4 | BlenderFDS, operators to check GEOM sanity and intersections.
5 | """
6 |
7 | import logging
8 | from bpy.types import Operator
9 | from ...types import BFException
10 | from ... import lang
11 |
12 | log = logging.getLogger(__name__)
13 |
14 |
15 | class OBJECT_OT_bf_check_intersections(Operator):
16 | """!
17 | Check self-intersections or intersections with other selected objects.
18 | """
19 |
20 | bl_label = "Check Intersections"
21 | bl_idname = "object.bf_geom_check_intersections"
22 | bl_description = (
23 | "Check self-intersections or intersections with other selected objects"
24 | )
25 |
26 | @classmethod
27 | def poll(cls, context):
28 | return context.object
29 |
30 | def execute(self, context):
31 | w = context.window_manager.windows[0]
32 | w.cursor_modal_set("WAIT")
33 | ob = context.object
34 | obs = context.selected_objects
35 | if obs:
36 | obs.remove(ob)
37 | try:
38 | lang.ON_GEOM.check_intersections(
39 | context, ob, obs, protect=ob.data.bf_geom_protect
40 | )
41 | except BFException as err:
42 | self.report({"ERROR"}, f"Check intersections: {err}")
43 | return {"CANCELLED"}
44 | else:
45 | self.report({"INFO"}, "No intersection detected")
46 | return {"FINISHED"}
47 | finally:
48 | w.cursor_modal_restore()
49 |
50 |
51 | class OBJECT_OT_bf_check_sanity(Operator):
52 | """!
53 | Check if closed orientable manifold, with no degenerate geometry.
54 | """
55 |
56 | bl_label = "Check Sanity"
57 | bl_idname = "object.bf_geom_check_sanity"
58 | bl_description = "Check if closed orientable manifold, with no degenerate geometry"
59 |
60 | @classmethod
61 | def poll(cls, context):
62 | return context.object
63 |
64 | def execute(self, context):
65 | w = context.window_manager.windows[0]
66 | w.cursor_modal_set("WAIT")
67 | ob = context.object
68 | try:
69 | lang.ON_GEOM.check_geom_sanity(
70 | context,
71 | ob,
72 | protect=ob.data.bf_geom_protect,
73 | is_open=ob.data.bf_geom_is_terrain,
74 | )
75 | except BFException as err:
76 | self.report({"ERROR"}, f"Check sanity: {err}")
77 | return {"CANCELLED"}
78 | else:
79 | self.report({"INFO"}, "Geometry sanity ok")
80 | return {"FINISHED"}
81 | finally:
82 | w.cursor_modal_restore()
83 |
84 |
85 | bl_classes = [OBJECT_OT_bf_check_intersections, OBJECT_OT_bf_check_sanity]
86 |
87 |
88 | def register():
89 | from bpy.utils import register_class
90 |
91 | for c in bl_classes:
92 | register_class(c)
93 |
94 |
95 | def unregister():
96 | from bpy.utils import unregister_class
97 |
98 | for c in reversed(bl_classes):
99 | unregister_class(c)
100 |
--------------------------------------------------------------------------------
/bl/operators/choose_namelist_id.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | """!
4 | BlenderFDS, operators to choose IDs for MATL_ID, PROP_ID in free text.
5 | """
6 |
7 | import logging, csv
8 | from bpy.types import Operator
9 | from bpy.props import EnumProperty
10 | from ...types import FDSList
11 | from ... import config
12 |
13 | log = logging.getLogger(__name__)
14 |
15 | # Helper function
16 |
17 |
18 | def get_referenced_ids(context, fds_label="SURF"):
19 | """!
20 | Get fds_label IDs referenced in Free Text.
21 | """
22 | fds_list = FDSList()
23 | sc = context.scene
24 |
25 | # Get namelists from Free Text
26 | if sc.bf_config_text:
27 | fds_list.from_fds(f90_namelists=sc.bf_config_text.as_string())
28 |
29 | # Prepare list of IDs
30 | items = list()
31 | for fds_namelist in fds_list.get_fds_namelists(fds_label=fds_label):
32 | fds_param = fds_namelist.get_fds_param(fds_label="ID")
33 | if fds_param:
34 | hid = fds_param.get_value()
35 | items.append((hid, hid, "")) # (identifier, name, description)
36 | items.sort(key=lambda k: k[0])
37 |
38 | return items
39 |
40 |
41 | # MATL_ID
42 |
43 |
44 | def _get_matl_items(self, context):
45 | return get_referenced_ids(context, fds_label="MATL")
46 |
47 |
48 | class MATERIAL_OT_bf_choose_matl_id(Operator):
49 | bl_label = "Choose MATL_ID"
50 | bl_idname = "material.bf_choose_matl_id"
51 | bl_description = "Choose MATL_ID from MATLs available in Free Text"
52 | bl_property = "bf_matl_id"
53 |
54 | bf_matl_id: EnumProperty(
55 | name="MATL_ID",
56 | description="MATL_ID parameter",
57 | items=_get_matl_items, # Updating function
58 | )
59 |
60 | @classmethod
61 | def poll(cls, context):
62 | return context.object and context.object.active_material
63 |
64 | def execute(self, context):
65 | if self.bf_matl_id:
66 | context.object.active_material.bf_matl_id = self.bf_matl_id
67 | self.report({"INFO"}, "MATL_ID set")
68 | return {"FINISHED"}
69 | else:
70 | self.report({"WARNING"}, "MATL_ID not set")
71 | return {"CANCELLED"}
72 |
73 | def invoke(self, context, event):
74 | context.window_manager.invoke_search_popup(self)
75 | return {"FINISHED"}
76 |
77 |
78 | def _get_devc_prop_items(self, context):
79 | return get_referenced_ids(context, fds_label="PROP")
80 |
81 |
82 | # PROP_ID
83 |
84 |
85 | class OBJECT_OT_bf_choose_devc_prop_id(Operator):
86 | bl_label = "Choose PROP_ID"
87 | bl_idname = "object.bf_choose_devc_prop_id"
88 | bl_description = "Choose PROP_ID from PROP namelists available in Free Text"
89 | bl_property = "bf_devc_prop_id"
90 |
91 | bf_devc_prop_id: EnumProperty(
92 | name="PROP_ID",
93 | description="PROP_ID parameter",
94 | items=_get_devc_prop_items, # Updating function
95 | )
96 |
97 | @classmethod
98 | def poll(cls, context):
99 | return context.object
100 |
101 | def execute(self, context):
102 | if self.bf_devc_prop_id:
103 | context.object.bf_devc_prop_id = self.bf_devc_prop_id
104 | self.report({"INFO"}, "PROP_ID set")
105 | return {"FINISHED"}
106 | else:
107 | self.report({"WARNING"}, "PROP_ID not set")
108 | return {"CANCELLED"}
109 |
110 | def invoke(self, context, event):
111 | context.window_manager.invoke_search_popup(self)
112 | return {"FINISHED"}
113 |
114 |
115 | # CTRL_ID
116 |
117 |
118 | def _get_devc_ctrl_items(self, context):
119 | return get_referenced_ids(context, fds_label="CTRL")
120 |
121 |
122 | class OBJECT_OT_bf_choose_devc_ctrl_id(Operator):
123 | bl_label = "Choose CTRL_ID"
124 | bl_idname = "object.bf_choose_devc_ctrl_id"
125 | bl_description = "Choose CTRL_ID from CTRL namelists available in Free Text"
126 | bl_property = "bf_devc_ctrl_id"
127 |
128 | bf_devc_ctrl_id: EnumProperty(
129 | name="CTRL_ID",
130 | description="CTRL_ID parameter",
131 | items=_get_devc_ctrl_items, # Updating function
132 | )
133 |
134 | @classmethod
135 | def poll(cls, context):
136 | return context.object
137 |
138 | def execute(self, context):
139 | if self.bf_devc_ctrl_id:
140 | context.object.bf_devc_ctrl_id = self.bf_devc_ctrl_id
141 | self.report({"INFO"}, "CTRL_ID set")
142 | return {"FINISHED"}
143 | else:
144 | self.report({"WARNING"}, "CTRL_ID not set")
145 | return {"CANCELLED"}
146 |
147 | def invoke(self, context, event):
148 | context.window_manager.invoke_search_popup(self)
149 | return {"FINISHED"}
150 |
151 |
152 | # DEVC QUANTITY from config.FDS_QUANTITIES
153 |
154 |
155 | def _get_devc_quantity_items(self, context):
156 | return (
157 | (q[0], q[0], q[0])
158 | for q in csv.reader(config.FDS_QUANTITIES.splitlines())
159 | if "D" in q[2]
160 | )
161 |
162 |
163 | class OBJECT_OT_bf_choose_devc_quantity(Operator):
164 | bl_label = "Choose QUANTITY for DEVC"
165 | bl_idname = "object.bf_choose_devc_quantity"
166 | bl_description = "Choose QUANTITY parameter for DEVC namelist"
167 | bl_property = "bf_quantity"
168 |
169 | bf_quantity: EnumProperty(
170 | name="QUANTITY",
171 | description="QUANTITY parameter for DEVC namelist",
172 | items=_get_devc_quantity_items,
173 | )
174 |
175 | @classmethod
176 | def poll(cls, context):
177 | return context.object
178 |
179 | def execute(self, context):
180 | if self.bf_quantity:
181 | context.object.bf_quantity = self.bf_quantity
182 | self.report({"INFO"}, "QUANTITY set")
183 | return {"FINISHED"}
184 | else:
185 | self.report({"WARNING"}, "QUANTITY not set")
186 | return {"CANCELLED"}
187 |
188 | def invoke(self, context, event):
189 | context.window_manager.invoke_search_popup(self)
190 | return {"FINISHED"}
191 |
192 |
193 | # Register/unregister
194 |
195 | bl_classes = [
196 | MATERIAL_OT_bf_choose_matl_id,
197 | OBJECT_OT_bf_choose_devc_prop_id,
198 | OBJECT_OT_bf_choose_devc_ctrl_id,
199 | OBJECT_OT_bf_choose_devc_quantity,
200 | ]
201 |
202 |
203 | def register():
204 | from bpy.utils import register_class
205 |
206 | for c in bl_classes:
207 | register_class(c)
208 |
209 |
210 | def unregister():
211 | from bpy.utils import unregister_class
212 |
213 | for c in reversed(bl_classes):
214 | unregister_class(c)
215 |
--------------------------------------------------------------------------------
/bl/operators/clean_ma_slots.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | """!
4 | BlenderFDS, operators for the MESH namelist.
5 | """
6 |
7 |
8 | import logging, bpy
9 | from bpy.types import Operator
10 |
11 | log = logging.getLogger(__name__)
12 |
13 |
14 | class OBJECT_OT_bf_clean_ma_slots(Operator):
15 | """!
16 | Clean Material slots of Object, keep only active Object.
17 | """
18 |
19 | bl_label = "Clean Material Slots"
20 | bl_idname = "object.bf_clean_ma_slots"
21 | bl_description = "Clean Material slots from unused or empty instances"
22 | bl_options = {"REGISTER", "UNDO"}
23 |
24 | @classmethod
25 | def poll(cls, context):
26 | ob = context.object
27 | return ob and len(ob.material_slots) > 1
28 |
29 | def execute(self, context):
30 | ob = context.object
31 | orig_active_ma_index = ob.active_material_index
32 | for i in reversed(range(len(ob.material_slots))):
33 | ms = ob.material_slots[i]
34 | ob.active_material_index = i
35 | if ms.material and i == orig_active_ma_index:
36 | continue
37 | bpy.ops.object.material_slot_remove()
38 | ob.active_material_index = 0
39 | self.report({"INFO"}, "Material slots cleaned")
40 | return {"FINISHED"}
41 |
42 |
43 | bl_classes = [
44 | OBJECT_OT_bf_clean_ma_slots,
45 | ]
46 |
47 |
48 | def register():
49 | from bpy.utils import register_class
50 |
51 | for c in bl_classes:
52 | register_class(c)
53 |
54 |
55 | def unregister():
56 | from bpy.utils import unregister_class
57 |
58 | for c in reversed(bl_classes):
59 | unregister_class(c)
60 |
--------------------------------------------------------------------------------
/bl/operators/copy_params.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | """!
4 | BlenderFDS, operators to copy FDS parameter values between entities.
5 | """
6 |
7 | import logging, bpy
8 | from bpy.types import Operator, Scene
9 | from bpy.props import StringProperty
10 |
11 | log = logging.getLogger(__name__)
12 |
13 |
14 | def _bf_props_copy(context, source_element, dest_elements):
15 | """!
16 | Copy all FDS parameters from source_element to dest_elements.
17 | """
18 | # Get bf_namelists
19 | if isinstance(source_element, Scene):
20 | bf_namelists = source_element.bf_namelists
21 | else:
22 | bf_namelists = tuple((source_element.bf_namelist,))
23 | for dest_element in dest_elements:
24 | dest_element.bf_namelist_cls = source_element.bf_namelist_cls
25 | # Copy them
26 | for bf_namelist in bf_namelists:
27 | for dest_element in dest_elements:
28 | bf_namelist.copy_to(context=context, dest_element=dest_element)
29 |
30 |
31 | class SCENE_OT_bf_copy_props_to_sc(Operator):
32 | """!
33 | Copy current FDS case parameters to another Scene.
34 | """
35 |
36 | bl_label = "Copy To"
37 | bl_idname = "scene.bf_props_to_sc"
38 | bl_description = "Copy case parameters to another Scene"
39 | bl_options = {"REGISTER", "UNDO"}
40 |
41 | bf_dest_element: StringProperty(name="Destination")
42 |
43 | def draw(self, context):
44 | self.layout.prop_search(self, "bf_dest_element", bpy.data, "scenes")
45 |
46 | def invoke(self, context, event):
47 | wm = context.window_manager
48 | return wm.invoke_props_dialog(self)
49 |
50 | def execute(self, context):
51 | # Get source and dest element
52 | source_element = context.scene
53 | dest_element = bpy.data.scenes.get(self.bf_dest_element, None)
54 | if source_element == dest_element:
55 | self.report({"WARNING"}, "Destination same as source")
56 | return {"CANCELLED"}
57 | if not dest_element:
58 | self.report({"ERROR"}, "No destination")
59 | return {"CANCELLED"}
60 | if not source_element:
61 | self.report({"ERROR"}, "No source")
62 | return {"CANCELLED"}
63 |
64 | # Copy
65 | _bf_props_copy(context, source_element, (dest_element,))
66 | self.report({"INFO"}, f"Parameters copied")
67 | return {"FINISHED"}
68 |
69 |
70 | class OBJECT_OT_bf_copy_FDS_properties_to_sel_obs(Operator):
71 | """!
72 | Copy current FDS parameters to selected Objects.
73 | """
74 |
75 | bl_label = "Copy To"
76 | bl_idname = "object.bf_props_to_sel_obs"
77 | bl_description = "Copy FDS parameters to selected Objects"
78 | bl_options = {"REGISTER", "UNDO"}
79 |
80 | @classmethod
81 | def poll(cls, context):
82 | return context.object
83 |
84 | def invoke(self, context, event):
85 | # Ask for confirmation
86 | wm = context.window_manager
87 | return wm.invoke_confirm(self, event)
88 |
89 | def execute(self, context):
90 | bpy.ops.object.mode_set(mode="OBJECT")
91 |
92 | # Get source and destination objects
93 | source_element = context.object
94 | dest_elements = set(
95 | ob
96 | for ob in context.selected_objects
97 | if ob.type == "MESH" and ob != source_element
98 | )
99 | if not dest_elements:
100 | self.report({"ERROR"}, "No destination, select Objects")
101 | return {"CANCELLED"}
102 | if not source_element:
103 | self.report({"ERROR"}, "No source")
104 | return {"CANCELLED"}
105 |
106 | # Copy
107 | _bf_props_copy(context, source_element, dest_elements)
108 | self.report(
109 | {"INFO"},
110 | f"Parameters copied to {len(dest_elements)} selected Object(s)",
111 | )
112 | return {"FINISHED"}
113 |
114 |
115 | class MATERIAL_OT_bf_copy_props_to_ma(Operator):
116 | """!
117 | Copy current FDS parameters to another Material.
118 | """
119 |
120 | bl_label = "Copy To"
121 | bl_idname = "material.bf_props_to_ma"
122 | bl_description = "Copy parameters to another Material"
123 | bl_options = {"REGISTER", "UNDO"}
124 |
125 | bf_dest_element: StringProperty(name="Destination")
126 |
127 | @classmethod
128 | def poll(cls, context):
129 | ob = context.object
130 | return ob and ob.active_material
131 |
132 | def draw(self, context):
133 | self.layout.prop_search(self, "bf_dest_element", bpy.data, "materials")
134 |
135 | def invoke(self, context, event):
136 | wm = context.window_manager
137 | return wm.invoke_props_dialog(self)
138 |
139 | def execute(self, context):
140 | # Get source and dest element
141 | source_element = context.object.active_material
142 | dest_element = bpy.data.materials.get(self.bf_dest_element, None)
143 | if source_element == dest_element:
144 | self.report({"WARNING"}, "Destination same as source")
145 | return {"CANCELLED"}
146 | if not dest_element:
147 | self.report({"ERROR"}, "No destination")
148 | return {"CANCELLED"}
149 | if not source_element:
150 | self.report({"ERROR"}, "No source")
151 | return {"CANCELLED"}
152 |
153 | # Copy
154 | _bf_props_copy(context, source_element, (dest_element,))
155 | self.report({"INFO"}, "Parameters copied")
156 | return {"FINISHED"}
157 |
158 |
159 | bl_classes = [
160 | SCENE_OT_bf_copy_props_to_sc,
161 | OBJECT_OT_bf_copy_FDS_properties_to_sel_obs,
162 | MATERIAL_OT_bf_copy_props_to_ma,
163 | ]
164 |
165 |
166 | def register():
167 | from bpy.utils import register_class
168 |
169 | for c in bl_classes:
170 | register_class(c)
171 |
172 |
173 | def unregister():
174 | from bpy.utils import unregister_class
175 |
176 | for c in reversed(bl_classes):
177 | unregister_class(c)
178 |
--------------------------------------------------------------------------------
/bl/operators/gis.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | """!
4 | BlenderFDS, operators for geographic operations.
5 | """
6 |
7 | import logging, bpy
8 | from bpy.types import Operator
9 | from bpy.props import BoolProperty, FloatProperty
10 | from ...config import LATLON_P
11 | from ... import utils
12 |
13 | log = logging.getLogger(__name__)
14 |
15 |
16 | class _bf_set_geoloc:
17 | """!
18 | Set geographic location (WGS84).
19 | """
20 |
21 | bl_label = "Set Geolocation"
22 | # bl_idname = "scene.bf_set_geoloc"
23 | bl_description = "Set geographic location (WGS84)"
24 | bl_options = {"REGISTER", "UNDO"}
25 |
26 | show: BoolProperty(name="Show Geolocation", default=False)
27 |
28 | bf_lon: FloatProperty(
29 | name="Longitude",
30 | description="Longitude (WGS84, EPSG:4326) in decimal degrees",
31 | min=-180,
32 | max=+180,
33 | precision=LATLON_P,
34 | )
35 |
36 | bf_lat: FloatProperty(
37 | name="Latitude",
38 | description="Latitude (WGS84, EPSG:4326) in decimal degrees",
39 | min=-80,
40 | max=+84,
41 | precision=LATLON_P,
42 | )
43 |
44 | def draw(self, context):
45 | """!
46 | Draw function for the operator.
47 | @param context: the Blender context.
48 | """
49 | col = self.layout.column(align=True)
50 | col.label(text="Error on horizontal geoposition < 1 m")
51 | col.prop(self, "bf_lon", text="Longitude")
52 | col.prop(self, "bf_lat", text="Latitude")
53 |
54 | def _get_loc(self, context):
55 | """!
56 | Placeholder method to get the XYZ location
57 | @param context: the Blender context.
58 | @return xyz location
59 | """
60 | return 0.0, 0.0, 0.0
61 |
62 | def _set_loc(self, context, xyz):
63 | """!
64 | Placeholder method to set the XYZ location
65 | @param context: the Blender context.
66 | @param xyz location
67 | """
68 | pass
69 |
70 | def execute(self, context):
71 | # Get loc, use only unmodified z
72 | _, _, z = self._get_loc(context)
73 | sc = context.scene
74 | utm = utils.gis.LonLat(
75 | lon=self.bf_lon,
76 | lat=self.bf_lat,
77 | ).to_UTM(force_zn=sc.bf_origin_utm_zn, force_ne=sc.bf_origin_utm_ne)
78 | # Compute loc relative to scene origin
79 | scale_length = sc.unit_settings.scale_length
80 | x = (utm.easting - sc.bf_origin_utm_easting) / scale_length
81 | y = (utm.northing - sc.bf_origin_utm_northing) / scale_length
82 | self._set_loc(context, (x, y, z))
83 | self.report({"INFO"}, "Geolocation set")
84 | return {"FINISHED"}
85 |
86 | def invoke(self, context, event):
87 | sc = context.scene
88 | # Check origin geolocation
89 | if not sc.bf_misc_export or not sc.bf_origin_export:
90 | self.report({"ERROR"}, "Undefined location, set origin geolocation in MISC")
91 | return {"FINISHED"}
92 | # Get loc, convert to set default
93 | x, y, _ = self._get_loc(context)
94 | scale_length = sc.unit_settings.scale_length
95 | utm = utils.gis.UTM(
96 | zn=sc.bf_origin_utm_zn,
97 | ne=sc.bf_origin_utm_ne,
98 | easting=sc.bf_origin_utm_easting + x * scale_length,
99 | northing=sc.bf_origin_utm_northing + y * scale_length,
100 | )
101 | lonlat = utm.to_LonLat()
102 | # Show
103 | if self.show:
104 | url = lonlat.to_url()
105 | bpy.ops.wm.url_open(url=url)
106 | self.report({"INFO"}, "Geolocation shown")
107 | return {"FINISHED"}
108 | # Set defaults
109 | self.bf_lon = lonlat.lon
110 | self.bf_lat = lonlat.lat
111 | # Call dialog
112 | wm = context.window_manager
113 | return wm.invoke_props_dialog(self)
114 |
115 |
116 | class SCENE_OT_bf_set_cursor_geoloc(Operator, _bf_set_geoloc):
117 | """!
118 | Set geographic location (WGS84).
119 | """
120 |
121 | bl_idname = "scene.bf_set_cursor_geoloc"
122 |
123 | @classmethod
124 | def poll(cls, context):
125 | return context.scene.cursor
126 |
127 | def _get_loc(self, context):
128 | return context.scene.cursor.location
129 |
130 | def _set_loc(self, context, xyz):
131 | context.scene.cursor.location = xyz
132 |
133 |
134 | class SCENE_OT_bf_set_ob_geoloc(Operator, _bf_set_geoloc):
135 | """!
136 | Set geographic location (WGS84).
137 | """
138 |
139 | bl_idname = "scene.bf_set_ob_geoloc"
140 |
141 | @classmethod
142 | def poll(cls, context):
143 | return context.object
144 |
145 | def _get_loc(self, context):
146 | return context.object.location
147 |
148 | def _set_loc(self, context, xyz):
149 | context.object.location = xyz
150 |
151 |
152 | bl_classes = [
153 | SCENE_OT_bf_set_cursor_geoloc,
154 | SCENE_OT_bf_set_ob_geoloc,
155 | ]
156 |
157 |
158 | def register():
159 | from bpy.utils import register_class
160 |
161 | for c in bl_classes:
162 | register_class(c)
163 |
164 |
165 | def unregister():
166 | from bpy.utils import unregister_class
167 |
168 | for c in reversed(bl_classes):
169 | unregister_class(c)
170 |
--------------------------------------------------------------------------------
/bl/operators/load_bf_settings.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | """!
4 | BlenderFDS, operators to load default settings.
5 | """
6 |
7 | import os, sys, bpy, logging
8 | from bpy.types import Operator
9 |
10 | log = logging.getLogger(__name__)
11 |
12 |
13 | class WM_OT_bf_load_blenderfds_settings(Operator):
14 | """!
15 | Load default BlenderFDS settings, deleting current data.
16 | """
17 |
18 | bl_label = "Load Default BlenderFDS Settings"
19 | bl_idname = "wm.bf_load_blenderfds_settings"
20 | bl_description = "Load default BlenderFDS settings, deleting current data!"
21 |
22 | def invoke(self, context, event):
23 | # Ask for confirmation
24 | wm = context.window_manager
25 | return wm.invoke_confirm(self, event)
26 |
27 | def execute(self, context):
28 | # Set default startup.blend
29 | filepath = os.path.join(
30 | os.path.dirname(sys.modules[__package__].__file__), "../../startup.blend"
31 | )
32 | bpy.ops.wm.open_mainfile(filepath=filepath, load_ui=True, use_scripts=True)
33 | bpy.ops.wm.save_homefile()
34 | # Load default commands
35 | bpy.ops.wm.bf_restore_default_commands()
36 | # Save user preferences
37 | bpy.ops.wm.save_userpref()
38 | # Open new file (unlink startup)
39 | bpy.ops.wm.read_homefile()
40 | # Report
41 | self.report({"INFO"}, "Default settings loaded")
42 | return {"FINISHED"}
43 |
44 |
45 | bl_classes = [
46 | WM_OT_bf_load_blenderfds_settings,
47 | ]
48 |
49 |
50 | def register():
51 | from bpy.utils import register_class
52 |
53 | for c in bl_classes:
54 | register_class(c)
55 |
56 |
57 | def unregister():
58 | from bpy.utils import unregister_class
59 |
60 | for c in reversed(bl_classes):
61 | unregister_class(c)
62 |
--------------------------------------------------------------------------------
/bl/operators/scene_export.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | """!
4 | BlenderFDS, operators to export an FDS case.
5 | """
6 |
7 | import bpy
8 | from bpy.types import Operator
9 | from bpy.props import StringProperty
10 | from bpy_extras.io_utils import ExportHelper
11 | from ... import utils
12 |
13 |
14 | class ExportSceneToFDS(Operator, ExportHelper):
15 | """!
16 | Export current Blender Scene to an FDS case file.
17 | """
18 |
19 | bl_idname = "export_scene.fds"
20 | bl_label = "Export to FDS"
21 | bl_description = "Export current Blender Scene to an FDS case file"
22 | bl_options = {"UNDO"}
23 |
24 | filename_ext = ".fds"
25 | filter_glob: StringProperty(default="*.fds", options={"HIDDEN"})
26 |
27 | @classmethod
28 | def poll(cls, context):
29 | return context.scene
30 |
31 | def invoke(self, context, event):
32 | # Check if saved
33 | if not bpy.data.is_saved:
34 | self.report({"ERROR"}, "Save the Blender file first!")
35 | return {"CANCELLED"}
36 |
37 | # Set best filepath as default, empty path is /home/user
38 | sc = context.scene
39 | self.filepath = utils.io.append_filename(
40 | path=sc.bf_config_directory, name=sc.name, extension=".fds"
41 | )
42 | return super().invoke(context, event)
43 |
44 | def execute(self, context):
45 | w = context.window_manager.windows[0]
46 | w.cursor_modal_set("WAIT")
47 |
48 | # If filepath was relative, keep it relative
49 | sc = context.scene
50 | if not utils.io.is_abs(sc.bf_config_directory):
51 | self.filepath = bpy.path.relpath(self.filepath)
52 | sc.bf_config_directory, sc.name = utils.io.extract_path_name(self.filepath)
53 |
54 | # Export
55 | try:
56 | sc.to_fds(context=context, full=True, save=True)
57 | except Exception as err:
58 | w.cursor_modal_restore()
59 | self.report({"ERROR"}, str(err))
60 | return {"CANCELLED"}
61 |
62 | # Close
63 | w.cursor_modal_restore()
64 | self.report({"INFO"}, "FDS case exported")
65 | return {"FINISHED"}
66 |
67 |
68 | # Register/unregister
69 |
70 | bl_classes = (ExportSceneToFDS,)
71 |
72 |
73 | def register():
74 | from bpy.utils import register_class
75 |
76 | for c in bl_classes:
77 | register_class(c)
78 |
79 |
80 | def unregister():
81 | from bpy.utils import unregister_class
82 |
83 | for c in reversed(bl_classes):
84 | unregister_class(c)
85 |
--------------------------------------------------------------------------------
/bl/operators/scene_import.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | """!
4 | BlenderFDS, operator to import an FDS case.
5 | """
6 |
7 | import bpy
8 | from bpy.types import Operator
9 | from bpy.props import StringProperty
10 | from bpy_extras.io_utils import ImportHelper
11 | from ... import utils
12 | from ...types import BFException
13 |
14 |
15 | def _import_fds_case_to_scene(context, filepath, to_new_scene=True):
16 | if to_new_scene:
17 | sc = bpy.data.scenes.new("New case")
18 | else:
19 | sc = context.scene
20 | fds_namelist_qty = sc.from_fds(
21 | context,
22 | filepath=filepath,
23 | )
24 | return fds_namelist_qty # Number of imported namelists
25 |
26 |
27 | class ImportFDSToScene(Operator, ImportHelper):
28 | """!
29 | Import an FDS case file to a new Blender Scene.
30 | """
31 |
32 | bl_idname = "import_to_scene.fds"
33 | bl_label = "Import from FDS"
34 | bl_description = "Import an FDS case file to a new Blender Scene"
35 | bl_options = {"UNDO"}
36 |
37 | filename_ext = ".fds"
38 | filter_glob: StringProperty(default="*.fds", options={"HIDDEN"})
39 |
40 | @classmethod
41 | def poll(cls, context):
42 | return context.scene
43 |
44 | def execute(self, context):
45 | w = context.window_manager.windows[0]
46 | w.cursor_modal_set("WAIT")
47 |
48 | # Import
49 | try:
50 | _import_fds_case_to_scene(context, self.filepath, to_new_scene=True)
51 | except BFException as err:
52 | w.cursor_modal_restore()
53 | self.report({"ERROR"}, str(err))
54 | return {"CANCELLED"}
55 |
56 | # Close
57 | utils.ui.view_all(context=context)
58 | w.cursor_modal_restore()
59 | self.report({"INFO"}, "FDS case imported")
60 | return {"FINISHED"}
61 |
62 |
63 | class ImportFDSToCurrentScene(Operator, ImportHelper):
64 | """!
65 | Import FDS case file to current Blender Scene.
66 | """
67 |
68 | bl_idname = "import_to_current_scene.fds"
69 | bl_label = "Import Snippet"
70 | bl_description = "Import FDS code snippet to current Blender Scene"
71 | bl_options = {"UNDO"}
72 |
73 | filename_ext = ".fds"
74 | filter_glob: StringProperty(default="*.fds", options={"HIDDEN"})
75 |
76 | @classmethod
77 | def poll(cls, context):
78 | return context.scene
79 |
80 | def execute(self, context):
81 | w = context.window_manager.windows[0]
82 | w.cursor_modal_set("WAIT")
83 |
84 | # Import
85 | try:
86 | fds_namelist_qty = _import_fds_case_to_scene(
87 | context, self.filepath, to_new_scene=False
88 | )
89 | except BFException as err:
90 | w.cursor_modal_restore()
91 | self.report({"ERROR"}, str(err))
92 | return {"CANCELLED"}
93 |
94 | # Close
95 | utils.ui.view_all(context=context)
96 | w.cursor_modal_restore()
97 | self.report(
98 | {"INFO"},
99 | fds_namelist_qty == 1
100 | and "1 namelist imported"
101 | or f"{fds_namelist_qty} namelists imported",
102 | )
103 | return {"FINISHED"}
104 |
105 |
106 | bl_classes = [
107 | ImportFDSToScene,
108 | ImportFDSToCurrentScene,
109 | ]
110 |
111 |
112 | def register():
113 | from bpy.utils import register_class
114 |
115 | for c in bl_classes:
116 | register_class(c)
117 |
118 |
119 | def unregister():
120 | from bpy.utils import unregister_class
121 |
122 | for c in reversed(bl_classes):
123 | unregister_class(c)
124 |
--------------------------------------------------------------------------------
/bl/operators/show_fds_code.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | """!
4 | BlenderFDS, operators to show generated FDS code.
5 | """
6 |
7 | import logging
8 | from bpy.types import Operator
9 | from bpy.props import StringProperty, EnumProperty
10 | from ...types import BFException
11 | from ... import utils
12 |
13 | log = logging.getLogger(__name__)
14 |
15 |
16 | class WM_OT_bf_dialog(Operator):
17 | """!
18 | BlenderFDS Dialog.
19 | """
20 |
21 | bl_label = "BlenderFDS"
22 | bl_idname = "wm.bf_dialog"
23 | bl_description = "BlenderFDS Dialog"
24 |
25 | type: EnumProperty(
26 | name="Type",
27 | items=(("INFO", "Information", "Information"), ("ERROR", "Error", "Error")),
28 | description="Dialog type",
29 | default="INFO",
30 | )
31 |
32 | msg: StringProperty(
33 | name="Message", description="Dialog message", default="No message"
34 | )
35 |
36 | description: StringProperty(name="Description", description="Dialog description")
37 |
38 | def execute(self, context):
39 | return {"FINISHED"}
40 |
41 | def invoke(self, context, event):
42 | wm = context.window_manager
43 | return wm.invoke_props_dialog(self)
44 |
45 | def draw(self, context):
46 | layout = self.layout
47 | col = layout.column()
48 | col.label(text=self.msg, icon=self.type)
49 | if self.description:
50 | col.separator()
51 | descriptions = self.description.splitlines()
52 | for description in descriptions:
53 | row = col.row()
54 | row.label(text=description)
55 |
56 |
57 | class _show_fds_code:
58 | """!
59 | Helper for showing fds code operators
60 | """
61 |
62 | def draw(self, context):
63 | if self.lines:
64 | lines = self.lines.split("\n")
65 | else:
66 | lines = ("No FDS code is exported",)
67 | if len(lines) > 20:
68 | lines = lines[:15] + [" ···"] + lines[-4:]
69 | layout = self.layout
70 | for line in lines:
71 | layout.label(text=line)
72 |
73 | def execute(self, context):
74 | self.report({"INFO"}, "FDS code shown")
75 | return {"FINISHED"}
76 |
77 | def _get_lines(self, context):
78 | """!
79 | Placeholder method to get text lines.
80 | """
81 | return str()
82 |
83 | def invoke(self, context, event):
84 | w = context.window_manager.windows[0]
85 | w.cursor_modal_set("WAIT")
86 | try:
87 | self.lines = self._get_lines(context) # get FDS code
88 | except BFException as err:
89 | self.report({"ERROR"}, str(err))
90 | return {"CANCELLED"}
91 | else:
92 | wm = context.window_manager
93 | return wm.invoke_props_dialog(self, width=600)
94 | finally:
95 | w.cursor_modal_restore()
96 |
97 |
98 | class OBJECT_OT_bf_show_fds_code(_show_fds_code, Operator):
99 | """!
100 | Show FDS code exported from current Object.
101 | """
102 |
103 | bl_label = "Show FDS Code"
104 | bl_idname = "object.bf_show_fds_code"
105 | bl_description = "Show FDS code exported from current Object"
106 |
107 | @classmethod
108 | def poll(cls, context):
109 | return context.object
110 |
111 | def _get_lines(self, context):
112 | return context.object.to_fds_list(context).to_string()
113 |
114 |
115 | class COLLECTION_OT_bf_show_fds_code(_show_fds_code, Operator):
116 | """!
117 | Show FDS code exported from current Object.
118 | """
119 |
120 | bl_label = "Show FDS Code"
121 | bl_idname = "collection.bf_show_fds_code"
122 | bl_description = "Show FDS code exported from current Collection"
123 |
124 | @classmethod
125 | def poll(cls, context):
126 | return context.collection
127 |
128 | def _get_lines(self, context):
129 | return context.collection.to_fds_list(context).to_string()
130 |
131 |
132 | class MATERIAL_OT_bf_show_fds_code(_show_fds_code, Operator):
133 | """!
134 | Show FDS code exported from current Material.
135 | """
136 |
137 | bl_label = "Show FDS Code"
138 | bl_idname = "material.bf_show_fds_code"
139 | bl_description = "Show FDS code exported from current Material"
140 |
141 | @classmethod
142 | def poll(cls, context):
143 | return context.object and context.object.active_material
144 |
145 | def _get_lines(self, context):
146 | return context.object.active_material.to_fds_list(context).to_string()
147 |
148 |
149 | class SCENE_OT_bf_show_fds_code(_show_fds_code, Operator):
150 | """!
151 | Show FDS code exported from Scene.
152 | """
153 |
154 | bl_label = "Show FDS Code"
155 | bl_idname = "scene.bf_show_fds_code"
156 | bl_description = "Show FDS code exported from Scene"
157 |
158 | @classmethod
159 | def poll(cls, context):
160 | return context.scene
161 |
162 | def _get_lines(self, context):
163 | return context.scene.to_fds_list(context).to_string()
164 |
165 |
166 | bl_classes = [
167 | WM_OT_bf_dialog,
168 | OBJECT_OT_bf_show_fds_code,
169 | COLLECTION_OT_bf_show_fds_code,
170 | MATERIAL_OT_bf_show_fds_code,
171 | SCENE_OT_bf_show_fds_code,
172 | ]
173 |
174 |
175 | def register():
176 | from bpy.utils import register_class
177 |
178 | for c in bl_classes:
179 | register_class(c)
180 |
181 |
182 | def unregister():
183 | from bpy.utils import unregister_class
184 |
185 | for c in reversed(bl_classes):
186 | unregister_class(c)
187 |
--------------------------------------------------------------------------------
/bl/operators/show_fds_geometry.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | """!
4 | BlenderFDS, operators to show generated FDS geometry.
5 | """
6 |
7 |
8 | import logging
9 | from bpy.types import Operator
10 | from bpy.props import BoolProperty
11 | from ...types import BFException
12 | from ... import utils, lang
13 |
14 | log = logging.getLogger(__name__)
15 |
16 |
17 | class OBJECT_OT_bf_show_fds_geometry(Operator):
18 | """!
19 | Show geometry of Object as exported to FDS.
20 | """
21 |
22 | bl_label = "Show FDS Geometry"
23 | bl_idname = "object.bf_show_fds_geometry"
24 | bl_description = "Show/Hide geometry as exported to FDS"
25 |
26 | @classmethod
27 | def poll(cls, context):
28 | ob = context.object # object or active_object? always object
29 | return ob and not ob.bf_is_tmp and not ob.bf_has_tmp
30 |
31 | def execute(self, context):
32 | # Init
33 | w = context.window_manager.windows[0]
34 | w.cursor_modal_set("WAIT")
35 | sc = context.scene
36 | ob = context.object
37 |
38 | # Export and import back as temporary geometry
39 | try:
40 | f90_namelists = ob.to_fds_list(context).to_string()
41 | sc.from_fds(
42 | context=context,
43 | f90_namelists=f90_namelists,
44 | set_tmp=True,
45 | )
46 | except BFException as err:
47 | utils.geometry.rm_tmp_objects()
48 | self.report({"ERROR"}, str(err))
49 | return {"CANCELLED"}
50 | else:
51 | utils.geometry.set_has_tmp(context=context, ob=ob)
52 | self.report({"INFO"}, "FDS geometry shown")
53 | return {"FINISHED"}
54 | finally:
55 | w.cursor_modal_restore()
56 |
57 |
58 | class SCENE_OT_bf_hide_tmp_geometry(Operator):
59 | """!
60 | Hide all temporary geometry.
61 | """
62 |
63 | bl_label = "Hide FDS Geometry"
64 | bl_idname = "scene.bf_hide_fds_geometry"
65 | bl_description = "Hide all generated temporary geometry"
66 |
67 | def execute(self, context):
68 | w = context.window_manager.windows[0]
69 | w.cursor_modal_set("WAIT")
70 |
71 | ob = context.object
72 | utils.geometry.rm_tmp_objects()
73 | try:
74 | context.view_layer.objects.active = ob
75 | except ReferenceError:
76 | try:
77 | context.view_layer.objects.active = context.selected_objects[0]
78 | except IndexError:
79 | pass
80 |
81 | w.cursor_modal_restore()
82 | self.report({"INFO"}, "Temporary geometry hidden")
83 | return {"FINISHED"}
84 |
85 |
86 | bl_classes = [
87 | OBJECT_OT_bf_show_fds_geometry,
88 | SCENE_OT_bf_hide_tmp_geometry,
89 | ]
90 |
91 |
92 | def register():
93 | from bpy.utils import register_class
94 |
95 | for c in bl_classes:
96 | register_class(c)
97 |
98 |
99 | def unregister():
100 | from bpy.utils import unregister_class
101 |
102 | for c in reversed(bl_classes):
103 | unregister_class(c)
104 |
--------------------------------------------------------------------------------
/bl/operators/show_ui.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | """!
4 | BlenderFDS, operators to show parts of Blender UI.
5 | """
6 |
7 | import bpy
8 | from bpy.types import Operator
9 | from ... import utils
10 |
11 |
12 | class SCENE_OT_bf_show_material_panel(Operator):
13 | """!
14 | Show the Material Panel.
15 | """
16 |
17 | bl_label = "Show the Material Panel"
18 | bl_idname = "scene.bf_show_material_panel"
19 | bl_description = "Show the Material Panel of the property editor"
20 |
21 | def execute(self, context):
22 | utils.ui.show_property_panel(context, space_context="MATERIAL")
23 | self.report({"INFO"}, f"See Blender Material Panel")
24 | return {"FINISHED"}
25 |
26 |
27 | class SCENE_OT_bf_show_text(Operator):
28 | """!
29 | Show free text in the editor.
30 | """
31 |
32 | bl_label = "Show Free Text"
33 | bl_idname = "scene.bf_show_text"
34 | bl_description = "Show free text in the editor"
35 |
36 | def execute(self, context):
37 | context.scene.bf_config_text = utils.ui.show_bl_text(
38 | context=context,
39 | bl_text=context.scene.bf_config_text,
40 | name="Text",
41 | )
42 | self.report(
43 | {"INFO"}, f"See Blender text editor: <{context.scene.bf_config_text.name}>"
44 | )
45 | return {"FINISHED"}
46 |
47 |
48 | bl_classes = [
49 | SCENE_OT_bf_show_text,
50 | SCENE_OT_bf_show_material_panel,
51 | ]
52 |
53 |
54 | def register():
55 | from bpy.utils import register_class
56 |
57 | for c in bl_classes:
58 | register_class(c)
59 |
60 |
61 | def unregister():
62 | from bpy.utils import unregister_class
63 |
64 | for c in reversed(bl_classes):
65 | unregister_class(c)
66 |
--------------------------------------------------------------------------------
/bl/operators/update_addon.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | """!
4 | BlenderFDS, operators to update the addon.
5 | """
6 |
7 | import logging
8 | from bpy.types import Operator
9 | from bpy.props import EnumProperty
10 | from ...utils import updater
11 |
12 | log = logging.getLogger(__name__)
13 |
14 |
15 | class WM_OT_bf_update_tags(Operator):
16 | bl_label = "Update Tags"
17 | bl_idname = "wm.bf_update_tags"
18 | bl_description = "Update tags"
19 |
20 | def execute(self, context):
21 | try:
22 | context.window_manager["tags"] = updater.get_tags()
23 | except Exception as err:
24 | self.report({"ERROR"}, str(err))
25 | return {"CANCELLED"}
26 | else:
27 | self.report({"INFO"}, "Tags updated")
28 | return {"FINISHED"}
29 |
30 |
31 | def _get_target_items(self, context):
32 | tags = context.window_manager.get("tags", ())
33 | return ((t["name"], t["name"], f"Install {t['name']} version") for t in tags)
34 |
35 |
36 | class WM_OT_bf_update_addon(Operator):
37 | bl_label = "Upgrade Addon"
38 | bl_idname = "wm.bf_update_addon"
39 | bl_description = "Upgrade, downgrade, or restore the addon from its repository"
40 |
41 | bf_target: EnumProperty(
42 | name="Version",
43 | description="Select the addon version to install",
44 | items=_get_target_items,
45 | )
46 |
47 | def draw(self, context):
48 | row = self.layout.row(align=True)
49 | row.prop(self, "bf_target")
50 | row.operator("wm.bf_update_tags", text="", icon="FILE_REFRESH")
51 |
52 | def invoke(self, context, event):
53 | wm = context.window_manager
54 | return wm.invoke_props_dialog(self)
55 |
56 | def execute(self, context):
57 |
58 | # Get zipball_url
59 | tags = context.window_manager.get("tags", list())
60 | url_file = None
61 | for t in tags:
62 | if t["name"] == self.bf_target:
63 | url_file = t["zipball_url"]
64 | break
65 | if not url_file:
66 | self.report({"ERROR"}, "Update cancelled, no zip file selected.")
67 | return {"CANCELLED"}
68 |
69 | # Install
70 | try:
71 | updater.install_addon(url_file=url_file)
72 | except Exception as err:
73 | self.report({"ERROR"}, f"Install failed: {err}")
74 | return {"CANCELLED"}
75 | self.report({"WARNING"}, f"Install completed, restart Blender!")
76 | context.window_manager["bf_restart_required"] = True
77 | return {"FINISHED"}
78 |
79 |
80 | bl_classes = [
81 | WM_OT_bf_update_tags,
82 | WM_OT_bf_update_addon,
83 | ]
84 |
85 |
86 | def register():
87 | from bpy.utils import register_class
88 |
89 | for c in bl_classes:
90 | register_class(c)
91 |
92 |
93 | def unregister():
94 | from bpy.utils import unregister_class
95 |
96 | for c in reversed(bl_classes):
97 | unregister_class(c)
98 |
--------------------------------------------------------------------------------
/bl/preferences.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | """!
4 | BlenderFDS, preferences panel.
5 | """
6 |
7 | import bpy
8 | import logging
9 |
10 | from bpy.types import AddonPreferences
11 | from bpy.props import BoolProperty, StringProperty
12 | from . import ui
13 |
14 | log = logging.getLogger(__name__)
15 |
16 | # Get preference value like this:
17 | # prefs = context.preferences.addons[__package__.split(".")[0]].preferences
18 | # prefs.bf_pref_simplify_ui
19 |
20 |
21 | def update_bf_pref_simplify_ui(prefs, context):
22 | ui.toggle_simple_ui(prefs, context)
23 |
24 |
25 | class BFPreferences(AddonPreferences):
26 | """!
27 | BlenderFDS, preferences panel
28 | """
29 |
30 | bl_idname = __package__.split(".")[0]
31 |
32 | bf_pref_simplify_ui: BoolProperty(
33 | name="Simplify Blender UI",
34 | description="Simplify Blender user interface",
35 | default=True,
36 | update=update_bf_pref_simplify_ui,
37 | )
38 |
39 | bf_pref_fds_command: StringProperty(
40 | name="Run FDS",
41 | description="\n".join(
42 | (
43 | "Run FDS command:",
44 | " <{n}> is replaced by the number of MPI processes,",
45 | " <{t}> by the number of threads,",
46 | " <{f}> by the fds case filepath (eg. /example/case.fds).",
47 | " <{i}> by the fds case inputfile (eg. case.fds).",
48 | " <{p}> by the fds case path (eg. /example/).",
49 | )
50 | ),
51 | default="",
52 | )
53 |
54 | bf_pref_smv_command: StringProperty(
55 | name="Open Smokeview",
56 | description="\n".join(
57 | (
58 | "Open Smokeview command:",
59 | " <{f}> is replaced by the smv filepath (eg. /example/case.smv),",
60 | " <{p}> by the fds case path (eg. /example/).",
61 | )
62 | ),
63 | default="",
64 | )
65 |
66 | bf_pref_term_command: StringProperty(
67 | name="Open Terminal",
68 | description="\n".join(
69 | (
70 | "Open terminal command:",
71 | " <{c}> is replaced by FDS or Smokeview command.",
72 | )
73 | ),
74 | default="",
75 | )
76 |
77 | def draw(self, context):
78 | """!
79 | Draw UI elements into the panel UI layout.
80 | @param context: the Blender context.
81 | @return Blender layout.
82 | """
83 | paths = context.preferences.filepaths
84 | layout = self.layout
85 |
86 | row = layout.row()
87 | row.operator("wm.bf_load_blenderfds_settings")
88 | if context.window_manager.get("bf_restart_required"):
89 | alert_row = row.row()
90 | alert_row.alert = True
91 | alert_row.operator("wm.quit_blender", text="Restart Blender", icon="ERROR")
92 | else:
93 | row.operator("wm.bf_update_addon")
94 |
95 | col = layout.column()
96 | col.prop(self, "bf_pref_simplify_ui")
97 | col.prop(paths, "use_load_ui", text="Load UI setup when loading .blend files")
98 | col.prop(
99 | paths,
100 | "use_relative_paths",
101 | text="Default to relative paths in the file selector",
102 | )
103 |
104 | box = layout.box()
105 | box.label(text="External Commands")
106 |
107 | row = box.row(align=True)
108 | row.prop(self, "bf_pref_fds_command", text="FDS")
109 | row.operator(
110 | "wm.bf_restore_default_commands", text="", icon="LOOP_BACK"
111 | ).bf_command = "FDS"
112 |
113 | row = box.row(align=True)
114 | row.prop(self, "bf_pref_smv_command", text="Smokeview")
115 | row.operator(
116 | "wm.bf_restore_default_commands", text="", icon="LOOP_BACK"
117 | ).bf_command = "Smokeview"
118 |
119 | row = box.row(align=True)
120 | row.prop(self, "bf_pref_term_command", text="Terminal")
121 | row.operator(
122 | "wm.bf_restore_default_commands", text="", icon="LOOP_BACK"
123 | ).bf_command = "Terminal"
124 |
125 | return layout
126 |
127 |
128 | def register():
129 | log.info("Register preferences...")
130 | bpy.utils.register_class(BFPreferences)
131 |
132 |
133 | def unregister():
134 | log.info("Unregister preferences...")
135 | bpy.utils.unregister_class(BFPreferences)
136 |
--------------------------------------------------------------------------------
/bl/ui/__init__.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | """!
4 | BlenderFDS, ui simplification.
5 | """
6 |
7 | from . import simplify_ui
8 | from .simplify_ui import toggle_simple_ui
9 |
10 | ms_to_register = (simplify_ui,)
11 |
12 |
13 | def register():
14 | for m in ms_to_register:
15 | m.register()
16 |
17 |
18 | def unregister():
19 | for m in reversed(ms_to_register):
20 | m.unregister()
21 |
--------------------------------------------------------------------------------
/bl/ui/bf_ui/__init__.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | """!
4 | BlenderFDS, classes for simplified ui.
5 | """
6 |
7 | from . import properties, topbar, view3d
8 |
9 | # Register/Unregister
10 |
11 | ms_to_register = (
12 | properties,
13 | topbar,
14 | view3d,
15 | )
16 |
17 |
18 | def register():
19 | for m in ms_to_register:
20 | m.register()
21 |
22 |
23 | def unregister():
24 | for m in ms_to_register:
25 | m.unregister()
26 |
--------------------------------------------------------------------------------
/bl/ui/bf_ui/properties.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | from bpy.types import Panel, Scene, Object, Collection
4 | from bpy.utils import register_class, unregister_class
5 |
6 |
7 | class PROPERTIES_PT_navigation_bar(Panel):
8 | bl_space_type = "PROPERTIES"
9 | bl_region_type = "NAVIGATION_BAR"
10 | bl_label = "Navigation Bar"
11 | bl_options = {"HIDE_HEADER"}
12 |
13 | def draw(self, context):
14 | space = context.space_data
15 | pin_id = space.pin_id
16 |
17 | layout = self.layout
18 | layout.scale_x = 1.4
19 | layout.scale_y = 1.4
20 |
21 | # Check space.context for strange situations
22 | if space.context not in (
23 | "OBJECT",
24 | "DATA",
25 | "MATERIAL",
26 | "SCENE",
27 | "COLLECTION",
28 | "MODIFIER",
29 | ):
30 | space.context = "SCENE"
31 |
32 | # Set UI
33 | if not pin_id:
34 | sc = context.scene
35 | ob = context.active_object
36 | co = context.collection
37 |
38 | col = layout.column(align=True)
39 | col.prop_enum(space, "context", "SCENE", text="", icon="SCENE_DATA")
40 |
41 | if co != sc.collection:
42 | col.prop_enum(
43 | space, "context", "COLLECTION", text="", icon="OUTLINER_COLLECTION"
44 | )
45 | if ob:
46 | col = layout.column(align=True)
47 | col.prop_enum(space, "context", "OBJECT", text="", icon="OBJECT_DATA")
48 | if ob.type == "MESH":
49 | col.prop_enum(
50 | space, "context", "MODIFIER", text="", icon="MODIFIER_DATA"
51 | )
52 | if ob.type == "MESH" or ob.type == "EMPTY":
53 | col.prop_enum(space, "context", "DATA", text="", icon="MESH_DATA")
54 | if ob.type == "MESH":
55 | col.prop_enum(
56 | space, "context", "MATERIAL", text="", icon="MATERIAL_DATA"
57 | )
58 |
59 | else:
60 | if isinstance(pin_id, Scene):
61 | sc = pin_id
62 | col = layout.column(align=True)
63 | col.prop_enum(space, "context", "SCENE", text="", icon="SCENE_DATA")
64 | elif isinstance(pin_id, Collection):
65 | co = pin_id
66 | col = layout.column(align=True)
67 | col.prop_enum(
68 | space, "context", "COLLECTION", text="", icon="OUTLINER_COLLECTION"
69 | )
70 | elif isinstance(pin_id, Object):
71 | ob = pin_id
72 | col = layout.column(align=True)
73 | col.prop_enum(space, "context", "OBJECT", text="", icon="OBJECT_DATA")
74 | if ob.type == "MESH":
75 | col.prop_enum(
76 | space, "context", "MODIFIER", text="", icon="MODIFIER_DATA"
77 | )
78 | if ob.type == "MESH" or ob.type == "EMPTY":
79 | col.prop_enum(space, "context", "DATA", text="", icon="MESH_DATA")
80 | if ob.type == "MESH":
81 | col.prop_enum(
82 | space, "context", "MATERIAL", text="", icon="MATERIAL_DATA"
83 | )
84 |
85 |
86 | # Register/Unregister
87 |
88 | bl_classes = [
89 | PROPERTIES_PT_navigation_bar,
90 | ]
91 |
92 |
93 | def register():
94 | from bpy.utils import register_class
95 |
96 | for c in bl_classes:
97 | register_class(c)
98 |
99 |
100 | def unregister():
101 | from bpy.utils import unregister_class
102 |
103 | for c in reversed(bl_classes):
104 | unregister_class(c)
105 |
--------------------------------------------------------------------------------
/bl/ui/bf_ui/topbar.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | from bpy.types import Menu
4 | from bpy.utils import register_class, unregister_class
5 |
6 |
7 | class TOPBAR_MT_editor_menus(Menu):
8 | bl_idname = "TOPBAR_MT_editor_menus"
9 | bl_label = ""
10 |
11 | def draw(self, context):
12 | layout = self.layout
13 |
14 | # Allow calling this menu directly (this might not be a header area).
15 | if getattr(context.area, "show_menus", False):
16 | layout.menu("TOPBAR_MT_blender", text="", icon="BLENDER")
17 | else:
18 | layout.menu("TOPBAR_MT_blender", text="Blender")
19 |
20 | layout.menu("TOPBAR_MT_file")
21 | layout.menu("TOPBAR_MT_edit")
22 |
23 | layout.menu("TOPBAR_MT_window")
24 | layout.menu("TOPBAR_MT_help")
25 |
26 |
27 | # Register/Unregister
28 |
29 | bl_classes = [
30 | TOPBAR_MT_editor_menus,
31 | ]
32 |
33 |
34 | def register():
35 | from bpy.utils import register_class
36 |
37 | for c in bl_classes:
38 | register_class(c)
39 |
40 |
41 | def unregister():
42 | from bpy.utils import unregister_class
43 |
44 | for c in reversed(bl_classes):
45 | unregister_class(c)
46 |
--------------------------------------------------------------------------------
/bl/ui/bf_ui/view3d.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | import bpy
4 | from bpy.types import Menu
5 | from bpy.app.translations import contexts as i18n_contexts
6 | from bpy.utils import register_class, unregister_class
7 |
8 |
9 | class VIEW3D_MT_view(Menu):
10 | bl_idname = "VIEW3D_MT_view"
11 | bl_label = "View"
12 |
13 | def draw(self, context):
14 | layout = self.layout
15 | view = context.space_data
16 |
17 | layout.prop(view, "show_region_toolbar")
18 | layout.prop(view, "show_region_ui")
19 | layout.prop(view, "show_region_tool_header")
20 | layout.prop(view, "show_region_hud")
21 |
22 | layout.separator()
23 |
24 | layout.operator(
25 | "view3d.view_selected", text="Frame Selected"
26 | ).use_all_regions = False
27 | if view.region_quadviews:
28 | layout.operator(
29 | "view3d.view_selected", text="Frame Selected (Quad View)"
30 | ).use_all_regions = True
31 |
32 | layout.operator("view3d.view_all").center = False
33 | layout.operator("view3d.view_persportho", text="Perspective/Orthographic")
34 | layout.menu("VIEW3D_MT_view_local")
35 |
36 | layout.separator()
37 | layout.menu("VIEW3D_MT_view_viewpoint")
38 | layout.menu("VIEW3D_MT_view_navigation")
39 | layout.menu("VIEW3D_MT_view_align")
40 |
41 | layout.separator()
42 |
43 | layout.menu("INFO_MT_area")
44 |
45 |
46 | class VIEW3D_MT_add(Menu):
47 | bl_label = "Add"
48 | bl_translation_context = i18n_contexts.operator_default
49 |
50 | def draw(self, context):
51 | layout = self.layout
52 | layout.operator_context = "EXEC_REGION_WIN"
53 | layout.menu("VIEW3D_MT_mesh_add", icon="OUTLINER_OB_MESH")
54 | layout.menu("VIEW3D_MT_curve_add", icon="OUTLINER_OB_CURVE")
55 | layout.menu("VIEW3D_MT_surface_add", icon="OUTLINER_OB_SURFACE")
56 | layout.operator_menu_enum(
57 | "object.gpencil_add",
58 | "type",
59 | text="Grease Pencil",
60 | icon="OUTLINER_OB_GREASEPENCIL",
61 | )
62 |
63 | layout.separator()
64 | layout.operator_menu_enum(
65 | "object.empty_add", "type", text="Empty", icon="OUTLINER_OB_EMPTY"
66 | )
67 | layout.menu("VIEW3D_MT_image_add", text="Image", icon="OUTLINER_OB_IMAGE")
68 |
69 | # FIXME Manage Collection instances
70 | # layout.separator()
71 | # has_collections = bool(bpy.data.collections)
72 | # col = layout.column()
73 | # col.enabled = has_collections
74 |
75 | # if not has_collections or len(bpy.data.collections) > 10:
76 | # col.operator_context = "INVOKE_REGION_WIN"
77 | # col.operator(
78 | # "object.collection_instance_add",
79 | # text="Collection Instance..."
80 | # if has_collections
81 | # else "No Collections to Instance",
82 | # icon="OUTLINER_OB_GROUP_INSTANCE",
83 | # )
84 | # else:
85 | # col.operator_menu_enum(
86 | # "object.collection_instance_add",
87 | # "collection",
88 | # text="Collection Instance",
89 | # icon="OUTLINER_OB_GROUP_INSTANCE",
90 | # )
91 |
92 |
93 | class VIEW3D_MT_object(Menu):
94 | bl_context = "objectmode"
95 | bl_label = "Object"
96 |
97 | def draw(self, _context):
98 | layout = self.layout
99 |
100 | layout.menu("VIEW3D_MT_transform_object")
101 | layout.operator_menu_enum(
102 | "object.origin_set", text="Set Origin", property="type"
103 | )
104 | layout.menu("VIEW3D_MT_mirror")
105 | layout.menu("VIEW3D_MT_object_clear")
106 | layout.menu("VIEW3D_MT_object_apply")
107 | layout.menu("VIEW3D_MT_snap")
108 |
109 | layout.separator()
110 |
111 | layout.operator("object.duplicate_move")
112 | layout.operator("object.duplicate_move_linked")
113 | layout.operator("object.join")
114 |
115 | layout.separator()
116 |
117 | layout.operator("view3d.copybuffer", text="Copy Objects", icon="COPYDOWN")
118 | layout.operator("view3d.pastebuffer", text="Paste Objects", icon="PASTEDOWN")
119 |
120 | layout.separator()
121 |
122 | layout.menu("VIEW3D_MT_object_asset")
123 | # layout.menu("VIEW3D_MT_object_parent")
124 | layout.menu("VIEW3D_MT_object_collection")
125 | layout.menu("VIEW3D_MT_object_relations")
126 | # layout.menu("VIEW3D_MT_object_constraints")
127 | # layout.menu("VIEW3D_MT_object_track")
128 | layout.menu("VIEW3D_MT_make_links")
129 |
130 | layout.separator()
131 |
132 | layout.operator("object.shade_smooth")
133 | layout.operator("object.shade_flat")
134 |
135 | layout.separator()
136 |
137 | # layout.menu("VIEW3D_MT_object_animation")
138 | # layout.menu("VIEW3D_MT_object_rigid_body")
139 |
140 | # layout.separator()
141 |
142 | # layout.menu("VIEW3D_MT_object_quick_effects")
143 |
144 | # layout.separator()
145 |
146 | layout.menu("VIEW3D_MT_object_convert")
147 |
148 | layout.separator()
149 |
150 | layout.menu("VIEW3D_MT_object_showhide")
151 | layout.menu("VIEW3D_MT_object_cleanup")
152 |
153 | layout.separator()
154 |
155 | layout.operator_context = "EXEC_REGION_WIN"
156 | layout.operator("object.delete", text="Delete").use_global = False
157 | layout.operator("object.delete", text="Delete Global").use_global = True
158 |
159 |
160 | # Register/Unregister
161 |
162 | bl_classes = [VIEW3D_MT_view, VIEW3D_MT_add, VIEW3D_MT_object]
163 |
164 |
165 | def register():
166 | from bpy.utils import register_class
167 |
168 | for c in bl_classes:
169 | register_class(c)
170 |
171 |
172 | def unregister():
173 | from bpy.utils import unregister_class
174 |
175 | for c in reversed(bl_classes):
176 | unregister_class(c)
177 |
--------------------------------------------------------------------------------
/bl/ui/simplify_ui.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | """!
4 | BlenderFDS, simplify Blender ui classes.
5 | """
6 |
7 | import logging, bpy
8 | from bpy.utils import register_class, unregister_class
9 | from . import bf_ui
10 |
11 | log = logging.getLogger(__name__)
12 |
13 | ## name of original Blender ui class to be checked for existance
14 | bl_check_cls_name = "WORKSPACE_PT_addons"
15 |
16 | ## names of original Blender ui classes to be unregistered
17 | bl_cls_names = list()
18 |
19 | # from: 3.0/scripts/startup/bl_ui/properties_workspace.py
20 | bl_cls_names.extend(
21 | (
22 | # "WORKSPACE_PT_main",
23 | "WORKSPACE_PT_addons",
24 | "WORKSPACE_PT_custom_props",
25 | )
26 | )
27 |
28 | # from: 3.0/scripts/startup/bl_ui/properties_scene.py
29 | bl_cls_names.extend(
30 | (
31 | # "SCENE_UL_keying_set_paths",
32 | "SCENE_PT_scene",
33 | # "SCENE_PT_unit", # for BlenderBIM addon compat
34 | "SCENE_PT_physics",
35 | "SCENE_PT_keying_sets",
36 | "SCENE_PT_keying_set_paths",
37 | "SCENE_PT_keyframing_settings",
38 | "SCENE_PT_audio",
39 | "SCENE_PT_rigid_body_world",
40 | "SCENE_PT_rigid_body_world_settings",
41 | "SCENE_PT_rigid_body_cache",
42 | "SCENE_PT_rigid_body_field_weights",
43 | "SCENE_PT_custom_props",
44 | )
45 | )
46 |
47 | # from: 3.0/scripts/startup/bl_ui/properties_collection.py
48 | bl_cls_names.extend(
49 | (
50 | # "COLLECTION_PT_collection_flags",
51 | # "COLLECTION_PT_instancing",
52 | "COLLECTION_PT_lineart_collection",
53 | )
54 | )
55 |
56 | # from: 3.0/scripts/startup/bl_ui/properties_object.py
57 | bl_cls_names.extend(
58 | (
59 | # "OBJECT_PT_context_object",
60 | # "OBJECT_PT_transform", # for BlenderBIM addon compat
61 | # "OBJECT_PT_delta_transform",
62 | # "OBJECT_PT_relations",
63 | # "COLLECTION_MT_context_menu",
64 | # "OBJECT_PT_collections",
65 | "OBJECT_PT_instancing",
66 | "OBJECT_PT_instancing_size",
67 | "OBJECT_PT_motion_paths",
68 | "OBJECT_PT_motion_paths_display",
69 | # "OBJECT_PT_display",
70 | # "OBJECT_PT_visibility",
71 | "OBJECT_PT_lineart",
72 | "OBJECT_PT_custom_props",
73 | )
74 | )
75 |
76 | # from: 3.0/scripts/startup/bl_ui/properties_data_mesh.py
77 | bl_cls_names.extend(
78 | (
79 | # "MESH_MT_vertex_group_context_menu",
80 | # "MESH_MT_shape_key_context_menu",
81 | # "MESH_UL_vgroups",
82 | # "MESH_UL_fmaps",
83 | # "MESH_UL_shape_keys",
84 | # "MESH_UL_uvmaps",
85 | # "MESH_UL_vcols",
86 | # "MESH_UL_attributes",
87 | # "DATA_PT_context_mesh",
88 | "DATA_PT_vertex_groups",
89 | "DATA_PT_shape_keys",
90 | "DATA_PT_uv_texture",
91 | "DATA_PT_vertex_colors",
92 | "DATA_PT_sculpt_vertex_colors",
93 | "DATA_PT_face_maps",
94 | "DATA_PT_mesh_attributes",
95 | "DATA_PT_normals",
96 | "DATA_PT_texture_space",
97 | # "DATA_PT_remesh",
98 | "DATA_PT_customdata",
99 | "DATA_PT_custom_props_mesh",
100 | )
101 | )
102 |
103 | # from: 3.0/scripts/startup/bl_ui/properties_material.py
104 | bl_cls_names.extend(
105 | (
106 | # "MATERIAL_MT_context_menu",
107 | # "MATERIAL_UL_matslots",
108 | # "MATERIAL_PT_preview",
109 | # "EEVEE_MATERIAL_PT_context_material",
110 | "EEVEE_MATERIAL_PT_surface",
111 | "EEVEE_MATERIAL_PT_volume",
112 | "EEVEE_MATERIAL_PT_settings",
113 | "MATERIAL_PT_lineart",
114 | "MATERIAL_PT_viewport",
115 | "EEVEE_MATERIAL_PT_viewport_settings",
116 | "MATERIAL_PT_custom_props",
117 | )
118 | )
119 |
120 | # Replaced classes
121 | # from: 3.0/scripts/startup/bl_ui/space_properties.py
122 | bl_cls_names.extend(
123 | (
124 | "PROPERTIES_PT_navigation_bar",
125 | "TOPBAR_MT_editor_menus",
126 | "VIEW3D_MT_view",
127 | "VIEW3D_MT_add",
128 | "VIEW3D_MT_object",
129 | )
130 | )
131 |
132 | ## references to original Blender ui classes to unregister
133 | bl_cls_refs = list()
134 |
135 |
136 | def _load_bl_cls_refs():
137 | log.debug(f"Load original Blender ui classes...")
138 | for n in bl_cls_names:
139 | module_name = f"bpy.types.{n}"
140 | # Check existance
141 | try:
142 | cls = eval(module_name)
143 | log.debug(f"Load: Found original ui class <{module_name}>...")
144 | except:
145 | log.debug(f"Load: Unknown original ui class <{module_name}>...")
146 | else:
147 | bl_cls_refs.append(cls)
148 |
149 |
150 | def _set_simple_ui():
151 | log.debug("Set simple ui...")
152 |
153 | # Check already simple
154 | if not hasattr(bpy.types, bl_check_cls_name):
155 | log.debug("Already simple, do not touch.")
156 | return
157 |
158 | # Unregister original
159 | for cls in bl_cls_refs:
160 | try:
161 | unregister_class(cls)
162 | except:
163 | log.debug(f"Unregister: unknown original class <{cls}>")
164 |
165 | # Register new ui
166 | bf_ui.register()
167 |
168 |
169 | def _set_normal_ui():
170 | log.debug("Set normal ui...")
171 |
172 | # Check already normal
173 | if hasattr(bpy.types, bl_check_cls_name):
174 | return
175 |
176 | # Unregister new ui
177 | bf_ui.unregister()
178 |
179 | # Register original
180 | for cls in bl_cls_refs:
181 | try:
182 | register_class(cls)
183 | except:
184 | log.debug(f"Register: unknown original class <{cls}>")
185 |
186 |
187 | def toggle_simple_ui(prefs=None, context=None, force_normal=False):
188 | log.info("Toggle simple ui...")
189 |
190 | # First start, load original references
191 | if not bl_cls_refs:
192 | _load_bl_cls_refs()
193 |
194 | if force_normal:
195 | _set_normal_ui()
196 | return
197 |
198 | if context is None:
199 | context = bpy.context
200 | if prefs is None:
201 | prefs = context.preferences.addons[__package__.split(".")[0]].preferences
202 | if prefs.bf_pref_simplify_ui:
203 | _set_simple_ui()
204 | else:
205 | _set_normal_ui()
206 |
207 |
208 | # Register/Unregister
209 |
210 |
211 | def register():
212 | toggle_simple_ui() # at start, check
213 |
214 |
215 | def unregister():
216 | toggle_simple_ui(force_normal=True) # at end, restore
217 |
--------------------------------------------------------------------------------
/bl/ui_lists.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | """!
4 | BlenderFDS, Blender UI lists.
5 | """
6 |
7 | from bpy.types import PropertyGroup, UIList
8 | from bpy.props import BoolProperty, StringProperty
9 | from bpy.utils import register_class, unregister_class
10 | import logging
11 |
12 | log = logging.getLogger(__name__)
13 |
14 | # Prepare for "Other" parameters
15 | # PropertyGroup and UIList
16 | # The PG properties should always be: bf_export, name
17 |
18 |
19 | class WM_PG_bf_other(PropertyGroup):
20 | """!
21 | Blender PropertyGroup for items of 'other' FDS parameters.
22 | """
23 |
24 | bf_export: BoolProperty(name="Export", default=True)
25 | name: StringProperty(name="Name")
26 |
27 |
28 | class WM_UL_bf_other_items(UIList):
29 | """!
30 | Blender UIList for items of 'other' FDS parameters.
31 | """
32 |
33 | def draw_item(self, context, layout, data, item, icon, active_data):
34 | row = layout.row()
35 | row.active = item.bf_export
36 | row = row.split(factor=0.08)
37 | row.prop(item, "bf_export", text="")
38 | row.prop(item, "name", text="", emboss=False, icon_value=icon)
39 |
40 |
41 | bl_classes = [
42 | WM_PG_bf_other,
43 | WM_UL_bf_other_items,
44 | ]
45 |
46 |
47 | def register():
48 | log.info("Register ui_lists...")
49 | for c in bl_classes:
50 | register_class(c)
51 |
52 |
53 | def unregister():
54 | log.info("Unregister ui_lists...")
55 | for c in reversed(bl_classes):
56 | unregister_class(c)
57 |
--------------------------------------------------------------------------------
/dev/doxygen/.gitignore:
--------------------------------------------------------------------------------
1 | html
2 | *.log
3 |
--------------------------------------------------------------------------------
/dev/doxygen/update-doxygen.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # Usage: bash /path/to/update-doxygen.sh
3 |
4 | # System variables
5 | DOXYGEN_PATHFILE="/opt/doxygen/doxygen"
6 |
7 | # Generation of new doxygen
8 | echo "Doxygen generation ..."
9 | DOXYGEN_PATHFILE doxyfile > doxygen.log 2>&1
10 |
11 | # Moving to branch gh-pages
12 | echo "Pulling gh-pages branch ..."
13 | git checkout gh-pages
14 | git pull
15 |
16 | # Replacing current doxygen
17 | echo "Deploying doxygen ..."
18 | rm -rf ../docs/html
19 | mv html ../docs/
20 |
21 | # Commit
22 | echo "Commit doxygen ..."
23 | git add ../docs/html
24 | COMMITMESSAGE="Doxygen update $(date)"
25 | git commit -m "$COMMITMESSAGE"
26 | git push
27 |
28 | # Restoring master
29 | git checkout master
30 |
--------------------------------------------------------------------------------
/dev/make_release.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | # BlenderFDS, an open tool for the NIST Fire Dynamics Simulator
4 | # Copyright (C) 2013 Emanuele Gissi, http://www.blenderfds.org
5 | #
6 | # This program is free software; you can redistribute it and/or modify
7 | # it under the terms of the GNU General Public License as published by
8 | # the Free Software Foundation; either version 3 of the License, or
9 | # (at your option) any later version.
10 | #
11 | # This program is distributed in the hope that it will be useful, but
12 | # WITHOUT ANY WARRANTY; without even the implied warranty of
13 | # MERCHANTIBILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 | # General Public License for more details.
15 | #
16 | # You should have received a copy of the GNU General Public License
17 | # along with this program. If not, see .
18 |
19 | """!
20 | Automatic creation of blenderfds release zip.
21 | """
22 |
23 | import shutil, tempfile
24 |
25 | # Config
26 | ignore_patterns = (
27 | "dev",
28 | "docs",
29 | "__pycache__",
30 | "*.pyc",
31 | "*.blend1",
32 | "TODO.md",
33 | ".git",
34 | ".gitignore",
35 | ".vscode",
36 | )
37 | output_filename = "blenderfds"
38 |
39 | # Make
40 | with tempfile.TemporaryDirectory() as tmpdirname:
41 | shutil.copytree(
42 | "..",
43 | tmpdirname + "/blenderfds",
44 | symlinks=False,
45 | ignore=shutil.ignore_patterns(*ignore_patterns),
46 | )
47 | shutil.make_archive(output_filename, "zip", tmpdirname)
48 |
49 | print(f"Done: {output_filename}.zip")
50 |
--------------------------------------------------------------------------------
/docs/CNAME:
--------------------------------------------------------------------------------
1 | blenderfds.org
--------------------------------------------------------------------------------
/docs/_config.yml:
--------------------------------------------------------------------------------
1 | theme: jekyll-theme-minimal
2 |
3 | title: BlenderFDS
4 | description: The open user interface for the
NIST Fire Dynamics Simulator (FDS)
5 | logo: https://github.com/firetools/blenderfds/wiki/p/web/logo.png
6 | show_downloads: false
7 | # google_analytics: [Your Google Analytics tracking ID]
8 |
9 |
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
12 | ## BlenderFDS, full control over the FDS input file
13 |
14 | BlenderFDS helps you build complex FDS models *faster*,
15 | because it eases geometric data entry with powerful 3D editing tools.
16 | But you remain in full control over all the input parameters,
17 | as BlenderFDS does not hide the FDS complexity and flexibility.
18 |
19 | You can import CAD models and existing FDS input files,
20 | and quickly adapt them to your needs.
21 |
22 | BlenderFDS is developed in Python
23 | as a free and open source [Blender](https://www.blender.org/) addon,
24 | and it is fully scriptable.
25 |
26 | ## [Install BlenderFDS](https://github.com/firetools/blenderfds/wiki/Install) on the supported platforms.
27 |
28 | [](https://github.com/firetools/blenderfds/wiki/Install)
29 |
30 | 
31 |
32 | ## What is FDS?
33 |
34 | [NIST Fire Dynamics Simulator (FDS)](https://pages.nist.gov/fds-smv/)
35 | is a large-eddy simulation (LES) code for low-speed flows,
36 | with an emphasis on smoke and heat transport from fires.
37 |
38 | ## What is Blender?
39 |
40 | [Blender](https://www.blender.org/) is the free and open source 3D creation suite.
41 | It supports the entirety of the 3D pipeline from modelling to visualisation,
42 | and BlenderFDS makes use of its 3D modeling tools.
43 |
44 | ## A family of open tools for FDS
45 |
46 | Another companion tool is [qgis2fds](https://github.com/firetools/qgis2fds/wiki),
47 | an open [QGIS](https://www.qgis.org) plugin
48 | that exports terrains and landuse for wildfire simulation and atmospheric dispersion of fire pollutants.
49 | The resulting FDS file can be imported to BlenderFDS for further customization.
50 |
51 | 
52 |
53 | ## Funded by the WUIFI-21 project
54 |
55 | The development of BlenderFDS was funded by a grant from
56 | the [Italian Ministry of Foreign Affairs and International Cooperation](https://www.esteri.it/).
57 |
58 | 
59 |
60 | ## License
61 |
62 | BlenderFDS is free software under the terms of
63 | the [GNU General Public License](https://www.gnu.org/licenses/gpl-3.0.en.html).
64 | It is distributed in the hope that it will be useful,
65 | but without any warranty.
66 |
67 | If you prefer buying professional support, take a look at
68 | [PyroSim](https://www.thunderheadeng.com/pyrosim)
69 |
--------------------------------------------------------------------------------
/lang/MN_SURF.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | import logging
4 | from ..types import BFNamelistMa
5 | from .bf_material import (
6 | MP_namelist_cls,
7 | MP_ID,
8 | MP_FYI,
9 | MP_DEFAULT,
10 | MP_RGB,
11 | MP_COLOR,
12 | MP_TRANSPARENCY,
13 | MP_other,
14 | )
15 | from .. import config
16 |
17 | log = logging.getLogger(__name__)
18 |
19 |
20 | class MN_SURF(BFNamelistMa):
21 | label = "SURF"
22 | description = "Boundary condition"
23 | enum_id = 2000
24 | fds_label = "SURF"
25 | bpy_export = "bf_surf_export"
26 | bpy_export_default = True
27 | bf_params = (
28 | MP_namelist_cls,
29 | MP_ID,
30 | MP_FYI,
31 | MP_DEFAULT,
32 | MP_RGB,
33 | MP_COLOR,
34 | MP_TRANSPARENCY,
35 | MP_other,
36 | )
37 | bf_import_order = 40
38 |
39 | def get_exported(self, context): # was in MP_namelist_cls
40 | if self.element.name in config.DEFAULT_MAS:
41 | return False
42 | return super().get_exported(context)
43 |
--------------------------------------------------------------------------------
/lang/ON_DEVC.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | import logging
4 | from bpy.types import Object
5 | from bpy.props import FloatProperty, BoolProperty, StringProperty
6 | from ..types import BFParam, BFNamelistOb
7 | from .bf_object import OP_namelist_cls, OP_ID, OP_FYI, OP_ID_suffix, OP_other
8 | from .OP_XB import OP_XB, OP_XB_voxel_size, OP_XB_center_voxels
9 | from .OP_XYZ import OP_XYZ
10 | from .OP_SURF_ID import OP_SURF_ID
11 |
12 | log = logging.getLogger(__name__)
13 |
14 |
15 | class OP_DEVC_QUANTITY(BFParam):
16 | label = "QUANTITY"
17 | description = "Output quantity"
18 | fds_label = "QUANTITY"
19 | bpy_type = Object
20 | bpy_prop = StringProperty
21 | bpy_idname = "bf_quantity"
22 |
23 | def draw_operators(self, context, layout):
24 | layout.operator("object.bf_choose_devc_quantity", icon="VIEWZOOM", text="")
25 |
26 |
27 | class OP_DEVC_PROP_ID(BFParam):
28 | label = "PROP_ID"
29 | description = "Reference to a PROP namelist"
30 | fds_label = "PROP_ID"
31 | bpy_type = Object
32 | bpy_prop = StringProperty
33 | bpy_idname = "bf_devc_prop_id"
34 |
35 | def draw_operators(self, context, layout):
36 | layout.operator("object.bf_choose_devc_prop_id", icon="VIEWZOOM", text="")
37 |
38 |
39 | class ON_DEVC(BFNamelistOb):
40 | label = "DEVC"
41 | description = "Device"
42 | collection = "Output"
43 | enum_id = 1011
44 | fds_label = "DEVC"
45 | bf_params = (
46 | OP_namelist_cls,
47 | OP_ID,
48 | OP_FYI,
49 | OP_DEVC_QUANTITY,
50 | OP_DEVC_PROP_ID,
51 | OP_SURF_ID,
52 | OP_XB,
53 | OP_XB_voxel_size,
54 | OP_XB_center_voxels,
55 | OP_XYZ,
56 | OP_ID_suffix,
57 | OP_other,
58 | )
59 | bf_other = {"appearance": "WIRE"}
60 |
--------------------------------------------------------------------------------
/lang/ON_GEOM/__init__.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | from .ON_GEOM import ON_GEOM
4 | from .geom_to_ob import geom_to_ob
5 | from .ob_to_geom import ob_to_geom, check_intersections, check_geom_sanity
6 |
--------------------------------------------------------------------------------
/lang/ON_GEOM/bingeom.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | """!
4 | BlenderFDS, FDS bingeom files input/output routines.
5 | """
6 |
7 | import struct, logging
8 | import numpy as np
9 | from ... import utils
10 | from ...types import BFException
11 |
12 | log = logging.getLogger(__name__)
13 |
14 | # Read/write fds bingeom files
15 |
16 | # The FDS bingeom file is written from Fortran90 like this:
17 | # WRITE(731) INTEGER_ONE
18 | # WRITE(731) N_VERTS,N_FACES,N_SURF_ID,N_VOLUS
19 | # WRITE(731) VERTS(1:3*N_VERTS)
20 | # WRITE(731) FACES(1:3*N_FACES)
21 | # WRITE(731) SURFS(1:N_FACES)
22 | # WRITE(731) VOLUS(1:4*N_VOLUS)
23 |
24 |
25 | def _read_record(f, req_dtype, req_dlen):
26 | """!
27 | Read a record from an open binary unformatted sequential Fortran90 file.
28 | @param f: open Python file object in 'rb' mode.
29 | @param req_dtype: requested type of data in 'int32' or 'float64'.
30 | @param req_dlen: requested length of the record.
31 | @return np.array of read data.
32 | """
33 | # The start tag is an int32 number (4 bytes) declaring the length of the record in bytes
34 | tag = struct.unpack("i", f.read(4))[0]
35 | # Calc the length of the record in numbers, using the byte size of the requested dtype
36 | dlen = int(tag / np.dtype(req_dtype).itemsize)
37 | if dlen != req_dlen:
38 | raise IOError(
39 | f"Different requested and declared record length: {req_dlen}, {dlen}"
40 | )
41 | # Read the record
42 | data = np.fromfile(f, dtype=req_dtype, count=req_dlen)
43 | # The end tag should be equal to the start tag
44 | end_tag = struct.unpack("i", f.read(4))[0] # end tag, last 4 bytes, int32
45 | if tag != end_tag: # check tags
46 | raise IOError(f"Different start and end record tags: {tag}, {end_tag}")
47 | # print(f"Read: record tag: {tag} dlen: {len(data)}\ndata: {data}") # TODO log debug
48 | return data
49 |
50 |
51 | def read_bingeom_file(filepath):
52 | """!
53 | Read FDS bingeom file
54 | @param filepath: filepath to be read from
55 | @return n_surf_id as integer, and fds_verts, fds_faces, fds_surfs, fds_volus as np.arrays in FDS flat format
56 | """
57 | try:
58 | with open(filepath, "rb") as f:
59 | geom_type = _read_record(f, req_dtype="int32", req_dlen=1)[0]
60 | if geom_type not in (1, 2):
61 | msg = f"Unknown GEOM type <{geom_type}> in bingeom file <{filepath}>"
62 | raise AssertionError(msg)
63 | n_verts, n_faces, n_surf_id, n_volus = _read_record(
64 | f, req_dtype="int32", req_dlen=4
65 | )
66 | fds_verts = _read_record(f, req_dtype="float64", req_dlen=3 * n_verts)
67 | fds_faces = _read_record(f, req_dtype="int32", req_dlen=3 * n_faces)
68 | fds_surfs = _read_record(f, req_dtype="int32", req_dlen=n_faces)
69 | fds_volus = _read_record(f, req_dtype="int32", req_dlen=4 * n_volus)
70 | except Exception as err:
71 | raise BFException(None, f"Error reading bingeom file: <{filepath}>\n{err}")
72 | return n_surf_id, fds_verts, fds_faces, fds_surfs, fds_volus, geom_type
73 |
74 |
75 | def _write_record(f, data):
76 | """!
77 | Write a record to a binary unformatted sequential Fortran90 file.
78 | @param f: open Python file object in 'wb' mode.
79 | @param data: np.array() of data.
80 | """
81 | # Calc start and end record tag
82 | tag = len(data) * data.dtype.itemsize
83 | # print(f"Write: record tag: {tag} dlen: {len(data)}\ndata: {data}") # TODO log debug
84 | # Write start tag, data, and end tag
85 | f.write(struct.pack("i", tag))
86 | data.tofile(f)
87 | f.write(struct.pack("i", tag))
88 |
89 |
90 | def write_bingeom_file(
91 | geom_type,
92 | n_surf_id,
93 | fds_verts,
94 | fds_faces,
95 | fds_surfs,
96 | fds_volus,
97 | filepath,
98 | force_dir=False,
99 | ):
100 | """!
101 | Write FDS bingeom file.
102 | @param geom_type: GEOM type (eg. 1 is manifold, 2 is terrain)
103 | @param n_surf_id: number of referred boundary conditions
104 | @param fds_verts: vertices coordinates in FDS flat format, eg. (x0, y0, z0, x1, y1, ...)
105 | @param fds_faces: faces connectivity in FDS flat format, eg. (i0, j0, k0, i1, ...)
106 | @param fds_surfs: boundary condition indexes in FDS flat format, eg. (b0, b1, ...)
107 | @param fds_volus: volumes connectivity in FDS flat format, eg. (i0, j0, k0, w0, i1, ...)
108 | @param filepath: destination filepath
109 | """
110 |
111 | try:
112 | if force_dir:
113 | utils.io.make_dir(filepath)
114 | with open(filepath, "wb") as f:
115 | _write_record(f, np.array((geom_type,), dtype="int32")) # 1 or 2 if terrain
116 | _write_record(
117 | f,
118 | np.array(
119 | (
120 | len(fds_verts) // 3,
121 | len(fds_faces) // 3,
122 | n_surf_id,
123 | len(fds_volus) // 4,
124 | ),
125 | dtype="int32",
126 | ),
127 | )
128 | _write_record(f, np.array(fds_verts, dtype="float64"))
129 | _write_record(f, np.array(fds_faces, dtype="int32"))
130 | _write_record(f, np.array(fds_surfs, dtype="int32"))
131 | _write_record(f, np.array(fds_volus, dtype="int32"))
132 | except Exception as err:
133 | raise BFException(None, f"Error writing bingeom file: <{filepath}>\n{err}")
134 |
--------------------------------------------------------------------------------
/lang/ON_HOLE.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | import logging
4 | from ..types import BFNamelistOb
5 | from .bf_object import (
6 | OP_namelist_cls,
7 | OP_ID,
8 | OP_FYI,
9 | OP_other,
10 | OP_RGB_override,
11 | OP_COLOR_override,
12 | OP_TRANSPARENCY_override,
13 | )
14 | from .OP_XB import OP_XB, OP_XB_voxel_size, OP_XB_center_voxels
15 | from .ON_MULT import OP_other_MULT_ID
16 |
17 | log = logging.getLogger(__name__)
18 |
19 |
20 | class ON_HOLE(BFNamelistOb):
21 | label = "HOLE"
22 | description = "Obstruction cutout"
23 | collection = "Obstacles"
24 | enum_id = 1009
25 | fds_label = "HOLE"
26 | bf_params = (
27 | OP_namelist_cls,
28 | OP_ID,
29 | OP_FYI,
30 | OP_XB,
31 | OP_XB_voxel_size,
32 | OP_XB_center_voxels,
33 | OP_other_MULT_ID,
34 | OP_RGB_override,
35 | OP_COLOR_override,
36 | OP_TRANSPARENCY_override,
37 | OP_other,
38 | )
39 | bf_other = {"appearance": "WIRE"}
40 |
--------------------------------------------------------------------------------
/lang/ON_INIT.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | import logging
4 | from ..types import BFNamelistOb
5 | from .bf_object import OP_namelist_cls, OP_ID, OP_FYI, OP_ID_suffix, OP_other
6 | from .OP_XB import OP_XB
7 | from .OP_XYZ import OP_XYZ
8 | from .ON_MULT import OP_other_MULT_ID
9 |
10 | log = logging.getLogger(__name__)
11 |
12 |
13 | class ON_INIT(BFNamelistOb):
14 | label = "INIT"
15 | description = "Initial condition"
16 | collection = "Domain"
17 | enum_id = 1015
18 | fds_label = "INIT"
19 | bf_params = (
20 | OP_namelist_cls,
21 | OP_ID,
22 | OP_FYI,
23 | OP_XB,
24 | OP_XYZ,
25 | OP_ID_suffix,
26 | OP_other_MULT_ID,
27 | OP_other,
28 | )
29 | bf_other = {"appearance": "WIRE"}
30 |
--------------------------------------------------------------------------------
/lang/ON_MESH/ON_MESH.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | """!
4 | MESH namelist definition
5 | """
6 |
7 | import logging
8 | from bpy.types import Object
9 | from bpy.props import IntVectorProperty
10 | from ...config import LP
11 | from ...types import BFParam, BFNamelistOb, FDSParam, FDSMulti, FDSList, BFException
12 | from ... import utils
13 | from ..bf_object import (
14 | OP_namelist_cls,
15 | OP_ID,
16 | OP_FYI,
17 | OP_other,
18 | OP_RGB_override,
19 | OP_COLOR_override,
20 | )
21 | from ..OP_XB import OP_XB_BBOX
22 | from ..ON_MULT import OP_other_MULT_ID
23 | from .calc_meshes import get_mesh_geometry
24 |
25 | log = logging.getLogger(__name__)
26 |
27 |
28 | def update_bf_mesh_nsplits(ob, context):
29 | utils.geometry.rm_geometric_cache(ob=ob)
30 | if ob.bf_has_tmp:
31 | utils.geometry.rm_tmp_objects()
32 |
33 | class OP_MESH_IJK(BFParam):
34 | label = "IJK"
35 | description = "Cell numbers along axis"
36 | fds_label = "IJK"
37 | bpy_type = Object
38 | bpy_idname = "bf_mesh_ijk"
39 | bpy_prop = IntVectorProperty
40 | bpy_default = (10, 10, 10)
41 | bpy_other = {"size": 3, "min": 1}
42 |
43 | def draw(self, context, layout):
44 | ob = self.element
45 | try:
46 | _, _, _, nmesh, nsplit, nmult, ncell_tot, ncell, cs, aspect, has_good_ijk = get_mesh_geometry(context=context, ob=ob)
47 | except BFException:
48 | col = layout.column(align=True)
49 | col.label(text="No MESH info while in Edit Mode.")
50 | else:
51 | col = layout.column(align=True)
52 | col.label(text=f"MESH Qty: {nmesh} | Splits: {nsplit} | Multiples: {nmult}")
53 | col.label(text=f"Cell Qty: {ncell_tot} (~{ncell} each)")
54 | col.label(text=f"Size: {cs[0]:.{LP}f}·{cs[1]:.{LP}f}·{cs[2]:.{LP}f}m | Aspect: {aspect:.1f} | Poisson: {has_good_ijk}")
55 |
56 | col = layout.column()
57 | row = col.row(align=True)
58 | row.prop(ob, "bf_mesh_ijk", text="IJK, Splits")
59 | sub = row.row(align=True)
60 | sub.active = ob.bf_mesh_nsplits_export
61 | sub.prop(ob, "bf_mesh_nsplits", text="")
62 |
63 | class OP_MESH_nsplits(BFParam):
64 | label = "Split IJK"
65 | description = "Evenly split IJK along each axis generating an array\nof MESH namelists, conserving the cell size"
66 | bpy_type = Object
67 | bpy_idname = "bf_mesh_nsplits"
68 | bpy_prop = IntVectorProperty
69 | bpy_default = (1, 1, 1)
70 | bpy_other = {"size": 3, "min": 1, "update": update_bf_mesh_nsplits}
71 | bpy_export = "bf_mesh_nsplits_export"
72 | bpy_export_default = False
73 |
74 | def draw(self, context, layout):
75 | layout.prop(self.element, "bf_mesh_nsplits_export", text="Split")
76 |
77 | class OP_MESH_XB_BBOX(OP_XB_BBOX):
78 | # This class implements OP_XB_BBOX
79 | # adding MESH split, IJK calculations, and MPI processes
80 |
81 | def to_fds_list(self, context) -> FDSList:
82 | ob = self.element
83 | hids, ijks, xbs, _, _, _, _, ncell, cs, aspect, has_good_ijk = get_mesh_geometry(context=context, ob=ob)
84 | msg = f" | Size: {cs[0]:.{LP}f}·{cs[1]:.{LP}f}·{cs[2]:.{LP}f}m | Aspect: {aspect:.1f} | Poisson: {has_good_ijk}"
85 | match len(xbs):
86 | case 0:
87 | return FDSList() # FIXME raise exception?
88 | case 1:
89 | msg = f"Cell Qty: {ncell}{msg}"
90 | return FDSParam(fds_label="XB", value=xbs[0], precision=LP, msgs=(msg,))
91 | case _:
92 | iterable = (
93 | (FDSParam(fds_label="ID", value=hid) for hid in hids),
94 | (FDSParam(fds_label="IJK", value=ijk, msgs=(f"Cell Qty: {ijk[0]*ijk[1]*ijk[2]}{msg}",)) for ijk in ijks),
95 | (FDSParam(fds_label="XB", value=xb, precision=LP) for xb in xbs),
96 | )
97 | return FDSMulti(iterable=iterable)
98 |
99 | class OP_MESH_MPI_PROCESS(BFParam): # only import
100 | label = "MPI_PROCESS"
101 | description = "MPI process number"
102 | fds_label = "MPI_PROCESS" # only import
103 | bpy_type = Object
104 |
105 | def draw(self, context, layout):
106 | pass
107 |
108 | def set_value(self, context, value=None):
109 | # Set the number of MPI processes from the FDS file
110 | sc = context.scene
111 | sc.bf_config_mpi_processes_export = True
112 | if value + 1 > sc.bf_config_mpi_processes:
113 | sc.bf_config_mpi_processes = value + 1
114 |
115 | class ON_MESH(BFNamelistOb):
116 | label = "MESH"
117 | description = "Domain of simulation"
118 | collection = "Domain"
119 | enum_id = 1014
120 | fds_label = "MESH"
121 | bf_params = (
122 | OP_namelist_cls,
123 | OP_ID,
124 | OP_FYI,
125 | OP_MESH_MPI_PROCESS,
126 | OP_MESH_IJK,
127 | OP_MESH_nsplits,
128 | OP_MESH_XB_BBOX,
129 | OP_other_MULT_ID,
130 | OP_RGB_override,
131 | OP_COLOR_override,
132 | OP_other,
133 | )
134 | bf_other = {"appearance": "BBOX"}
135 | bf_import_order = 30
136 |
137 | def draw_operators(self, context, layout):
138 | col = layout.column(align=True)
139 | col.operator("object.bf_set_suggested_mesh_cell_size")
140 | col.operator("object.bf_set_mesh_cell_size")
141 | col.operator("object.bf_align_to_mesh")
142 |
--------------------------------------------------------------------------------
/lang/ON_MESH/__init__.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | from .ON_MESH import ON_MESH
4 | from .align_meshes import align_meshes
5 | from .calc_meshes import get_cell_sizes, get_ijk_from_desired_cs
6 |
--------------------------------------------------------------------------------
/lang/ON_MESH/calc_meshes.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | """!
4 | Calc all MESH parameters.
5 | """
6 |
7 | from math import ceil
8 | from ... import utils
9 | from .split_mesh import split_mesh
10 | from ..ON_MULT import multiply_xbs
11 |
12 |
13 | def get_factor(n):
14 | """!Generator for prime factors of n (from http://dhananjaynene.com/)."""
15 | yield 1
16 | i = 2
17 | limit = n ** 0.5
18 | while i <= limit:
19 | if n % i == 0:
20 | yield i
21 | n = n / i
22 | limit = n ** 0.5
23 | else:
24 | i += 1
25 | if n > 1:
26 | yield int(n)
27 |
28 |
29 | def get_n_for_poisson(n):
30 | """!Get a good number for poisson solver at least bigger than n."""
31 | good = set((1, 2, 3, 5))
32 | while True:
33 | if [i for i in get_factor(n) if i not in good]:
34 | n += 1
35 | else:
36 | break
37 | return n
38 |
39 |
40 | def get_poisson_ijk(ijk):
41 | """!Get an IJK respecting the Poisson constraint, close to the current one."""
42 | return ijk[0], get_n_for_poisson(ijk[1]), get_n_for_poisson(ijk[2])
43 |
44 |
45 | def get_ijk_from_desired_cs(context, ob, desired_cs, poisson): # Used by: mesh_tools.py
46 | """!Calc MESH IJK from cell sizes."""
47 | xb = utils.geometry.get_bbox_xb(context=context, ob=ob, world=True)
48 | ijk = (
49 | round((xb[1] - xb[0]) / desired_cs[0]) or 1,
50 | round((xb[3] - xb[2]) / desired_cs[1]) or 1,
51 | round((xb[5] - xb[4]) / desired_cs[2]) or 1,
52 | )
53 | if poisson:
54 | return get_poisson_ijk(ijk)
55 | else:
56 | return ijk
57 |
58 |
59 | def get_cell_sizes(context, ob): # used by: mesh_tools.py
60 | """!Get cell sizes."""
61 | xb = utils.geometry.get_bbox_xb(context=context, ob=ob, world=True)
62 | ijk = ob.bf_mesh_ijk
63 | return (
64 | (xb[1] - xb[0]) / ijk[0],
65 | (xb[3] - xb[2]) / ijk[1],
66 | (xb[5] - xb[4]) / ijk[2],
67 | )
68 |
69 |
70 | def get_mesh_geometry(context, ob):
71 | """!Get geometry and info on generated MESH instances."""
72 | # Split
73 | hids, ijks, xbs, ncell, cs, nsplit = split_mesh(
74 | hid=ob.name,
75 | ijk=ob.bf_mesh_ijk,
76 | export=ob.bf_mesh_nsplits_export,
77 | nsplits=ob.bf_mesh_nsplits,
78 | xb=utils.geometry.get_bbox_xb(context=context, ob=ob, world=True),
79 | )
80 |
81 | # Multiply
82 | hids, xbs, _, nmult = multiply_xbs(
83 | context=context, ob=ob, hids=hids, xbs=xbs, msgs=list()
84 | )
85 | ijks *= nmult
86 |
87 | # Prepare header
88 | ijk = ob.bf_mesh_ijk
89 | ncell_tot = ijk[0] * ijk[1] * ijk[2] * nmult
90 | has_good_ijk = tuple(ijk) == get_poisson_ijk(ijk) and "Yes" or "No"
91 | aspect = get_cell_aspect(cs)
92 | nmesh = nmult * nsplit
93 | return (
94 | hids,
95 | ijks,
96 | xbs,
97 | nmesh,
98 | nsplit,
99 | nmult,
100 | ncell_tot,
101 | ncell,
102 | cs,
103 | aspect,
104 | has_good_ijk,
105 | )
106 |
107 |
108 | def get_cell_aspect(cell_sizes):
109 | """!Get max cell aspect ratio."""
110 | scs = sorted(cell_sizes)
111 | try:
112 | return max(
113 | scs[2] / scs[0],
114 | scs[2] / scs[1],
115 | scs[1] / scs[0],
116 | )
117 | except ZeroDivisionError:
118 | return 999.0
119 |
--------------------------------------------------------------------------------
/lang/ON_MESH/split_mesh.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | """!
4 | Split MESH parameters according to nsplits.
5 | """
6 |
7 |
8 | from ...types import BFException
9 |
10 | # FIXME split when there is a coarser mesh?
11 |
12 |
13 | def split_cells(ncell, nsplit):
14 | """!
15 | Split ncell cells in nsplit parts, conserving the total number ncell of cells
16 | """
17 | if ncell < nsplit * 3:
18 | return list((ncell,))
19 | base = ncell // nsplit
20 | remainder = ncell % nsplit
21 | ncells = list((base,)) * nsplit
22 | for i in range(0, remainder):
23 | ncells[i] += 1
24 | return ncells
25 |
26 |
27 | def split_mesh(hid, ijk, export, nsplits, xb):
28 | """!
29 | Split ijk cells along the axis in nsplits, calc new hids, ijks and xbs
30 | """
31 | # Init
32 | ijks, xbs = list(), list()
33 | if not export:
34 | nsplits = 1, 1, 1
35 | # Split cells along axis
36 | icells = split_cells(ijk[0], nsplits[0])
37 | jcells = split_cells(ijk[1], nsplits[1])
38 | kcells = split_cells(ijk[2], nsplits[2])
39 | ncell = icells[0] * jcells[0] * kcells[0]
40 | expected_nsplits = nsplits[0] * nsplits[1] * nsplits[2]
41 | # Prepare new mesh ijks and origins (in cell number)
42 | corigins = list()
43 | corigin_i, corigin_j, corigin_k = 0, 0, 0
44 | for i in icells:
45 | for j in jcells:
46 | for k in kcells:
47 | ijks.append((i, j, k))
48 | corigins.append((corigin_i, corigin_j, corigin_k))
49 | corigin_k += k
50 | corigin_k = 0
51 | corigin_j += j
52 | corigin_j = 0
53 | corigin_i += i
54 | # Calc cell sizes along axis
55 | cs = (
56 | (xb[1] - xb[0]) / ijk[0],
57 | (xb[3] - xb[2]) / ijk[1],
58 | (xb[5] - xb[4]) / ijk[2],
59 | )
60 | # Prepare xbs
61 | # corigin (0,0,0) is at coordinates xb[0], xb[2], xb [4]
62 | for i, corigin in enumerate(corigins):
63 | xbs.append(
64 | (
65 | xb[0] + corigin[0] * cs[0],
66 | xb[0] + (corigin[0] + ijks[i][0]) * cs[0],
67 | xb[2] + corigin[1] * cs[1],
68 | xb[2] + (corigin[1] + ijks[i][1]) * cs[1],
69 | xb[4] + corigin[2] * cs[2],
70 | xb[4] + (corigin[2] + ijks[i][2]) * cs[2],
71 | )
72 | )
73 | # Prepare hids
74 | nsplit = len(xbs)
75 | if nsplit > 1:
76 | hids = tuple(f"{hid}_s{i}" for i in range(len(xbs)))
77 | else:
78 | hids = tuple((hid,))
79 | if nsplit != expected_nsplits:
80 | raise BFException(sender=None, msg="Too much splitting, not enough cells.")
81 | return hids, ijks, xbs, ncell, cs, nsplit
82 |
--------------------------------------------------------------------------------
/lang/ON_MOVE/ON_MOVE.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | import logging
4 | from bpy.types import Object
5 | from bpy.props import FloatProperty, IntProperty, FloatVectorProperty
6 | from ...config import T34_P
7 | from ...types import BFNamelist, BFParam, BFNotImported, FDSList, FDSNamelist, FDSParam
8 | from ... import utils
9 | from .t34 import calc_t34, calc_bl_matrix
10 |
11 | log = logging.getLogger(__name__)
12 |
13 |
14 | class OP_MOVE_ID(BFParam):
15 | label = "ID"
16 | description = "Geometric transformation name"
17 | fds_label = "ID"
18 | bpy_type = Object
19 |
20 | def get_value(self, context):
21 | return f"{self.element.name}_move"
22 |
23 | def set_value(self, context, value=None):
24 | pass
25 |
26 |
27 | class OP_MOVE_T34(BFParam):
28 | label = "T34"
29 | description = "Geometric transformation 3x4 matrix"
30 | fds_label = "T34"
31 | bpy_type = Object
32 | bpy_other = {"precision": T34_P}
33 |
34 | def get_value(self, context):
35 | return calc_t34(m=self.element.matrix_world)
36 |
37 | def set_value(self, context, value=None):
38 | utils.geometry.transform_ob(
39 | ob=self.element,
40 | m=calc_bl_matrix(t34=value),
41 | force_othogonal=False,
42 | )
43 |
44 |
45 | class ON_MOVE(BFNamelist): # not in namelist menu
46 | label = "MOVE"
47 | description = "Geometric Transformation"
48 | bpy_type = Object
49 | enum_id = None
50 | fds_label = "MOVE"
51 | bf_params = (
52 | OP_MOVE_ID,
53 | OP_MOVE_T34,
54 | )
55 |
56 | def from_fds_list(self, context, fds_list, fds_label=None):
57 | # Read fds_params
58 | ps = { # label: default value
59 | "T34": None,
60 | "X0": 0.0,
61 | "Y0": 0.0,
62 | "Z0": 0.0,
63 | "AXIS": (0.0, 0.0, 1.0),
64 | "ROTATION_ANGLE": 0.0,
65 | "SCALE": None,
66 | "SCALEX": 1.0,
67 | "SCALEY": 1.0,
68 | "SCALEZ": 1.0,
69 | "DX": 0.0,
70 | "DY": 0.0,
71 | "DZ": 0.0,
72 | }
73 | for fds_label in ps:
74 | fds_param = fds_list.get_fds_param(fds_label=fds_label, remove=True)
75 | if fds_param:
76 | ps[fds_label] = fds_param.get_value() # assign value
77 | m = calc_bl_matrix(
78 | t34=ps["T34"],
79 | dx=ps["DX"],
80 | dy=ps["DY"],
81 | dz=ps["DZ"],
82 | scale=ps["SCALE"],
83 | scalex=ps["SCALEX"],
84 | scaley=ps["SCALEY"],
85 | scalez=ps["SCALEZ"],
86 | x0=ps["X0"],
87 | y0=ps["Y0"],
88 | z0=ps["Z0"],
89 | rotation_angle=ps["ROTATION_ANGLE"],
90 | axis=ps["AXIS"],
91 | )
92 | utils.geometry.transform_ob(ob=self.element, m=m, force_othogonal=False)
93 |
94 |
95 | # Called by other namelists
96 | # that support a MOVE_ID
97 | # and the relative MOVE namelist
98 | # (See also: ON_MULT)
99 |
100 |
101 | class OP_other_MOVE_ID(BFParam):
102 | label = "MOVE_ID"
103 | description = "Reference to geometric transformation"
104 | fds_label = "MOVE_ID"
105 | bpy_type = Object
106 |
107 | def get_value(self, context):
108 | return f"{self.element.name}_move"
109 |
110 | def set_value(self, context, value=None):
111 | # Get required MOVE parameters from dict created by SN_MOVE
112 | try:
113 | f90_params = context.scene["bf_move_coll"][value]
114 | except KeyError as err:
115 | raise BFNotImported(self, f"Missing MOVE ID='{value}'")
116 | ON_MOVE(element=self.element).from_fds_list(
117 | context=context,
118 | fds_list=FDSList(f90_params=f90_params),
119 | )
120 |
121 | def get_active(self, context): # set in namelists that use it (eg. ON_GEOM)
122 | return False
123 |
124 | def get_exported(self, context):
125 | return self.get_active(context)
126 |
127 | def to_fds_list(self, context) -> FDSList:
128 | if self.get_exported(context):
129 | return FDSList(
130 | iterable=(
131 | super().to_fds_list(context),
132 | ON_MOVE(element=self.element).to_fds_list(context),
133 | )
134 | )
135 | else:
136 | return FDSList()
137 |
138 | def draw(self, context, layout): # only label
139 | row = layout.split(factor=0.4)
140 | active = self.get_active(context)
141 | row.active = active
142 | row.alignment = "RIGHT"
143 | row.label(text=self.label)
144 | row.alignment = "EXPAND"
145 | row.label(icon=active and "LINKED" or "UNLINKED")
146 |
--------------------------------------------------------------------------------
/lang/ON_MOVE/__init__.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | from .ON_MOVE import ON_MOVE, OP_other_MOVE_ID
4 |
--------------------------------------------------------------------------------
/lang/ON_MOVE/t34.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | import logging
4 | from math import radians
5 | from mathutils import Matrix, Vector
6 |
7 | log = logging.getLogger(__name__)
8 |
9 |
10 | # As a reminder:
11 | # context.object.matrix_world =
12 | # Matrix(((1.0, 0.0, 0.0, 1.0),
13 | # (0.0, 1.0, 0.0, 2.0),
14 | # (0.0, 0.0, 1.0, 3.0),
15 | # (0.0, 0.0, 0.0, 1.0)))
16 | # context.object.matrix_world[1][3] = 2.
17 | # t34 = 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 2.0, 3.0
18 |
19 | # m = mathutils.Matrix.Rotation(0.7, 4, "Z")
20 | # bpy.data.objects["Cube"].matrix_world @= m # apply rotation
21 | # bpy.data.objects["Cube"].matrix_world @= m.copy().invert() # apply inverse rotation
22 |
23 |
24 | def calc_t34(m) -> tuple:
25 | """!
26 | Transform the Blender transformation matrix into the FDS MOVE T34 notation.
27 | """
28 | return tuple(m[i][j] for j in range(4) for i in range(3))
29 |
30 |
31 | def calc_bl_matrix(
32 | t34=None,
33 | dx=0.0,
34 | dy=0.0,
35 | dz=0.0,
36 | scale=None,
37 | scalex=1.0,
38 | scaley=1.0,
39 | scalez=1.0,
40 | x0=0.0,
41 | y0=0.0,
42 | z0=0.0,
43 | rotation_angle=0.0,
44 | axis=(0.0, 0.0, 1.0),
45 | ) -> Matrix:
46 | """!
47 | Transform the FDS MOVE notation into a Blender 4x4 transformation matrix.
48 | """
49 | if t34:
50 | m = list(tuple(t34[j * 3 + i] for j in range(4)) for i in range(3))
51 | m.append((0.0, 0.0, 0.0, 1.0)) # add last row
52 | m = Matrix(m)
53 | else:
54 | if scale:
55 | scalex = scaley = scalez = scale
56 | m = (
57 | Matrix().Translation((dx, dy, dz)) # last applied
58 | @ Matrix().Scale(scalex, 4, (1, 0, 0))
59 | @ Matrix().Scale(scaley, 4, (0, 1, 0))
60 | @ Matrix().Scale(scalez, 4, (0, 0, 1))
61 | @ Matrix().Translation((x0, y0, z0))
62 | @ Matrix().Rotation(radians(rotation_angle), 4, Vector(axis))
63 | @ Matrix().Translation((-x0, -y0, -z0)) # first applied
64 | )
65 | return m
66 |
--------------------------------------------------------------------------------
/lang/ON_MULT/__init__.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | from .ON_MULT import ON_MULT, OP_other_MULT_ID
4 | from .multiply import multiply_xbs
5 |
--------------------------------------------------------------------------------
/lang/ON_MULT/multiply.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | from ... import utils
4 | from ..OP_XB.xbs_to_ob import xbs_to_ob
5 |
6 |
7 | def multiply_xbs(context, ob, hids, xbs, msgs):
8 | """!
9 | Return lists of xb and their hid as multiplied by FDS MULT from ob.
10 | """
11 | # Calc, fast track
12 | if not ob.bf_mult_export:
13 | nmult = 1
14 | return hids, xbs, msgs, nmult
15 |
16 | # Init the rest
17 | dxb = ob.bf_mult_dxb
18 | d = (ob.bf_mult_dx, ob.bf_mult_dy, ob.bf_mult_dz)
19 | d0 = (ob.bf_mult_dx0, ob.bf_mult_dy0, ob.bf_mult_dz0)
20 | lower = (
21 | ob.bf_mult_i_lower,
22 | ob.bf_mult_j_lower,
23 | ob.bf_mult_k_lower,
24 | ob.bf_mult_n_lower,
25 | )
26 | lower_skip = (
27 | ob.bf_mult_i_lower_skip,
28 | ob.bf_mult_j_lower_skip,
29 | ob.bf_mult_k_lower_skip,
30 | ob.bf_mult_n_lower_skip,
31 | )
32 | upper = (
33 | ob.bf_mult_i_upper,
34 | ob.bf_mult_j_upper,
35 | ob.bf_mult_k_upper,
36 | ob.bf_mult_n_upper,
37 | )
38 | upper_skip = (
39 | ob.bf_mult_i_upper_skip,
40 | ob.bf_mult_j_upper_skip,
41 | ob.bf_mult_k_upper_skip,
42 | ob.bf_mult_n_upper_skip,
43 | )
44 |
45 | # Calc, slow track
46 | multi_xbs, multi_hids = list(), list()
47 | for xb, hid in zip(xbs, hids):
48 | new_xbs, new_hids = multiply_xb(
49 | xb,
50 | hid,
51 | dxb,
52 | d, # dx,dy,dz
53 | d0, # dx0,dy0,dz0,
54 | lower, # i,j,k,n
55 | lower_skip, # i,j,k,n
56 | upper, # i,j,k,n
57 | upper_skip,
58 | )
59 | multi_xbs.extend(new_xbs)
60 | multi_hids.extend(new_hids)
61 | nmult = len(new_xbs)
62 |
63 | # Msgs
64 | if nmult > 1:
65 | msgs.append(f"Multiples: {nmult}")
66 |
67 | # Generate multiples
68 | return multi_hids, multi_xbs, msgs, nmult
69 |
70 |
71 | def multiply_xb(
72 | xb,
73 | hid,
74 | dxb,
75 | d, # dx,dy,dz
76 | d0, # dx0,dy0,dz0,
77 | lower, # i,j,k,n
78 | lower_skip, # i,j,k,n
79 | upper, # i,j,k,n
80 | upper_skip, # i,j,k,n
81 | ):
82 | """!
83 | Return lists of xb and their hid as multiplied by FDS MULT.
84 | """
85 | xbs, hids = list(), list()
86 | dx, dy, dz = d
87 | dx0, dy0, dz0 = d0
88 | i_lower, j_lower, k_lower, n_lower = lower
89 | i_lower_skip, j_lower_skip, k_lower_skip, n_lower_skip = lower_skip
90 | i_upper, j_upper, k_upper, n_upper = upper
91 | i_upper_skip, j_upper_skip, k_upper_skip, n_upper_skip = upper_skip
92 | has_skip = False
93 | if any(dxb):
94 | # N, DXB: OBST MULT example
95 | if n_lower_skip >= n_lower or n_upper_skip <= n_upper:
96 | has_skip = True
97 | for n in range(n_lower, n_upper + 1):
98 | if has_skip and n_lower_skip <= n <= n_upper_skip:
99 | continue
100 | xbs.append(
101 | (
102 | xb[0] + dx0 + n * dxb[0],
103 | xb[1] + dx0 + n * dxb[1],
104 | xb[2] + dy0 + n * dxb[2],
105 | xb[3] + dy0 + n * dxb[3],
106 | xb[4] + dz0 + n * dxb[4],
107 | xb[5] + dz0 + n * dxb[5],
108 | )
109 | )
110 | hids.append(f"{hid}_{n}")
111 | else:
112 | # I,J,K: MESH refining example
113 | if (
114 | i_lower_skip >= i_lower
115 | or j_lower_skip >= j_lower
116 | or k_lower_skip >= k_lower
117 | or i_upper_skip <= i_upper
118 | or j_upper_skip <= j_upper
119 | or k_upper_skip <= k_upper
120 | ):
121 | has_skip = True
122 | i_lower_skip = max(i_lower, i_lower_skip)
123 | for i in range(i_lower, i_upper + 1):
124 | for j in range(j_lower, j_upper + 1):
125 | for k in range(k_lower, k_upper + 1):
126 | if has_skip and (
127 | i_lower_skip <= i <= i_upper_skip
128 | and j_lower_skip <= j <= j_upper_skip
129 | and k_lower_skip <= k <= k_upper_skip
130 | ):
131 | continue
132 | xbs.append(
133 | (
134 | xb[0] + dx0 + i * dx,
135 | xb[1] + dx0 + i * dx,
136 | xb[2] + dy0 + j * dy,
137 | xb[3] + dy0 + j * dy,
138 | xb[4] + dz0 + k * dz,
139 | xb[5] + dz0 + k * dz,
140 | )
141 | )
142 | hids.append(f"{hid}_i{i}_j{j}_k{k}")
143 | return xbs, hids
144 |
--------------------------------------------------------------------------------
/lang/ON_OBST.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | import logging
4 | from ..types import BFNamelistOb
5 | from .bf_object import (
6 | OP_namelist_cls,
7 | OP_ID,
8 | OP_FYI,
9 | OP_ID_suffix,
10 | OP_other,
11 | OP_RGB_override,
12 | OP_COLOR_override,
13 | OP_TRANSPARENCY_override,
14 | )
15 | from .OP_SURF_ID import OP_SURF_ID
16 | from .OP_XB import OP_XB, OP_XB_voxel_size, OP_XB_center_voxels
17 | from .ON_MULT import OP_other_MULT_ID
18 |
19 | log = logging.getLogger(__name__)
20 |
21 |
22 | class ON_OBST(BFNamelistOb):
23 | label = "OBST"
24 | description = "Obstruction"
25 | collection = "Obstacles"
26 | enum_id = 1000
27 | fds_label = "OBST"
28 | bf_params = (
29 | OP_namelist_cls,
30 | OP_ID,
31 | OP_FYI,
32 | OP_SURF_ID,
33 | OP_XB,
34 | OP_XB_voxel_size,
35 | OP_XB_center_voxels,
36 | OP_ID_suffix,
37 | OP_other_MULT_ID,
38 | OP_RGB_override,
39 | OP_COLOR_override,
40 | OP_TRANSPARENCY_override,
41 | OP_other,
42 | )
43 | bf_import_order = 100
44 |
--------------------------------------------------------------------------------
/lang/ON_PROF.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | import logging
4 | from ..types import BFNamelistOb
5 | from .bf_object import OP_namelist_cls, OP_ID, OP_FYI, OP_ID_suffix, OP_other
6 | from .ON_DEVC import OP_DEVC_QUANTITY
7 | from .OP_XYZ import OP_XYZ
8 |
9 | log = logging.getLogger(__name__)
10 |
11 |
12 | class ON_PROF(BFNamelistOb):
13 | label = "PROF"
14 | description = "Wall profile output"
15 | collection = "Output"
16 | enum_id = 1013
17 | fds_label = "PROF"
18 | bf_params = (
19 | OP_namelist_cls,
20 | OP_ID,
21 | OP_FYI,
22 | OP_DEVC_QUANTITY,
23 | OP_XYZ,
24 | OP_ID_suffix,
25 | OP_other,
26 | )
27 | bf_other = {"appearance": "WIRE"}
28 |
--------------------------------------------------------------------------------
/lang/ON_SLCF.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | import logging, re
4 | from bpy.types import Object
5 | from bpy.props import BoolProperty
6 | from ..types import BFParam, BFNamelistOb, BFException
7 | from .bf_object import OP_namelist_cls, OP_ID, OP_FYI, OP_ID_suffix, OP_other
8 | from .ON_DEVC import OP_DEVC_QUANTITY
9 | from .OP_XB import OP_XB, OP_XB_voxel_size, OP_XB_center_voxels
10 | from .OP_PB import OP_PB, OP_PBX, OP_PBY, OP_PBZ
11 |
12 | log = logging.getLogger(__name__)
13 |
14 |
15 | class OP_SLCF_VECTOR(BFParam):
16 | label = "VECTOR"
17 | description = "Create animated vectors"
18 | fds_label = "VECTOR"
19 | fds_default = False
20 | bpy_type = Object
21 | bpy_prop = BoolProperty
22 | bpy_idname = "bf_slcf_vector"
23 |
24 |
25 | class OP_SLCF_CELL_CENTERED(BFParam):
26 | label = "CELL_CENTERED"
27 | description = "Output the actual cell-centered data with no averaging"
28 | fds_label = "CELL_CENTERED"
29 | fds_default = False
30 | bpy_type = Object
31 | bpy_prop = BoolProperty
32 | bpy_idname = "bf_slcf_cell_centered"
33 |
34 |
35 | class ON_SLCF(BFNamelistOb):
36 | label = "SLCF"
37 | description = "Slice file"
38 | collection = "Output"
39 | enum_id = 1012
40 | fds_label = "SLCF"
41 | bf_params = (
42 | OP_namelist_cls,
43 | OP_ID,
44 | OP_FYI,
45 | OP_DEVC_QUANTITY,
46 | OP_SLCF_VECTOR,
47 | OP_SLCF_CELL_CENTERED,
48 | OP_XB,
49 | OP_XB_voxel_size,
50 | OP_XB_center_voxels,
51 | OP_PB,
52 | OP_PBX,
53 | OP_PBY,
54 | OP_PBZ,
55 | OP_ID_suffix,
56 | OP_other,
57 | )
58 | bf_other = {"appearance": "WIRE"}
59 |
--------------------------------------------------------------------------------
/lang/ON_VENT.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | import logging, bpy
4 | from bpy.types import Object
5 | from bpy.props import PointerProperty
6 | from ..types import BFParam, BFNamelistOb, BFException
7 | from .bf_object import (
8 | OP_namelist_cls,
9 | OP_ID,
10 | OP_FYI,
11 | OP_ID_suffix,
12 | OP_other,
13 | OP_RGB_override,
14 | OP_COLOR_override,
15 | OP_TRANSPARENCY_override,
16 | )
17 | from .OP_SURF_ID import OP_SURF_ID
18 | from .OP_XB import OP_XB, OP_XB_voxel_size, OP_XB_center_voxels
19 | from .OP_XYZ import OP_XYZ
20 | from .OP_PB import OP_PB, OP_PBX, OP_PBY, OP_PBZ
21 | from .ON_MULT import OP_other_MULT_ID
22 |
23 | log = logging.getLogger(__name__)
24 |
25 |
26 | class OP_VENT_OBST_ID(BFParam):
27 | label = "OBST_ID"
28 | description = "Specify OBST on which projecting the condition"
29 | fds_label = "OBST_ID"
30 | bpy_type = Object
31 | bpy_prop = PointerProperty
32 | bpy_idname = "bf_vent_obst_id"
33 | bpy_other = {"type": Object}
34 |
35 | def get_value(self, context):
36 | if self.element.bf_vent_obst_id:
37 | return self.element.bf_vent_obst_id.name
38 |
39 | def set_value(self, context, value):
40 | if value:
41 | ob = bpy.data.objects.get(value)
42 | if ob:
43 | self.element.bf_vent_obst_id = ob
44 | else:
45 | raise BFException(self, f"Object <{value}> not found")
46 | else:
47 | self.element.bf_vent_obst_id = None
48 |
49 |
50 | class ON_VENT(BFNamelistOb):
51 | label = "VENT"
52 | description = "Boundary condition patch"
53 | collection = "Obstacles"
54 | enum_id = 1010
55 | fds_label = "VENT"
56 | bf_params = (
57 | OP_namelist_cls,
58 | OP_ID,
59 | OP_FYI,
60 | OP_SURF_ID,
61 | OP_VENT_OBST_ID,
62 | OP_XB,
63 | OP_XB_voxel_size,
64 | OP_XB_center_voxels,
65 | OP_XYZ,
66 | OP_PB,
67 | OP_PBX,
68 | OP_PBY,
69 | OP_PBZ,
70 | OP_ID_suffix,
71 | OP_other_MULT_ID,
72 | OP_RGB_override,
73 | OP_COLOR_override,
74 | OP_TRANSPARENCY_override,
75 | OP_other,
76 | )
77 | bf_import_order = 300
78 |
--------------------------------------------------------------------------------
/lang/ON_ZONE.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | import logging
4 | from ..types import BFNamelistOb
5 | from .bf_object import OP_namelist_cls, OP_ID, OP_FYI, OP_other
6 | from .OP_XYZ import OP_XYZ
7 |
8 | log = logging.getLogger(__name__)
9 |
10 |
11 | class ON_ZONE(BFNamelistOb):
12 | label = "ZONE"
13 | description = "Pressure zone"
14 | collection = "Domain"
15 | enum_id = 1016
16 | fds_label = "ZONE"
17 | bf_params = OP_namelist_cls, OP_ID, OP_FYI, OP_XYZ, OP_other
18 | bf_other = {"appearance": "WIRE"}
19 |
--------------------------------------------------------------------------------
/lang/ON_other.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | import logging, re
4 | from bpy.types import Object
5 | from bpy.props import StringProperty
6 | from ..types import BFParam, BFNamelistOb, BFException
7 | from .bf_object import (
8 | OP_namelist_cls,
9 | OP_ID,
10 | OP_FYI,
11 | OP_ID_suffix,
12 | OP_other,
13 | OP_RGB_override,
14 | OP_COLOR_override,
15 | OP_TRANSPARENCY_override,
16 | )
17 | from .OP_SURF_ID import OP_SURF_ID
18 | from .OP_XB import OP_XB, OP_XB_voxel_size, OP_XB_center_voxels
19 | from .OP_XYZ import OP_XYZ
20 | from .OP_PB import OP_PB, OP_PBX, OP_PBY, OP_PBZ
21 | from .ON_MULT import OP_other_MULT_ID
22 |
23 | log = logging.getLogger(__name__)
24 |
25 |
26 | class OP_other_namelist(BFParam):
27 | label = "Label"
28 | description = "Other namelist label, eg "
29 | bpy_type = Object
30 | bpy_prop = StringProperty
31 | bpy_idname = "bf_other_namelist"
32 | bpy_default = "ABCD"
33 | bpy_other = {"maxlen": 4}
34 |
35 | def check(self, context):
36 | if not re.match("^[A-Z0-9_]{4}$", self.element.bf_other_namelist):
37 | raise BFException(
38 | self,
39 | f"Malformed other namelist label <{self.element.bf_other_namelist}>",
40 | )
41 |
42 |
43 | class ON_other(BFNamelistOb):
44 | label = "Other"
45 | description = "Other namelist"
46 | enum_id = 1007
47 | bf_params = (
48 | OP_namelist_cls,
49 | OP_other_namelist,
50 | OP_ID,
51 | OP_FYI,
52 | OP_SURF_ID,
53 | OP_XB,
54 | OP_XB_voxel_size,
55 | OP_XB_center_voxels,
56 | OP_XYZ,
57 | OP_PB,
58 | OP_PBX,
59 | OP_PBY,
60 | OP_PBZ,
61 | OP_ID_suffix,
62 | OP_other_MULT_ID,
63 | OP_RGB_override,
64 | OP_COLOR_override,
65 | OP_TRANSPARENCY_override,
66 | OP_other,
67 | )
68 |
69 | @property
70 | def fds_label(self):
71 | return self.element.bf_other_namelist
72 |
--------------------------------------------------------------------------------
/lang/OP_PB/OP_PB.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | import logging
4 | from bpy.types import Object
5 | from bpy.props import EnumProperty, BoolProperty
6 | from ...config import LP
7 | from ...types import BFParam, FDSParam, FDSList, FDSMulti
8 | from ... import utils
9 | from .ob_to_pbs import ob_to_pbs
10 | from .pbs_to_ob import pbs_to_ob
11 |
12 | log = logging.getLogger(__name__)
13 |
14 |
15 | def update_bf_pb(ob, context):
16 | # Rm tmp geometry and cache
17 | utils.geometry.rm_geometric_cache(ob=ob)
18 | if ob.bf_has_tmp:
19 | utils.geometry.rm_tmp_objects()
20 | # Prevent double multiparam
21 | if ob.bf_pb == "PLANES" and ob.bf_pb_export:
22 | if ob.bf_xb in ("VOXELS", "FACES", "PIXELS", "EDGES"):
23 | ob["bf_xb_export"] = False # prevent multiple update
24 | if ob.bf_xyz == "VERTICES":
25 | ob["bf_xyz_export"] = False
26 | return
27 |
28 |
29 | class OP_PB(BFParam):
30 | label = "PBX, PBY, PBZ"
31 | description = "Export as planes"
32 | fds_label = None # used for importing
33 | bpy_type = Object
34 | bpy_idname = "bf_pb"
35 | bpy_prop = EnumProperty
36 | bpy_other = {
37 | "update": update_bf_pb,
38 | "items": (
39 | ("PLANES", "Planes", "Planes, one for each face of this object", 100),
40 | ),
41 | }
42 | bpy_export = "bf_pb_export"
43 | bpy_export_default = False
44 |
45 | def to_fds_list(self, context) -> FDSList:
46 | if not self.get_exported(context):
47 | return FDSList()
48 | ob = self.element
49 | hids, pbs, msgs = ob_to_pbs(context, ob, bf_pb=ob.bf_pb)
50 | match len(pbs):
51 | case 0:
52 | return FDSList()
53 | case 1:
54 | return FDSParam(fds_label=pbs[0][0], value=pbs[0][1], precision=LP)
55 | case _:
56 | iterable=(
57 | (FDSParam(fds_label="ID", value=hid) for hid in hids),
58 | (FDSParam(fds_label=axis, value=pb, precision=LP) for axis, pb in pbs),
59 | )
60 | return FDSMulti(iterable=iterable, msgs=msgs)
61 |
62 | def set_value(self, context, value=None):
63 | bf_pb = pbs_to_ob(
64 | context=context,
65 | ob=self.element,
66 | pbs=((self.fds_label, value),),
67 | set_origin=True,
68 | add=True,
69 | )
70 | self.element.bf_pb = bf_pb
71 | self.element.bf_pb_export = True
72 |
73 |
74 | class OP_PBX(OP_PB): # only for importing
75 | fds_label = "PBX"
76 | bpy_prop = None # already defined
77 |
78 | def draw(self, context, layout):
79 | pass
80 |
81 | def get_exported(self, context):
82 | return False
83 |
84 |
85 | class OP_PBY(OP_PBX): # only for importing
86 | fds_label = "PBY"
87 |
88 |
89 | class OP_PBZ(OP_PBX): # only for importing
90 | fds_label = "PBZ"
91 |
--------------------------------------------------------------------------------
/lang/OP_PB/__init__.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | from .OP_PB import OP_PB, OP_PBX, OP_PBY, OP_PBZ
4 | from .ob_to_pbs import ob_to_pbs
5 | from .pbs_to_ob import pbs_to_ob
6 |
--------------------------------------------------------------------------------
/lang/OP_PB/ob_to_pbs.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | """!
4 | BlenderFDS, translate Blender object geometry to FDS PB notation.
5 | """
6 |
7 | import logging
8 | from ... import config
9 | from ...types import BFException
10 | from ..OP_XB.ob_to_xbs import _ob_to_xbs_faces
11 |
12 | log = logging.getLogger(__name__)
13 |
14 | EFD = config.FLAT_DIFFERENCE
15 |
16 | def _ob_to_pbs_planes(context, ob, world) -> tuple((list, list)):
17 | """!
18 | Transform Object faces to pbs notation.
19 | @param context: the Blender context.
20 | @param ob: the Blender object.
21 | @param world: True to return the object in world coordinates.
22 | @return the pbs notation and messages.
23 | """
24 | # pbs is: ("PBX", 3.5), ("PBX", 4.), ("PBY", .5) ...
25 | pbs = list()
26 | xbs, _ = _ob_to_xbs_faces(context, ob, world)
27 |
28 | # For each face build a plane...
29 | for xb in xbs:
30 | if abs(xb[1] - xb[0]) < EFD:
31 | pbs.append(("PBX", xb[0]))
32 | elif abs(xb[3] - xb[2]) < EFD:
33 | pbs.append(("PBY", xb[2]))
34 | elif abs(xb[5] - xb[4]) < EFD:
35 | pbs.append(("PBZ", xb[4]))
36 | else:
37 | raise ValueError(
38 | "BFDS: Building planes impossible, problem in ob_to_xbs_faces."
39 | )
40 | pbs.sort()
41 | if not pbs:
42 | raise BFException(ob, "PB*: No exported planes!")
43 | msgs = list((f"PB* Planes: {len(pbs)}",))
44 | return pbs, msgs
45 |
46 |
47 | def ob_to_pbs(context, ob, bf_pb, world=True) -> tuple((list, list)):
48 | """!
49 | Transform Object geometry according to bf_pb to pbs notation.
50 | @param context: the Blender context.
51 | @param ob: the Blender object.
52 | @param bf_pb: string in (PLANES,)
53 | @param world: True to return the object in world coordinates.
54 | @return the pbs notation and messages.
55 | """
56 | # Calc pbs and msgs
57 | # pbs is: ("PBX", 3.5), ("PBX", 4.), ("PBY", .5) ...
58 | pbs, msgs = tuple(), tuple()
59 | if ob.bf_pb_export:
60 | pbs, msgs = _ob_to_pbs_planes(context, ob, world=world)
61 |
62 | # Calc hids
63 | n = ob.name
64 | match ob.bf_id_suffix:
65 | case "IDI":
66 | hids = (f"{n}_{i}" for i, _ in enumerate(pbs))
67 | case _:
68 | hids = (
69 | {"PBX": f"{n}_x{pb:+.3f}", "PBY": f"{n}_y{pb:+.3f}", "PBZ": f"{n}_z{pb:+.3f}"}[axis]
70 | for axis, pb in pbs
71 | )
72 |
73 | return tuple(hids), tuple(pbs), tuple(msgs)
74 |
--------------------------------------------------------------------------------
/lang/OP_PB/pbs_to_ob.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | """!
4 | BlenderFDS, translate geometry from FDS PB notation to a Blender mesh.
5 | """
6 |
7 | import logging
8 | from ..OP_XB.xbs_to_ob import xbs_to_ob
9 |
10 | log = logging.getLogger(__name__)
11 |
12 |
13 | def pbs_to_ob(context, ob, pbs, add=False, set_origin=False) -> str():
14 | """!
15 | Import pbs planes (("PBX",x3,), ("PBX",x7,), ("PBY",y9,), ...) into existing Blender Object.
16 | @param context: the Blender context.
17 | @param ob: the Blender Object.
18 | @param pbs: the pbs planes.
19 | @param add: add to existing Mesh.
20 | @param set_origin: set reasonable origin.
21 | @return "PLANES"
22 | """
23 | if not pbs:
24 | return "PLANES"
25 | # Convert the geometry
26 | xbs, sl = list(), context.scene.unit_settings.scale_length
27 | for pb in pbs:
28 | match pb[0]:
29 | case "PBX":
30 | xbs.append((pb[1], pb[1], -sl, +sl, -sl, +sl)) # PBX is 0
31 | case "PBY":
32 | xbs.append((-sl, +sl, pb[1], pb[1], -sl, +sl)) # PBY is 1
33 | case "PBZ":
34 | xbs.append((-sl, +sl, -sl, +sl, pb[1], pb[1])) # PBZ is 2
35 | case _:
36 | raise AssertionError(f"Unrecognized PB* <{pb}>")
37 | # Inject geometry
38 | xbs_to_ob(context=context, ob=ob, xbs=xbs, bf_xb="FACES", add=add, set_origin=set_origin)
39 | return "PLANES"
40 |
--------------------------------------------------------------------------------
/lang/OP_SURF_ID.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | import logging, bpy
4 | from bpy.types import Object
5 | from ..types import BFParam, BFException
6 |
7 | log = logging.getLogger(__name__)
8 |
9 |
10 | class OP_SURF_ID(BFParam):
11 | label = "SURF_ID"
12 | description = "Reference to SURF"
13 | fds_label = "SURF_ID"
14 | bpy_type = Object
15 | bpy_idname = "active_material"
16 | bpy_export = "bf_surf_id_export"
17 | bpy_export_default = False
18 |
19 | def get_value(self, context):
20 | if self.element.active_material:
21 | return self.element.active_material.name
22 |
23 | def set_value(self, context, value):
24 | ob = self.element
25 | if value is None:
26 | ob.active_material = None
27 | else:
28 | try: # TODO use utils
29 | ma = bpy.data.materials.get(value)
30 | except IndexError:
31 | raise BFException(self, f"Blender Material <{value}> does not exists")
32 | else:
33 | ob.active_material = ma
34 | ob.bf_surf_id_export = True
35 |
36 | def get_exported(self, context):
37 | ob = self.element
38 | return ob.bf_surf_id_export and bool(ob.active_material)
39 |
40 | def check(self, context):
41 | ob = self.element
42 | if (
43 | ob.bf_surf_id_export
44 | and ob.active_material
45 | and not ob.active_material.bf_surf_export
46 | ):
47 | raise BFException(
48 | self, f"Referenced SURF <{ob.active_material.name}> not exported"
49 | )
50 |
51 | def draw_operators(self, context, layout):
52 | layout.operator("object.bf_clean_ma_slots", icon="BRUSH_DATA", text="")
53 |
--------------------------------------------------------------------------------
/lang/OP_XB/OP_XB.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | import logging
4 | from bpy.types import Object
5 | from bpy.props import EnumProperty, BoolProperty, FloatProperty
6 | from ...config import LP
7 | from ...types import BFParam, FDSParam, FDSList, FDSMulti
8 | from ... import utils
9 | from .ob_to_xbs import ob_to_xbs
10 | from .xbs_to_ob import xbs_to_ob
11 |
12 | log = logging.getLogger(__name__)
13 |
14 |
15 | def update_bf_xb(ob, context):
16 | # Rm tmp geometry and cache
17 | utils.geometry.rm_geometric_cache(ob=ob)
18 | if ob.bf_has_tmp:
19 | utils.geometry.rm_tmp_objects()
20 | # Prevent double multiparam
21 | if ob.bf_xb in ("VOXELS", "FACES", "PIXELS", "EDGES") and ob.bf_xb_export:
22 | if ob.bf_xyz == "VERTICES":
23 | ob["bf_xyz_export"] = False # prevent multiple update
24 | if ob.bf_pb == "PLANES":
25 | ob["bf_pb_export"] = False
26 | return
27 |
28 |
29 | class OP_XB_voxel_size(BFParam):
30 | label = "Voxel/Pixel Size"
31 | description = "Voxel/pixel size for the current Object"
32 | bpy_type = Object
33 | bpy_idname = "bf_xb_voxel_size"
34 | bpy_prop = FloatProperty
35 | bpy_default = 0.1
36 | bpy_other = {
37 | "step": 1.0,
38 | "precision": LP,
39 | "min": 0.001,
40 | "max": 20.0,
41 | "unit": "LENGTH",
42 | "update": update_bf_xb,
43 | }
44 | bpy_export = "bf_xb_custom_voxel"
45 | bpy_export_default = False
46 |
47 | def get_active(self, context):
48 | ob = self.element
49 | return ob.bf_xb_export and ob.bf_xb in ("VOXELS", "PIXELS")
50 |
51 |
52 | class OP_XB_center_voxels(BFParam):
53 | label = "Center Voxels/Pixels"
54 | description = "Center voxels/pixels to Object bounding box"
55 | bpy_type = Object
56 | bpy_idname = "bf_xb_center_voxels"
57 | bpy_prop = BoolProperty
58 | bpy_default = False
59 | bpy_other = {"update": update_bf_xb}
60 |
61 | def get_active(self, context):
62 | ob = self.element
63 | return ob.bf_xb_export and ob.bf_xb in ("VOXELS", "PIXELS")
64 |
65 |
66 | class OP_XB(BFParam):
67 | label = "XB"
68 | description = "Export as volumes/faces/edges"
69 | fds_label = "XB"
70 | bpy_type = Object
71 | bpy_idname = "bf_xb"
72 | bpy_prop = EnumProperty
73 | bpy_other = {
74 | "update": update_bf_xb,
75 | "items": (
76 | ("BBOX", "Bounding Box", "Export object bounding box", 100),
77 | ("VOXELS", "Voxels", "Export voxels from voxelized solid Object", 200),
78 | ("FACES", "Faces", "Export faces, one for each face", 300),
79 | ("PIXELS", "Pixels", "Export pixels from pixelized flat Object", 400),
80 | ("EDGES", "Edges", "Export segments, one for each edge", 500),
81 | ),
82 | }
83 | bpy_export = "bf_xb_export"
84 | bpy_export_default = False
85 |
86 | def to_fds_list(self, context) -> FDSList:
87 | if not self.get_exported(context):
88 | return FDSList()
89 | ob = self.element
90 | hids, xbs, msgs = ob_to_xbs(context=context, ob=ob, bf_xb=ob.bf_xb)
91 | match len(xbs):
92 | case 0:
93 | return FDSList()
94 | case 1:
95 | return FDSParam(fds_label="XB", value=xbs[0], precision=LP)
96 | case _:
97 | iterable=(
98 | (FDSParam(fds_label="ID", value=hid) for hid in hids),
99 | (FDSParam(fds_label="XB", value=xb, precision=LP) for xb in xbs),
100 | )
101 | return FDSMulti(iterable=iterable, msgs=msgs)
102 |
103 | def set_value(self, context, value=None):
104 | ob = self.element
105 | bf_xb = xbs_to_ob(
106 | context=context,
107 | ob=ob,
108 | xbs=(value,),
109 | set_origin=True,
110 | add=True
111 | )
112 | ob.bf_xb = bf_xb
113 | ob.bf_xb_export = True
114 |
115 |
116 | class OP_XB_BBOX(OP_XB): # should always work, even without bf_xb_export and bf_xb
117 | label = "XB"
118 | description = "Export as object bounding box (BBOX)"
119 | fds_label = "XB"
120 | bpy_type = Object
121 | bpy_idname = None
122 | bpy_export = None
123 |
124 | def to_fds_list(self, context) -> FDSList:
125 | ob = self.element
126 | xb = utils.geometry.get_bbox_xb(context=context, ob=ob, world=True)
127 | return FDSParam(fds_label="XB", value=xb, precision=LP)
128 |
129 | def draw(self, context, layout): # draw label only
130 | row = layout.split(factor=0.4)
131 | row.alignment = "RIGHT"
132 | row.label(text=f"XB")
133 | row.alignment = "EXPAND"
134 | row.label(text=f"Bounding Box")
135 |
--------------------------------------------------------------------------------
/lang/OP_XB/__init__.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | from .OP_XB import (
4 | OP_XB,
5 | OP_XB_voxel_size,
6 | OP_XB_center_voxels,
7 | OP_XB_BBOX,
8 | )
9 | from .ob_to_xbs import ob_to_xbs
10 | from .xbs_to_ob import xbs_to_ob
11 |
--------------------------------------------------------------------------------
/lang/OP_XB/calc_pixels.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | """!
4 | BlenderFDS, pixelization algorithms.
5 | """
6 |
7 | import bpy, logging
8 | from ...types import BFException
9 | from ... import utils
10 | from .calc_voxels import _get_voxel_size, get_voxels
11 |
12 | log = logging.getLogger(__name__)
13 |
14 |
15 | def get_pixels(context, ob):
16 | """!
17 | Get pixels from flat object in xbs format.
18 | @param context: the Blender context.
19 | @param ob: the Blender object.
20 | @return the xbs and the voxel size.
21 | """
22 | log.debug(f"Get pixels in Object <{ob.name}>...")
23 | # Check object and init
24 | if ob.type not in {"MESH", "CURVE", "SURFACE", "FONT", "META"}:
25 | raise BFException(ob, "Object can not be converted to mesh.")
26 | if not ob.data.vertices:
27 | raise BFException(ob, "Empty object, no available geometry")
28 | voxel_size = _get_voxel_size(context, ob)
29 | # Get evaluated ob (eg. modifiers applied) and its Mesh
30 | dg = context.evaluated_depsgraph_get()
31 | ob_eval = ob.evaluated_get(dg) # no need to clean up, it is tmp
32 | me_eval = bpy.data.meshes.new_from_object(ob_eval) # static
33 | # Create a new Object, in world coo
34 | ob_copy = bpy.data.objects.new(f"{ob.name}_voxels_tmp", me_eval)
35 | ob_copy.data.transform(ob.matrix_world)
36 | context.collection.objects.link(ob_copy)
37 | # Set data for ob_copy
38 | ob_copy.bf_xb_custom_voxel = ob.bf_xb_custom_voxel
39 | ob_copy.bf_xb_center_voxels = ob.bf_xb_center_voxels
40 | ob_copy.bf_xb_voxel_size = ob.bf_xb_voxel_size
41 | # Get flat axis of evaluated ob
42 | flat_axis = _get_flat_axis(ob_copy, voxel_size)
43 | # Check how flat it is
44 | if ob_copy.dimensions[flat_axis] > voxel_size / 2.0:
45 | bpy.data.meshes.remove(ob_copy.data, do_unlink=True)
46 | raise BFException(ob, "Object is not flat enough.")
47 | # Get origin for flat xbs
48 | xb = utils.geometry.get_bbox_xb(context, ob_copy, world=True)
49 | flat_origin = (
50 | (xb[1] + xb[0]) / 2.0,
51 | (xb[3] + xb[2]) / 2.0,
52 | (xb[5] + xb[4]) / 2.0,
53 | )
54 | # Add solidify modifier
55 | _add_solidify_mod(context, ob_copy, voxel_size)
56 | # Voxelize (already corrected for unit_settings)
57 | try:
58 | xbs, voxel_size = get_voxels(context=context, ob=ob_copy)
59 | except BFException as err:
60 | raise BFException(ob, f"No pixel created: {err}")
61 | finally:
62 | bpy.data.meshes.remove(ob_copy.data, do_unlink=True) # clean up
63 | # Flatten the solidified object xbs
64 | choice = (_x_flatten_xbs, _y_flatten_xbs, _z_flatten_xbs)[flat_axis]
65 | xbs = choice(xbs, flat_origin)
66 | return xbs, voxel_size
67 |
68 |
69 | def _add_solidify_mod(context, ob, voxel_size):
70 | """!
71 | Add new solidify modifier.
72 | @param context: the Blender context.
73 | @param ob: the Blender object.
74 | @param voxel_size: the voxel size of the object.
75 | @return the modifier.
76 | """
77 | mo = ob.modifiers.new("solidify_tmp", "SOLIDIFY")
78 | mo.thickness = voxel_size * 3
79 | mo.offset = 0.0 # centered
80 | return mo
81 |
82 |
83 | def _get_flat_axis(ob, voxel_size):
84 | """!
85 | Get object flat axis.
86 | @param ob: the Blender object.
87 | @param voxel_size: the voxel size of the object.
88 | @return the object flat axis.
89 | """
90 | dimensions = ob.dimensions
91 | choices = [
92 | (dimensions[0], 0), # object faces are normal to x axis
93 | (dimensions[1], 1), # ... to y axis
94 | (dimensions[2], 2), # ... to z axis
95 | ]
96 | choices.sort(key=lambda k: k[0]) # sort by dimension
97 | return choices[0][1]
98 |
99 |
100 | def _x_flatten_xbs(xbs, flat_origin):
101 | """!
102 | Flatten voxels to obtain pixels (normal to x axis) at flat_origin height.
103 | @param xbs: voxelized xbs.
104 | @param flat_origin: local origin of flat object.
105 | @return xbs.
106 | """
107 | return [[flat_origin[0], flat_origin[0], xb[2], xb[3], xb[4], xb[5]] for xb in xbs]
108 |
109 |
110 | def _y_flatten_xbs(xbs, flat_origin):
111 | """!
112 | Flatten voxels to obtain pixels (normal to y axis) at flat_origin height.
113 | @param xbs: voxelized xbs.
114 | @param flat_origin: local origin of flat object.
115 | @return xbs.
116 | """
117 | return [[xb[0], xb[1], flat_origin[1], flat_origin[1], xb[4], xb[5]] for xb in xbs]
118 |
119 |
120 | def _z_flatten_xbs(xbs, flat_origin):
121 | """!
122 | Flatten voxels to obtain pixels (normal to z axis) at flat_origin height.
123 | @param xbs: voxelized xbs.
124 | @param flat_origin: local origin of flat object.
125 | @return xbs.
126 | """
127 | return [[xb[0], xb[1], xb[2], xb[3], flat_origin[2], flat_origin[2]] for xb in xbs]
128 |
--------------------------------------------------------------------------------
/lang/OP_XB/xbs_to_ob.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | """!
4 | BlenderFDS, translate geometry from FDS XB notation to a Blender mesh.
5 | """
6 |
7 | import bmesh, logging
8 | from mathutils import Matrix, Vector
9 | from ... import config
10 | from ...types import BFException
11 |
12 | log = logging.getLogger(__name__)
13 |
14 | EFD = config.FLAT_DIFFERENCE
15 |
16 |
17 | def _xbs_edges_to_bm(bm, xbs, scale_length):
18 | for xb in xbs:
19 | x0, x1, y0, y1, z0, z1 = (c / scale_length for c in xb)
20 | v0 = bm.verts.new((x0, y0, z0))
21 | v1 = bm.verts.new((x1, y1, z1))
22 | bm.edges.new((v0, v1))
23 |
24 | def _xbs_faces_to_bm(bm, xbs, scale_length):
25 | for xb in xbs:
26 | x0, x1, y0, y1, z0, z1 = (c / scale_length for c in xb)
27 | if abs(x1 - x0) <= EFD: # i face
28 | v0 = bm.verts.new((x0, y0, z0))
29 | v1 = bm.verts.new((x0, y1, z0))
30 | v2 = bm.verts.new((x0, y1, z1))
31 | v3 = bm.verts.new((x0, y0, z1))
32 | elif abs(y1 - y0) <= EFD: # j face
33 | v0 = bm.verts.new((x0, y0, z0))
34 | v1 = bm.verts.new((x1, y0, z0))
35 | v2 = bm.verts.new((x1, y0, z1))
36 | v3 = bm.verts.new((x0, y0, z1))
37 | elif abs(z1 - z0) <= EFD: # k face
38 | v0 = bm.verts.new((x0, y0, z0))
39 | v1 = bm.verts.new((x0, y1, z0))
40 | v2 = bm.verts.new((x1, y1, z0))
41 | v3 = bm.verts.new((x1, y0, z0))
42 | else:
43 | raise AssertionError(f"Unrecognized face <{xb}> in XB")
44 | bm.faces.new((v0, v1, v2, v3))
45 |
46 | def _xbs_bbox_to_bm(bm, xbs, scale_length):
47 | for xb in xbs:
48 | x0, x1, y0, y1, z0, z1 = (c / scale_length for c in xb)
49 | v000 = bm.verts.new((x0, y0, z0))
50 | v100 = bm.verts.new((x1, y0, z0))
51 | v110 = bm.verts.new((x1, y1, z0))
52 | v010 = bm.verts.new((x0, y1, z0))
53 | v001 = bm.verts.new((x0, y0, z1))
54 | v101 = bm.verts.new((x1, y0, z1))
55 | v111 = bm.verts.new((x1, y1, z1))
56 | v011 = bm.verts.new((x0, y1, z1))
57 | bm.faces.new((v000, v001, v011, v010)) # -x 0
58 | bm.faces.new((v111, v101, v100, v110)) # +x 1
59 | bm.faces.new((v000, v100, v101, v001)) # -y 2
60 | bm.faces.new((v111, v110, v010, v011)) # +y 3
61 | bm.faces.new((v000, v010, v110, v100)) # -z 4 bottom
62 | bm.faces.new((v111, v011, v001, v101)) # +z 5 top
63 |
64 | def _is_faces(xbs, scale_length):
65 | """!
66 | Check if xbs are only faces.
67 | """
68 | for xb in xbs:
69 | x0, x1, y0, y1, z0, z1 = (c / scale_length for c in xb)
70 | if abs(x1 - x0) > EFD and abs(y1 - y0) > EFD and abs(z1 - z0) > EFD:
71 | return False
72 | return True
73 |
74 | _xbs_to_bm = {
75 | "BBOX": _xbs_bbox_to_bm,
76 | "VOXELS": _xbs_bbox_to_bm,
77 | "PIXELS": _xbs_bbox_to_bm,
78 | "EDGES": _xbs_edges_to_bm,
79 | "FACES": _xbs_faces_to_bm,
80 | }
81 |
82 | def xbs_to_ob(context, ob, xbs, bf_xb=None, add=False, set_origin=False):
83 | """!
84 | Set xbs geometry ((x0,x1,y0,y1,z0,z1,), ...) to Blender Object.
85 | @param context: the Blender context.
86 | @param ob: the Blender object.
87 | @param xbs: the xbs values.
88 | @param bf_xb: the xb parameter between BBOX, VOXELS, FACES, PIXELS, EDGES.
89 | @param add: add to existing Mesh.
90 | @param set_origin: set reasonable origin.
91 | @return the new xb parameter between BBOX, VOXELS, FACES, PIXELS, EDGES.
92 | """
93 | if not xbs:
94 | return "BBOX"
95 | # Generate the new bmesh
96 | bm = bmesh.new()
97 | if add: # get existing data in world coo
98 | bm.from_mesh(ob.data)
99 | bm.transform(ob.matrix_world)
100 | scale_length = context.scene.unit_settings.scale_length
101 | if not bf_xb:
102 | if _is_faces(xbs=xbs, scale_length=scale_length):
103 | bf_xb = "FACES"
104 | else:
105 | bf_xb = "BBOX"
106 | # Inject geometry
107 | _xbs_to_bm[bf_xb](bm=bm, xbs=xbs, scale_length=scale_length)
108 | # Transform to local coo
109 | if set_origin:
110 | origin = Vector((xbs[0][0],xbs[0][2],xbs[0][4]))
111 | ma = Matrix.Translation(-origin)
112 | ob.matrix_world = Matrix.Translation(+origin)
113 | else:
114 | ma = ob.matrix_world.inverted()
115 | bm.transform(ma)
116 | bm.to_mesh(ob.data)
117 | bm.free()
118 | return bf_xb
119 |
120 | def set_materials(ob) -> None:
121 | """!
122 | Set Blender Materials from Material Slots to Object Mesh faces.
123 | @param ob: the Blender object.
124 | """
125 | me = ob.data
126 | match len(me.materials):
127 | case 0: # no SURF_ID
128 | return
129 | case 1: # SURF_ID = 'A'
130 | for face in me.polygons:
131 | face.material_index = 0
132 | case 3: # SURF_IDS = 'A', 'B', 'C' (top, sides, bottom)
133 | if len(me.polygons) > 6:
134 | raise AssertionError(f"Too many polygons in Object {ob.name}: {len(me.polygons)}")
135 | for iface, face in enumerate(me.polygons):
136 | if iface % 6 == 5:
137 | face.material_index = 0 # top
138 | elif iface % 6 == 4:
139 | face.material_index = 2 # bottom
140 | else:
141 | face.material_index = 1 # sides
142 | case 6: # SURF_ID6 = ... (x0, x1, y0, y1, z0, z1)
143 | if len(me.polygons) > 6:
144 | raise AssertionError(f"Too many polygons in Object {ob.name}: {len(me.polygons)}")
145 | for iface, face in enumerate(me.polygons):
146 | face.material_index = iface % 6
147 | case _:
148 | raise BFException(ob, f"Wrong number of Material Slots")
149 |
--------------------------------------------------------------------------------
/lang/OP_XYZ/OP_XYZ.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | import logging
4 | from bpy.types import Object
5 | from bpy.props import EnumProperty, BoolProperty, FloatProperty
6 | from ...config import LP
7 | from ...types import BFParam, FDSParam, FDSList, FDSMulti
8 | from ... import utils
9 | from .ob_to_xyzs import ob_to_xyzs
10 | from .xyzs_to_ob import xyzs_to_ob
11 |
12 | log = logging.getLogger(__name__)
13 |
14 |
15 | def update_bf_xyz(ob, context):
16 | # Rm tmp geometry and cache
17 | utils.geometry.rm_geometric_cache(ob=ob)
18 | if ob.bf_has_tmp:
19 | utils.geometry.rm_tmp_objects()
20 | # Prevent double multiparam
21 | if ob.bf_xyz == "VERTICES" and ob.bf_xyz_export:
22 | if ob.bf_xb in ("VOXELS", "FACES", "PIXELS", "EDGES"):
23 | ob["bf_xb_export"] = False # prevent multiple update
24 | if ob.bf_pb == "PLANES":
25 | ob["bf_pb_export"] = False
26 | return
27 |
28 |
29 | class OP_XYZ(BFParam):
30 | label = "XYZ"
31 | description = "Export as points"
32 | fds_label = "XYZ"
33 | bpy_type = Object
34 | bpy_idname = "bf_xyz"
35 | bpy_prop = EnumProperty
36 | bpy_other = {
37 | "update": update_bf_xyz,
38 | "items": (
39 | ("CENTER", "Center", "Point, center point of this object", 100),
40 | ("VERTICES", "Vertices", "Points, one for each vertex of this object", 200),
41 | ),
42 | }
43 | bpy_export = "bf_xyz_export"
44 | bpy_export_default = False
45 |
46 | def to_fds_list(self, context) -> FDSList:
47 | if not self.get_exported(context):
48 | return FDSList()
49 | ob = self.element
50 | hids, xyzs, msgs = ob_to_xyzs(context=context, ob=ob, bf_xyz=ob.bf_xyz)
51 | match len(xyzs):
52 | case 0:
53 | return FDSList()
54 | case 1:
55 | return FDSParam(fds_label="XYZ", value=xyzs[0], precision=LP)
56 | case _:
57 | iterable=(
58 | (FDSParam(fds_label="ID", value=hid) for hid in hids),
59 | (FDSParam(fds_label="XYZ", value=xyz, precision=LP) for xyz in xyzs),
60 | )
61 |
62 | return FDSMulti(iterable=iterable, msgs=msgs)
63 |
64 | def set_value(self, context, value=None):
65 | bf_xyz = xyzs_to_ob(
66 | context=context,
67 | ob=self.element,
68 | xyzs=(value,),
69 | set_origin=True,
70 | add=True,
71 | )
72 | self.element.bf_xyz = bf_xyz
73 | self.element.bf_xyz_export = True
74 |
75 |
76 | class OP_XYZ_center(OP_XYZ):
77 | description = "Export as points (center)"
78 | bpy_prop = None # do not redefine
79 | bf_xyz_idxs = (0,) # CENTER, VERTICES
80 |
--------------------------------------------------------------------------------
/lang/OP_XYZ/__init__.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | from .OP_XYZ import OP_XYZ
4 | from .ob_to_xyzs import ob_to_xyzs
5 | from .xyzs_to_ob import xyzs_to_ob
6 |
--------------------------------------------------------------------------------
/lang/OP_XYZ/ob_to_xyzs.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | """!
4 | BlenderFDS, translate Blender object geometry to FDS XYZ notation.
5 | """
6 |
7 | import logging
8 | from ...types import BFException
9 | from ... import utils
10 |
11 | log = logging.getLogger(__name__)
12 |
13 |
14 | def _ob_to_xyzs_vertices(context, ob, world) -> tuple((list, list)):
15 | """!
16 | Transform Object vertices to xyzs notation.
17 | @param context: the Blender context.
18 | @param ob: the Blender object.
19 | @param world: True to return the object in world coordinates.
20 | @return the xyzs notation and any error message: ((x0,y0,z0,), ...), 'Msg'.
21 | """
22 | bm = utils.geometry.get_object_bmesh(context, ob, world=world)
23 | bm.verts.ensure_lookup_table()
24 | scale_length = context.scene.unit_settings.scale_length
25 | xyzs = list(tuple(c * scale_length for c in v.co) for v in bm.verts)
26 | bm.free()
27 | xyzs.sort()
28 | if not xyzs:
29 | raise BFException(ob, "XYZ: No exported vertices!")
30 | msgs = list((f"XYZ Vertices: {len(xyzs)}",))
31 | return xyzs, msgs
32 |
33 |
34 | def _ob_to_xyzs_center(context, ob, world) -> tuple((list, list)):
35 | """!
36 | Transform Object center to xyzs notation.
37 | @param context: the Blender context.
38 | @param ob: the Blender object.
39 | @param world: True to return the object in world coordinates.
40 | @return the xyzs notation and any error message: ((x0,y0,z0,), ...), 'Msg'.
41 | """
42 | scale_length = context.scene.unit_settings.scale_length
43 | if world:
44 | xyzs = list((tuple(l * scale_length for l in ob.location),))
45 | else:
46 | xyzs = list(((0.0, 0.0, 0.0),))
47 | msgs = list()
48 | return xyzs, msgs
49 |
50 |
51 | _choice_to_xyzs = {"CENTER": _ob_to_xyzs_center, "VERTICES": _ob_to_xyzs_vertices}
52 |
53 |
54 | def ob_to_xyzs(context, ob, bf_xyz, world=True) -> tuple((list, list, list)):
55 | """!
56 | Transform Object geometry according to bf_xyz to xyzs notation.
57 | @param context: the Blender context.
58 | @param ob: the Blender object.
59 | @param bf_xyz: string in (CENTER, VERTICES).
60 | @param world: True to return the object in world coordinates.
61 | @return the xyzs notation and any error message: ((x0,y0,z0,), ...), 'Msg'.
62 | """
63 | # Calc xyzs and msgs
64 | xyzs, msgs = tuple(), tuple()
65 | if ob.bf_xyz_export:
66 | xyzs, msgs = _choice_to_xyzs[bf_xyz](context, ob, world)
67 |
68 | # Calc hids
69 | n = ob.name
70 | match ob.bf_id_suffix:
71 | case "IDI":
72 | hids = (f"{n}_{i}" for i, _ in enumerate(xyzs))
73 | case "IDX":
74 | hids = (f"{n}_x{xyz[0]:+.3f}" for xyz in xyzs)
75 | case "IDY":
76 | hids = (f"{n}_y{xyz[1]:+.3f}" for xyz in xyzs)
77 | case "IDZ":
78 | hids = (f"{n}_z{xyz[2]:+.3f}" for xyz in xyzs)
79 | case "IDXY":
80 | hids = (f"{n}_x{xyz[0]:+.3f}_y{xyz[1]:+.3f}" for xyz in xyzs)
81 | case "IDXZ":
82 | hids = (f"{n}_x{xyz[0]:+.3f}_z{xyz[2]:+.3f}" for xyz in xyzs)
83 | case "IDYZ":
84 | hids = (f"{n}_y{xyz[1]:+.3f}_z{xyz[2]:+.3f}" for xyz in xyzs)
85 | case "IDXYZ":
86 | hids = (f"{n}_x{xyz[0]:+.3f}_y{xyz[1]:+.3f}_z{xyz[2]:+.3f}" for xyz in xyzs)
87 | case _:
88 | raise AssertionError(f"Unknown suffix <{ob.bf_id_suffix}>")
89 |
90 | return tuple(hids), tuple(xyzs), tuple(msgs)
91 |
--------------------------------------------------------------------------------
/lang/OP_XYZ/xyzs_to_ob.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | """!
4 | BlenderFDS, translate geometry from FDS XYZ notation to a Blender mesh.
5 | """
6 |
7 | import bmesh, logging
8 | from mathutils import Matrix, Vector
9 |
10 | log = logging.getLogger(__name__)
11 |
12 |
13 | def _xyzs_to_bm(bm, xyzs, scale_length):
14 | for xyz in xyzs:
15 | bm.verts.new(c / scale_length for c in xyz)
16 |
17 |
18 | def xyzs_to_ob(context, ob, xyzs, add=False, set_origin=False) -> str():
19 | """!
20 | Import xyzs vertices ((x0,y0,z0,), ...) into existing Blender Object.
21 | @param context: the Blender context.
22 | @param ob: the Blender object.
23 | @param xyzs: the xyzs vertices.
24 | @param add: add to existing Mesh.
25 | @param set_origin: set reasonable origin.
26 | @return: the new bf_xyz in CENTER or VERTICES
27 | """
28 | if not xyzs:
29 | return "VERTICES"
30 | # Generate the new bmesh
31 | bm = bmesh.new()
32 | if add: # get existing data in world coo
33 | bm.from_mesh(ob.data)
34 | bm.transform(ob.matrix_world)
35 | scale_length = context.scene.unit_settings.scale_length
36 | # Inject geometry
37 | if len(xyzs) > 1:
38 | _xyzs_to_bm(bm, xyzs, scale_length)
39 | # Transform to local coo
40 | if set_origin:
41 | origin = Vector(xyzs[0])
42 | ma = Matrix.Translation(-origin)
43 | ob.matrix_world = Matrix.Translation(+origin)
44 | else:
45 | ma = ob.matrix_world.inverted()
46 | # same ob.matrix_world
47 | bm.transform(ma)
48 | bm.to_mesh(ob.data)
49 | bm.free()
50 | if len(xyzs) == 1:
51 | return "CENTER"
52 | else:
53 | return "VERTICES"
54 |
--------------------------------------------------------------------------------
/lang/SN_CATF.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | import logging, os
4 | from bpy.types import Scene
5 | from .. import utils
6 | from ..types import BFParam, BFNamelistSc, BFNotImported
7 |
8 |
9 | log = logging.getLogger(__name__)
10 |
11 |
12 | class SP_CATF_files(BFParam):
13 | label = "Concatenated File Paths"
14 | description = "Concatenated file paths"
15 | fds_label = "OTHER_FILES"
16 | bpy_type = Scene
17 |
18 | def set_value(self, context, value=None):
19 | if not value:
20 | return
21 | if isinstance(value, str):
22 | value = (value,)
23 | for v in value:
24 | filepath = utils.io.transform_rfds_to_abs(context=context, filepath_rfds=v)
25 | context.scene.from_fds(context=context, filepath=filepath)
26 |
27 |
28 | class SN_CATF(BFNamelistSc): # for importing only
29 | label = "CATF"
30 | description = "Concatenated file paths"
31 | fds_label = "CATF"
32 | bf_params = (SP_CATF_files,)
33 | bf_import_order = 50
34 |
35 | def get_exported(self, context):
36 | return False
37 |
--------------------------------------------------------------------------------
/lang/SN_DUMP/SN_DUMP.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | import logging, io
4 | from bpy.types import Scene
5 | from bpy.props import BoolProperty, FloatProperty, IntProperty
6 | from ...config import TIME_P
7 | from ... import utils
8 | from ...types import BFParam, BFParamOther, BFParamFYI, BFNamelistSc, FDSParam, FDSList
9 | from ...bl.ui_lists import WM_PG_bf_other, WM_UL_bf_other_items
10 | from .sc_to_ge1 import scene_to_ge1
11 |
12 | log = logging.getLogger(__name__)
13 |
14 |
15 | class SP_DUMP_FYI(BFParamFYI):
16 | bpy_type = Scene
17 | bpy_idname = "bf_dump_fyi"
18 |
19 |
20 | class SP_DUMP_render_file(BFParam):
21 | label = "Export Geometric Description File"
22 | description = "Export geometric description file GE1"
23 | fds_label = "RENDER_FILE"
24 | bpy_type = Scene
25 | bpy_idname = "bf_dump_render_file"
26 | bpy_prop = BoolProperty
27 | fds_default = False
28 |
29 | def to_fds_list(self, context) -> FDSList:
30 | if not self.get_exported(context):
31 | return FDSList()
32 | filepath = utils.io.transform_rbl_to_abs(
33 | context=context,
34 | filepath_rbl=context.scene.bf_config_directory,
35 | name=self.element.name,
36 | extension=".ge1",
37 | )
38 | ge1_text = scene_to_ge1(context, self)
39 | utils.io.write_txt_file(filepath, ge1_text)
40 | return FDSParam(fds_label="RENDER_FILE", value=f"{self.element.name}.ge1")
41 |
42 | def set_value(self, context, value=None):
43 | self.element.bf_dump_render_file = bool(value)
44 |
45 |
46 | class SP_DUMP_STATUS_FILES(BFParam):
47 | label = "STATUS_FILES"
48 | description = "Export status file (*.notready), deleted when the simulation is completed successfully"
49 | fds_label = "STATUS_FILES"
50 | fds_default = False
51 | bpy_type = Scene
52 | bpy_prop = BoolProperty
53 | bpy_idname = "bf_dump_status_files"
54 |
55 |
56 | class SP_DUMP_NFRAMES(BFParam):
57 | label = "NFRAMES"
58 | description = "Number of output dumps per calculation"
59 | fds_label = "NFRAMES"
60 | fds_default = 1000
61 | bpy_type = Scene
62 | bpy_idname = "bf_dump_nframes"
63 | bpy_prop = IntProperty
64 | bpy_other = {"min": 1}
65 | bpy_export = "bf_dump_nframes_export"
66 | bpy_export_default = False
67 |
68 | def get_exported(self, context):
69 | return (
70 | super().get_exported(context)
71 | and not self.element.bf_dump_frames_freq_export
72 | )
73 |
74 |
75 | class SP_DUMP_frames_freq(BFParam):
76 | label = "Dump Output Frequency [s]"
77 | description = "Dump output frequency in seconds"
78 | bpy_type = Scene
79 | bpy_idname = "bf_dump_frames_freq"
80 | bpy_prop = FloatProperty
81 | bpy_other = {"min": 0.01, "precision": TIME_P}
82 | bpy_default = 1.0
83 | bpy_export = "bf_dump_frames_freq_export"
84 | bpy_export_default = False
85 |
86 | def to_fds_list(self, context) -> FDSList:
87 | if not self.get_exported(context):
88 | return FDSList()
89 | nframes = int(
90 | (self.element.bf_time_t_end - self.element.bf_time_t_begin)
91 | // self.element.bf_dump_frames_freq
92 | )
93 | if nframes < 1:
94 | nframes = 1
95 | return FDSParam(fds_label="NFRAMES", value=nframes)
96 |
97 |
98 | class SP_DUMP_DT_RESTART(BFParam):
99 | label = "DT_RESTART [s]"
100 | description = "Time interval between restart files are saved"
101 | fds_label = "DT_RESTART"
102 | fds_default = 600.0
103 | bpy_type = Scene
104 | bpy_idname = "bf_dump_dt_restart"
105 | bpy_prop = FloatProperty
106 | bpy_other = {"min": 1.0, "precision": TIME_P}
107 | bpy_export = "bf_dump_dt_restart_export"
108 | bpy_export_default = False
109 |
110 |
111 | class SP_DUMP_other(BFParamOther):
112 | bpy_type = Scene
113 | bpy_idname = "bf_dump_other"
114 | bpy_pg = WM_PG_bf_other
115 | bpy_ul = WM_UL_bf_other_items
116 |
117 |
118 | class SN_DUMP(BFNamelistSc):
119 | label = "DUMP"
120 | description = "Output parameters"
121 | enum_id = 3005
122 | fds_label = "DUMP"
123 | bpy_export = "bf_dump_export"
124 | bpy_export_default = False
125 | bf_params = (
126 | SP_DUMP_FYI,
127 | SP_DUMP_render_file,
128 | SP_DUMP_STATUS_FILES,
129 | SP_DUMP_NFRAMES,
130 | SP_DUMP_frames_freq,
131 | SP_DUMP_DT_RESTART,
132 | SP_DUMP_other,
133 | )
134 |
--------------------------------------------------------------------------------
/lang/SN_DUMP/__init__.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | from .SN_DUMP import SN_DUMP
4 |
--------------------------------------------------------------------------------
/lang/SN_DUMP/sc_to_ge1.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | """!
4 | BlenderFDS, export geometry to ge1 cad file format.
5 | """
6 |
7 | import bpy, bmesh
8 | from ... import utils
9 | from ...config import LP
10 |
11 | # GE1 file format:
12 |
13 | # [APPEARANCE] < immutable title
14 | # 3 < number of appearances (from Blender materials)
15 | # INERT < appearance name
16 | # 0 200 200 50 0.0 0.0 0.5 < index, red, green, blue, twidth, theight, alpha, tx0, ty0, tz0 (t is texture)
17 | # < texture file, blank if None
18 | # Burner
19 | # 1 255 0 0 0.0 0.0 0.5
20 | #
21 | # Dummy Hole < automatically inserted to render HOLEs
22 | # 2 150 150 150 0.0 0.0 0.5
23 | #
24 | # [FACES] < immutable title
25 | # 2 < number of *quad* faces (from OBST and SURF objects triamgulated bm.faces)
26 | # 6.0 3.9 0.5 6.0 1.9 0.5 6.0 1.9 1.9 6.0 3.9 1.9 0 < x0, y0, z0, x1, y1, z1, ..., ref to appearance index
27 | # 6.0 3.9 0.5 6.0 1.9 0.5 6.0 1.9 1.9 6.0 3.9 1.9 0
28 | # EOF
29 |
30 |
31 | def _get_appearance(name, i, rgb):
32 | return f"{name}\n{i} {rgb[0]} {rgb[1]} {rgb[2]} 0. 0. {rgb[3]:.3f} 0. 0. 0.\n\n"
33 |
34 |
35 | def scene_to_ge1(context, scene):
36 | """!
37 | Export scene geometry in FDS GE1 notation.
38 | @param context: the blender context.
39 | @param scene: the Blender scene.
40 | @return FDS GE1 notation.
41 | """
42 | # Cursor
43 | w = context.window_manager.windows[0]
44 | w.cursor_modal_set("WAIT")
45 | # Get GE1 appearances from materials
46 | appearances, ma_to_appearance = list(), dict()
47 | for i, ma in enumerate(bpy.data.materials):
48 | appearances.append(
49 | _get_appearance(
50 | name=ma.name,
51 | i=i,
52 | rgb=(
53 | int(ma.diffuse_color[0] * 255),
54 | int(ma.diffuse_color[1] * 255),
55 | int(ma.diffuse_color[2] * 255),
56 | ma.diffuse_color[3],
57 | ),
58 | )
59 | )
60 | ma_to_appearance[ma.name] = i
61 | # Append dummy material for HOLEs
62 | i += 1
63 | appearances.append(
64 | _get_appearance(name="Dummy Hole", i=i, rgb=(150, 150, 150, 0.5))
65 | )
66 | ma_to_appearance[ma.name] = i
67 | # Select GE1 objects
68 | allowed_nls = ("ON_OBST", "ON_GEOM", "ON_VENT", "ON_HOLE")
69 | obs = (
70 | ob
71 | for ob in context.scene.objects
72 | if ob.type == "MESH"
73 | and not ob.hide_render # show only exported obs
74 | and not ob.bf_is_tmp # do not show tmp obs
75 | and ob.bf_namelist_cls in allowed_nls # show only allowed namelists
76 | and (ob.active_material and ob.active_material.name != "OPEN") # no OPEN
77 | )
78 | # Get GE1 faces from selected objects
79 | gefaces = list()
80 | for ob in obs:
81 | # Get the bmesh from the Object and triangulate
82 | bm = utils.geometry.get_object_bmesh(context=context, ob=ob, world=True)
83 | bmesh.ops.triangulate(bm, faces=bm.faces)
84 | # Get ob material_slots
85 | material_slots = ob.material_slots
86 | # Get default_material_name
87 | if ob.bf_namelist_cls == "ON_HOLE":
88 | default_material_name = "Dummy Hole"
89 | elif ob.bf_namelist_cls == "ON_GEOM":
90 | default_material_name = None
91 | elif ob.active_material:
92 | default_material_name = ob.active_material.name
93 | else:
94 | default_material_name = "INERT"
95 | scale_length = context.scene.unit_settings.scale_length
96 | for f in bm.faces:
97 | # Grab ordered vertices coordinates
98 | coos = [co for v in f.verts for co in v.co]
99 | coos.extend((coos[-3], coos[-2], coos[-1])) # tri to quad
100 | items = [f"{coo * scale_length:.{LP}f}" for coo in coos]
101 | # Get appearance_index
102 | if default_material_name:
103 | material_name = default_material_name
104 | else:
105 | material_name = material_slots[f.material_index].material.name
106 | appearance_index = str(ma_to_appearance.get(material_name, 0)) + "\n"
107 | items.append(appearance_index)
108 | # Append GE1 face
109 | gefaces.append(" ".join(items))
110 |
111 | # Prepare GE1 file and return
112 | ge1_file_a = f"[APPEARANCE]\n{len(appearances)}\n{''.join(appearances)}"
113 | ge1_file_f = f"[FACES]\n{len(gefaces)}\n{''.join(gefaces)}"
114 | # Close
115 | w.cursor_modal_restore()
116 | return "".join((ge1_file_a, ge1_file_f))
117 |
--------------------------------------------------------------------------------
/lang/SN_HEAD.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | import logging
4 | from bpy.types import Scene
5 | from ..types import BFParam, BFParamFYI, BFNamelistSc, FDSList, BFException
6 | from .. import utils
7 |
8 | log = logging.getLogger(__name__)
9 |
10 |
11 | class SP_HEAD_CHID(BFParam):
12 | label = "CHID"
13 | description = "Case identificator, also used as case filename"
14 | fds_label = "CHID"
15 | bpy_type = Scene
16 | bpy_idname = "name"
17 |
18 | def check(self, context):
19 | name = self.element.name
20 | if "." in name:
21 | raise BFException(self, f"No periods allowed")
22 |
23 | def copy_to(self, context, dest_element):
24 | pass
25 |
26 |
27 | class SP_HEAD_TITLE(BFParamFYI):
28 | label = "TITLE"
29 | description = "Case description"
30 | fds_label = "TITLE"
31 | bpy_type = Scene
32 | bpy_idname = "bf_head_title"
33 | bpy_other = {"maxlen": 64}
34 |
35 |
36 | class SN_HEAD(BFNamelistSc):
37 | label = "HEAD"
38 | description = "Case header"
39 | enum_id = 3001
40 | fds_label = "HEAD"
41 | bpy_export = "bf_head_export"
42 | bpy_export_default = True
43 | bf_params = SP_HEAD_CHID, SP_HEAD_TITLE
44 | bf_import_order = 0 # first imported
45 |
46 |
47 | class SN_TAIL(BFNamelistSc): # importing only, prevent free_text
48 | label = "TAIL"
49 | description = "Case closing"
50 | enum_id = 3010 # to avoid TAIL import into free_text
51 | fds_label = "TAIL"
52 | bf_import_order = 1e5 # last imported
53 |
54 | def get_exported(self, context):
55 | return False
56 |
57 | def from_fds_list(self, context, fds_list):
58 | pass
59 |
--------------------------------------------------------------------------------
/lang/SN_MOVE.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | import logging
4 | from ..types import BFNamelistSc, BFNotImported
5 |
6 | log = logging.getLogger(__name__)
7 |
8 | # This namelist is not displayed in the bf_namelist_cls menu,
9 | # and has no panel.
10 | # Used for importing only, it translates
11 | # FDS MOVE transformations to a Scene dict
12 | # It is used in conjuction with ON_MOVE
13 |
14 |
15 | class SN_MOVE(BFNamelistSc):
16 | label = "MOVE"
17 | description = "Geometric transformations"
18 | enum_id = False # no bf_namelist_cls menu
19 | fds_label = "MOVE"
20 | bf_import_order = 10
21 |
22 | def get_exported(self, context): # No automatic export
23 | return False
24 |
25 | def from_fds_list(self, context, fds_list):
26 | # Get ID
27 | fds_param = fds_list.get_fds_param(fds_label="ID", remove=True)
28 | if not fds_param:
29 | raise BFNotImported(self, f"Missing ID in: {fds_list}")
30 |
31 | # Prepare Scene dict
32 | if "bf_move_coll" not in context.scene:
33 | context.scene["bf_move_coll"] = dict()
34 | bf_move_coll = context.scene["bf_move_coll"]
35 |
36 | # Set Scene dict by ID, eg. {"ob_move": "A=3 B=4 C=5"}
37 | f90_params = " ".join((item.to_string() for item in fds_list))
38 | bf_move_coll[fds_param.get_value()] = f90_params
39 |
--------------------------------------------------------------------------------
/lang/SN_MULT.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | import logging
4 | from ..types import BFNamelistSc, BFNotImported
5 |
6 | log = logging.getLogger(__name__)
7 |
8 | # This namelist is not displayed in the bf_namelist_cls menu,
9 | # and has no panel.
10 | # Used for importing only, it translates
11 | # FDS MULT transformations to a Scene dict
12 | # It is used in conjuction with ON_MULT
13 |
14 |
15 | class SN_MULT(BFNamelistSc):
16 | label = "MULT"
17 | description = "Multiplier transformations"
18 | enum_id = False # No bf_namelist_cls menu
19 | fds_label = "MULT"
20 | bf_import_order = 20
21 |
22 | def get_exported(self, context): # No automatic export
23 | return False
24 |
25 | def from_fds_list(self, context, fds_list):
26 | # Get ID
27 | fds_param = fds_list.get_fds_param(fds_label="ID", remove=True)
28 | if not fds_param:
29 | raise BFNotImported(self, f"Missing ID in: {fds_list}")
30 |
31 | # Prepare Scene dict
32 | if "bf_mult_coll" not in context.scene:
33 | context.scene["bf_mult_coll"] = dict()
34 | bf_mult_coll = context.scene["bf_mult_coll"]
35 |
36 | # Set Scene dict by ID, eg. {"ob_mult": "A=3 B=4 C=5"}
37 | f90_params = " ".join((item.to_string() for item in fds_list))
38 | bf_mult_coll[fds_param.get_value()] = f90_params
39 |
--------------------------------------------------------------------------------
/lang/SN_PRES.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | import logging
4 | from bpy.types import Scene
5 | from bpy.props import BoolProperty, FloatProperty, IntProperty
6 | from ..config import VELOCITY_TOLERANCE_P
7 | from ..types import (
8 | BFParam,
9 | BFParamOther,
10 | BFParamFYI,
11 | BFNamelistSc,
12 | )
13 | from ..bl.ui_lists import (
14 | WM_PG_bf_other,
15 | WM_UL_bf_other_items,
16 | )
17 |
18 | log = logging.getLogger(__name__)
19 |
20 |
21 | class SP_PRES_FYI(BFParamFYI):
22 | bpy_type = Scene
23 | bpy_idname = "bf_pres_fyi"
24 |
25 |
26 | class SP_PRES_BAROCLINIC(BFParam):
27 | label = "BAROCLINIC"
28 | description = "Consider baroclinic torque"
29 | fds_label = "BAROCLINIC"
30 | fds_default = True
31 | bpy_type = Scene
32 | bpy_prop = BoolProperty
33 | bpy_idname = "bf_pres_baroclinic"
34 |
35 |
36 | class SP_PRES_VELOCITY_TOLERANCE(BFParam):
37 | label = "VELOCITY_TOLERANCE"
38 | description = "Maximum allowable normal velocity component\non the solid boundary or the largest error at a mesh interface"
39 | fds_label = "VELOCITY_TOLERANCE"
40 | bpy_type = Scene
41 | bpy_idname = "bf_pres_velocity_tolerance"
42 | bpy_prop = FloatProperty
43 | bpy_export = "bf_pres_velocity_tolerance_export"
44 | bpy_export_default = False
45 | bpy_other = {"precision": VELOCITY_TOLERANCE_P, "min": 0.0, "max": 1.0}
46 |
47 |
48 | class SP_PRES_MAX_PRESSURE_ITERATIONS(BFParam):
49 | label = "MAX_PRESSURE_ITERATIONS"
50 | description = "Maximum number of pressure iterations for each half of the time step"
51 | fds_label = "MAX_PRESSURE_ITERATIONS"
52 | fds_default = 10
53 | bpy_type = Scene
54 | bpy_idname = "bf_pres_max_pressure_iterations"
55 | bpy_prop = IntProperty
56 | bpy_other = {"min": 1}
57 | bpy_export = "bf_pres_max_pressure_iterations_export"
58 | bpy_export_default = False
59 |
60 |
61 | class SP_PRES_other(BFParamOther):
62 | bpy_type = Scene
63 | bpy_idname = "bf_pres_other"
64 | bpy_pg = WM_PG_bf_other
65 | bpy_ul = WM_UL_bf_other_items
66 |
67 |
68 | class SN_PRES(BFNamelistSc):
69 | label = "PRES"
70 | description = "Pressure Solver"
71 | enum_id = 3007
72 | fds_label = "PRES"
73 | bpy_export = "bf_pres_export"
74 | bpy_export_default = False
75 | bf_params = (
76 | SP_PRES_FYI,
77 | SP_PRES_BAROCLINIC,
78 | SP_PRES_VELOCITY_TOLERANCE,
79 | SP_PRES_MAX_PRESSURE_ITERATIONS,
80 | SP_PRES_other,
81 | )
82 |
--------------------------------------------------------------------------------
/lang/SN_RADI.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | import logging
4 | from bpy.types import Scene
5 | from bpy.props import BoolProperty, IntProperty
6 | from ..types import (
7 | BFParam,
8 | BFParamOther,
9 | BFParamFYI,
10 | BFNamelistSc,
11 | )
12 | from ..bl.ui_lists import (
13 | WM_PG_bf_other,
14 | WM_UL_bf_other_items,
15 | )
16 |
17 | log = logging.getLogger(__name__)
18 |
19 |
20 | class SP_RADI_FYI(BFParamFYI):
21 | bpy_type = Scene
22 | bpy_idname = "bf_radi_fyi"
23 |
24 |
25 | class SP_RADI_RADIATION(BFParam):
26 | label = "RADIATION"
27 | description = "Turn on/off the radiation solver"
28 | fds_label = "RADIATION"
29 | fds_default = True
30 | bpy_type = Scene
31 | bpy_prop = BoolProperty
32 | bpy_idname = "bf_radi_radiation"
33 |
34 |
35 | class SP_RADI_NUMBER_RADIATION_ANGLES(BFParam):
36 | label = "NUMBER_RADIATION_ANGLES"
37 | description = "Number of angles for spatial resolution of radiation solver"
38 | fds_label = "NUMBER_RADIATION_ANGLES"
39 | fds_default = 100
40 | bpy_type = Scene
41 | bpy_idname = "bf_radi_number_radiation_angles"
42 | bpy_prop = IntProperty
43 | bpy_other = {"min": 1}
44 | bpy_export = "bf_radi_number_radiation_angles_export"
45 | bpy_export_default = False
46 |
47 |
48 | class SP_RADI_TIME_STEP_INCREMENT(BFParam):
49 | label = "TIME_STEP_INCREMENT"
50 | description = "Frequency of calls to the radiation solver in time steps"
51 | fds_label = "TIME_STEP_INCREMENT"
52 | fds_default = 3
53 | bpy_type = Scene
54 | bpy_idname = "bf_radi_time_step_increment"
55 | bpy_prop = IntProperty
56 | bpy_other = {"min": 1}
57 | bpy_export = "bf_radi_time_step_increment_export"
58 | bpy_export_default = False
59 |
60 |
61 | class SP_RADI_ANGLE_INCREMENT(BFParam):
62 | label = "ANGLE_INCREMENT"
63 | description = "Increment over which the angles are updated"
64 | fds_label = "ANGLE_INCREMENT"
65 | fds_default = 5
66 | bpy_type = Scene
67 | bpy_idname = "bf_radi_angle_increment"
68 | bpy_prop = IntProperty
69 | bpy_other = {"min": 1}
70 | bpy_export = "bf_radi_angle_increment_export"
71 | bpy_export_default = False
72 |
73 |
74 | class SP_RADI_RADIATION_ITERATIONS(BFParam):
75 | label = "RADIATION_ITERATIONS"
76 | description = "Number of times the radiative intensity is updated in a time step"
77 | fds_label = "RADIATION_ITERATIONS"
78 | fds_default = 1
79 | bpy_type = Scene
80 | bpy_idname = "bf_radi_radiation_iterations"
81 | bpy_prop = IntProperty
82 | bpy_other = {"min": 1}
83 | bpy_export = "bf_radi_radiation_iterations_export"
84 | bpy_export_default = False
85 |
86 |
87 | class SP_RADI_other(BFParamOther):
88 | bpy_type = Scene
89 | bpy_idname = "bf_radi_other"
90 | bpy_pg = WM_PG_bf_other
91 | bpy_ul = WM_UL_bf_other_items
92 |
93 |
94 | class SN_RADI(BFNamelistSc):
95 | label = "RADI"
96 | description = "Radiation parameters"
97 | enum_id = 3006
98 | fds_label = "RADI"
99 | bpy_export = "bf_radi_export"
100 | bpy_export_default = False
101 | bf_params = (
102 | SP_RADI_FYI,
103 | SP_RADI_RADIATION,
104 | SP_RADI_NUMBER_RADIATION_ANGLES,
105 | SP_RADI_TIME_STEP_INCREMENT,
106 | SP_RADI_ANGLE_INCREMENT,
107 | SP_RADI_RADIATION_ITERATIONS,
108 | SP_RADI_other,
109 | )
110 |
--------------------------------------------------------------------------------
/lang/SN_REAC.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | import logging
4 | from bpy.types import Scene
5 | from bpy.props import BoolProperty, FloatProperty, StringProperty
6 | from ..config import HOC_P, RADIATIVE_FRACTION_P, YIELD_P
7 | from ..types import BFParam, BFParamOther, BFParamFYI, BFNamelistSc
8 | from ..bl.ui_lists import (
9 | WM_PG_bf_other,
10 | WM_UL_bf_other_items,
11 | )
12 |
13 | log = logging.getLogger(__name__)
14 |
15 |
16 | class SP_REAC_FYI(BFParamFYI):
17 | bpy_type = Scene
18 | bpy_idname = "bf_reac_fyi"
19 |
20 |
21 | class SP_REAC_FUEL(BFParam):
22 | label = "FUEL"
23 | description = "Identificator of fuel species"
24 | fds_label = "FUEL"
25 | bpy_type = Scene
26 | bpy_prop = StringProperty
27 | bpy_idname = "bf_reac_fuel"
28 |
29 |
30 | class SP_REAC_FORMULA(BFParam):
31 | label = "FORMULA"
32 | description = "Chemical formula of fuel species, it can only contain C, H, O, or N"
33 | fds_label = "FORMULA"
34 | bpy_type = Scene
35 | bpy_prop = StringProperty
36 | bpy_idname = "bf_reac_formula"
37 | bpy_export = "bf_reac_formula_export"
38 | bpy_export_default = False
39 |
40 |
41 | class SP_REAC_CO_YIELD(BFParam):
42 | label = "CO_YIELD [kg/kg]"
43 | description = "Fraction of fuel mass converted into carbon monoxide"
44 | fds_label = "CO_YIELD"
45 | fds_default = 0.0
46 | bpy_type = Scene
47 | bpy_prop = FloatProperty
48 | bpy_idname = "bf_reac_co_yield"
49 | bpy_other = {"step": 1.0, "precision": YIELD_P, "min": 0.0, "max": 1.0}
50 | bpy_export = "bf_reac_co_yield_export"
51 | bpy_export_default = False
52 |
53 |
54 | class SP_REAC_SOOT_YIELD(SP_REAC_CO_YIELD):
55 | label = "SOOT_YIELD [kg/kg]"
56 | description = "Fraction of fuel mass converted into smoke particulate"
57 | fds_label = "SOOT_YIELD"
58 | bpy_type = Scene
59 | bpy_idname = "bf_reac_soot_yield"
60 | bpy_export = "bf_reac_soot_yield_export"
61 | bpy_export_default = False
62 |
63 |
64 | class SP_REAC_HEAT_OF_COMBUSTION(BFParam):
65 | label = "HEAT_OF_COMBUSTION [kJ/kg]"
66 | description = "Fuel heat of combustion"
67 | fds_label = "HEAT_OF_COMBUSTION"
68 | fds_default = 0.0
69 | bpy_type = Scene
70 | bpy_idname = "bf_reac_heat_of_combustion"
71 | bpy_prop = FloatProperty
72 | bpy_other = {"precision": HOC_P, "min": 0.0}
73 | bpy_export = "bf_reac_heat_of_combustion_export"
74 | bpy_export_default = False
75 |
76 |
77 | class SP_REAC_IDEAL(BFParam):
78 | label = "IDEAL"
79 | description = "Set ideal heat of combustion"
80 | fds_label = "IDEAL"
81 | fds_default = False
82 | bpy_type = Scene
83 | bpy_prop = BoolProperty
84 | bpy_idname = "bf_reac_ideal"
85 |
86 |
87 | class SP_REAC_RADIATIVE_FRACTION(BFParam):
88 | label = "RADIATIVE_FRACTION"
89 | description = (
90 | "Fraction of the total combustion energy that is released "
91 | "in the form of thermal radiation"
92 | )
93 | fds_label = "RADIATIVE_FRACTION"
94 | fds_default = 0.35
95 | bpy_type = Scene
96 | bpy_idname = "bf_reac_radiative_fraction"
97 | bpy_prop = FloatProperty
98 | bpy_other = {"precision": RADIATIVE_FRACTION_P, "min": 0.0, "max": 1.0}
99 | bpy_export = "bf_reac_radiative_fraction_export"
100 | bpy_export_default = False
101 |
102 |
103 | class SP_REAC_other(BFParamOther):
104 | bpy_type = Scene
105 | bpy_idname = "bf_reac_other"
106 | bpy_pg = WM_PG_bf_other
107 | bpy_ul = WM_UL_bf_other_items
108 |
109 |
110 | class SN_REAC(BFNamelistSc):
111 | label = "REAC"
112 | description = "Reaction"
113 | enum_id = 3004
114 | fds_label = "REAC"
115 | bpy_export = "bf_reac_export"
116 | bpy_export_default = False
117 | bf_params = (
118 | SP_REAC_FYI,
119 | SP_REAC_FUEL,
120 | SP_REAC_FORMULA,
121 | SP_REAC_CO_YIELD,
122 | SP_REAC_SOOT_YIELD,
123 | SP_REAC_HEAT_OF_COMBUSTION,
124 | SP_REAC_IDEAL,
125 | SP_REAC_RADIATIVE_FRACTION, # moved from RADI
126 | SP_REAC_other,
127 | )
128 |
--------------------------------------------------------------------------------
/lang/SN_TIME.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | import logging
4 | from bpy.types import Scene
5 | from bpy.props import BoolProperty, FloatProperty
6 | from ..config import TIME_P
7 | from ..types import BFParam, BFParamOther, BFNamelistSc, FDSParam, FDSList
8 | from ..bl.ui_lists import WM_PG_bf_other, WM_UL_bf_other_items
9 |
10 | log = logging.getLogger(__name__)
11 |
12 |
13 | class SP_TIME_setup_only(BFParam):
14 | label = "Smokeview Geometry Setup"
15 | description = "Set Smokeview to setup only geometry"
16 | bpy_type = Scene
17 | bpy_idname = "bf_time_setup_only"
18 | bpy_prop = BoolProperty
19 | bpy_default = False
20 |
21 |
22 | class SP_TIME_T_BEGIN(BFParam):
23 | label = "T_BEGIN [s]"
24 | description = "Simulation starting time"
25 | fds_label = "T_BEGIN"
26 | fds_default = 0.0
27 | bpy_type = Scene
28 | bpy_idname = "bf_time_t_begin"
29 | bpy_prop = FloatProperty
30 | bpy_other = {
31 | "step": 100.0,
32 | "precision": TIME_P,
33 | } # "unit": "TIME", not working
34 |
35 | def get_active(self, context):
36 | return not self.element.bf_time_setup_only
37 |
38 |
39 | class SP_TIME_T_END(BFParam):
40 | label = "T_END [s]"
41 | description = "Simulation ending time"
42 | fds_label = "T_END"
43 | bpy_type = Scene
44 | bpy_idname = "bf_time_t_end"
45 | bpy_prop = FloatProperty
46 | bpy_default = 1.0
47 | bpy_other = {
48 | "step": 100.0,
49 | "precision": TIME_P,
50 | } # "unit": "TIME", not working
51 |
52 | def to_fds_list(self, context) -> FDSList:
53 | if self.element.bf_time_setup_only:
54 | return FDSParam(
55 | fds_label="T_END",
56 | value=0.0,
57 | msg="Smokeview setup only",
58 | precision=TIME_P,
59 | )
60 | return super().to_fds_list(context)
61 |
62 | def get_active(self, context):
63 | return not self.element.bf_time_setup_only
64 |
65 | def get_exported(self, context):
66 | return True
67 |
68 |
69 | class SP_TIME_other(BFParamOther):
70 | bpy_type = Scene
71 | bpy_idname = "bf_time_other"
72 | bpy_pg = WM_PG_bf_other
73 | bpy_ul = WM_UL_bf_other_items
74 |
75 |
76 | class SN_TIME(BFNamelistSc):
77 | label = "TIME"
78 | description = "Simulation time settings"
79 | enum_id = 3002
80 | fds_label = "TIME"
81 | bpy_export = "bf_time_export"
82 | bpy_export_default = True
83 | bf_params = SP_TIME_T_BEGIN, SP_TIME_T_END, SP_TIME_setup_only, SP_TIME_other
84 |
--------------------------------------------------------------------------------
/lang/SN_config.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | import logging, os, bpy
4 | from bpy.types import Scene, Material
5 | from bpy.props import (
6 | BoolProperty,
7 | FloatProperty,
8 | StringProperty,
9 | PointerProperty,
10 | EnumProperty,
11 | IntProperty,
12 | )
13 | from ..config import LP, DEFAULT_MAS
14 | from ..types import BFParam, BFNamelistSc, BFException
15 |
16 | log = logging.getLogger(__name__)
17 |
18 |
19 | class SP_config_case_name(BFParam):
20 | label = "Case Filename"
21 | description = "Filename for exported FDS case,\nalso used as HEAD CHID"
22 | bpy_type = Scene
23 | bpy_idname = "name"
24 |
25 | def copy_to(self, context, dest_element):
26 | pass
27 |
28 | def draw_operators(self, context, layout):
29 | layout.operator("scene.bf_props_to_sc", icon="COPYDOWN", text="")
30 |
31 |
32 | class SP_config_directory(BFParam):
33 | label = "Case Directory"
34 | description = "Default destination directory for the exported FDS case"
35 | bpy_type = Scene
36 | bpy_idname = "bf_config_directory"
37 | bpy_prop = StringProperty
38 | # no bpy_default, user choice when saving
39 | bpy_other = {"subtype": "DIR_PATH", "maxlen": 1024}
40 |
41 | def check(self, context):
42 | value = self.element.bf_config_directory
43 | if not os.path.exists(bpy.path.abspath(value or "//.")):
44 | raise BFException(self, f"Case directory <{value}> not available")
45 |
46 | def copy_to(self, context, dest_element):
47 | pass
48 |
49 |
50 | class SP_config_text(BFParam):
51 | label = "Free Text"
52 | description = "Internal free text, included verbatim"
53 | bpy_type = Scene
54 | bpy_idname = "bf_config_text"
55 | bpy_prop = PointerProperty
56 | bpy_other = {"type": bpy.types.Text}
57 |
58 | def draw_operators(self, context, layout):
59 | layout.operator("scene.bf_show_text", text="", icon="GREASEPENCIL")
60 |
61 |
62 | class SP_config_text_position(BFParam):
63 | label = "Free Text Position"
64 | description = "Set Free Text position in the exported file"
65 | bpy_type = Scene
66 | bpy_idname = "bf_config_text_position"
67 | bpy_prop = EnumProperty
68 | bpy_default = "BEGIN"
69 | bpy_other = {
70 | "items": (
71 | (
72 | "BEGIN",
73 | "At Beginning",
74 | "Insert at the beginning of the exported file",
75 | 100,
76 | ),
77 | (
78 | "END",
79 | "At End",
80 | "Insert at the end of the exported file",
81 | 500,
82 | ),
83 | ),
84 | }
85 |
86 |
87 | class SP_config_default_voxel_size(BFParam):
88 | label = "Default Voxel/Pixel Size"
89 | description = "Default voxel/pixel resolution"
90 | bpy_type = Scene
91 | bpy_idname = "bf_default_voxel_size"
92 | bpy_prop = FloatProperty
93 | bpy_default = 0.1
94 | bpy_other = {
95 | "step": 1.0,
96 | "precision": LP,
97 | "min": 0.001,
98 | "max": 20.0,
99 | "unit": "LENGTH",
100 | }
101 |
102 |
103 | class SP_config_default_SURF(BFParam):
104 | label = "Default SURF"
105 | description = (
106 | "Specify the particular SURF to be applied as the default boundary condition"
107 | )
108 | bpy_type = Scene
109 | bpy_prop = PointerProperty
110 | bpy_idname = "bf_default_surf"
111 | bpy_other = {"type": Material}
112 |
113 | def check(self, context):
114 | if (
115 | self.element.bf_default_surf
116 | and self.element.bf_default_surf.name in DEFAULT_MAS
117 | ):
118 | raise BFException(
119 | self,
120 | f"Cannot set predefined boundary condition <{self.element.name}> as default SURF",
121 | )
122 |
123 |
124 | class SP_config_mpi_processes(BFParam):
125 | label = "MPI Processes"
126 | description = (
127 | "Number of MPI processes automatically allocated to the MESH instances."
128 | )
129 | bpy_type = Scene
130 | bpy_idname = "bf_config_mpi_processes"
131 | bpy_prop = IntProperty
132 | bpy_default = 1
133 | bpy_other = {"min": 1}
134 | bpy_export = "bf_config_mpi_processes_export"
135 | bpy_export_default = True
136 |
137 |
138 | class SP_config_openmp_threads(BFParam):
139 | label = "OpenMP Threads"
140 | description = "Number of OpenMP threads assigned to each process."
141 | bpy_type = Scene
142 | bpy_idname = "bf_config_openmp_threads"
143 | bpy_prop = IntProperty
144 | bpy_default = 1
145 | bpy_other = {"min": 1}
146 | bpy_export = "bf_config_openmp_threads_export"
147 | bpy_export_default = False
148 |
149 |
150 | class SN_config(BFNamelistSc):
151 | label = "FDS Case Config"
152 | bf_params = (
153 | SP_config_case_name,
154 | SP_config_directory,
155 | SP_config_text,
156 | SP_config_text_position,
157 | SP_config_default_voxel_size,
158 | SP_config_default_SURF,
159 | SP_config_mpi_processes,
160 | SP_config_openmp_threads,
161 | )
162 |
163 | def draw(self, context, layout):
164 | row = layout.column(align=True)
165 | row.operator("scene.bf_show_fds_code", icon="HIDE_OFF")
166 | return super().draw(context, layout)
167 |
--------------------------------------------------------------------------------
/lang/__init__.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | # Import all py file to have them loaded
4 | # the order of scene namelists is also the exporting order
5 |
6 | from . import (
7 | bf_scene,
8 | bf_material,
9 | bf_collection,
10 | bf_object,
11 | SN_MOVE,
12 | SN_config,
13 | SN_HEAD,
14 | SN_TIME,
15 | SN_MISC,
16 | SN_PRES,
17 | SN_RADI,
18 | SN_REAC,
19 | SN_CATF,
20 | SN_DUMP,
21 | SN_MOVE,
22 | SN_MULT,
23 | MN_SURF,
24 | OP_XB,
25 | OP_XYZ,
26 | OP_PB,
27 | OP_SURF_ID,
28 | ON_DEVC,
29 | ON_GEOM,
30 | ON_HOLE,
31 | ON_INIT,
32 | ON_MESH,
33 | ON_MOVE,
34 | ON_MULT,
35 | ON_OBST,
36 | ON_other,
37 | ON_PROF,
38 | ON_SLCF,
39 | ON_VENT,
40 | ON_ZONE,
41 | )
42 |
43 |
44 | def register():
45 | import logging
46 | from ..types import BFParam, BFNamelist
47 |
48 | log = logging.getLogger(__name__)
49 | log.info("Register lang...")
50 |
51 | # Update namelist_cls items (after importing all namelists)
52 | bf_object.update_OP_namelist_cls_items()
53 | bf_material.update_MP_namelist_cls_items()
54 |
55 | # Register Blender entities extensions
56 | bf_object.BFObject.register()
57 | bf_material.BFMaterial.register()
58 | bf_scene.BFScene.register()
59 | bf_collection.BFCollection.register()
60 |
61 | # Register all lang classes, as recorded in BFParam and BFNamelist
62 | for bf_param in BFParam.subclasses:
63 | bf_param.register()
64 | for bf_namelist in BFNamelist.subclasses:
65 | bf_namelist.register()
66 |
67 |
68 | def unregister():
69 | import logging
70 | from ..types import BFParam, BFNamelist
71 |
72 | log = logging.getLogger(__name__)
73 | log.info("Unregister lang...")
74 |
75 | # Unregister Blender entities extensions
76 | bf_object.BFObject.unregister()
77 | bf_material.BFMaterial.unregister()
78 | bf_scene.BFScene.unregister()
79 | bf_collection.BFCollection.unregister()
80 |
81 | # Unregister all lang classes, as recorded in BFParam and BFNamelist
82 | for bf_namelist in BFNamelist.subclasses:
83 | bf_namelist.unregister()
84 | for bf_param in BFParam.subclasses:
85 | bf_param.unregister()
86 |
--------------------------------------------------------------------------------
/lang/bf_collection.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | import logging
4 |
5 | from bpy.types import Collection
6 | from ..types import FDSList
7 |
8 | log = logging.getLogger(__name__)
9 |
10 |
11 | class BFCollection:
12 | """!
13 | Extension of Blender Collection.
14 | """
15 |
16 | def get_layer_collection(self, context, _layer_collection=None):
17 | """!
18 | Return related layer_collection in current context.
19 | @param context: the Blender context.
20 | @param _layer_collection: internal use for recursivity.
21 | @return layer_collection related to self in current context.
22 | """
23 | if not _layer_collection:
24 | _layer_collection = context.view_layer.layer_collection
25 | found = None
26 | if _layer_collection.name == self.name:
27 | return _layer_collection
28 | for c in _layer_collection.children:
29 | found = self.get_layer_collection(context, _layer_collection=c)
30 | if found:
31 | return found
32 |
33 | def to_fds_list(self, context, full=False) -> FDSList:
34 | """!
35 | Return the FDSList instance from self, never None.
36 | """
37 | layer_collection = self.get_layer_collection(context)
38 | if self.hide_render or layer_collection.exclude:
39 | return FDSList() # exclude self from exporting
40 | header = f"\n-- Blender Collection: <{self.name}>"
41 | if full:
42 | # MESH are centrally managed and exported by bf_scene
43 | obs = list(ob for ob in self.objects if ob.bf_namelist_cls != "ON_MESH")
44 | else:
45 | obs = list(self.objects)
46 | obs.sort(key=lambda k: k.name) # alphabetic by name
47 | iterable = (ob.to_fds_list(context=context) for ob in obs)
48 | fds_list = FDSList(header=header, iterable=iterable)
49 | fds_list.extend(
50 | child.to_fds_list(context=context, full=full) for child in self.children
51 | )
52 | return fds_list
53 |
54 | @classmethod
55 | def register(cls):
56 | """!
57 | Register related Blender properties.
58 | @param cls: class to be registered.
59 | """
60 | Collection.to_fds_list = cls.to_fds_list
61 | Collection.get_layer_collection = cls.get_layer_collection
62 |
63 | @classmethod
64 | def unregister(cls):
65 | """!
66 | Unregister related Blender properties.
67 | @param cls: class to be unregistered.
68 | """
69 | del Collection.get_layer_collection
70 | del Collection.to_fds_list
71 |
--------------------------------------------------------------------------------
/lang/bf_material.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | import logging
4 | from bpy.types import Material
5 | from bpy.props import EnumProperty
6 | from ..types import (
7 | BFParam,
8 | BFParamOther,
9 | BFParamFYI,
10 | BFNamelist,
11 | BFException,
12 | BFNotImported,
13 | FDSList,
14 | )
15 | from ..bl.ui_lists import WM_PG_bf_other, WM_UL_bf_other_items
16 | from .. import config
17 |
18 | log = logging.getLogger(__name__)
19 |
20 |
21 | class BFMaterial:
22 | """!
23 | Extension of Blender Material.
24 | """
25 |
26 | @property
27 | def bf_namelist(self):
28 | """!
29 | Return related bf_namelist, instance of BFNamelist.
30 | """
31 | return BFNamelist.get_subclass(cls_name=self.bf_namelist_cls)(element=self)
32 |
33 | def to_fds_list(self, context) -> FDSList:
34 | """!
35 | Return the FDSList instance from self, never None.
36 | """
37 | return self.bf_namelist.to_fds_list(context)
38 |
39 | @classmethod
40 | def register(cls):
41 | """!
42 | Register related Blender properties.
43 | @param cls: class to be registered.
44 | """
45 | Material.bf_namelist = cls.bf_namelist
46 | Material.to_fds_list = cls.to_fds_list
47 |
48 | @classmethod
49 | def unregister(cls):
50 | """!
51 | Unregister related Blender properties.
52 | @param cls: class to be unregistered.
53 | """
54 | del Material.to_fds_list
55 | del Material.bf_namelist
56 |
57 |
58 | # Before updating the items, the lang classes should be imported
59 | # Only after updating the items, the MP_namelist_cls can be registered
60 | def update_MP_namelist_cls_items():
61 | items = [
62 | (cls.__name__, cls.label, cls.description, cls.enum_id)
63 | for cls in BFNamelist.subclasses
64 | if cls.bpy_type == Material and cls.enum_id
65 | ]
66 | items.sort(key=lambda k: k[1])
67 | MP_namelist_cls.bpy_other["items"] = items
68 | # log.debug(f"Updated MP_namelist_cls items (before registration): {items}")
69 |
70 |
71 | def update_MP_namelist_cls(self, context):
72 | self.bf_namelist.set_appearance(context)
73 |
74 |
75 | class MP_namelist_cls(BFParam):
76 | label = "Namelist"
77 | description = "Identification of FDS namelist"
78 | bpy_type = Material
79 | bpy_idname = "bf_namelist_cls"
80 | bpy_prop = EnumProperty
81 | bpy_other = {
82 | "items": (("MN_SURF", "SURF", "Generic boundary condition", 2000),),
83 | "update": update_MP_namelist_cls,
84 | }
85 | bpy_default = "MN_SURF"
86 |
87 | def draw_operators(self, context, layout):
88 | layout.operator("material.bf_props_to_ma", icon="COPYDOWN", text="")
89 |
90 |
91 | class MP_ID(BFParam):
92 | label = "ID"
93 | description = "Material identification name"
94 | fds_label = "ID"
95 | bpy_type = Material
96 | bpy_prop = None # to avoid creation
97 | bpy_idname = "name"
98 |
99 | def copy_to(self, context, dest_element):
100 | pass
101 |
102 | def draw(self, context, layout):
103 | row = layout.row(align=True)
104 | row.prop(self.element, "name", text="ID")
105 | row.prop(self.element, "use_fake_user", text="") # force export
106 |
107 |
108 | class MP_FYI(BFParamFYI):
109 | bpy_type = Material
110 | bpy_idname = "bf_fyi"
111 |
112 |
113 | class MP_RGB(BFParam):
114 | label = "RGB, TRANSPARENCY"
115 | description = "Set color and transparency of the boundary condition"
116 | fds_label = "RGB"
117 | bpy_type = Material
118 | bpy_prop = None # Do not register
119 | bpy_idname = "diffuse_color"
120 |
121 | def get_value(self, context):
122 | c = getattr(self.element, self.bpy_idname)
123 | return (int(c[0] * 255), int(c[1] * 255), int(c[2] * 255))
124 |
125 | def set_value(self, context, value):
126 | c = getattr(self.element, self.bpy_idname)
127 | c[0], c[1], c[2] = value[0] / 255.0, value[1] / 255.0, value[2] / 255.0
128 |
129 |
130 | class MP_COLOR(BFParam): # only import
131 | label = "COLOR"
132 | description = "Color"
133 | fds_label = "COLOR"
134 | bpy_type = Material
135 | bpy_prop = None # Do not register
136 | bpy_idname = "diffuse_color"
137 |
138 | def get_exported(self, context):
139 | return False
140 |
141 | def set_value(self, context, value):
142 | c = getattr(self.element, self.bpy_idname)
143 | rgb = config.FDS_COLORS.get(value)
144 | if not rgb:
145 | raise BFException(self, f"Unknown color <{value}>")
146 | c[0], c[1], c[2] = rgb[0] / 255.0, rgb[1] / 255.0, rgb[2] / 255.0
147 |
148 | def draw(self, context, layout): # see MP_RGB
149 | pass
150 |
151 |
152 | class MP_TRANSPARENCY(BFParam): # no draw
153 | label = "TRANSPARENCY"
154 | description = "Transparency"
155 | fds_label = "TRANSPARENCY"
156 | fds_default = 1.0
157 | bpy_type = Material
158 | bpy_prop = None # Do not register
159 | bpy_idname = "diffuse_color"
160 |
161 | def get_value(self, context):
162 | c = getattr(self.element, self.bpy_idname)
163 | return c[3]
164 |
165 | def set_value(self, context, value):
166 | c = getattr(self.element, self.bpy_idname)
167 | c[3] = value
168 |
169 | def draw(self, context, layout): # see MP_RGB
170 | pass
171 |
172 |
173 | # class MP_MATL_ID(BFParam):
174 | # label = "MATL_ID"
175 | # description = "Reference to a MATL (Material) line for self properties"
176 | # fds_label = "MATL_ID"
177 | # bpy_type = Material
178 | # bpy_prop = StringProperty
179 | # bpy_idname = "bf_matl_id"
180 |
181 | # def draw_operators(self, context, layout):
182 | # layout.operator("material.bf_choose_matl_id", icon="VIEWZOOM", text="")
183 |
184 | # def set_value(self, context, value):
185 | # if isinstance(value, str):
186 | # return super().set_value(context, value)
187 | # else:
188 | # raise BFNotImported(self, "Material list not handled")
189 |
190 | # When this Material is default, export DEFAULT=T
191 | class MP_DEFAULT(BFParam): # no label
192 | label = "DEFAULT"
193 | description = "Set default SURF"
194 | fds_label = "DEFAULT"
195 | fds_default = False
196 | bpy_type = Material
197 |
198 | def get_value(self, context):
199 | return context.scene.bf_default_surf == self.element
200 |
201 | def set_value(self, context, value=None):
202 | context.scene.bf_default_surf = self.element
203 |
204 |
205 | class MP_other(BFParamOther):
206 | bpy_type = Material
207 | bpy_idname = "bf_other"
208 | bpy_pg = WM_PG_bf_other
209 | bpy_ul = WM_UL_bf_other_items
210 |
--------------------------------------------------------------------------------
/lang/bf_scene/__init__.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | from .bf_scene import BFScene
--------------------------------------------------------------------------------
/lang/bf_scene/bf_scene.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | import logging, bpy, os
4 | from bpy.types import Scene
5 | from bpy.props import IntVectorProperty
6 | from ...config import MAXLEN
7 | from ...types import BFNamelist, FDSList, BFParam
8 | from ... import utils
9 | from . import export_helper, import_helper
10 |
11 | log = logging.getLogger(__name__)
12 |
13 |
14 | class BFScene:
15 | """!
16 | Extension of Blender Scene.
17 | """
18 |
19 | @property
20 | def bf_namelists(self):
21 | """!
22 | Return related bf_namelist, instance of BFNamelist.
23 | """
24 | return (n(element=self) for n in BFNamelist.subclasses if n.bpy_type == Scene)
25 |
26 | def to_fds_list(self, context, full=False) -> FDSList:
27 | """!
28 | Return the FDSList instance from self, never None.
29 | """
30 | # Set mysef as the right Scene instance in the context
31 | # It is needed, because context.scene is needed elsewhere
32 | bpy.context.window.scene = self # set context.scene
33 | return export_helper.sc_to_fds_list(context=context, sc=self, full=full)
34 |
35 | def to_fds(self, context, full=False, save=False):
36 | """!
37 | Return the FDS formatted string.
38 | @param context: the Blender context.
39 | @param full: if True, return full FDS case.
40 | @param save: if True, save to disk.
41 | @return FDS formatted string (eg. "&OBST ID='Test' /"), or None.
42 | """
43 | log.info(f"Export from Scene {self.name}...")
44 | text = self.to_fds_list(context=context, full=full).to_string()
45 | if save:
46 | filepath = utils.io.transform_rbl_to_abs(
47 | context=context,
48 | filepath_rbl=self.bf_config_directory,
49 | name=self.name,
50 | extension=".fds",
51 | )
52 | log.info(f"Save to {filepath}...")
53 | utils.io.write_txt_file(filepath, text)
54 | log.info("Done!")
55 | return text
56 |
57 | def from_fds(
58 | self,
59 | context,
60 | filepath=None,
61 | f90_namelists=None,
62 | fds_list=None,
63 | set_tmp=False,
64 | ):
65 | """!
66 | Set self.bf_namelists from FDSList, on error raise BFException.
67 | @param context: the Blender context.
68 | @param filepath: filepath of FDS case to be imported.
69 | @param f90_namelists: FDS formatted string of namelists, eg. "&OBST ID='Test' /\n&TAIL /".
70 | @param fds_list: FDSList of FDSNamelists.
71 | @param set_tmp: set temporary Objects.
72 | """
73 | log.info(f"Import to Scene {self.name}...")
74 |
75 | # Set mysef as the right Scene instance in the context
76 | # this is used by context.scene calls elsewhere
77 | # Also for visibility
78 | context.window.scene = self
79 |
80 | # Load fds case from filepath
81 | if filepath:
82 | log.info(f"Load from {filepath}...")
83 | filepath = utils.io.transform_rbl_to_abs(
84 | context=context,
85 | filepath_rbl=filepath,
86 | )
87 | f90_namelists = utils.io.read_txt_file(filepath=filepath)
88 | # and set imported fds case dir, because others rely on it
89 | # it is restored later
90 | bf_config_directory = self.bf_config_directory
91 | self.bf_config_directory = os.path.dirname(filepath)
92 |
93 | # Load fds case from f90_namelists
94 | if f90_namelists:
95 | fds_list = FDSList(f90_namelists=f90_namelists)
96 |
97 | # Load fds_case from fds_list (protect from None)
98 | if not fds_list:
99 | log.info("Prepare FDSList...")
100 | fds_list = FDSList()
101 |
102 | fds_namelist_qty = len(fds_list)
103 |
104 | # Prepare free text for unmanaged namelists, no rewind
105 | # if not existing, create
106 | log.info("Prepare free text...")
107 | self.bf_config_text = utils.ui.show_bl_text(
108 | context=context,
109 | bl_text=self.bf_config_text,
110 | name="New Free Text",
111 | )
112 |
113 | # Import
114 | texts = list()
115 | filename = bpy.path.basename(filepath or "")
116 | import_helper.sc_from_fds_list(
117 | context,
118 | sc=self,
119 | fds_list=fds_list,
120 | set_tmp=set_tmp,
121 | texts=texts,
122 | filename=filename,
123 | )
124 |
125 | # Finally, write free text
126 | log.info("Write free text...")
127 | header = None
128 | if filepath:
129 | header = f"-- From: <{filename}>"
130 | utils.ui.write_bl_text(
131 | context, bl_text=self.bf_config_text, header=header, texts=texts
132 | )
133 |
134 | # Restore fds case dir, to avoid overwriting imported case
135 | if filepath:
136 | self.bf_config_directory = bf_config_directory
137 |
138 | log.info("Done!")
139 | return fds_namelist_qty # feedback
140 |
141 | @classmethod
142 | def register(cls):
143 | """!
144 | Register related Blender properties.
145 | @param cls: class to be registered.
146 | """
147 | Scene.bf_namelists = cls.bf_namelists
148 | Scene.to_fds_list = cls.to_fds_list
149 | Scene.to_fds = cls.to_fds
150 | Scene.from_fds = cls.from_fds
151 |
152 | @classmethod
153 | def unregister(cls):
154 | """!
155 | Unregister related Blender properties.
156 | @param cls: class to be unregistered.
157 | """
158 | del Scene.bf_file_version
159 | del Scene.from_fds
160 | del Scene.to_fds
161 | del Scene.to_fds_list
162 | del Scene.bf_namelists
163 |
164 |
165 | # Automatically filled by an handler
166 | class OP_file_version(BFParam):
167 | label = "BlenderFDS File Version"
168 | description = "BlenderFDS File Version"
169 | bpy_type = Scene
170 | bpy_idname = "bf_file_version"
171 | bpy_prop = IntVectorProperty
172 | bpy_default = (0, 0, 0)
173 | bpy_other = {"size": 3}
174 |
--------------------------------------------------------------------------------
/startup.blend:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/firetools/blenderfds/a0542f2b20d79dc8ec56da138a07da74b3abfb61/startup.blend
--------------------------------------------------------------------------------
/types/__init__.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | """!
4 | BlenderFDS, types.
5 | """
6 |
7 | from .bf_exception import BFException, BFNotImported
8 | from .bf_namelist import BFNamelist, BFNamelistMa, BFNamelistOb, BFNamelistSc
9 | from .bf_param import BFParam, BFParamFYI, BFParamOther
10 | from .fds_list import FDSList, FDSMulti, FDSNamelist, FDSParam
11 |
12 | # Nothing to register here
13 |
--------------------------------------------------------------------------------
/types/bf_exception.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | """!
4 | BlenderFDS, general utilities and exception types.
5 | """
6 |
7 |
8 | class BFException(Exception):
9 | """!
10 | Exception raised by BlenderFDS methods in case of an error.
11 | """
12 |
13 | def __init__(self, sender=None, msg=None):
14 | """!
15 | Class constructor.
16 | @param sender: the object that generates the exception
17 | @param msg: exception message
18 | """
19 | ## The object that generates the exception
20 | self.sender = sender
21 | ## Exception message
22 | self.msg = msg or "Unknown error"
23 |
24 | def __str__(self):
25 | sender = self.sender
26 | if sender:
27 | element = getattr(sender, "element", None)
28 | if element:
29 | name = f"{element.name}: {sender.fds_label or sender.label or sender.__class__.__name__}"
30 | else:
31 | name = getattr(sender, "name", None) or sender.__class__.__name__
32 | return f"ERROR: {name}: {self.msg}"
33 | else:
34 | return self.msg
35 |
36 | def __repr__(self) -> str:
37 | return f"{self.__class__.__name__}(sender={self.sender}, msg={self.msg})"
38 |
39 |
40 | class BFNotImported(BFException):
41 | """!
42 | Exception raised by BlenderFDS methods when some FDS importing fails.
43 | """
44 |
45 | pass
46 |
--------------------------------------------------------------------------------
/utils/__init__.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | from . import geometry, io, gis, ui, binpacking, text, updater
4 |
5 | # Nothing to register here
6 |
--------------------------------------------------------------------------------
/utils/binpacking.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | """!
4 | Simple binpacking algorithm.
5 | """
6 |
7 |
8 | def _argmin(l):
9 | """Get the index of min item in an iterable."""
10 | l = tuple(l)
11 | return min(range(len(l)), key=l.__getitem__)
12 |
13 |
14 | def binpack(nbin, item_weigths):
15 | """First-fit binpacking algorithm with fixed number of bins.
16 |
17 | @param nbin: fixed number of bins
18 | @param item_weigths: list of items and their weigths: ((w0, item0), (w1, item1), ...)
19 | @return: list of bins: [[wb0, [item0, item1, ...]], [wb1, [item5, ...]], ...]
20 | """
21 | # Reverse sort items by weigth
22 | item_weigths = list(item_weigths)
23 | item_weigths.sort(key=lambda k: k[0], reverse=True)
24 |
25 | # Init bins
26 | bins = list(list((0, list())) for i in range(nbin))
27 |
28 | # Pack
29 | for i, (weigth, item) in enumerate(item_weigths):
30 | # Get the index of the lighter bin
31 | j = _argmin(tuple(bin[0] for bin in bins))
32 | # Add the weigth and assign the item to the bin
33 | bins[j][0] += weigth
34 | bins[j][1].append(item)
35 |
36 | return bins
37 |
38 |
39 | def test():
40 | item_weigths = ((10, "A"), (12, "B"), (11, "C"), (4, "D"), (3, "E"), (2, "F"))
41 |
42 | bins = binpack(nbin=1, item_weigths=item_weigths)
43 | print(len(bins), ":", bins)
44 | assert bins == [[42, ["B", "C", "A", "D", "E", "F"]]]
45 |
46 | bins = binpack(nbin=2, item_weigths=item_weigths)
47 | print(len(bins), ":", bins)
48 | assert bins == [[21, ["B", "D", "E", "F"]], [21, ["C", "A"]]]
49 |
50 | bins = binpack(nbin=3, item_weigths=item_weigths)
51 | print(len(bins), ":", bins)
52 | assert bins == [[14, ["B", "F"]], [14, ["C", "E"]], [14, ["A", "D"]]]
53 |
54 | bins = binpack(nbin=4, item_weigths=item_weigths)
55 | print(len(bins), ":", bins)
56 | assert bins == [[12, ["B"]], [11, ["C"]], [10, ["A"]], [9, ["D", "E", "F"]]]
57 |
58 | bins = binpack(nbin=5, item_weigths=item_weigths)
59 | print(len(bins), ":", bins)
60 | assert bins == [[12, ["B"]], [11, ["C"]], [10, ["A"]], [4, ["D"]], [5, ["E", "F"]]]
61 |
62 | bins = binpack(nbin=6, item_weigths=item_weigths)
63 | print(len(bins), ":", bins)
64 | assert bins == [
65 | [12, ["B"]],
66 | [11, ["C"]],
67 | [10, ["A"]],
68 | [4, ["D"]],
69 | [3, ["E"]],
70 | [2, ["F"]],
71 | ]
72 |
73 | bins = binpack(nbin=7, item_weigths=item_weigths)
74 | print(len(bins), ":", bins)
75 | assert bins == [
76 | [12, ["B"]],
77 | [11, ["C"]],
78 | [10, ["A"]],
79 | [4, ["D"]],
80 | [3, ["E"]],
81 | [2, ["F"]],
82 | [0, []],
83 | ]
84 |
85 |
86 | if __name__ == "__main__":
87 | test()
88 |
--------------------------------------------------------------------------------
/utils/text.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | """!
4 | BlenderFDS, text management utilities.
5 | """
6 |
7 | from ..config import MAXLEN, INDENT
8 |
9 |
10 | def append_word(lines, word, separator=" ", force_break=False) -> list:
11 | """!
12 | Append word to the last of lines, generate newline and ident if needed.
13 | """
14 | if not force_break and len(lines[-1]) + len(separator) + len(word) <= MAXLEN:
15 | # append to current line
16 | lines[-1] += separator + word
17 | else:
18 | # append to new line w indent, wo separator
19 | lines.append(" " * INDENT + word)
20 | return lines
--------------------------------------------------------------------------------
/utils/ui.py:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | """!
4 | BlenderFDS, Blender user interface utilities.
5 | """
6 |
7 | import bpy
8 |
9 |
10 | def get_screen_area(context, area_type="PROPERTIES"):
11 | """!
12 | Get existing ui area or create one
13 | """
14 | selected_area = None
15 | for w in context.window_manager.windows:
16 | for area in w.screen.areas:
17 | if area.type == area_type:
18 | selected_area = area
19 | break
20 | if not selected_area:
21 | # Call user prefs window
22 | bpy.ops.screen.userpref_show("INVOKE_DEFAULT")
23 | # Change area type
24 | area = context.window_manager.windows[-1].screen.areas[0]
25 | area.type = area_type
26 | selected_area = area
27 | return selected_area
28 |
29 |
30 | def show_bl_text(context, bl_text=None, name=None):
31 | """!
32 | Show bl_text in Blender Text Editor.
33 | """
34 | # If not given, create text
35 | if not bl_text:
36 | bl_text = bpy.data.texts.new(name or str())
37 | # Rewind to the first line
38 | bl_text.current_line_index = 0
39 | # Search existing ui area or create one
40 | selected_area = get_screen_area(context, area_type="TEXT_EDITOR")
41 | # Set highlighting
42 | space = selected_area.spaces[0]
43 | space.text = bl_text
44 | space.show_line_numbers = True
45 | space.show_line_highlight = True
46 | space.show_word_wrap = True
47 | space.show_margin = True
48 | space.margin_column = 130
49 | space.show_syntax_highlight = True
50 | return bl_text
51 |
52 |
53 | def write_bl_text(context, bl_text, header=None, texts=()):
54 | """!
55 | Write text in bl_text from Blender Text Editor.
56 | """
57 | if not texts:
58 | return
59 | # Prepare body
60 | body = bl_text.as_string().strip()
61 | if body:
62 | body += "\n"
63 | if header:
64 | body += "\n"
65 | if header:
66 | body += f"{header}\n"
67 | body += "\n".join(texts)
68 | bl_text.from_string(body)
69 |
70 |
71 | def show_property_panel(context, space_context="MATERIAL"):
72 | """!
73 | Show Material Panel.
74 | """
75 | selected_area = get_screen_area(context, area_type="PROPERTIES")
76 | selected_area.spaces[0].context = space_context
77 |
78 |
79 | def view_all(context):
80 | """!
81 | Run view3d.view_all operator.
82 | """
83 | for area in context.screen.areas:
84 | if area.type == "VIEW_3D":
85 | for region in area.regions:
86 | if region.type == "WINDOW":
87 | override = {
88 | "area": area,
89 | "region": region,
90 | "edit_object": context.edit_object,
91 | }
92 | with bpy.context.temp_override(**override):
93 | bpy.ops.view3d.view_all()
94 | for space in area.spaces:
95 | if space.type == "VIEW_3D":
96 | space.clip_end = 1e6
97 |
--------------------------------------------------------------------------------