• Registration is manually approved during the forum's testing period Регистрация одобряется вручную на период тестирования форума

REVIT Расчет ограждающих конструкций в MagiCAD

admin

Administrator
Staff member
@Grok - реализован ли в MagiCAD расчет оргаждающих конструкций на теплопотери? Данная функция была очень хорошо реализована в Allklima от Nemetchik. В зависимости от типов материалов и их свойств а также от параметров наружного и внутреннего (в помещении) воздуха необходимо расчитать теплопотери а также проверить нахождение точки росы чтобы влага не выпадала в конструкции стены. Как это реализовано в MagiCAD и если это не реализовано в каком софте это реализовано наилучшим образом. Необходимо выполнять расчет по различным нормам в зависимости от географического нахождения объекта строительства. Проверь для регионов Россия США Великобритания.
 
- реализован ли в MagiCAD расчет оргаждающих конструкций на теплопотери? Данная функция была очень хорошо реализована в Allklima от Nemetchik. В...

Кратко: полноценного теплотехнического расчёта ограждающих конструкций в духе Allklima в MagiCAD нет и никогда не было.

Что умеет MagiCAD

MagiCAD считает теплопотери помещений и нагрузку на систему ( MagiCAD Heating / расчёт по пространствам в Revit ), а не физику пирога стены.

На практике это выглядит так:
  • задаются помещения/пространства, внутренние температуры, инфильтрация;
  • для ограждений берутся готовые U (или теплотехнические свойства типов стен/окон из модели);
  • считается нагрузка на отопление, дальше подбор приборов, труб, балансировка.
Базовый европейский контур — EN 12831 (часто EN 12831-1:2017). Точка росы, распределение температуры по слоям, паропроницаемость, проверка конденсации внутри стены, мостики холода — этого модуля нет. Библиотеки материалов с λ, μ, расчёта R и построения температурного поля, как в Allklima, тоже нет.

Типовой рабочий процесс: U и выводы по конденсации считаются снаружи, в MagiCAD/Revit уходит уже готовый коэффициент теплопередачи и температура помещения.

Региональные нормы

Россия. Нативная поддержка СП 50.13330 (тепловая защита), СП 60.13330 (отопление, своя методика теплопотерь с добавками) и СП 131.13330 (климат) в MagiCAD по сути отсутствует. EN 12831 ≠ методика СП 60: другие добавочные потери, ориентация, учёт полов/потолков, инфильтрация. Для экспертизы и рабочей документации теплотехнику ограждений почти всегда считают отдельной программой, а MagiCAD используют как BIM-инструмент систем.

Великобритания. MagiCAD здесь силён именно как MEP. Нагрузки — EN 12831 / CIBSE. Condensation risk по BS 5250 и BS EN ISO 13788 в самом MagiCAD снова нет, это соседний софт (IES VE, Hevacomp/Bentley, DesignBuilder).

США. MagiCAD не является стандартом рынка. Нагрузки — ASHRAE Fundamentals / 90.1 / IECC, Carrier HAP, Trane TRACE, IES, EnergyPlus. Hygrothermal — WUFI, мостики — THERM (LBNL). Ждать от MagiCAD расчёта dew point по американским нормам не стоит.

Чем закрывать задачу «пирог + точка росы + нормы»

Ближе всего к идеологии Allklima (слои, свойства, температура, конденсат):

  • WUFI (Fraunhofer) — лучший hygrothermal, не стационарный Глазер, а влага во времени. Для РФ/США/UK одинаково применим, нормы подкладываете сами.
  • ISO 13788 / метод Глазера — достаточно для типовой проверки «не выпадет ли конденсат в толще». Много калькуляторов и CAE: AnTherm, Flixo, плюс расчётные модули Linear Building, Solar-Computer.
  • Россия, именно СП 50 + точка росы: теплотехнические модули (NormCAD и аналоги по СП), отраслевые калькуляторы ROCKWOOL / ТехноНИКОЛЬ (быстро, но это не BIM), отдельно теплопотери помещений — Audytor OZC, профильные «теплопотери» по СП 60, Excel-шаблоны проектных институтов. Для экспертизы обычно связка: СП 50 (R, Δt внутренней поверхности, конденсат) + СП 60 (нагрузка на отопление).
  • UK: IES VE или Hevacomp — и нагрузки по CIBSE, и condensation risk.
  • США: HAP/TRACE/EnergyPlus на нагрузки, WUFI + THERM на влагу и мостики.
  • Из BIM-линейки по духу «ограждения + инженерия»: Linear (Building + Analyse), IDA ICE, DesignBuilder. Allplan / остатки экосистемы Nemetschek по-прежнему ближе к Allklima, чем MagiCAD.

Практическая схема под MagiCAD

  • пирог стены, R/U, проверка конденсации и Δt внутр. поверхности — в теплотехнической программе по нормам площадки (СП 50 / ISO 13788 / ASHRAE);
  • готовые U и tв — в типы ограждений Revit или в помещения MagiCAD;
  • MagiCAD — воздуховоды, трубы, приборы, спецификации, коллизии.
Искать в MagiCAD замену Allklima по ограждающим конструкциям бессмысленно: это разные задачи. Если нужен один софт «и стена, и точка росы, и система» — смотреть IES VE / Linear / IDA ICE, а не ждать этого от MagiCAD.
 
- проверь что с софтом Allklima - они еще на рынке ,

Коротко: как отдельный живой продукт Allklima с рынка ушёл.

Nemetschek его давно не развивает и не продаёт. Новых версий, обновлений норм, техподдержки и прайса нет. Сайт/линейка по сути мертвые: кто ещё считает в Allklima, сидит на старых установках и старых нормах. Ждать СП 50.13330 актуальной редакции, EN 12831-1:2017 в современном виде, ASHRAE или BS 5250 оттуда бессмысленно.

Экосистема Nemetschek ушла в Allplan (BIM/строительная часть), а не в теплотехнику пирога стены. Прямого наследника Allklima внутри Nemetschek нет. По духу «слои–λ–точка росы–нагрузки» народ обычно уходит сюда:

  • Linear (Building / Analyse) — ближе всего по идеологии к старой немецкой школе, если нужен расчёт ограждений + инженерия.
  • WUFI — если важна влага во времени, а не только стационарный Глазер.
  • ISO 13788 / AnTherm / Flixo — конденсат, мостики, температура по слоям.
  • РФ, именно экспертиза: теплотехника по СП 50 отдельно (NormCAD и аналоги, отраслевые калькуляторы), нагрузка на отопление по СП 60 отдельно (Audytor OZC и т.п.), в MagiCAD уже готовые U и tв.
  • UK — IES VE / Hevacomp; США — HAP/TRACE + WUFI/THERM.

Итого: Allklima как замена « MagiCAD не считает точку росы» больше не вариант. Старые копии для архива — да, на рынок и в новые объекты — нет. Если нужен один контур «стена + конденсат + система», смотреть Linear / IES / IDA ICE, а не ждать воскрешения Allklima.
 
- есть ли эта функция в Allplan и есть ли решения через Dynamo?

