Python Scripting API

HeatSimulator embeds a Python interpreter that lets you automate the application: load projects, transform geometry, assign boundary conditions, run simulations and read back results.

The whole API is exposed through a single, pre-created global named **heatsim**. You never construct it yourself — it is available in every script’s namespace.

AI assistant (MCP). HeatSimulator can also expose this same API to an AI assistant through the Model Context Protocol (MCP). With it enabled, an assistant such as Claude can drive the project you currently have open — load and edit models, assign materials and boundaries, run simulations and read back results — all from a chat. Crucially, the assistant also gets full access to the documentation: this entire scripting reference, every example script, and the complete software user manual. It can search and read them on demand, so it already knows how the software works and how to script it — turning it into a genuinely useful assistant that can answer “how do I…?” questions *and* carry the steps out for you. To enable it: open the Python Console (*View → Python console*) and click the MCP button in its toolbar — this starts the in-app MCP server. The console then prints the connection details: point Claude Code at the printed http://127.0.0.1:… URL, or paste the printed configuration into Claude Desktop. Keep the console open with MCP on while you use the assistant.

The API is organised into namespaces hanging off heatsim, one per area of the application:

NamespaceWhat it does
heatsim.projectLoad / save / merge / clear projects, templates
heatsim.geometryTranslate, rotate, scale, mirror the project
heatsim.areaMaterials and adiabatic areas
heatsim.elementsSection elements and element groups
heatsim.boundaryBoundary conditions
heatsim.simulationRun simulations and read results
heatsim.componentsSimulation components (3D)
heatsim.threeD3D stack levels
heatsim.reportReport views and PDF export
heatsim.appApplication-level settings (flow/gravity direction, close, …)

A couple of environment toggles (set_verbose, set_verbose_console) live directly on heatsim. So a call reads, for example, heatsim.project.load("model.mos") or heatsim.simulation.simulate().

Scripting is only available when the scripting module is active. When it is, the menu entries described below appear.


Running scripts

There are two ways to run a script:

MethodHow
Python Console*View → Python console*. Type single commands (press Enter) or load and run a whole .py file from its toolbar.
At startupPass a .py file as the project to open. With the scripting module active it is run as a script instead of being loaded as a project.

All console output appears in the Python Console’s text area (see Output and messages).

The Python Console’s command line behaves like a regular prompt:

  • Up / Down arrows browse the history of commands you have entered.
  • Expression results are echoed automatically — typing heatsim.report.get_views() prints its result without wrapping it in print() (like a Python REPL). This applies to single typed commands, not to scripts run from a file.

Script folder and relative paths

heatsim.get_script_folder() returns the folder the running script lives in.

heatsim.project.load and heatsim.project.merge resolve their file argument like this:

  1. Absolute path — used as-is.
  2. Relative, with a script running — relative to the script folder.
  3. Relative, no script (e.g. the Python console or an MCP session) — relative to the last loaded project’s folder.
  4. Bundled sample projects — when the file is not found above, it is looked up among the sample projects shipped with the application. This is why the examples work pasted anywhere: heatsim.project.load("window_frame_1.mos") finds the bundled sample no matter where the script lives.

