Spaces:
Sleeping
Sleeping
""" | |
Drapery module for HVAC Load Calculator. | |
This module provides classes and functions for handling drapery properties | |
and calculating their effects on window heat transfer. | |
Based on ASHRAE principles for drapery thermal characteristics. | |
""" | |
from typing import Dict, Any, Optional, Tuple | |
from enum import Enum | |
class DraperyOpenness(Enum): | |
"""Enum for drapery openness classification.""" | |
OPEN = "Open (>25%)" | |
SEMI_OPEN = "Semi-open (7-25%)" | |
CLOSED = "Closed (0-7%)" | |
class DraperyColor(Enum): | |
"""Enum for drapery color/reflectance classification.""" | |
DARK = "Dark (0-25%)" | |
MEDIUM = "Medium (25-50%)" | |
LIGHT = "Light (>50%)" | |
class Drapery: | |
"""Class for drapery properties and calculations.""" | |
def __init__(self, | |
openness: float = 0.05, | |
reflectance: float = 0.5, | |
transmittance: float = 0.3, | |
fullness: float = 1.0, | |
enabled: bool = True): | |
""" | |
Initialize drapery object. | |
Args: | |
openness: Openness factor (0-1), fraction of fabric area that is open | |
reflectance: Reflectance factor (0-1), fraction of incident radiation reflected | |
transmittance: Transmittance factor (0-1), fraction of incident radiation transmitted | |
fullness: Fullness factor (0-2), ratio of fabric width to covered width | |
enabled: Whether the drapery is enabled/present | |
""" | |
self.openness = max(0.0, min(1.0, openness)) | |
self.reflectance = max(0.0, min(1.0, reflectance)) | |
self.transmittance = max(0.0, min(1.0, transmittance)) | |
self.fullness = max(0.0, min(2.0, fullness)) | |
self.enabled = enabled | |
# Calculate derived properties | |
self.absorptance = 1.0 - self.reflectance - self.transmittance | |
# Classify drapery based on openness and reflectance | |
self.openness_class = self._classify_openness(self.openness) | |
self.color_class = self._classify_color(self.reflectance) | |
def _classify_openness(openness: float) -> DraperyOpenness: | |
"""Classify drapery based on openness factor.""" | |
if openness > 0.25: | |
return DraperyOpenness.OPEN | |
elif openness > 0.07: | |
return DraperyOpenness.SEMI_OPEN | |
else: | |
return DraperyOpenness.CLOSED | |
def _classify_color(reflectance: float) -> DraperyColor: | |
"""Classify drapery based on reflectance factor.""" | |
if reflectance > 0.5: | |
return DraperyColor.LIGHT | |
elif reflectance > 0.25: | |
return DraperyColor.MEDIUM | |
else: | |
return DraperyColor.DARK | |
def get_classification(self) -> str: | |
"""Get drapery classification string.""" | |
openness_map = { | |
DraperyOpenness.OPEN: "I", | |
DraperyOpenness.SEMI_OPEN: "II", | |
DraperyOpenness.CLOSED: "III" | |
} | |
color_map = { | |
DraperyColor.DARK: "D", | |
DraperyColor.MEDIUM: "M", | |
DraperyColor.LIGHT: "L" | |
} | |
return f"{openness_map[self.openness_class]}{color_map[self.color_class]}" | |
def calculate_iac(self, glazing_shgc: float = 0.87) -> float: | |
""" | |
Calculate Interior Attenuation Coefficient (IAC) for the drapery. | |
The IAC represents the fraction of heat flow that enters the room | |
after being modified by the drapery. | |
Args: | |
glazing_shgc: Solar Heat Gain Coefficient of the glazing (default: 0.87 for clear glass) | |
Returns: | |
IAC value (0-1) | |
""" | |
if not self.enabled: | |
return 1.0 # No attenuation if drapery is not enabled | |
# Calculate base IAC for flat drapery (no fullness) | |
# This is based on the principles from the Keyes Universal Chart | |
# and ASHRAE's IAC calculation methods | |
# Calculate yarn reflectance (based on openness and reflectance) | |
if self.openness < 0.0001: # Prevent division by zero | |
yarn_reflectance = self.reflectance | |
else: | |
yarn_reflectance = self.reflectance / (1.0 - self.openness) | |
yarn_reflectance = min(1.0, yarn_reflectance) # Cap at 1.0 | |
# Base IAC calculation using fabric properties | |
# This is a simplified version of the ASHWAT model calculations | |
base_iac = 1.0 - (1.0 - self.openness) * (1.0 - self.transmittance / (1.0 - self.openness)) * yarn_reflectance | |
# Adjust for fullness | |
# Fullness creates multiple reflections between adjacent fabric surfaces | |
if self.fullness <= 0.0: | |
fullness_factor = 1.0 | |
else: | |
# Fullness effect increases with higher fullness values | |
# More fullness means more fabric area and more reflections | |
fullness_factor = 1.0 - 0.15 * (self.fullness / 2.0) | |
# Apply fullness adjustment | |
adjusted_iac = base_iac * fullness_factor | |
# Ensure IAC is within valid range | |
adjusted_iac = max(0.1, min(1.0, adjusted_iac)) | |
return adjusted_iac | |
def calculate_shading_coefficient(self, glazing_shgc: float = 0.87) -> float: | |
""" | |
Calculate shading coefficient for the drapery. | |
The shading coefficient is the ratio of solar heat gain through | |
the window with the drapery to that of standard clear glass. | |
Args: | |
glazing_shgc: Solar Heat Gain Coefficient of the glazing (default: 0.87 for clear glass) | |
Returns: | |
Shading coefficient (0-1) | |
""" | |
# Calculate IAC | |
iac = self.calculate_iac(glazing_shgc) | |
# Calculate shading coefficient | |
# SC = IAC * SHGC / 0.87 | |
shading_coefficient = iac * glazing_shgc / 0.87 | |
return shading_coefficient | |
def calculate_u_value_adjustment(self, base_u_value: float) -> float: | |
""" | |
Calculate U-value adjustment for the drapery. | |
The drapery adds thermal resistance to the window assembly, | |
reducing the overall U-value. | |
Args: | |
base_u_value: Base U-value of the window without drapery (W/m²K) | |
Returns: | |
Adjusted U-value (W/m²K) | |
""" | |
if not self.enabled: | |
return base_u_value # No adjustment if drapery is not enabled | |
# Calculate additional thermal resistance based on drapery properties | |
# This is a simplified approach based on ASHRAE principles | |
# Base resistance from drapery | |
# More closed fabrics provide more resistance | |
base_resistance = 0.05 # m²K/W, typical for medium-weight drapery | |
# Adjust for openness (more closed = more resistance) | |
openness_factor = 1.0 - self.openness | |
# Adjust for fullness (more fullness = more resistance due to air gaps) | |
fullness_factor = 1.0 + 0.25 * self.fullness | |
# Calculate total additional resistance | |
additional_resistance = base_resistance * openness_factor * fullness_factor | |
# Convert base U-value to resistance | |
base_resistance = 1.0 / base_u_value | |
# Add drapery resistance | |
total_resistance = base_resistance + additional_resistance | |
# Convert back to U-value | |
adjusted_u_value = 1.0 / total_resistance | |
return adjusted_u_value | |
def to_dict(self) -> Dict[str, Any]: | |
"""Convert drapery object to dictionary.""" | |
return { | |
"openness": self.openness, | |
"reflectance": self.reflectance, | |
"transmittance": self.transmittance, | |
"fullness": self.fullness, | |
"enabled": self.enabled, | |
"absorptance": self.absorptance, | |
"openness_class": self.openness_class.value, | |
"color_class": self.color_class.value, | |
"classification": self.get_classification() | |
} | |
def from_dict(cls, data: Dict[str, Any]) -> 'Drapery': | |
"""Create drapery object from dictionary.""" | |
return cls( | |
openness=data.get("openness", 0.05), | |
reflectance=data.get("reflectance", 0.5), | |
transmittance=data.get("transmittance", 0.3), | |
fullness=data.get("fullness", 1.0), | |
enabled=data.get("enabled", True) | |
) | |
def from_classification(cls, classification: str, fullness: float = 1.0) -> 'Drapery': | |
""" | |
Create drapery object from ASHRAE classification. | |
Args: | |
classification: ASHRAE classification (ID, IM, IL, IID, IIM, IIL, IIID, IIIM, IIIL) | |
fullness: Fullness factor (0-2) | |
Returns: | |
Drapery object | |
""" | |
# Parse classification | |
if len(classification) < 2: | |
raise ValueError(f"Invalid classification: {classification}") | |
# Handle single-character openness class (I) vs two-character (II, III) | |
if classification.startswith("II") or classification.startswith("III"): | |
if classification.startswith("III"): | |
openness_class = "III" | |
color_class = classification[3] if len(classification) > 3 else "" | |
else: # II | |
openness_class = "II" | |
color_class = classification[2] if len(classification) > 2 else "" | |
else: # I | |
openness_class = "I" | |
color_class = classification[1] if len(classification) > 1 else "" | |
# Set default values | |
openness = 0.05 | |
reflectance = 0.5 | |
transmittance = 0.3 | |
# Set openness based on class | |
if openness_class == "I": | |
openness = 0.3 # Open (>25%) | |
elif openness_class == "II": | |
openness = 0.15 # Semi-open (7-25%) | |
elif openness_class == "III": | |
openness = 0.03 # Closed (0-7%) | |
# Set reflectance and transmittance based on color class | |
if color_class == "D": | |
reflectance = 0.2 # Dark (0-25%) | |
transmittance = 0.05 | |
elif color_class == "M": | |
reflectance = 0.4 # Medium (25-50%) | |
transmittance = 0.15 | |
elif color_class == "L": | |
reflectance = 0.7 # Light (>50%) | |
transmittance = 0.2 | |
return cls( | |
openness=openness, | |
reflectance=reflectance, | |
transmittance=transmittance, | |
fullness=fullness | |
) | |
# Predefined drapery types based on ASHRAE classifications | |
PREDEFINED_DRAPERIES = { | |
"ID": Drapery.from_classification("ID"), | |
"IM": Drapery.from_classification("IM"), | |
"IL": Drapery.from_classification("IL"), | |
"IID": Drapery.from_classification("IID"), | |
"IIM": Drapery.from_classification("IIM"), | |
"IIL": Drapery.from_classification("IIL"), | |
"IIID": Drapery.from_classification("IIID"), | |
"IIIM": Drapery.from_classification("IIIM"), | |
"IIIL": Drapery.from_classification("IIIL") | |
} | |