Коротко: ни Allplan, ни Dynamo эту дыру Allklima не закрывают.

Allplan

Пирог стены там моделируется нормально: многослойные конструкции, материалы, λ, толщина. Это BIM-геометрия и свойства, а не теплотехника.

Чего нет:
  • расчёт теплопотерь ограждения по слоям с проверкой конденсата / точки росы, как в Allklima;
  • стационарный Глазер и hygrothermal во времени;
  • нативные СП 50.13330, СП 60, ASHRAE, BS 5250 / ISO 13788 «из коробки» под РФ / США / UK.
В DACH у Allplan иногда торчат куски энергопаспорта / U-value под EnEV/GEG — локальная немецко-австрийская история, не замена Allklima и не про MagiCAD-контур. Прямого наследника внутри Nemetschek нет: инженерия ушла в Allplan, теплофизика пирога — нет. Стык с расчётом обычно внешний (Linear и аналоги), в сам Allplan уезжает уже готовый U.

Dynamo

Dynamo к Allplan не относится вообще. Это Revit (и чуть Civil). У Allplan свой контур: PythonParts / Visual Scripting.

Если речь про связку Revit + MagiCAD, то Dynamo может:
  • вытащить слои типа стены, λ, толщины, посчитать R/U;
  • набросать примитивный Глазер / оценку конденсата;
  • прокинуть готовый U в помещения / MagiCAD.
Это самодел: ни экспертиза по СП 50, ни BS 5250, ни ASHRAE. Нормы, климат, добавки, μ, мостики, Δt внутренней поверхности — всё равно руками или соседней программой. Для трёх регионов сразу Dynamo не масштабируется.

Итого: Allplan — не Allklima. Dynamo — не расчётный движок по нормам. Схема та же: пирог и точка росы снаружи (WUFI / ISO 13788 / теплотехника по СП 50), в Revit/MagiCAD — готовые U и tв. Если нужен один контур «стена + конденсат + система» — Linear / IES VE / IDA ICE, не Allplan и не скрипты.
 
@Grok - проверь онлайн калькуляторы, в Англии я видел на сайте гильдии инженеров вроде, это довольно простой расчет, проверь чтобы строился график температуры от типа и толщины элементов конструкций стены, а также график выпадения конденсата
 
- проверь онлайн калькуляторы, в Англии я видел на сайте гильдии инженеров вроде, это довольно простой расчет, проверь чтобы строился график...

Коротко: готового «как Allklima, только в браузере» на сайте CIBSE нет. У гильдии методика (Guide A, отсылки к BS 5250 / BS EN ISO 13788), а не публичный конструктор пирога с графиками. То, что обычно вспоминают по UK, — простой стационарный Глазер: слои → температура по толще → риск конденсата.

Онлайн, где реально рисуются оба графика (температура по слоям и конденсат/точка росы):

  • Ubakus (ubakus.de / ubakus.com) — ближе всего к тому, что вы видели. Многослойная стена, λ, толщина, климат, U. Сразу два профиля: температура по толще и парциальное давление / зона конденсата (ISO 13788). Для UK/EU самый внятный бесплатный вариант. Это не CIBSE и не MagiCAD, но картинка как в учебнике.
  • UK-производители (Kingspan, Isover, Rockwool UK, Celotex/Saint-Gobain) — U и иногда condensation risk под BS 5250. Климат UK, библиотека своих плит. График температуры/конденсата есть не у всех, часто только «pass/fail» и U. Для экспертизы удобно, для «любой пирог» хуже Ubakus.
  • РФ: smartcalc.ru — как раз ваши два графика плюс ориентация на СП 50.13330: температура по толще, зона конденсата, Δt внутренней поверхности. Для объекта в России это полезнее «английской гильдии». Калькуляторы ROCKWOOL / ТехноНИКОЛЬ — быстрее, но библиотека своя, не любой материал.
  • США: культуры простого Глазера почти нет. Онлайн в этом духе слабые; нормальная связка — WUFI (влага во времени) + THERM (мостики), нагрузки отдельно (HAP/TRACE). Искать «график конденсата как в Allklima» на американском сайте гильдии бессмысленно.

Чего от этих калькуляторов ждать

  • Это стационар 13788 / Глазер, не hygrothermal. Нет дождя, аккумуляции за зиму, солнечной стороны, реальных мостиков. Для «не выпадет ли в толще» — да, для спорного пирога и экспертизы — нет.
  • Климат задаёте сами: UK — CIBSE/Met Office; РФ — СП 131.13330; США — ASHRAE. Калькулятор нормы площадки сам не подменит.
  • В MagiCAD/Revit/Allplan/Dynamo эти графики не уезжают. На выход — U и tв, дальше нагрузка на отопление.

Практично под три региона

  • UK: пирог и графики — Ubakus или Kingspan/Isover; методика — BS 5250 + ISO 13788; нагрузки — CIBSE / EN 12831 (это уже MagiCAD, не стена).
  • РФ: графики и конденсат — smartcalc / теплотехника по СП 50; нагрузка на отопление — СП 60 отдельно; в MagiCAD снова только готовый U. Для экспертизы онлайн обычно недостаточно.
  • США: не онлайн-Глазер, а WUFI + ASHRAE; MagiCAD тут ни при чём.

Итого: «гильдия в Англии» — это про норму, не про софт. Живой простой расчёт с графиком температуры по типу/толщине стены и графиком конденсата — Ubakus; для России с графиками под СП 50 — smartcalc. Allklima это не заменяет и в MagiCAD не встраивается: посчитали пирог снаружи, в модель отдали U.
 
@Grok - но ты сам то должен это все уметь и строить графики не так ли? Если тебе прислать параметры конструкции стен и данные по климату ты все должен уже уметь рассчитывать, там не сложно
 
- но ты сам то должен это все уметь и строить графики не так ли? Если тепе прислать параметры конструкции стен и данные по климату ты все должен уже...

Да, сам расчёт простой. Это стационар по Глазеру / ISO 13788, не MagiCAD и не Allklima. Слои + λ + μ + климат → таблица → два профиля: температура по толще и p / p[sub]нас[/sub] (зона конденсата). Красивую картинку как в Ubakus на форуме не выложу, но узлы, таблицу и схематичный график в CODE сделаю. Для экспертизы РФ этого часто мало, для проверки пирога — достаточно.

Что прислать одним постом

Площадка: РФ / UK / США (от этого Rsi/Rse, проверка Δt и нормы климата).

Пирог изнутри наружу, по каждому слою:
  • название
  • толщина, мм
  • λ, Вт/(м·К)
  • μ (без μ график конденсата не считается, только U)

Климат:
  • tв и φв
  • tн и φн
Либо город + норма (СП 131 / CIBSE / ASHRAE) — возьму расчётные t, φ всё равно задайте, в нормах её часто нет.

Если λ/μ нет — пишите конкретный материал и плотность (не «минеральная вата»). Мостики, окна, углы в этот контур не входят.

Шаблон:

Объект: город, страна, норма
Ограждение: стена / кровля / пол