So a script/example can sit next to its .mos / .frs / .m3d data files, and an interactive/MCP session can load a project and then load or merge sibling files by name. (heatsim.project.save_as resolves relative paths against the script folder; to write where the user can always create files, use [heatsim.get_output_folder()](#the-heatsim-utilities).)

import os
project = os.path.join(heatsim.get_script_folder(), "model.mos")
heatsim.project.load(project)

heatsim.get_script_folder() and the other standalone helpers (dump, the direction enums, …) live on the heatsim object too — see The heatsim utilities — so no import is ever needed.


Output and messages

Four things show up in the Python Console:

  1. **print() / stdout / stderr** — redirected into the console automatically.
  2. API failure messages — when a method fails it can print its own [Error] <method>: <reason> line (e.g. [Error] load: unable to load "model.mos" ...), so you get a concrete reason instead of only a False. These are off by default — turn them on with heatsim.set_verbose_console(True).
  3. Project simulation warnings and errors — anything the simulation reports (e.g. mesh problems, missing boundary conditions, convergence warnings) is echoed into the console while a script runs, prefixed with [Error], [Warning] or [Info]. This happens even though the modal message dialogs are suppressed during scripting.
  4. Return values — most methods return True/False for success/failure (see each method below).

The two verbosity switches — they are independent

SwitchControlsDefault
heatsim.set_verbose(bool)The main frame’s GUI message dialogs.on
heatsim.set_verbose_console(bool)The API’s own **[Error] … console messages** (point 2 above).off

These are fully independent: set_verbose(False) keeps the dialogs quiet, while set_verbose_console(True) adds the API’s own [Error] … reason lines. Note that the project’s simulation warnings/errors echoed to the console while a script runs (point 3) are governed by neither switch — they always appear during scripting.

Because methods return a boolean, check it. Enable set_verbose_console(True) if you want the API to print the failure reason:

heatsim.set_verbose_console(True)   # print the API's [Error] reason lines
if not heatsim.project.load("model.mos"):
    print("Load failed — see the [Error] message above for the reason.")
    heatsim.app.close(1)   # non-zero exit code signals failure

Examples

Runnable, commented example scripts bundled with the application. Click one to jump to its full source further down this page — every example has a copy-to-clipboard button there: copy it, paste it into the Python Console and press Enter. The project files the examples load ship with the application, so they run as-is.

Method reference

All coordinates and dimensions are in the project’s units. Unless noted, methods return True on success and False on failure.

heatsim (environment)

MethodReturnsDescription
heatsim.set_verbose(verbose=True)boolEnable/disable the main frame’s GUI message dialogs. Independent of the console output.
heatsim.set_verbose_console(verbose=True)boolEnable/disable the API’s own [Error] … console messages. Off by default. Independent of set_verbose().

The script folder is heatsim.get_script_folder() — one of the standalone heatsim utilities, not a namespaced method.

heatsim.project

MethodReturnsDescription
project.clear()boolClear the current project.
project.load(filename)boolLoad a project. A relative path is resolved against the script folder (if a script is running), then the last loaded project’s folder, then the bundled sample projects (see relative paths).
project.load_template(software, category, template)boolLoad a template by software name and category/template numbers — the leading numbers of the folder/file names under Templates/<software>/<lang>/<category>/<template>.mos. The language comes from the current settings. E.g. project.load_template("MoldSimulatorPro", 2, 1).
project.save_as(filename)boolSave the current project to a file (relative paths are script-relative).
project.merge(filename, dx=0, dy=0, rotation=0, rotation_center_x=0, rotation_center_y=0, exclude_adiabatic=False, match_boundary_by_name=False)boolMerge another project into the current one. The merged project is rotated by rotation degrees about (rotation_center_x, rotation_center_y) — in its original coordinates — before being translated by (dx, dy). If exclude_adiabatic is True, adiabatic areas are not merged. If match_boundary_by_name is True, boundaries are matched by name (not just by parameters), avoiding duplicates when the same project is merged repeatedly. Same path resolution as project.load.
project.invalidate()boolMark the project (and its construction lines) as needing re-validation/re-simulation.

heatsim.geometry

MethodReturnsDescription
geometry.translate(dx, dy)boolTranslate the whole project.
geometry.rotate(rotation, center_x=0, center_y=0)boolRotate around a center point.
geometry.scale(scale_x, scale_y, center_x=0, center_y=0)boolScale around a center point.
geometry.horizontal_mirror()boolFlip horizontally.
geometry.vertical_mirror()boolFlip vertically.
geometry.center()boolCenter the project on the origin — to the selected nodes, or the whole project when nothing is selected.

heatsim.area

MethodReturnsDescription
area.set_material_by_name(name, parameters)boolUpdate the material(s) named name with a dict of parameters. Include a new "name" key in parameters to rename it.
area.get_material_by_name(name)dict | FalseThe properties of the material named name as a dict, or False if there is no such material.
area.sample_material(x, y, z=0)dict | FalseThe material properties at a point (like the eyedropper), or False if nothing is there. In 3D the point is hit-tested against the mesh geometry (nearest surface, else the containing volume).
area.assign_material(properties, x, y, z=0, propagation_mode=0)boolAssign a material (a dict, e.g. from get_material_by_name / sample_material) at a point, like the fill tool. In 3D, segment materials target the hit surface triangle; otherwise the containing volume. propagation_mode (a heatsim.FillPropagationMode, see Propagation mode) is 2D only and applies to segment materials.
area.create_default_material(type)dictA default material (dict) for a heatsim.MaterialType (see Material types), ready to pass to assign_material / set_material_by_name.
area.remove_adiabatic()boolRemove adiabatic areas and their construction lines (when not shared).

heatsim.elements

MethodReturnsDescription
elements.add_section_element(x1, y1, x2, y2, length_offset=0, width=0, width_offset=0, u=0, psi=0, name="", x1_1=0, y1_1=0, x2_1=0, y2_1=0)boolAdd a section element between two points. Only the first four coordinates are required. width = 0 uses a default width; u = 0 auto-detects U. The trailing _1 coordinates define an optional second (double-length) segment.
elements.set_group_params(parameters)boolSet parameters (a dict) on the current elements group.
elements.set_group_type(type)boolSet the current elements group type. Pass a heatsim.GroupType value (see Element group types).

heatsim.boundary

MethodReturnsDescription
boundary.get_count()intNumber of boundary conditions.
boundary.get(id)dict | FalseParameters of a boundary condition, or False on failure.
boundary.assign(hit_x, hit_y, hit_z, id)boolAssign boundary id at a point.
boundary.assign_strip(hit_x, hit_y, hit_z, id)boolAssign a boundary at a point, expanding to a whole strip.
boundary.update_variant(parameters, variant, id)boolUpdate a boundary variant with new parameters (existing keys not included are kept). variant is a heatsim.BoundaryVariant (see Boundary variants).
boundary.expand()boolExpand boundary conditions onto unassigned boundaries.
boundary.clear_assigned_boundaries()boolClear all assigned boundary conditions.
boundary.set_climatic_zone(country=None, city=None, town=None, altitude=None)dict | FalseApply a climatic zone to all boundaries, smart-searching the climatic database (all parameters accept keywords). Give at least a city or a town: with only a city, the city’s altitude is used; a town refines the altitude (and implies its city when city is omitted); omitting country searches every country; an explicit altitude overrides the city’s/town’s. Returns a dict describing the match (country, country_code, city, town, climatic_zone, altitude), or False if nothing matched.

heatsim.simulation

MethodReturnsDescription
simulation.simulate()boolRun the simulation for the current component.
simulation.get_results()dictSimulation results as a nested dictionary (see below).
simulation.sample_temperature(x, y, z=0)floatTemperature at a point (simulating first if needed).

heatsim.components (3D)

MethodReturnsDescription
components.set_current(index)boolSelect the current simulation component.
components.set_current_3d()boolSelect the 3D component as current.
components.clone()boolClone (duplicate) the current component. 3D simulator only.
components.delete(ids=None)boolDelete the components with the given ids. With no ids, deletes all components except the current one. The current component is never deleted (an error is returned if its id is passed). 3D simulator only.

heatsim.threeD

MethodReturnsDescription
threeD.set_stack_level_parameters(level, parameters)boolSet parameters of a 3D stack level. Keys: extrusion, subs (subdivisions), s_tx, s_ty, s_gamma, visible, enabled.

heatsim.app

MethodReturnsDescription
app.close(exit_code=0)boolClose the application, returning exit_code as the process exit code. Tests use a non-zero code to signal failure.
app.set_flow_direction_hint(direction)boolSet the flow direction hint. Pass a FlowDirection enum value (see Direction enums).
app.set_gravity_direction(direction)boolSet the gravity direction. Pass a GravityDirection enum value (see Direction enums).
app.set_reserved_value(id, value)boolSet an internal reserved value.

heatsim.report

MethodReturnsDescription
report.get_views()list[dict]List of the report views, one dict per view with its id, name and section.
report.print_to_pdf(filename, ids=None)boolPrint report views to a PDF file (relative paths are script-relative). With no ids, prints all report views; otherwise prints only the views whose id (from report.get_views) is listed. May be unavailable in builds without PDF printing.

Direction enums

heatsim.app.set_flow_direction_hint and heatsim.app.set_gravity_direction take enum values, not raw integers. The enums live on the heatsim object as heatsim.FlowDirection and heatsim.GravityDirection:

heatsim.app.set_flow_direction_hint(heatsim.FlowDirection.DOWN)
heatsim.app.set_gravity_direction(heatsim.GravityDirection.INTO_SCREEN)
FlowDirectionGravityDirection
DOWN0LEFT0
UP1RIGHT1
LEFT2UP2
RIGHT3DOWN3
INTO_SCREEN4
OUT_OF_SCREEN5

They are IntEnums, so they compare equal to and pass through as their integer value.


Boundary variants

heatsim.boundary.update_variant (and heatsim.set_variant_use_climatic_zone) take a heatsim.BoundaryVariant value (a string enum) identifying the boundary parameter variant to update:

BoundaryVariantMeaning
FLOWSFlows
DEWSurface condensation
MOLDMold
heatsim.boundary.update_variant({"temperature": 5.0}, heatsim.BoundaryVariant.DEW, 0)

To make a boundary take its temperatures and humidity from the project’s climatic zone:

heatsim.boundary.set_climatic_zone(city="Milano")
heatsim.set_use_climatic_zone(0)                  # all variants of boundary 0
# or a single variant:
heatsim.set_variant_use_climatic_zone(heatsim.BoundaryVariant.MOLD, 0)

Element group types

heatsim.elements.set_group_type takes a heatsim.GroupType value (a string enum):

heatsim.elements.set_group_type(heatsim.GroupType.BRIDGE_SECTION_ELEMENTS)
GroupTypeMeaning
BRIDGE_SECTION_ELEMENTSBridge section elements
BRIDGE_AREASBridge areas
INSULATION_10077_2EN ISO 10077-2 insulation panel

GroupType is a str-based enum, so a member passes straight through as its internal type-id string.


Material types

heatsim.area.create_default_material takes a heatsim.MaterialType value:

cavity = heatsim.area.create_default_material(heatsim.MaterialType.CAVITY)
heatsim.area.assign_material(cavity, 0.1, 0.2)
MaterialTypeMaterialType
STANDARD0GAS6
STANDARD_SEGMENT1TRANSPARENT7
ADIABATIC2SCREW_139478
FIXED_TEMPERATURE3RSB_SEGMENT_100779
INSULATION_10077_24GLAZING_CAVITY10
CAVITY5NONE11

MaterialType is an IntEnum mirroring ci::heat_conduction_2d::Material::Type.


Propagation mode

heatsim.area.assign_material takes an optional heatsim.FillPropagationMode. It is 2D only (ignored in 3D) and only affects *segment* materials — it controls how the fill propagates along the construction lines:

FillPropagationModeMeaning
NONE0Fill only the picked segment.
BOUNDARY_OR_CAVITY_PATH1Propagate along the boundary / cavity path.
SAME_DIRECTION2Propagate to aligned (same-direction) segments.
seg = heatsim.area.create_default_material(heatsim.MaterialType.STANDARD_SEGMENT)
heatsim.area.assign_material(seg, 0.1, 0.2, 0,
                            heatsim.FillPropagationMode.BOUNDARY_OR_CAVITY_PATH)

The results dictionary

heatsim.simulation.get_results() returns a nested dictionary whose exact shape depends on the project and simulation type. Navigate it with normal dictionary access. Common paths:

results = heatsim.simulation.get_results()

# Frame-simulator frame U-value:
uf = results["uf"]

# Advanced boundary group results:
groups = results["simulation_1"]["advanced_results"]["boundary"]["groups"]

# Dynamic (transient) thermal lag:
lag = results["simulation_1"]["dynamic_lag"]

Use heatsim.dump(results) to print the structure of the dictionary and discover the paths available for your project.


The heatsim utilities

Besides the namespaces, the heatsim object carries a few standalone helpers (defined in Scripting/heatsim.py and the C++ module, then surfaced on the object at startup — no import needed):

MemberDescription
heatsim.get_script_folder()Folder of the currently running script ("" for typed console commands).
heatsim.get_scripts_path()Folder containing the bundled script-editor helper modules.
heatsim.get_output_folder()Writable per-user folder for files produced by scripts (PDFs, CSVs, saved projects, …), created on first use. The install folder may be read-only, so the examples write here.
heatsim.open_output_folder()Show the output folder in the system file manager (so the user sees where the files land) and return its path. The examples call it before writing.
heatsim.clone_parameter_map(d)Deep-clone a parameter map (dict).
heatsim.set_variant_use_climatic_zone(variant, boundary_id)Switch one boundary variant (a [BoundaryVariant](#boundary-variants)) to take its values from the climatic zone: DEW the temperatures, MOLD the temperatures and the humidity (FLOWS never uses the climatic zone — no-op).
heatsim.set_use_climatic_zone(boundary_id)Switch every variant of a boundary to the climatic zone, each with its own rules (see above).
heatsim.dump(value, indent=0, max_depth=3)Pretty-print the structure of a nested dict/list (e.g. the results from get_results()), shallowly.
heatsim.FlowDirection / heatsim.GravityDirectionThe direction enums.
heatsim.dump(heatsim.simulation.get_results())
print(heatsim.get_script_folder())

Example scripts

The full source of every bundled example.

01_basics/01_load_and_simulate.py

Load a project, run a simulation and read the results.

Run this from the HeatSimulator Python Console (View -> Python console -> “Run script”). Use a Mold Simulator build, because it loads a .mos sample.

# Example 01 - Load a project, run a simulation and read the results.
#
# Run this from the HeatSimulator Python Console (View -> Python console ->
# "Run script"). Use a Mold Simulator build, because it loads a .mos sample.

import os

# The global "heatsim" object is injected automatically - never construct it.
# The shared sample project lives in samples/projects/.
project_path = "window_frame_1.mos"  # resolved against the bundled sample projects (see the API doc)

print("Loading: " + project_path)
if not heatsim.project.load(project_path):
    print("Could not load the project - see the messages above.")
else:
    # project.invalidate() forces a fresh validation/simulation.
    heatsim.project.invalidate()

    print("Simulating...")
    if not heatsim.simulation.simulate():
        print("Simulation failed - see the messages above.")
    else:
        results = heatsim.simulation.get_results()
        print("Simulation finished. Top-level result keys:")
        for key in results.keys():
            print("  - " + str(key))

01_basics/02_inspect_results.py

Inspect the results dictionary returned by simulation.get_results().

simulation.get_results() returns a nested dictionary whose shape depends on the project and simulation type. This example walks it generically so you can discover the available keys for your own projects, then reads a few common values.

It uses heatsim.dump(), a helper bundled in the HeatSimulator module.

Run in a Mold Simulator build (loads a .mos sample).

# Example 02 - Inspect the results dictionary returned by simulation.get_results().
#
# simulation.get_results() returns a nested dictionary whose shape depends on the project
# and simulation type. This example walks it generically so you can discover
# the available keys for your own projects, then reads a few common values.
#
# It uses heatsim.dump(), a helper bundled in the HeatSimulator module.
#
# Run in a Mold Simulator build (loads a .mos sample).

import os

# The shared sample project lives in samples/projects/.
# The heatsim object is injected automatically, so no import is needed.
project_path = "window_frame_1.mos"  # resolved against the bundled sample projects (see the API doc)

if heatsim.project.load(project_path):
    heatsim.project.invalidate()
    if heatsim.simulation.simulate():
        results = heatsim.simulation.get_results()

        print("=== Results structure (shallow) ===")
        heatsim.dump(results)

        # Common access patterns (guarded - keys vary by project):
        if "simulation_1" in results:
            sim = results["simulation_1"]
            if "advanced_results" in sim and "boundary" in sim["advanced_results"]:
                groups = sim["advanced_results"]["boundary"].get("groups")
                print("Boundary groups: " + str(groups))

01_basics/03_warnings_in_console.py

See errors and project warnings in the Python Console.

When an API call fails it prints its own “[Error] …” line explaining what went wrong. In addition, while a script runs the project’s own simulation warnings/errors are echoed into the console (prefixed [Error]/[Warning]/ [Info]) – even though the main frame’s GUI message dialogs are suppressed during scripting. Together they tell you *why* a call returned False.

Note: heatsim.set_verbose(False), the default, only silences the GUI dialogs; it does NOT silence the console output. The console output is controlled by heatsim.set_verbose_console. The two are independent.

# Example 03 - See errors and project warnings in the Python Console.
#
# When an API call fails it prints its own "[Error] ..." line explaining what
# went wrong. In addition, while a script runs the project's own simulation
# warnings/errors are echoed into the console (prefixed [Error]/[Warning]/
# [Info]) - even though the main frame's GUI message dialogs are suppressed
# during scripting. Together they tell you *why* a call returned False.
#
# Note: heatsim.set_verbose(False), the default, only silences the GUI dialogs; it does NOT
# silence the console output. The console output is controlled by heatsim.set_verbose_console. The two are independent.

import os

heatsim.set_verbose_console(True)

# 1) A call that fails: loading a file that does not exist.
#    Watch the console - "[Error] project.load: unable to load ..." is printed.
print(">>> Trying to load a non-existent project...")
missing = os.path.join(heatsim.get_script_folder(), "does_not_exist.mos")
if not heatsim.project.load(missing):
    print(">>> project.load returned False (see the [Error] line above).")

# 2) A call that succeeds: load the shared sample project.
print(">>> Loading a real sample project...")
project = "window_frame_1.mos"  # resolved against the bundled sample projects (see the API doc)
if heatsim.project.load(project):
    print(">>> Loaded OK.")
    heatsim.project.invalidate()

    # 3) Any warnings produced while simulating (mesh, boundaries, convergence,
    #    etc.) will appear in the console as the simulation runs.
    print(">>> Simulating (watch for any [Warning]/[Error] lines)...")
    heatsim.simulation.simulate()
    print(">>> Done.")

01_basics/04_transform_and_merge.py

Geometry transforms and merging projects.

Demonstrates translate / rotate / scale / mirror and project.merge. Run in a Mold Simulator build (loads a .mos sample).

# Example 04 - Geometry transforms and merging projects.
#
# Demonstrates translate / rotate / scale / mirror and project.merge.
# Run in a Mold Simulator build (loads a .mos sample).

import os

# The shared sample project lives in samples/projects/.
project_path = "window_frame_1.mos"  # resolved against the bundled sample projects (see the API doc)

if heatsim.project.load(project_path):
    # With nothing selected, center() recenters the whole project on the origin.
    print("center:    " + str(heatsim.geometry.center()))
    # Transforms operate on the whole project. Each returns True/False.
    print("rotate:    " + str(heatsim.geometry.rotate(90.0)))        # center defaults to (0,0)
    print("scale:     " + str(heatsim.geometry.scale(1.0, 1.0)))     # center defaults to (0,0)
    print("h-mirror:  " + str(heatsim.geometry.horizontal_mirror()))
    print("v-mirror:  " + str(heatsim.geometry.vertical_mirror()))

    # Merge a second copy of the same project, translated by (dX, dY). dX/dY/
    # excludeAdiabatic are optional and default to 0 / 0 / False.
    print("merge:     " + str(heatsim.project.merge(project_path, -2.3, -0.3)))

    print("boundaries after merge: " + str(heatsim.boundary.get_count()))

    # Save the combined project to the per-user script-output folder (the install
    # folder may be read-only).
    # open_output_folder shows the destination in the file manager and returns it.
    out = os.path.join(heatsim.open_output_folder(), "merged_output.mos")
    print("save_as:    " + str(heatsim.project.save_as(out)))

01_basics/05_boundaries.py

Query and assign boundary conditions.

Demonstrates boundary.get_count / boundary.get / boundary.assign / boundary.expand. Run in a Mold Simulator build (loads a .mos sample).

# Example 05 - Query and assign boundary conditions.
#
# Demonstrates boundary.get_count / boundary.get / boundary.assign /
# boundary.expand. Run in a Mold Simulator build (loads a .mos sample).

import os

# The shared sample project lives in samples/projects/.
project_path = "window_frame_1.mos"  # resolved against the bundled sample projects (see the API doc)
boundary = heatsim.boundary  # Just to shorten the code below - not required.

if heatsim.project.load(project_path):
    # How many boundary conditions does this project define?
    count = heatsim.boundary.get_count()
    print("Number of boundaries: " + str(count))

    # Inspect each boundary's parameters. boundary.get returns a dict, or False
    # on failure (an invalid id also prints an [Error] line to the console).
    for boundary_id in range(count):
        boundary_info = heatsim.boundary.get(boundary_id)
        if boundary_info is False:
            print("  boundary " + str(boundary_id) + ": <unavailable>")
            continue
        # The exact keys depend on the boundary; print a couple if present.
        name = boundary_info.get("name") if isinstance(boundary_info, dict) else None
        print("  boundary " + str(boundary_id) + ": " + str(name if name else boundary_info))

    # First we clear all existing boundaries
    print("boundary.clear: " + str(boundary.clear_assigned_boundaries()))

    # Assign internal boundary (id=0)
    #print("boundary.assign: " + str(boundary.assign(2.430, 0.63320, 0.0, 0)))

    # Assign external boundary (id=2)
    #print("boundary.assign: " + str(boundary.assign(2.430, 0.70303, 0.0, 2)))

    # Expand assigned boundaries onto any unassigned ones. It will stop on adiabatic areas
    #print("boundary.expand: " + str(boundary.expand()))

    # As an alternative to assign/expand, you can simply call assign_strip and no expand.
    print("boundary.assign_strip: " + str(heatsim.boundary.assign_strip(2.430, 0.70303, 0.0, 2)))
    print("boundary.assign_strip: " + str(heatsim.boundary.assign_strip(2.430, 0.63320, 0.0, 0)))

02_variations/01_material.py

Vary a material, re-simulate and export a PDF.

Loads window_frame_1.mos, then replaces the project’s “Softwood” material with a series of real timbers – only the thermal conductivity changes – and runs a *new simulation for every material*. After each simulation it prints all the report views to a PDF in a “reports” sub-folder next to this script.

The wood list below is taken from materials.csv (name, conductivity in W/mK), hardcoded here. Run in a Mold Simulator build.

# Example 02_variations/01 - Vary a material, re-simulate and export a PDF.
#
# Loads window_frame_1.mos, then replaces the project's "Softwood" material with
# a series of real timbers - only the thermal conductivity changes - and runs a
# *new simulation for every material*. After each simulation it prints all the
# report views to a PDF in a "reports" sub-folder next to this script.
#
# The wood list below is taken from materials.csv (name, conductivity in W/mK),
# hardcoded here. Run in a Mold Simulator build.

import os
import csv

# (name, conductivity [W/(m.K)]) - from materials.csv
WOODS = [
    ("Western hemlock", 0.13),
    ("Western red cedar", 0.11),
    ("Makore", 0.16),
    ("Idigbo", 0.13),
    ("Teak", 0.16),
    ("American mahogany", 0.13),
    ("Light red meranti", 0.13),
    ("Dark red meranti", 0.16),
    ("Robinia", 0.18),
]

# The shared sample project lives in samples/projects/.
project_path = "window_frame_panel_1.mos"  # resolved against the bundled sample projects (see the API doc)

# PDFs go in a "reports" sub-folder next to this script.
# open_output_folder shows the destination in the file manager, so the user
# sees where the files land, and returns its path.
reports_dir = os.path.join(heatsim.open_output_folder(), "reports")
os.makedirs(reports_dir, exist_ok=True)

if not heatsim.project.load(project_path):
    print("Could not load " + project_path)
else:
    # Collected results, written to a CSV at the end.
    rows = []

    if not heatsim.simulation.simulate():
        print("base: simulation failed")
    else:
        results = heatsim.simulation.get_results()
        uf = results.get("uf") if isinstance(results, dict) else None
        uf_str = "{:.2f}".format(uf) if uf is not None else "?"
        print("{0:<20} uf={1}".format("base", uf_str))
        rows.append(("base", "", uf))

    currentMaterialName = "Softwood"

    for index, (name, conductivity) in enumerate(WOODS):
        # Replace the "Softwood" material - only the conductivity changes. The
        # material is isotropic, so set all three components to the same value.
        # (The first argument is the material to find; a "name" key in the dict
        # would rename it.)
        ok = heatsim.area.set_material_by_name(currentMaterialName, {
            "name": name,
            "conductivity_x": conductivity,
            "conductivity_y": conductivity,
            "conductivity_z": conductivity,
        })
        if not ok:
            print(name + ": could not set the Softwood material")
            continue

        currentMaterialName = name

        # A fresh simulation is required after every material change.
        heatsim.project.invalidate()
        if not heatsim.simulation.simulate():
            print(name + ": simulation failed")
            continue

        results = heatsim.simulation.get_results()

        # "uf" is the frame U-value in many projects; fall back gracefully.
        uf = results.get("uf") if isinstance(results, dict) else None
        uf_str = "{:.2f}".format(uf) if uf is not None else "?"
        print("{0:<20} lambda={1:<5} uf={2}".format(name, conductivity, uf_str))
        rows.append((name, conductivity, uf))

        # Print all report views of this simulation to a PDF for this material.
        pdf_path = os.path.join(reports_dir, "{0:02d}_{1}.pdf".format(index, name.replace(" ", "_")))
        if not heatsim.report.print_to_pdf(pdf_path):
            print(name + ": could not export PDF")

    # Save every material's U-value to a CSV next to the PDFs.
    csv_path = os.path.join(reports_dir, "results.csv")
    with open(csv_path, "w", newline="") as csv_file:
        writer = csv.writer(csv_file)
        writer.writerow(["material", "conductivity", "uf"])
        writer.writerows(rows)
    print("Saved results to " + csv_path)

02_variations/02_material_multi_component.py

Material study across cloned components.

Like 01_material.py, but instead of re-using one component it builds a separate component for every timber: it selects the base component, clones it, applies the material to the clone and simulates. At the end it exports a *single* PDF containing the report views of all the components.

components.clone() and components.delete() are 3D-only, so run this in a Mold Simulator 3D build.

# Example 02_variations/02 - Material study across cloned components.
#
# Like 01_material.py, but instead of re-using one component it builds a separate
# component for every timber: it selects the base component, clones it, applies
# the material to the clone and simulates. At the end it exports a *single* PDF
# containing the report views of all the components.
#
# components.clone() and components.delete() are 3D-only, so run this in a Mold
# Simulator 3D build.

import os

# (name, conductivity [W/(m.K)]) - from materials.csv
WOODS = [
    ("Western hemlock", 0.13),
    ("Western red cedar", 0.11),
    ("Makore", 0.16),
    ("Idigbo", 0.13),
    ("Teak", 0.16),
    ("American mahogany", 0.13),
    ("Light red meranti", 0.13),
    ("Dark red meranti", 0.16),
    ("Robinia", 0.18),
]

# The shared sample project lives in samples/projects/.
project_path = "window_frame_panel_1.mos"  # resolved against the bundled sample projects (see the API doc)

# The single PDF goes in a "reports" sub-folder next to this script.
# open_output_folder shows the destination in the file manager, so the user
# sees where the files land, and returns its path.
reports_dir = os.path.join(heatsim.open_output_folder(), "reports")
os.makedirs(reports_dir, exist_ok=True)

if not heatsim.project.load(project_path):
    print("Could not load " + project_path)
else:
    # Delete every component but the current one, so we start from a single
    # "base" component (index 0).
    heatsim.components.delete()

    # Make sure to simulate also the base component
    heatsim.project.invalidate()
    if not heatsim.simulation.simulate():
        print("base: simulation failed")
    else:
        results = heatsim.simulation.get_results()
        uf = results.get("uf") if isinstance(results, dict) else None
        if uf is not None:
            print("{0:<20} uf={1}".format("base", uf))
        else:
            print("{0:<20} lambda={1:<5} simulated".format("base", "conductivity"))
    
    for name, conductivity in WOODS:
        # Always clone the pristine base component (index 0).
        heatsim.components.set_current(0)
        if not heatsim.components.clone():
            print(name + ": could not clone the base component")
            continue

        # The clone is now the current component. Replace its "Softwood" material
        # (only the conductivity changes; "name" renames it to the timber).
        ok = heatsim.area.set_material_by_name("Softwood", {
            "name": name,
            "conductivity_x": conductivity,
            "conductivity_y": conductivity,
            "conductivity_z": conductivity,
        })
        if not ok:
            print(name + ": could not set the Softwood material")
            continue

        # A fresh simulation is required for the new component.
        heatsim.project.invalidate()
        if not heatsim.simulation.simulate():
            print(name + ": simulation failed")
            continue

        results = heatsim.simulation.get_results()
        uf = results.get("uf") if isinstance(results, dict) else None
        uf_str = "{:.2f}".format(uf) if uf is not None else "?"
        print("{0:<20} lambda={1:<5} uf={2}".format(name, conductivity, uf_str))

    # Export every component's report views into one single PDF.
    pdf_path = os.path.join(reports_dir, "all_materials.pdf")
    if not heatsim.report.print_to_pdf(pdf_path):
        print("Could not export the PDF")

02_variations/03_climatic_zones.py

Condensation risk across climatic zones.

Loads bridge_1.mos, switches a boundary to follow the climatic zone, then applies a series of climatic zones with boundary.set_climatic_zone, re-simulating after each one and printing the minimum fRsi and the critical simulation month. The zones below exercise the smart search: by city, by town only (the city is derived from the town), and with an explicit altitude override. Run in a Mold Simulator build.

# Example 02_variations/03 - Condensation risk across climatic zones.
#
# Loads bridge_1.mos, switches a boundary to follow the climatic zone, then
# applies a series of climatic zones with boundary.set_climatic_zone,
# re-simulating after each one and printing the minimum fRsi and the critical
# simulation month. The zones below exercise the smart search: by city, by town
# only (the city is derived from the town), and with an explicit altitude
# override. Run in a Mold Simulator build.

import os

# The shared sample project lives in samples/projects/.
project_path = "bridge_1.mos"  # resolved against the bundled sample projects (see the API doc)

# Five climatic zones, as keyword-argument dicts for set_climatic_zone.
ZONES = [
    {"city": "Milano"},
    {"city": "Roma"},
    {"city": "Palermo"},
    {"town": "Tarvisio"},                    # city derived from the town
    {"city": "Udine", "altitude": 500.0},    # explicit altitude override
]

if not heatsim.project.load(project_path):
    print("Could not load " + project_path)
else:
    # Make boundary 1 take its temperatures and humidity from the climatic zone
    # (per-variant rules: DEW the temperatures, MOLD also the humidity).
    if not heatsim.set_use_climatic_zone(1):
        print("Could not switch boundary 1 to the climatic zone")

    for zone in ZONES:
        info = heatsim.boundary.set_climatic_zone(**zone)
        if not info:
            print(str(zone) + ": no matching climatic zone")
            continue

        # A fresh simulation is required after every climatic-zone change.
        heatsim.project.invalidate()
        if not heatsim.simulation.simulate():
            print(str(zone) + ": simulation failed")
            continue

        results = heatsim.simulation.get_results()
        min_frsi = results["simulation_3"]["frsi_min"]
        month = results["simulation_3"]["advanced_results"]["boundary"]["simulation_month"]

        label = info.get("city", info.get("town", "?"))
        print("{0:<12} zone={1} altitude={2:>6.1f} m  min fRsi={3:.4f}  month={4}".format(
            label, info.get("climatic_zone"), info.get("altitude"), min_frsi, month))

03_composition/01_glazing_psi.py

Glazing position vs. psi.

Loads a window frame, then sweeps a glazing across a range of horizontal offsets. For each offset it merges the glazing onto a *fresh* frame, expands the boundary conditions, re-simulates and prints the resulting linear thermal transmittance (psi).

# Composition example 01 - Glazing position vs. psi.
#
# Loads a window frame, then sweeps a glazing across a range of horizontal
# offsets. For each offset it merges the glazing onto a *fresh* frame, expands
# the boundary conditions, re-simulates and prints the resulting linear thermal
# transmittance (psi).

import os

# Bare names are resolved against the bundled sample projects (see the API doc).
frame = "window_frame_only_1.mos"
glazing = "window_glazing_only_with_path_1.mos"

# Horizontal offsets from -0.003 to 0.003 in 0.001 steps. Integer steps avoid
# floating-point drift.
for i in range(-3, 4):
    x = i * 0.001

    # Start from a fresh frame each time, so the glazing offsets don't stack up.
    if not heatsim.project.load(frame):
        print("Could not load " + frame)
        break

    # Merge the glazing translated by (x, 0).
    if not heatsim.project.merge(glazing, x, 0.0):
        print("merge failed at x={0:+.3f}".format(x))
        continue

    # Expand boundary conditions onto the newly merged geometry, then re-simulate.
    heatsim.boundary.expand()
    heatsim.project.invalidate()
    if not heatsim.simulation.simulate():
        print("simulation failed at x={0:+.3f}".format(x))
        continue

    results = heatsim.simulation.get_results()
    psi = results.get("psi") if isinstance(results, dict) else None
    psi_str = "{:.4f}".format(psi) if isinstance(psi, (int, float)) else str(psi)
    print("x={0:+.3f}  psi={1}".format(x, psi_str))

03_composition/02_wall_window_joint.py

Wall / window joint.

For each of two wall types, builds the wall-window junction: it merges a window frame and its glazing onto a template wall (rotating and translating them into place), adds a section element, sets the flow/gravity directions, assigns an adiabatic segment material, then simulates and prints the resulting linear thermal transmittance (psi).

# Composition example 02 - Wall / window joint.
#
# For each of two wall types, builds the wall-window junction: it merges a window
# frame and its glazing onto a template wall (rotating and translating them into
# place), adds a section element, sets the flow/gravity directions, assigns an
# adiabatic segment material, then simulates and prints the resulting linear thermal
# transmittance (psi).

import os

# project and area are used a lot, so alias them to drop the "heatsim." prefix.
project = heatsim.project
area = heatsim.area
elements = heatsim.elements

# Bare names are resolved against the bundled sample projects (see the API doc).
walls = [ "wall_1.mos", "wall_2.mos" ]
frame = "window_frame_only_with_path_1.mos"
glazing = "window_glazing_only_1.mos"

# Reference point of the frame is at (2.40915, 0.63304).
# The frame has first to be rotated 90 degrees around that point.
# This point has to be placed at (-0.01717, -0.01104) on the wall.
frame_ref_x = 2.40915
frame_ref_y = 0.63304
target_x = -0.01717
target_y = -0.01104
rotation = -90
translate_x = target_x - frame_ref_x
translate_y = target_y - frame_ref_y

# Wall upper section element position
wall_upper_x = 0.0
wall_upper_y = 0.99896

# Wall lower section element position.
wall_lower_x = wall_upper_x
wall_lower_y = target_y

for wall in walls:
    # Load the proper template: psi + 13788, window joint
    project.load_template("MoldSimulatorPro", 2, 3)

    # Merge wall project first
    if not project.merge(wall, match_boundary_by_name=True):
        print("Could not load " + wall)

    elements.add_section_element(
        wall_upper_x,
        wall_upper_y,
        wall_lower_x,
        wall_lower_y,
        0.0,
        1.0
    )

    # Merge the frame node, rotating it 90 degrees around a specified point and then translating it.
    # Remove the adiabatic areas, setting the last parameter to True
    if not project.merge(
        frame,
        translate_x,
        translate_y,
        rotation,
        frame_ref_x,
        frame_ref_y,
        True,
        match_boundary_by_name=True
    ):
        print("Could not load " + frame)

    # Glazing has been modelled together with the frame, so we can use the same transformation parameters to merge it in the correct position.
    if not project.merge(
        glazing,
        translate_x,
        translate_y,
        rotation,
        frame_ref_x,
        frame_ref_y,
        match_boundary_by_name=True
    ):
        print("Could not load " + glazing)

    if not heatsim.app.set_flow_direction_hint(heatsim.FlowDirection.RIGHT):
        print("Could not set flow direction hint")

    if not heatsim.app.set_gravity_direction(heatsim.GravityDirection.INTO_SCREEN):
        print("Could not set gravity direction")

    # Create adiabatic segment material to be assigned between subframe and wall
    segmentMaterial = area.create_default_material(heatsim.MaterialType.STANDARD_SEGMENT)
    segmentMaterial["name"] = "Adiabatic"

    area.assign_material(segmentMaterial, 0.018, -0.01104, 0.0, heatsim.FillPropagationMode.SAME_DIRECTION)

    # Simulate and print results
    if not heatsim.simulation.simulate():
        print("Simulation failed")
    else:
        results = heatsim.simulation.get_results()
        psi = results["simulation_1"]["psi"]
        print(f"Psi for {wall}: {psi:.4f} W/mK")