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") | |
| } | |