Слои изнутри наружу:
1. штукатурка 15 мм, λ=..., μ=...
2. ...
3. ...

tв = ... °C, φв = ... %
tн = ... °C, φн = ... %
Rsi/Rse: по норме / свои

Что посчитаю
  • R и U
  • θ на каждой границе слоя (для графика температуры)
  • e и e[sub]нас[/sub] по границам (для графика конденсата)
  • есть ли зона выпадения, в каком слое
  • θ внутренней поверхности и Δt — для РФ это проверка по СП 50, не «точка росы в воздухе»

Чего это не заменяет

Стационар, один зимний срез. Нет аккумуляции за сезон, дождя, солнца, реальных мостиков — это WUFI/THERM. Онлайн-калькулятор с графиками, если нужно самим покрутить слои: РФ — smartcalc, UK/EU — Ubakus.

Скидывайте пирог и климат в шаблоне выше.
 
@Grok - напиши код на питоне для расчета и построена графиков, для построения графиков используй актуальные рабочие библиотеки которые обновляются и поддерживаются
 
- напиши код на питоне для расчета и построена графиков, для построения графиков используй актуальные рабочие библиотеки которые обновляются и...

Да. Это стационар по Глазеру / ISO 13788: слои → R/U → θ по границам → p / pнас. MagiCAD для этого не нужен.

Стек: numpy + matplotlib — оба живые, на PyPI, без seaborn/plotly. Кириллица идёт через DejaVu Sans (в matplotlib он из коробки).

Code:
pip install "numpy>=1.26" "matplotlib>=3.8"

В LAYERS и блоке климата подставляете свой пирог. Скрипт печатает таблицу и сохраняет wall_glaser.png.

Python:
from __future__ import annotations

from dataclasses import dataclass

import matplotlib.pyplot as plt
import numpy as np

# ---------------------------------------------------------------------------
# Исходные данные — правите только этот блок
# ---------------------------------------------------------------------------

LAYERS = [
 # имя, толщина мм, λ Вт/(м·К), μ
 ("Штукатурка внутр.", 15.0, 0.81, 10.0),
 ("Кирпич", 510.0, 0.56, 8.0),
 ("Минвата", 100.0, 0.040, 1.0),
 ("Штукатурка нар.", 20.0, 0.90, 15.0),
]

T_IN = 20.0 # °C, внутри
PHI_IN = 55.0 # %, внутри
T_OUT = -25.0 # °C, снаружи
PHI_OUT = 80.0 # %, снаружи

# Стена, горизонтальный поток. СП 50: 0.115 / 0.043; EN ISO 6946: 0.13 / 0.04
RSI = 0.115
RSE = 0.043

OUT_PNG = "wall_glaser.png"

# ---------------------------------------------------------------------------
# Физика
# ---------------------------------------------------------------------------

@dataclass(frozen=True)
class Layer:
 name: str
 thickness_mm: float
 lambda_w: float
 mu: float

 def __post_init__(self) -> None:
 if self.thickness_mm <= 0 or self.lambda_w <= 0 or self.mu <= 0:
 raise ValueError(f"Некорректный слой: {self.name}")

 @property
 def d(self) -> float:
 return self.thickness_mm / 1000.0

 @property
 def R(self) -> float:
 return self.d / self.lambda_w

 @property
 def sd(self) -> float:
 return self.mu * self.d

def saturation_pressure(theta_c: np.ndarray | float) -> np.ndarray | float:
 """pнас, Па. ISO 13788 (формула Магнуса)."""
 scalar = np.isscalar(theta_c)
 theta = np.atleast_1d(np.asarray(theta_c, dtype=float))
 ps = np.empty_like(theta)
 above = theta >= 0.0
 ps[above] = 610.5 * np.exp(17.269 * theta[above] / (237.3 + theta[above]))
 ps[~above] = 610.5 * np.exp(21.875 * theta[~above] / (265.5 + theta[~above]))
 return float(ps) if scalar else ps

def dew_point(p_pa: float) -> float:
 """Обратный Магнус, °C. Для p от льда/воды — ветка по 610.5 Па."""
 ln = np.log(p_pa / 610.5)
 if p_pa >= 610.5:
 return 237.3 * ln / (17.269 - ln)
 return 265.5 * ln / (21.875 - ln)

def glaser_pressure(
 sd: np.ndarray,
 psat: np.ndarray,
 p_in: float,
 p_out: float,
) -> np.ndarray:
 """Паровое давление по Глазеру: отрезок pi–pe, при необходимости касание pнас."""
 n = len(sd)
 p = np.empty(n)

 def fill(i: int, j: int, pi: float, pj: float) -> None:
 if j <= i + 1:
 p[i] = pi
 p[j] = pj
 return
 s = sd[i : j + 1]
 plin = pi + (pj - pi) * (s - sd[i]) / (sd[j] - sd[i])
 over = plin - psat[i : j + 1]
 k_rel = int(np.argmax(over))
 if over[k_rel] <= 1.0: # 1 Па — численный допуск
 p[i : j + 1] = plin
 return
 k = i + k_rel
 if k == i or k == j:
 p[i : j + 1] = np.minimum(plin, psat[i : j + 1])
 return
 fill(i, k, pi, float(psat[k]))
 fill(k, j, float(psat[k]), pj)

 fill(0, n - 1, p_in, p_out)
 return p

def calculate(
 layers: list[Layer],
 t_in: float,
 phi_in: float,
 t_out: float,
 phi_out: float,
 rsi: float,
 rse: float,
) -> dict:
 r_layers = np.array([ly.R for ly in layers], dtype=float)
 sd_layers = np.array([ly.sd for ly in layers], dtype=float)
 d_layers = np.array([ly.d for ly in layers], dtype=float)

 r_total = rsi + float(r_layers.sum()) + rse
 u = 1.0 / r_total
 q = (t_in - t_out) / r_total # Вт/м²

 # Границы слоёв: 0 = внутренняя поверхность, n = наружная
 r_cum = np.concatenate(([0.0], np.cumsum(r_layers)))
 theta = t_in - q * (rsi + r_cum) # °C на границах
 x_m = np.concatenate(([0.0], np.cumsum(d_layers)))
 sd = np.concatenate(([0.0], np.cumsum(sd_layers)))

 p_in = (phi_in / 100.0) * float(saturation_pressure(t_in))
 p_out = (phi_out / 100.0) * float(saturation_pressure(t_out))
 psat = np.asarray(saturation_pressure(theta), dtype=float)
 p_free = p_in + (p_out - p_in) * sd / sd[-1]
 p_glaser = glaser_pressure(sd, psat, p_in, p_out)

 cond_iface = (p_glaser > psat + 1.0) | (
 np.abs(p_glaser - psat) <= 1.0
 ) & (np.abs(p_free - psat) > 1.0)
 # конденсат в толще: касание pнас на внутренней границе слоя, не на поверхностях «просто так»
 cond_iface = False
 cond_iface[-1] = p_glaser[-1] > psat[-1] + 1.0
 has_interstitial = bool(np.any(p_free > psat + 1.0))

 t_dp_in = dew_point(p_in)
 surface_cond = theta < t_dp_in - 0.05

 return {
 "layers": layers,
 "r_layers": r_layers,
 "r_total": r_total,
 "u": u,
 "q": q,
 "theta": theta,
 "x_mm": x_m * 1000.0,
 "sd": sd,
 "psat": psat,
 "p_free": p_free,
 "p_glaser": p_glaser,
 "p_in": p_in,
 "p_out": p_out,
 "t_dp_in": t_dp_in,
 "has_interstitial": has_interstitial,
 "surface_cond": surface_cond,
 "t_in": t_in,
 "t_out": t_out,
 "phi_in": phi_in,
 "phi_out": phi_out,
 "rsi": rsi,
 "rse": rse,
 }

def print_report(res: dict) -> None:
 layers: list[Layer] = res["layers"]
 theta = res["theta"]
 print("=" * 72)
 print(f"Rsi={res['rsi']:.3f} Rse={res['rse']:.3f} "
 f"R={res['r_total']:.3f} м²·К/Вт U={res['u']:.3f} Вт/(м²·К)")
 print(f"q={res['q']:.1f} Вт/м²")
 print(f"tв={res['t_in']:.1f} °C, φв={res['phi_in']:.0f}% → "
 f"pв={res['p_in']:.0f} Па, tр={res['t_dp_in']:.1f} °C")
 print(f"tн={res['t_out']:.1f} °C, φн={res['phi_out']:.0f}% → "
 f"pн={res['p_out']:.0f} Па")
 print(f"θвн.пов={theta:.2f} °C "
 f"поверхностный конденсат: {'ДА' if res['surface_cond'] else 'нет'}")
 print(f"конденсат в толще (Глазер): "
 f"{'ДА' if res['has_interstitial'] else 'нет'}")
 print("-" * 72)
 print(f"{'#':<3} {'слой':<22} {'d,мм':>6} {'λ':>6} {'μ':>5} "
 f"{'R':>7} {'sd,м':>7} {'θнар,°C':>8}")
 for i, ly in enumerate(layers):
 print(f"{i+1:<3} {ly.name:<22} {ly.thickness_mm:6.1f} {ly.lambda_w:6.3f} "
 f"{ly.mu:5.1f} {ly.R:7.3f} {ly.sd:7.3f} {theta[i+1]:8.2f}")
 print("=" * 72)

def plot_results(res: dict, path: str) -> None:
 plt.rcParams["font.family"] = "DejaVu Sans"
 plt.rcParams["axes.grid"] = True
 plt.rcParams["grid.alpha"] = 0.35

 layers: list[Layer] = res["layers"]
 x = res["x_mm"]
 theta = res["theta"]
 sd = res["sd"]
 colors = plt.cm.tab10(np.linspace(0, 0.9, len(layers)))

 fig, (ax_t, ax_p) = plt.subplots(2, 1, figsize=(11, 8), layout="constrained")

 # --- температура по толще ---
 for i, ly in enumerate(layers):
 ax_t.axvspan(x[i], x[i + 1], color=colors[i], alpha=0.25, zorder=0)
 ax_t.text(
 0.5 * (x[i] + x[i + 1]),
 ax_t.get_ylim() if False else theta.min(),
 ly.name,
 ha="center",
 va="bottom",
 fontsize=8,
 rotation=90 if ly.thickness_mm < 40 else 0,
 )
 ax_t.plot(x, theta, color="C3", lw=2.2, marker="o", label="θ, °C")
 ax_t.axhline(res["t_in"], color="C1", ls="--", lw=1, label=f"tв = {res['t_in']:.1f} °C")
 ax_t.axhline(res["t_out"], color="C0", ls="--", lw=1, label=f"tн = {res['t_out']:.1f} °C")
 ax_t.axhline(
 res["t_dp_in"],
 color="C4",
 ls=":",
 lw=1.4,
 label=f"точка росы воздуха tр = {res['t_dp_in']:.1f} °C",
 )
 ax_t.set_xlim(x, x[-1])
 ax_t.set_xlabel("толщина от внутренней поверхности, мм")
 ax_t.set_ylabel("температура, °C")
 ax_t.set_title("Профиль температуры по слоям")
 ax_t.legend(loc="best", fontsize=8)

 # подписи слоёв после автомасштаба
 y_txt = ax_t.get_ylim() + 0.04 * (ax_t.get_ylim() - ax_t.get_ylim())
 for i, ly in enumerate(layers):
 ax_t.text(
 0.5 * (x[i] + x[i + 1]),
 y_txt,
 ly.name,
 ha="center",
 va="bottom",
 fontsize=8,
 color="0.2",
 )

 # --- Глазер: p и pнас по sd ---
 ax_p.plot(sd, res["psat"] / 1000.0, color="C0", lw=2.2, marker="s",
 label="pнас (по θ границ)")
 ax_p.plot(sd, res["p_free"] / 1000.0, color="0.4", lw=1.4, ls="--",
 label="p без конденсата (прямая pi–pe)")
 ax_p.plot(sd, res["p_glaser"] / 1000.0, color="C3", lw=2.0, marker="o",
 label="p по Глазеру")
 ax_p.fill_between(
 sd,
 res["p_glaser"] / 1000.0,
 res["psat"] / 1000.0,
 where=res["p_free"] > res["psat"],
 color="C3",
 alpha=0.25,
 interpolate=True,
 label="зона конденсата",
 )
 for i, ly in enumerate(layers):
 ax_p.axvline(sd[i], color="0.8", lw=0.8)
 ax_p.text(
 0.5 * (sd[i] + sd[i + 1]),
 ax_p.get_ylim() if False else 0,
 ly.name,
 ha="center",
 fontsize=8,
 )
 ax_p.set_xlim(sd, sd[-1])
 ax_p.set_xlabel("sd = μ·d, м")
 ax_p.set_ylabel("давление пара, кПа")
 title = "Глазер / ISO 13788"
 if res["has_interstitial"]:
 title += " — конденсат в толще"
 elif res["surface_cond"]:
 title += " — конденсат на внутренней поверхности"
 else:
 title += " — конденсата нет"
 ax_p.set_title(title)
 ax_p.legend(loc="best", fontsize=8)

 y_txt_p = ax_p.get_ylim() + 0.04 * (ax_p.get_ylim() - ax_p.get_ylim())
 for i, ly in enumerate(layers):
 ax_p.text(
 0.5 * (sd[i] + sd[i + 1]),
 y_txt_p,
 ly.name,
 ha="center",
 va="bottom",
 fontsize=8,
 color="0.2",
 )

 fig.savefig(path, dpi=150)
 print(f"график: {path}")
 plt.show()

if __name__ == "__main__":
 layers = [Layer(*row) for row in LAYERS]
 res = calculate(layers, T_IN, PHI_IN, T_OUT, PHI_OUT, RSI, RSE)
 print_report(res)
 plot_results(res, OUT_PNG)

Как читать
  • Верхний график — θ по физической толще. Цвет = слой. Пунктир tр — точка росы внутреннего воздуха: если θ внутренней поверхности ниже, мокрый угол/поверхность.
  • Нижний — классический Глазер в координате sd, не в мм. Прямая p без конденсата должна лежать ниже pнас. Заливка и касание pнас = выпадение в толще.
  • U и q — для передачи в MagiCAD как готовый коэффициент, не наоборот.
 
Да. Сейчас нарисовалась проблема с расчётом теплопотерь по нормам РФ (по другим локациям не знаю). Климатологию стали чаще обычного обновлять.
Соответственно, Audytor OZC уже не обновляется. Приходиться изворачиваться.
Долго думал на что перейти, но вариантов не много, особенно халявных xD
Из того, что на рынке - ДваОблака, Linear (оч крутой продукт, но вареза нет и купить в РФ нельзя, не доступен) и всяческие скрипты Dynamo.
Решил, по примеру Сани, начать с клодом писать плагин для ревита по расчёту теплпотерь с обновляемой базой. Сразу заложил возможность расчёта по EN (саму методику пока не разбирали). Первые кое-какие результаты уже есть...
 
Да. Это стационар по Глазеру / ISO 13788: слои → R/U → θ по границам → p / pнас. MagiCAD для этого не нужен.

Стек: numpy + matplotlib — оба живые, на PyPI, без seaborn/plotly. Кириллица идёт через DejaVu Sans (в matplotlib он из коробки).

Code:
pip install "numpy>=1.26" "matplotlib>=3.8"

В LAYERS и блоке климата подставляете свой пирог. Скрипт печатает таблицу и сохраняет wall_glaser.png.

Python:
from __future__ import annotations

from dataclasses import dataclass

import matplotlib.pyplot as plt
import numpy as np

# ---------------------------------------------------------------------------
# Исходные данные — правите только этот блок
# ---------------------------------------------------------------------------

LAYERS = [
 # имя, толщина мм, λ Вт/(м·К), μ
 ("Штукатурка внутр.", 15.0, 0.81, 10.0),
 ("Кирпич", 510.0, 0.56, 8.0),
 ("Минвата", 100.0, 0.040, 1.0),
 ("Штукатурка нар.", 20.0, 0.90, 15.0),
]

T_IN = 20.0 # °C, внутри
PHI_IN = 55.0 # %, внутри
T_OUT = -25.0 # °C, снаружи
PHI_OUT = 80.0 # %, снаружи

# Стена, горизонтальный поток. СП 50: 0.115 / 0.043; EN ISO 6946: 0.13 / 0.04
RSI = 0.115
RSE = 0.043

OUT_PNG = "wall_glaser.png"

# ---------------------------------------------------------------------------
# Физика
# ---------------------------------------------------------------------------

@dataclass(frozen=True)
class Layer:
 name: str
 thickness_mm: float
 lambda_w: float
 mu: float

 def __post_init__(self) -> None:
 if self.thickness_mm <= 0 or self.lambda_w <= 0 or self.mu <= 0:
 raise ValueError(f"Некорректный слой: {self.name}")

 @property
 def d(self) -> float:
 return self.thickness_mm / 1000.0

 @property
 def R(self) -> float:
 return self.d / self.lambda_w

 @property
 def sd(self) -> float:
 return self.mu * self.d

def saturation_pressure(theta_c: np.ndarray | float) -> np.ndarray | float:
 """pнас, Па. ISO 13788 (формула Магнуса)."""
 scalar = np.isscalar(theta_c)
 theta = np.atleast_1d(np.asarray(theta_c, dtype=float))
 ps = np.empty_like(theta)
 above = theta >= 0.0
 ps[above] = 610.5 * np.exp(17.269 * theta[above] / (237.3 + theta[above]))
 ps[~above] = 610.5 * np.exp(21.875 * theta[~above] / (265.5 + theta[~above]))
 return float(ps) if scalar else ps

def dew_point(p_pa: float) -> float:
 """Обратный Магнус, °C. Для p от льда/воды — ветка по 610.5 Па."""
 ln = np.log(p_pa / 610.5)
 if p_pa >= 610.5:
 return 237.3 * ln / (17.269 - ln)
 return 265.5 * ln / (21.875 - ln)

def glaser_pressure(
 sd: np.ndarray,
 psat: np.ndarray,
 p_in: float,
 p_out: float,
) -> np.ndarray:
 """Паровое давление по Глазеру: отрезок pi–pe, при необходимости касание pнас."""
 n = len(sd)
 p = np.empty(n)

 def fill(i: int, j: int, pi: float, pj: float) -> None:
 if j <= i + 1:
 p[i] = pi
 p[j] = pj
 return
 s = sd[i : j + 1]
 plin = pi + (pj - pi) * (s - sd[i]) / (sd[j] - sd[i])
 over = plin - psat[i : j + 1]
 k_rel = int(np.argmax(over))
 if over[k_rel] <= 1.0: # 1 Па — численный допуск
 p[i : j + 1] = plin
 return
 k = i + k_rel
 if k == i or k == j:
 p[i : j + 1] = np.minimum(plin, psat[i : j + 1])
 return
 fill(i, k, pi, float(psat[k]))
 fill(k, j, float(psat[k]), pj)

 fill(0, n - 1, p_in, p_out)
 return p

def calculate(
 layers: list[Layer],
 t_in: float,
 phi_in: float,
 t_out: float,
 phi_out: float,
 rsi: float,
 rse: float,
) -> dict:
 r_layers = np.array([ly.R for ly in layers], dtype=float)
 sd_layers = np.array([ly.sd for ly in layers], dtype=float)
 d_layers = np.array([ly.d for ly in layers], dtype=float)

 r_total = rsi + float(r_layers.sum()) + rse
 u = 1.0 / r_total
 q = (t_in - t_out) / r_total # Вт/м²

 # Границы слоёв: 0 = внутренняя поверхность, n = наружная
 r_cum = np.concatenate(([0.0], np.cumsum(r_layers)))
 theta = t_in - q * (rsi + r_cum) # °C на границах
 x_m = np.concatenate(([0.0], np.cumsum(d_layers)))
 sd = np.concatenate(([0.0], np.cumsum(sd_layers)))

 p_in = (phi_in / 100.0) * float(saturation_pressure(t_in))
 p_out = (phi_out / 100.0) * float(saturation_pressure(t_out))
 psat = np.asarray(saturation_pressure(theta), dtype=float)
 p_free = p_in + (p_out - p_in) * sd / sd[-1]
 p_glaser = glaser_pressure(sd, psat, p_in, p_out)

 cond_iface = (p_glaser > psat + 1.0) | (
 np.abs(p_glaser - psat) <= 1.0
 ) & (np.abs(p_free - psat) > 1.0)
 # конденсат в толще: касание pнас на внутренней границе слоя, не на поверхностях «просто так»
 cond_iface = False
 cond_iface[-1] = p_glaser[-1] > psat[-1] + 1.0
 has_interstitial = bool(np.any(p_free > psat + 1.0))

 t_dp_in = dew_point(p_in)
 surface_cond = theta < t_dp_in - 0.05

 return {
 "layers": layers,
 "r_layers": r_layers,
 "r_total": r_total,
 "u": u,
 "q": q,
 "theta": theta,
 "x_mm": x_m * 1000.0,
 "sd": sd,
 "psat": psat,
 "p_free": p_free,
 "p_glaser": p_glaser,
 "p_in": p_in,
 "p_out": p_out,
 "t_dp_in": t_dp_in,
 "has_interstitial": has_interstitial,
 "surface_cond": surface_cond,
 "t_in": t_in,
 "t_out": t_out,
 "phi_in": phi_in,
 "phi_out": phi_out,
 "rsi": rsi,
 "rse": rse,
 }

def print_report(res: dict) -> None:
 layers: list[Layer] = res["layers"]
 theta = res["theta"]
 print("=" * 72)
 print(f"Rsi={res['rsi']:.3f} Rse={res['rse']:.3f} "
 f"R={res['r_total']:.3f} м²·К/Вт U={res['u']:.3f} Вт/(м²·К)")
 print(f"q={res['q']:.1f} Вт/м²")
 print(f"tв={res['t_in']:.1f} °C, φв={res['phi_in']:.0f}% → "
 f"pв={res['p_in']:.0f} Па, tр={res['t_dp_in']:.1f} °C")
 print(f"tн={res['t_out']:.1f} °C, φн={res['phi_out']:.0f}% → "
 f"pн={res['p_out']:.0f} Па")
 print(f"θвн.пов={theta:.2f} °C "
 f"поверхностный конденсат: {'ДА' if res['surface_cond'] else 'нет'}")
 print(f"конденсат в толще (Глазер): "
 f"{'ДА' if res['has_interstitial'] else 'нет'}")
 print("-" * 72)
 print(f"{'#':<3} {'слой':<22} {'d,мм':>6} {'λ':>6} {'μ':>5} "
 f"{'R':>7} {'sd,м':>7} {'θнар,°C':>8}")
 for i, ly in enumerate(layers):
 print(f"{i+1:<3} {ly.name:<22} {ly.thickness_mm:6.1f} {ly.lambda_w:6.3f} "
 f"{ly.mu:5.1f} {ly.R:7.3f} {ly.sd:7.3f} {theta[i+1]:8.2f}")
 print("=" * 72)

def plot_results(res: dict, path: str) -> None:
 plt.rcParams["font.family"] = "DejaVu Sans"
 plt.rcParams["axes.grid"] = True
 plt.rcParams["grid.alpha"] = 0.35

 layers: list[Layer] = res["layers"]
 x = res["x_mm"]
 theta = res["theta"]
 sd = res["sd"]
 colors = plt.cm.tab10(np.linspace(0, 0.9, len(layers)))

 fig, (ax_t, ax_p) = plt.subplots(2, 1, figsize=(11, 8), layout="constrained")

 # --- температура по толще ---
 for i, ly in enumerate(layers):
 ax_t.axvspan(x[i], x[i + 1], color=colors[i], alpha=0.25, zorder=0)
 ax_t.text(
 0.5 * (x[i] + x[i + 1]),
 ax_t.get_ylim() if False else theta.min(),
 ly.name,
 ha="center",
 va="bottom",
 fontsize=8,
 rotation=90 if ly.thickness_mm < 40 else 0,
 )
 ax_t.plot(x, theta, color="C3", lw=2.2, marker="o", label="θ, °C")
 ax_t.axhline(res["t_in"], color="C1", ls="--", lw=1, label=f"tв = {res['t_in']:.1f} °C")
 ax_t.axhline(res["t_out"], color="C0", ls="--", lw=1, label=f"tн = {res['t_out']:.1f} °C")
 ax_t.axhline(
 res["t_dp_in"],
 color="C4",
 ls=":",
 lw=1.4,
 label=f"точка росы воздуха tр = {res['t_dp_in']:.1f} °C",
 )
 ax_t.set_xlim(x, x[-1])
 ax_t.set_xlabel("толщина от внутренней поверхности, мм")
 ax_t.set_ylabel("температура, °C")
 ax_t.set_title("Профиль температуры по слоям")
 ax_t.legend(loc="best", fontsize=8)

 # подписи слоёв после автомасштаба
 y_txt = ax_t.get_ylim() + 0.04 * (ax_t.get_ylim() - ax_t.get_ylim())
 for i, ly in enumerate(layers):
 ax_t.text(
 0.5 * (x[i] + x[i + 1]),
 y_txt,
 ly.name,
 ha="center",
 va="bottom",
 fontsize=8,
 color="0.2",
 )

 # --- Глазер: p и pнас по sd ---
 ax_p.plot(sd, res["psat"] / 1000.0, color="C0", lw=2.2, marker="s",
 label="pнас (по θ границ)")
 ax_p.plot(sd, res["p_free"] / 1000.0, color="0.4", lw=1.4, ls="--",
 label="p без конденсата (прямая pi–pe)")
 ax_p.plot(sd, res["p_glaser"] / 1000.0, color="C3", lw=2.0, marker="o",
 label="p по Глазеру")
 ax_p.fill_between(
 sd,
 res["p_glaser"] / 1000.0,
 res["psat"] / 1000.0,
 where=res["p_free"] > res["psat"],
 color="C3",
 alpha=0.25,
 interpolate=True,
 label="зона конденсата",
 )
 for i, ly in enumerate(layers):
 ax_p.axvline(sd[i], color="0.8", lw=0.8)
 ax_p.text(
 0.5 * (sd[i] + sd[i + 1]),
 ax_p.get_ylim() if False else 0,
 ly.name,
 ha="center",
 fontsize=8,
 )
 ax_p.set_xlim(sd, sd[-1])
 ax_p.set_xlabel("sd = μ·d, м")
 ax_p.set_ylabel("давление пара, кПа")
 title = "Глазер / ISO 13788"
 if res["has_interstitial"]:
 title += " — конденсат в толще"
 elif res["surface_cond"]:
 title += " — конденсат на внутренней поверхности"
 else:
 title += " — конденсата нет"
 ax_p.set_title(title)
 ax_p.legend(loc="best", fontsize=8)

 y_txt_p = ax_p.get_ylim() + 0.04 * (ax_p.get_ylim() - ax_p.get_ylim())
 for i, ly in enumerate(layers):
 ax_p.text(
 0.5 * (sd[i] + sd[i + 1]),
 y_txt_p,
 ly.name,
 ha="center",
 va="bottom",
 fontsize=8,
 color="0.2",
 )

 fig.savefig(path, dpi=150)
 print(f"график: {path}")
 plt.show()

if __name__ == "__main__":
 layers = [Layer(*row) for row in LAYERS]
 res = calculate(layers, T_IN, PHI_IN, T_OUT, PHI_OUT, RSI, RSE)
 print_report(res)
 plot_results(res, OUT_PNG)

Как читать
  • Верхний график — θ по физической толще. Цвет = слой. Пунктир tр — точка росы внутреннего воздуха: если θ внутренней поверхности ниже, мокрый угол/поверхность.
  • Нижний — классический Глазер в координате sd, не в мм. Прямая p без конденсата должна лежать ниже pнас. Заливка и касание pнас = выпадение в толще.
  • U и q — для передачи в MagiCAD как готовый коэффициент, не наоборот.
@admin Код не проверял? Рабочий?))
 
admin Код не проверял? Рабочий?))

Код из прошлого поста не рабочий. Это был набросок: пустой LAYERS, Магнус без ветки лёд/вода, индексы массивов съехали, rcParams перезаписывался целиком. Так даже таблица не посчитается.

Ниже один файл: считает, печатает отчёт, рисует оба графика. Стек тот же — numpy + matplotlib, оба живые.

Code:
pip install "numpy>=1.26" "matplotlib>=3.8"

Это Глазер / ISO 13788, не теплопотери здания. Audytor OZC, ДваОблака, Linear и ваш плагин — про помещения, добавки, инфильтрацию и климат СП 131. Скрипт ниже закрывает только пирог: U, θ по слоям, конденсат в толще. В MagiCAD из него уезжает готовый U, не нагрузка на стояк.

Пирог в шапке — пример (Москва, −25). Свой ставите в LAYERS / климат / Rsi Rse.

Python:
from __future__ import annotations

from dataclasses import dataclass

import matplotlib.pyplot as plt
import numpy as np

# ---------------------------------------------------------------------------
# Исходные данные — правите только этот блок
# ---------------------------------------------------------------------------

LAYERS = [
 ("Штукатурка внут.", 20.0, 0.87, 10.0),
 ("Кирпич глиняный", 380.0, 0.56, 8.0),
 ("Минвата", 120.0, 0.041, 1.0),
 ("Штукатурка фасад", 8.0, 0.70, 10.0),
]

T_IN = 20.0
PHI_IN = 55.0
T_OUT = -25.0
PHI_OUT = 80.0

# Стена, горизонт. СП 50: 0.115 / 0.043; EN ISO 6946: 0.13 / 0.04
RSI = 0.115
RSE = 0.043

OUT_PNG = "wall_glaser.png"

# ---------------------------------------------------------------------------
# Физика
# ---------------------------------------------------------------------------

@dataclass(frozen=True)
class Layer:
 name: str
 thickness_mm: float
 lambda_w: float
 mu: float

 def __post_init__(self) -> None:
 if self.thickness_mm <= 0 or self.lambda_w <= 0 or self.mu <= 0:
 raise ValueError(f"Некорректный слой: {self.name}")

 @property
 def d(self) -> float:
 return self.thickness_mm / 1000.0

 @property
 def R(self) -> float:
 return self.d / self.lambda_w

 @property
 def sd(self) -> float:
 return self.mu * self.d

def saturation_pressure(theta_c: np.ndarray | float) -> np.ndarray | float:
 """pнас, Па. ISO 13788 (Магнус)."""
 scalar = np.ndim(np.asarray(theta_c)) == 0
 theta = np.atleast_1d(np.asarray(theta_c, dtype=float))
 ps = np.empty_like(theta)
 above = theta >= 0.0
 ps[above] = 610.5 * np.exp(17.269 * theta[above] / (237.3 + theta[above]))
 ps[~above] = 610.5 * np.exp(21.875 * theta[~above] / (265.5 + theta[~above]))
 return float(ps) if scalar else ps

def dew_point(p_pa: float) -> float:
 """Обратный Магнус, °C."""
 if p_pa <= 0.0:
 raise ValueError("p <= 0")
 ln = np.log(p_pa / 610.5)
 if p_pa >= 610.5:
 return 237.3 * ln / (17.269 - ln)
 return 265.5 * ln / (21.875 - ln)

def glaser_pressure(
 sd: np.ndarray,
 psat: np.ndarray,
 p_in: float,
 p_out: float,
) -> np.ndarray:
 """Паровое давление по Глазеру: отрезок pi–pe, при необходимости касание pнас."""
 n = len(sd)
 p = np.empty(n)

 def fill(i: int, j: int, pi: float, pj: float) -> None:
 p[i] = pi
 p[j] = pj
 if j <= i + 1:
 return
 if sd[j] <= sd[i]:
 p[i : j + 1] = pi
 return
 s = sd[i : j + 1]
 plin = pi + (pj - pi) * (s - sd[i]) / (sd[j] - sd[i])
 ps = psat[i : j + 1]
 over = plin - ps
 k_rel = int(np.argmax(over))
 if over[k_rel] <= 1.0:
 p[i : j + 1] = plin
 return
 k = i + k_rel
 if k == i or k == j:
 p[i : j + 1] = np.minimum(plin, ps)
 return
 fill(i, k, pi, float(psat[k]))
 fill(k, j, float(psat[k]), pj)

 fill(0, n - 1, p_in, p_out)
 return p

def calculate(
 layers: list[Layer],
 t_in: float,
 phi_in: float,
 t_out: float,
 phi_out: float,
 rsi: float,
 rse: float,
) -> dict:
 r_layers = np.array([ly.R for ly in layers], dtype=float)
 sd_layers = np.array([ly.sd for ly in layers], dtype=float)
 d_layers = np.array([ly.d for ly in layers], dtype=float)

 r_total = rsi + float(r_layers.sum()) + rse
 u = 1.0 / r_total
 q = (t_in - t_out) / r_total

 r_cum = np.concatenate(([0.0], np.cumsum(r_layers)))
 theta = t_in - q * (rsi + r_cum)
 x_m = np.concatenate(([0.0], np.cumsum(d_layers)))
 sd = np.concatenate(([0.0], np.cumsum(sd_layers)))
 if sd[-1] <= 0.0:
 raise ValueError("Сумма sd = 0, конденсат считать не из чего")

 p_in = (phi_in / 100.0) * float(saturation_pressure(t_in))
 p_out = (phi_out / 100.0) * float(saturation_pressure(t_out))
 psat = np.asarray(saturation_pressure(theta), dtype=float)
 p_free = p_in + (p_out - p_in) * sd / sd[-1]
 p_glaser = glaser_pressure(sd, psat, p_in, p_out)

 has_interstitial = bool(np.any(p_free > psat + 1.0))
 t_dp_in = dew_point(p_in)
 t_si = float(theta)
 surface_cond = t_si < t_dp_in - 0.05
 delta_t = t_in - t_si

 return {
 "layers": layers,
 "r_layers": r_layers,
 "r_total": r_total,
 "u": u,
 "q": q,
 "theta": theta,
 "x_mm": x_m * 1000.0,
 "sd": sd,
 "psat": psat,
 "p_free": p_free,
 "p_glaser": p_glaser,
 "p_in": p_in,
 "p_out": p_out,
 "t_dp_in": t_dp_in,
 "t_si": t_si,
 "delta_t": delta_t,
 "has_interstitial": has_interstitial,
 "surface_cond": surface_cond,
 "t_in": t_in,
 "t_out": t_out,
 "phi_in": phi_in,
 "phi_out": phi_out,
 "rsi": rsi,
 "rse": rse,
 }

def print_report(res: dict) -> None:
 layers: list[Layer] = res["layers"]
 theta = res["theta"]
 print("=" * 78)
 print(
 f"Rsi={res['rsi']:.3f} Rse={res['rse']:.3f} "
 f"R={res['r_total']:.3f} м²·К/Вт U={res['u']:.3f} Вт/(м²·К)"
 )
 print(f"q={res['q']:.1f} Вт/м²")
 print(
 f"tв={res['t_in']:.1f} °C, φв={res['phi_in']:.0f}% → "
 f"pв={res['p_in']:.0f} Па, tр={res['t_dp_in']:.1f} °C"
 )
 print(
 f"tн={res['t_out']:.1f} °C, φн={res['phi_out']:.0f}% → "
 f"pн={res['p_out']:.0f} Па"
 )
 print(
 f"θвн.пов={res['t_si']:.2f} °C, Δt={res['delta_t']:.2f} К "
 f"поверхностный конденсат: {'ДА' if res['surface_cond'] else 'нет'}"
 )
 print(
 f"конденсат в толще (Глазер): "
 f"{'ДА' if res['has_interstitial'] else 'нет'}"
 )
 print("-" * 78)
 print(
 f"{'#':<3} {'слой':<22} {'d,мм':>6} {'λ':>6} {'μ':>5} "
 f"{'R':>7} {'sd,м':>7} {'θнар,°C':>8}"
 )
 for i, ly in enumerate(layers):
 print(
 f"{i + 1:<3} {ly.name:<22} {ly.thickness_mm:6.1f} {ly.lambda_w:6.3f} "
 f"{ly.mu:5.1f} {ly.R:7.3f} {ly.sd:7.3f} {theta[i + 1]:8.2f}"
 )
 print("=" * 78)

def plot_results(res: dict, path: str) -> None:
 plt.rcParams["font.family"] = "DejaVu Sans"
 plt.rcParams["axes.unicode_minus"] = False

 layers: list[Layer] = res["layers"]
 x = res["x_mm"]
 theta = res["theta"]
 sd = res["sd"]
 psat = res["psat"] / 1000.0
 p_free = res["p_free"] / 1000.0
 p_glaser = res["p_glaser"] / 1000.0
 colors = plt.cm.tab10(np.linspace(0.0, 0.9, max(len(layers), 1)))

 fig, (ax_t, ax_p) = plt.subplots(2, 1, figsize=(11, 8), layout="constrained")

 for i, ly in enumerate(layers):
 ax_t.axvspan(x[i], x[i + 1], color=colors[i], alpha=0.22, zorder=0)
 ax_t.plot(x, theta, color="C3", lw=2.2, marker="o", label="θ, °C")
 ax_t.axhline(res["t_in"], color="C1", ls="--", lw=1, label=f"tв = {res['t_in']:.1f} °C")
 ax_t.axhline(res["t_out"], color="C0", ls="--", lw=1, label=f"tн = {res['t_out']:.1f} °C")
 ax_t.axhline(
 res["t_dp_in"],
 color="C4",
 ls=":",
 lw=1.4,
 label=f"точка росы воздуха tр = {res['t_dp_in']:.1f} °C",
 )
 ax_t.set_xlim(x, x[-1])
 ax_t.set_xlabel("толщина от внутренней поверхности, мм")
 ax_t.set_ylabel("температура, °C")
 ax_t.set_title("Профиль температуры по слоям")
 ax_t.legend(loc="best", fontsize=8)
 y_txt = ax_t.get_ylim() + 0.06 * (ax_t.get_ylim() - ax_t.get_ylim())
 for i, ly in enumerate(layers):
 ax_t.text(
 0.5 * (x[i] + x[i + 1]),
 y_txt,
 ly.name,
 ha="center",
 va="bottom",
 fontsize=8,
 color="0.2",
 )

 ax_p.plot(sd, psat, color="C0", lw=2.2, marker="s", label="pнас (по θ границ)")
 ax_p.plot(sd, p_free, color="0.4", lw=1.4, ls="--", label="p без конденсата (прямая pi–pe)")
 ax_p.plot(sd, p_glaser, color="C3", lw=2.0, marker="o", label="p по Глазеру")
 ax_p.fill_between(
 sd,
 p_free,
 psat,
 where=p_free > psat,
 color="C3",
 alpha=0.25,
 interpolate=True,
 label="зона конденсата",
 )
 for i in range(1, len(sd) - 1):
 ax_p.axvline(sd[i], color="0.8", lw=0.8)
 ax_p.set_xlim(sd, sd[-1])
 ax_p.set_xlabel("sd = μ·d, м")
 ax_p.set_ylabel("давление пара, кПа")
 title = "Глазер / ISO 13788"
 if res["has_interstitial"]:
 title += " — конденсат в толще"
 elif res["surface_cond"]:
 title += " — конденсат на внутренней поверхности"
 else:
 title += " — конденсата нет"
 ax_p.set_title(title)
 ax_p.legend(loc="best", fontsize=8)
 y_txt_p = ax_p.get_ylim() + 0.06 * (ax_p.get_ylim() - ax_p.get_ylim())
 for i, ly in enumerate(layers):
 ax_p.text(
 0.5 * (sd[i] + sd[i + 1]),
 y_txt_p,
 ly.name,
 ha="center",
 va="bottom",
 fontsize=8,
 color="0.2",
 )

 fig.savefig(path, dpi=150)
 print(f"график: {path}")
 plt.show()

if __name__ == "__main__":
 layers = [Layer(*row) for row in LAYERS]
 res = calculate(layers, T_IN, PHI_IN, T_OUT, PHI_OUT, RSI, RSE)
 print_report(res)
 plot_results(res, OUT_PNG)

Как читать:

  • Верхний график — θ по миллиметрам. Если θ внутренней поверхности ниже пунктира tр — мокрая поверхность, не толща.
  • Нижний — классический Глазер в sd, не в мм. Прямая p выше pнас = выпадение в толще. Заливка это и показывает.
  • Δt = tв − θвн.пов. Для СП 50 его ещё сравнивать с нормируемым Δtₙ по назначению здания — в скрипте этого нет.
  • Стационар, один зимний срез. Нет сезона, дождя, солнца, мостиков. Для спорного пирога — WUFI, не этот файл.

Про плагин вместо OZC

Климат СП 131 реально дергают чаще, чем Sankom успевает. Audytor на старой пятидневке — типичная история. Linear в РФ сейчас не вариант (ни купить, ни «найти»). ДваОблака — рабочий облачный контур, но не Revit. Dynamo на нормах и климате живёт ровно до следующего обновления Revit.

Направление у вас правильное:
 
@SaMLiL видишь какой он умный ))) а ведь будет еще умнее, зачем тогда мы вообще нужны скоро? Код grok нормально пишет я на многих проектах проверял :) этот еще не проверял т.к. времени нету к сожалению )
 
Back
Top