Source code for honeybee_phhvac.hot_water_piping

# -*- coding: utf-8 -*-
# -*- Python Version: 2.7 -*-

"""Honeybee-PH-HVAC: Hot Water Piping."""

from copy import copy
from math import radians

try:
    from typing import Any, Dict, List, Optional, Union
except ImportError:
    pass  # IronPython

try:
    from ladybug_geometry.geometry3d.pointvector import Point3D, Vector3D
    from ladybug_geometry.geometry3d.polyline import LineSegment3D
except ImportError as e:
    raise ImportError("Failed to import ladybug_geometry", e)

try:
    from honeybee_phhvac import _base
except ImportError as e:
    raise ImportError("Failed to import honeybee_phhvac", e)

try:
    from honeybee_ph_utils import enumerables
except ImportError as e:
    raise ImportError("Failed to import honeybee_ph_utils", e)


# -- Piping  Enums ------------------------------------------------------------


[docs] class PhHvacPipeMaterial(enumerables.CustomEnum): allowed = [ "1-COPPER_M", "2-COPPER_L", "3-COPPER_K", "4-CPVC_CTS_SDR", "5-CPVC_SCH_40", "6-PEX", "7-PE", "8-PEX_CTS_SDR", ] def __init__(self, _value=2): # type: (Union[str, int]) -> None super(PhHvacPipeMaterial, self).__init__(_value) def __eq__(self, other): # type: (PhHvacPipeMaterial) -> bool return self.value == other.value def __ne__(self, other): # type: (PhHvacPipeMaterial) -> bool return self.value != other.value def __hash__(self): # type: () -> int return hash(self.value)
# -- Piping -------------------------------------------------------------------
[docs] class PhHvacPipeSegment(_base._PhHVACBase): """A single pipe segment (linear) with geometry and a diameter Note: Following the LBT convention, while the geometry can be in a variety of units, thicknesses are all required to be in meters. This means that while the geometry will scale, the thickness and diameter will not. """ # fmt: off def __init__( self, _geom, _diameter_mm=12.7, _insul_thickness_mm=12.7, _insul_conductivity=0.04, _insul_refl=True, _insul_quality=None, _daily_period=24, _water_temp_c=60.0, _material=2, *args, **kwargs ): # fmt: on # type: (LineSegment3D, float, float, float, bool, None, float, float, int, *Any, **Any) -> None super(PhHvacPipeSegment, self).__init__() self.geometry = _geom self.diameter_mm = _diameter_mm self.insulation_thickness_mm = _insul_thickness_mm self.insulation_conductivity = _insul_conductivity self.insulation_reflective = _insul_refl self.insulation_quality = _insul_quality self.daily_period = _daily_period self.water_temp_c = _water_temp_c self.material = PhHvacPipeMaterial(_material) @property def length(self): # type: () -> float """Return the length of the pipe segment in model-units.""" return self.geometry.length @property def diameter_m(self): # type: () -> float """Return the diameter of the pipe segment in meters.""" return self.diameter_mm * 0.001 @property def insulation_thickness_m(self): # type: () -> float """Return the insulation thickness of the pipe segment in meters.""" return self.insulation_thickness_mm * 0.001 def __copy__(self): # type: () -> PhHvacPipeSegment new_obj = PhHvacPipeSegment(self.geometry.duplicate()) new_obj.diameter_mm = self.diameter_mm new_obj.material = PhHvacPipeMaterial(self.material.value) new_obj.insulation_thickness_mm = self.insulation_thickness_mm new_obj.insulation_conductivity = self.insulation_conductivity new_obj.insulation_reflective = self.insulation_reflective new_obj.insulation_quality = self.insulation_quality new_obj.daily_period = self.daily_period new_obj.water_temp_c = self.water_temp_c new_obj.identifier = self.identifier new_obj.display_name = self.display_name new_obj.user_data = copy(self.user_data) return new_obj
[docs] def duplicate(self): # type: () -> PhHvacPipeSegment return self.__copy__()
[docs] def to_dict(self, _include_properties=False): # type: (bool) -> Dict[str, Union[str, Dict]] d = super(PhHvacPipeSegment, self).to_dict() d["geometry"] = self.geometry.to_dict() d["diameter_mm"] = self.diameter_mm d["material_value"] = self.material.value d["insulation_thickness_mm"] = self.insulation_thickness_mm d["insulation_conductivity"] = self.insulation_conductivity d["insulation_reflective"] = self.insulation_reflective d["insulation_quality"] = self.insulation_quality d["daily_period"] = self.daily_period d["water_temp_c"] = self.water_temp_c if _include_properties: d["length"] = self.length return d
[docs] @classmethod def from_dict(cls, _input_dict): # type: (Dict) -> PhHvacPipeSegment new_obj = cls(_geom=LineSegment3D.from_dict(_input_dict["geometry"])) new_obj.diameter_mm = _input_dict["diameter_mm"] new_obj.material = PhHvacPipeMaterial(_input_dict["material_value"]) new_obj.insulation_thickness_mm = _input_dict["insulation_thickness_mm"] new_obj.insulation_conductivity = _input_dict["insulation_conductivity"] new_obj.insulation_reflective = _input_dict["insulation_reflective"] new_obj.insulation_quality = _input_dict["insulation_quality"] new_obj.daily_period = _input_dict["daily_period"] new_obj.water_temp_c = _input_dict["water_temp_c"] new_obj.identifier = _input_dict["identifier"] new_obj.display_name = _input_dict["display_name"] new_obj.user_data = _input_dict["user_data"] return new_obj
def __str__(self): return "{}: diam={:.3f} (MM), length={:.3f} (Model-Unit)".format( self.__class__.__name__, self.diameter_mm, self.length ) def __repr__(self): return str(self)
[docs] def ToString(self): return self.__repr__()
[docs] def move(self, moving_vec3D): # type: (Vector3D) -> PhHvacPipeSegment """Move the pipe's geometry along a vector. Args: moving_vec3D: A Vector3D with the direction and distance to move the ray. Returns: A new PhHvacPipeSegment with the moved geometry. """ dup = self.duplicate() dup.geometry = self.geometry.move(moving_vec3D) return dup
[docs] def rotate(self, axis_3D, angle_degrees, origin_pt3D): # type: (Vector3D, float, Point3D) -> PhHvacPipeSegment """Rotate the pipe's geometry by a certain angle_degrees around an axis_3D and origin_pt3D. Right hand rule applies: If axis_3D has a positive orientation, rotation will be clockwise. If axis_3D has a negative orientation, rotation will be counterclockwise. Args: axis_3D: A Vector3D axis_3D representing the axis_3D of rotation. angle_degrees: An angle_degrees for rotation in degrees. origin_pt3D: A Point3D for the origin_pt3D around which the object will be rotated. Returns: A new PhHvacPipeSegment with the rotated geometry. """ dup = self.duplicate() dup.geometry = self.geometry.rotate(axis_3D, radians(angle_degrees), origin_pt3D) return dup
[docs] def rotate_xy(self, angle_degrees, origin_pt3D): # type: (float, Point3D) -> PhHvacPipeSegment """Rotate the pipe's geometry counterclockwise in the XY plane by a certain angle_degrees. Args: angle_degrees: An angle_degrees in degrees. origin_pt3D: A Point3D for the origin_pt3D around which the object will be rotated. Returns: A new PhHvacPipeSegment with the rotated geometry. """ dup = self.duplicate() dup.geometry = self.geometry.rotate_xy(radians(angle_degrees), origin_pt3D) return dup
[docs] def reflect(self, normal_vec3D, origin_pt3D): # type: (Vector3D, Point3D) -> PhHvacPipeSegment """Reflected the pipe's geometry across a plane with the input normal vector and origin_pt3D. Args: normal_vec3D: A Vector3D representing the normal vector for the plane across which the line segment will be reflected. THIS VECTOR MUST BE NORMALIZED. origin_pt3D: A Point3D representing the origin_pt3D from which to reflect. Returns: A new PhHvacPipeSegment with the reflected geometry. """ dup = self.duplicate() dup.geometry = self.geometry.reflect(normal_vec3D, origin_pt3D) return dup
[docs] def scale(self, scale_factor, origin_pt3D=None): # type: (float, Union[None, Point3D]) -> PhHvacPipeSegment """Scale the pipe's geometry by a factor from an origin_pt3D point. Note that following the LBT convention, while the geometry can be in a variety of units, thicknesses are all required to be in meters. This means that while the geometry will scale, the thickness and diameter will not. Args: scale_factor: A number representing how much the line segment should be scaled. origin_pt3D: A Point3D representing the origin_pt3D from which to scale. If None, it will be scaled from the World origin_pt3D (0, 0, 0). """ new_pipe_segment = self.duplicate() new_pipe_segment.geometry = self.geometry.scale(scale_factor, origin_pt3D) return new_pipe_segment
[docs] class PhHvacPipeElement(_base._PhHVACBase): """A Pipe Element (Fixture) made up of one or more individual Pipe Segments.""" def __init__(self): super(PhHvacPipeElement, self).__init__() self._segments = {} # type: Dict[str, PhHvacPipeSegment] @property def segments(self): # type: () -> List[PhHvacPipeSegment] """Return a list of a;; the Pipe-Segments in the Pipe-Element.""" return list(self._segments.values()) @property def length(self): # type: () -> float """Return the total length of the pipe element in model-units.""" return sum(s.length for s in self.segments) @property def diameter_mm(self): # type: () -> float """Return the length-weighted average diameter of all the pipe segments""" try: return sum(s.length * s.diameter_mm for s in self.segments) / self.length except ZeroDivisionError: return 0 @property def water_temp_c(self): # type: () -> float """Return the length-weighted average water temperature of all the pipe segments""" try: return sum(s.length * s.water_temp_c for s in self.segments) / self.length except ZeroDivisionError: return 60.0 @property def daily_period(self): # type: () -> float """Return the length-weighted average daily period of all the pipe segments""" try: return sum(s.length * s.daily_period for s in self.segments) / self.length except ZeroDivisionError: return 24.0 @property def segment_names(self): # type: () -> List[str] """Return a list of the names of all the PipeSegments in the PipeElement.""" return [s.display_name for s in self.segments] @property def material_name(self): # type: () -> str """Return the material name of the pipe element.""" materials = {s.material for s in self.segments} if len(materials) == 0: return PhHvacPipeMaterial.allowed[0] elif len(materials) == 1: mat = materials.pop() return mat.value else: raise ValueError("Pipe segments: {} have different materials.".format(self.segment_names))
[docs] def add_segment(self, _segment): # type: (PhHvacPipeSegment) -> None """Add a new Pipe Segment to the Pipe Element.""" self._segments[_segment.identifier] = _segment
[docs] def clear_segments(self): # type: () -> None """Clear all the segments from the pipe element.""" self._segments = {}
def __copy__(self): # type: () -> PhHvacPipeElement """Duplicate the pipe element.""" new_obj = PhHvacPipeElement() for segment in self.segments: new_obj.add_segment(segment.duplicate()) new_obj.identifier = self.identifier new_obj.display_name = self.display_name new_obj.user_data = self.user_data return new_obj
[docs] def duplicate(self): # type: () -> PhHvacPipeElement return self.__copy__()
[docs] def to_dict(self, _include_properties=False): # type: (bool) -> dict[str, Union[str, dict]] d = super(PhHvacPipeElement, self).to_dict() d["segments"] = {} for segment in self.segments: d["segments"][segment.identifier] = segment.to_dict(_include_properties) if _include_properties: d["length"] = self.length d["water_temp"] = self.water_temp_c d["daily_period"] = self.daily_period d["material_name"] = self.material_name d["diameter"] = self.diameter_mm return d
[docs] @classmethod def from_dict(cls, _input_dict): # type: (dict) -> PhHvacPipeElement new_obj = cls() for seg_dict in _input_dict["segments"].values(): new_obj._segments[seg_dict["identifier"]] = PhHvacPipeSegment.from_dict(seg_dict) new_obj.identifier = _input_dict["identifier"] new_obj.display_name = _input_dict["display_name"] new_obj.user_data = _input_dict["user_data"] return new_obj
def __str__(self): return "{}: (display_name={}, identifier={} ) [{} segments, len={:.3f}]".format( self.__class__.__name__, self.display_name, self.identifier_short, len(self.segments), self.length, ) def __repr__(self): return str(self)
[docs] def ToString(self): return self.__repr__()
[docs] def move(self, moving_vec3D): # type: (Vector3D) -> PhHvacPipeElement """Move the pipe's segments along a vector. Args: moving_vec3D: A Vector3D with the direction and distance to move the ray. Returns: A new PhHvacPipeElement with the moved segments. """ new_pipe_element = self.duplicate() new_pipe_element.clear_segments() for segment in self.segments: new_pipe_element.add_segment(segment.move(moving_vec3D)) return new_pipe_element
[docs] def rotate(self, axis_3D, angle_degrees, origin_pt3D): # type: (Vector3D, float, Point3D) -> PhHvacPipeElement """Rotate the pipe's segments by a certain angle_degrees around an axis_3D and origin_pt3D. Right hand rule applies: If axis_3D has a positive orientation, rotation will be clockwise. If axis_3D has a negative orientation, rotation will be counterclockwise. Args: axis_3D: A Vector3D axis_3D representing the axis_3D of rotation. angle_degrees: An angle_degrees for rotation in degrees. origin_pt3D: A Point3D for the origin_pt3D around which the object will be rotated. Returns: A new PhHvacPipeElement with the rotated segments. """ new_pipe_element = self.duplicate() new_pipe_element.clear_segments() for segment in self.segments: new_pipe_element.add_segment(segment.rotate(axis_3D, angle_degrees, origin_pt3D)) return new_pipe_element
[docs] def rotate_xy(self, angle_degrees, origin_pt3D): # type: (float, Point3D) -> PhHvacPipeElement """Rotate the pipe's segments counterclockwise in the XY plane by a certain angle_degrees. Args: angle_degrees: An angle_degrees in degrees. origin_pt3D: A Point3D for the origin_pt3D around which the object will be rotated. Returns: A new PhHvacPipeElement with the rotated segments. """ new_pipe_element = self.duplicate() new_pipe_element.clear_segments() for segment in self.segments: new_pipe_element.add_segment(segment.rotate_xy(angle_degrees, origin_pt3D)) return new_pipe_element
[docs] def reflect(self, normal_vec3D, origin_pt3D): # type: (Vector3D, Point3D) -> PhHvacPipeElement """Reflected the pipe's segments across a plane with the input normal_vec3D vector and origin_pt3D. Args: normal_vec3D: A Vector3D representing the normal_vec3D vector for the plane across which the line segment will be reflected. THIS VECTOR MUST BE NORMALIZED. origin_pt3D: A Point3D representing the origin_pt3D from which to reflect. Returns: A new PhHvacPipeElement with the reflected segments. """ new_pipe_element = self.duplicate() new_pipe_element.clear_segments() for segment in self.segments: new_pipe_element.add_segment(segment.reflect(normal_vec3D, origin_pt3D)) return new_pipe_element
[docs] def scale(self, factor, origin_pt3D=None): # type: (float, Optional[Point3D]) -> PhHvacPipeElement """Scale the pipe's segments by a factor from an origin_pt3D point. Args: factor: A number representing how much the line segment should be scaled. origin_pt3D: A Point3D representing the origin_pt3D from which to scale. If None, it will be scaled from the World origin_pt3D (0, 0, 0). Returns: A new PhHvacPipeElement with the scaled segments. """ new_pipe_element = self.duplicate() new_pipe_element.clear_segments() for segment in self.segments: new_pipe_element.add_segment(segment.scale(factor, origin_pt3D)) return new_pipe_element
[docs] class PhHvacPipeBranch(_base._PhHVACBase): """A 'Branch' Pipe which has geometry, and serves one or more 'Fixture' (Twig) pipe elements.""" def __init__(self): # type: () -> None super(PhHvacPipeBranch, self).__init__() self.pipe_element = PhHvacPipeElement() self.fixtures = [] # type: (List[PhHvacPipeElement]) @property def material_name(self): # type: () -> str """Return the material name of the pipe element.""" return self.pipe_element.material_name @property def diameter_mm(self): # type: () -> float """Return the length-weighted diameter (MM) of the pipe element.""" return self.pipe_element.diameter_mm @property def twigs(self): # type () -> List[PhPipeElement] """Alias for the 'fixtures' to better match Phius terminology.""" return self.fixtures @property def segments(self): # type () -> List[PhPipeSegment] """Return a list of all the Pipe-Segments in the Branch.""" return self.pipe_element.segments @property def length(self): # type: () -> float """Return the total length of the branch itself in model-units. For the total length of the Branch PLUS all fixtures, use 'total_length'. """ return float(self.pipe_element.length) @property def water_temp_c(self): # type: () -> float """Return the length-weighted average water temperature of all the pipe segments.""" return self.pipe_element.water_temp_c @property def daily_period(self): # type: () -> float """Return the length-weighted average daily period of all the pipe segments.""" return self.pipe_element.daily_period @property def num_fixtures(self): # type: () -> int """Return the number of fixtures connected to the branch.""" return len(self.fixtures) @property def total_length(self): # type: () -> float """Return the total length of the branch PLUS all fixture pipes in model-units.""" return self.length + sum(f.length for f in self.fixtures) @property def total_home_run_fixture_length(self): # type: () -> float """Return the total length (in model-units) of all fixture pipes as measured from end to end. NOTE: This method will include the branch's length for EACH of the fixture pipes. The result will be a total hot-water transport length as if all the pipes were 'home-run' style. This value is used for the PHPP calculations and is not a true representation of the piping in the model. """ return sum(fixture.length + self.length for fixture in self.fixtures)
[docs] def add_fixture(self, _fixture): # type: (PhHvacPipeElement) -> None """Add a new HBPH Fixture (twig) PhPipeBranch to the Trunk.""" self.fixtures.append(_fixture)
def __copy__(self): # type: () -> PhHvacPipeBranch new_obj = PhHvacPipeBranch() new_obj.identifier = self.identifier new_obj.display_name = self.display_name new_obj.user_data = self.user_data new_obj.pipe_element = self.pipe_element.duplicate() for fixture in self.fixtures: new_obj.add_fixture(fixture.duplicate()) return new_obj
[docs] def duplicate(self): # type: () -> PhHvacPipeBranch return self.__copy__()
[docs] def to_dict(self, _include_properties=False): # type: (bool) -> Dict[str, Union[str, Dict]] d = super(PhHvacPipeBranch, self).to_dict() d["pipe_element"] = self.pipe_element.to_dict(_include_properties) d["fixtures"] = {} for branch in self.fixtures: d["fixtures"][branch.identifier] = branch.to_dict(_include_properties) if _include_properties: d["length"] = self.length d["water_temp_c"] = self.water_temp_c d["daily_period"] = self.daily_period d["num_fixtures"] = self.num_fixtures d["total_length"] = self.total_length d["total_home_run_fixture_length"] = self.total_home_run_fixture_length return d
[docs] @classmethod def from_dict(cls, _input_dict): # type: (Dict) -> PhHvacPipeBranch new_obj = cls() new_obj.identifier = _input_dict["identifier"] new_obj.display_name = _input_dict["display_name"] new_obj.user_data = _input_dict["user_data"] new_obj.pipe_element = PhHvacPipeElement.from_dict(_input_dict["pipe_element"]) for fixture_dict in _input_dict["fixtures"].values(): new_obj.add_fixture(PhHvacPipeElement.from_dict(fixture_dict)) return new_obj
def __str__(self): # type: () -> str return "{}: (display_name={}, identifier={} ) [{} segments, len={:.1f}, {} fixtures connected]".format( self.__class__.__name__, self.display_name, self.identifier_short, len(self.segments), float(self.length), len(self.fixtures), ) def __repr__(self): # type: () -> str return str(self)
[docs] def ToString(self): # type: () -> str return self.__repr__()
[docs] def move(self, moving_vec3D): # type: (Vector3D) -> PhHvacPipeBranch """Move the pipe's elements along a vector. Args: moving_vec3D: A Vector3D with the direction and distance to move the ray. Returns: A new PhHvacPipeBranch with the moved elements. """ new_branch = self.duplicate() new_branch.fixtures = [fixture.move(moving_vec3D) for fixture in self.fixtures] new_branch.pipe_element = self.pipe_element.move(moving_vec3D) return new_branch
[docs] def rotate(self, axis_3D, angle_degrees, origin_pt3D): # type: (Vector3D, float, Point3D) -> PhHvacPipeBranch """Rotate the pipe's elements by a certain angle_degrees around an axis_3D and origin_pt3D. Right hand rule applies: If axis_3D has a positive orientation, rotation will be clockwise. If axis_3D has a negative orientation, rotation will be counterclockwise. Args: axis_3D: A Vector3D axis_3D representing the axis_3D of rotation. angle_degrees: An angle_degrees for rotation in degrees. origin_pt3D: A Point3D for the origin_pt3D around which the object will be rotated. Returns: A new PhHvacPipeBranch with the rotated elements. """ new_branch = self.duplicate() new_branch.fixtures = [fixture.rotate(axis_3D, angle_degrees, origin_pt3D) for fixture in self.fixtures] new_branch.pipe_element = self.pipe_element.rotate(axis_3D, angle_degrees, origin_pt3D) return new_branch
[docs] def rotate_xy(self, angle_degrees, origin_pt3D): # type: (float, Point3D) -> PhHvacPipeBranch """Rotate the pipe's elements counterclockwise in the XY plane by a certain angle_degrees. Args: angle_degrees: An angle_degrees in degrees. origin_pt3D: A Point3D for the origin_pt3D around which the object will be rotated. Returns: A new PhHvacPipeBranch with the rotated elements. """ new_branch = self.duplicate() new_branch.fixtures = [fixture.rotate_xy(angle_degrees, origin_pt3D) for fixture in self.fixtures] new_branch.pipe_element = self.pipe_element.rotate_xy(angle_degrees, origin_pt3D) return new_branch
[docs] def reflect(self, normal_vec3D, origin_pt3D): # type: (Vector3D, Point3D) -> PhHvacPipeBranch """Reflected the pipe's elements across a plane with the input normal_vec3D vector and origin_pt3D. Args: normal_vec3D: A Vector3D representing the normal_vec3D vector for the plane across which the line segment will be reflected. THIS VECTOR MUST BE NORMALIZED. origin_pt3D: A Point3D representing the origin_pt3D from which to reflect. Returns: A new PhHvacPipeBranch with the reflected elements. """ new_branch = self.duplicate() new_branch.fixtures = [fixture.reflect(normal_vec3D, origin_pt3D) for fixture in self.fixtures] new_branch.pipe_element = self.pipe_element.reflect(normal_vec3D, origin_pt3D) return new_branch
[docs] def scale(self, factor, origin_pt3D=None): # type: (float, Optional[Point3D]) -> PhHvacPipeBranch """Scale the pipe's elements by a factor from an origin_pt3D point. Args: factor: A number representing how much the line segment should be scaled. origin_pt3D: A Point3D representing the origin_pt3D from which to scale. If None, it will be scaled from the World origin_pt3D (0, 0, 0). Returns: A new PhHvacPipeBranch with the scaled elements. """ new_branch = self.duplicate() new_branch.fixtures = [fixture.scale(factor, origin_pt3D) for fixture in self.fixtures] new_branch.pipe_element = self.pipe_element.scale(factor, origin_pt3D) return new_branch
[docs] class PhHvacPipeTrunk(_base._PhHVACBase): """A 'Trunk' Pipe which has geometry, and serves one or more 'Branches'.""" def __init__(self): # type: () -> None super(PhHvacPipeTrunk, self).__init__() self.pipe_element = PhHvacPipeElement() self.multiplier = 1 # type: int self.branches = [] # type: (List[PhHvacPipeBranch]) self.demand_recirculation = False # type: bool @property def material_name(self): # type: () -> str """Return the material name of the pipe element.""" return self.pipe_element.material_name @property def diameter_mm(self): # type: () -> float """Return the length-weighted diameter (MM) name of the pipe element.""" return self.pipe_element.diameter_mm @property def segments(self): # type () -> List[PhPipeSegment] """Return a list of all the Pipe-Segments in the Trunk.""" return self.pipe_element.segments @property def length(self): # type: () -> float """Return the total length of the trunk itself in model-units.""" return self.pipe_element.length @property def water_temp_c(self): # type: () -> float """Return the length-weighted average water temperature (deg-C) of all the pipe segments.""" return self.pipe_element.water_temp_c @property def daily_period(self): # type: () -> float """Return the length-weighted average daily period of all the pipe segments.""" return self.pipe_element.daily_period @property def num_fixtures(self): # type: () -> int """Return the number of fixtures connected to the trunk.""" return sum(branch.num_fixtures for branch in self.branches) @property def total_length(self): # type: () -> float """Return the total length (in model-units) of the trunk PLUS all branches and fixture pipes in model-units.""" return self.length + sum(branch.total_length for branch in self.branches) @property def total_home_run_fixture_length(self): # type: () -> float """Return the total length (in model-units) of all fixture pipes as measured from end to end. NOTE: This method will include the trunk's and branch's length for EACH of the fixture pipes. The result will be a total hot-water transport length as if all the pipes were 'home-run' style. This value is used for the PHPP calculations and is not a true representation of the piping in the model. """ return sum(self.length + branch.total_home_run_fixture_length for branch in self.branches)
[docs] def add_branch(self, _branch): # type: (PhHvacPipeBranch) -> None """Add a new HBPH PhPipeBranch to the Trunk.""" self.branches.append(_branch)
def __copy__(self): # type: () -> PhHvacPipeTrunk new_obj = PhHvacPipeTrunk() new_obj.identifier = self.identifier new_obj.display_name = self.display_name new_obj.user_data = self.user_data new_obj.pipe_element = self.pipe_element.duplicate() new_obj.multiplier = self.multiplier new_obj.demand_recirculation = self.demand_recirculation for branch in self.branches: new_obj.add_branch(branch.duplicate()) return new_obj
[docs] def duplicate(self): # type: () -> PhHvacPipeTrunk return self.__copy__()
[docs] def to_dict(self, _include_properties=False): # type: (bool) -> Dict[str, Union[str, Dict]] d = super(PhHvacPipeTrunk, self).to_dict() d["pipe_element"] = self.pipe_element.to_dict(_include_properties) d["multiplier"] = self.multiplier d["demand_recirculation"] = self.demand_recirculation d["branches"] = {} for branch in self.branches: d["branches"][branch.identifier] = branch.to_dict(_include_properties) if _include_properties: d["length"] = self.length d["water_temp_c"] = self.water_temp_c d["daily_period"] = self.daily_period d["num_fixtures"] = self.num_fixtures d["total_length"] = self.total_length d["total_home_run_fixture_length"] = self.total_home_run_fixture_length return d
[docs] @classmethod def from_dict(cls, _input_dict): # type: (Dict) -> PhHvacPipeTrunk new_obj = cls() new_obj.identifier = _input_dict["identifier"] new_obj.display_name = _input_dict["display_name"] new_obj.user_data = _input_dict["user_data"] new_obj.pipe_element = PhHvacPipeElement.from_dict(_input_dict["pipe_element"]) new_obj.multiplier = _input_dict["multiplier"] new_obj.demand_recirculation = _input_dict.get("demand_recirculation", False) for branch_dict in _input_dict["branches"].values(): new_obj.add_branch(PhHvacPipeBranch.from_dict(branch_dict)) return new_obj
def __str__(self): # type: () -> str return "{}: (display_name={}, identifier={}, demand_recirculation={}, multiplier={}) [{} segments, len={:.1f}, {} branches connected]".format( self.__class__.__name__, self.display_name, self.identifier_short, self.demand_recirculation, self.multiplier, len(self.segments), float(self.length), len(self.branches), ) def __repr__(self): # type: () -> str return str(self)
[docs] def ToString(self): # type: () -> str return self.__repr__()
[docs] def move(self, moving_vec3D): # type: (Vector3D) -> PhHvacPipeTrunk """Move the pipe's elements along a vector. Args: moving_vec3D: A Vector3D with the direction and distance to move the ray. Returns: A new PhHvacPipeTrunk with the moved elements. """ new_trunk = self.duplicate() new_trunk.branches = [branch.move(moving_vec3D) for branch in self.branches] new_trunk.pipe_element = self.pipe_element.move(moving_vec3D) return new_trunk
[docs] def rotate(self, axis_3D, angle_degrees, origin_pt3D): # type: (Vector3D, float, Point3D) -> PhHvacPipeTrunk """Rotate the pipe's elements by a certain angle_degrees around an axis_3D and origin_pt3D. Right hand rule applies: If axis_3D has a positive orientation, rotation will be clockwise. If axis_3D has a negative orientation, rotation will be counterclockwise. Args: axis_3D: A Vector3D axis_3D representing the axis_3D of rotation. angle_degrees: An angle_degrees for rotation in degrees. origin_pt3D: A Point3D for the origin_pt3D around which the object will be rotated. Returns: A new PhHvacPipeTrunk with the rotated elements. """ new_trunk = self.duplicate() new_trunk.branches = [branch.rotate(axis_3D, angle_degrees, origin_pt3D) for branch in self.branches] new_trunk.pipe_element = self.pipe_element.rotate(axis_3D, angle_degrees, origin_pt3D) return new_trunk
[docs] def rotate_xy(self, angle_degrees, origin_pt3D): # type: (float, Point3D) -> PhHvacPipeTrunk """Rotate the pipe's elements counterclockwise in the XY plane by a certain angle_degrees. Args: angle_degrees: An angle_degrees in degrees. origin_pt3D: A Point3D for the origin_pt3D around which the object will be rotated. Returns: A new PhHvacPipeTrunk with the rotated elements. """ new_trunk = self.duplicate() new_trunk.branches = [branch.rotate_xy(angle_degrees, origin_pt3D) for branch in self.branches] new_trunk.pipe_element = self.pipe_element.rotate_xy(angle_degrees, origin_pt3D) return new_trunk
[docs] def reflect(self, normal_vec3D, origin_pt3D): # type (Vector3D, Point3D) -> PhHvacPipeTrunk """Reflected the pipe's elements across a plane with the input normal_vec3D vector and origin_pt3D. Args: normal_vec3D: A Vector3D representing the normal_vec3D vector for the plane across which the line segment will be reflected. THIS VECTOR MUST BE NORMALIZED. origin_pt3D: A Point3D representing the origin_pt3D from which to reflect. Returns: A new PhHvacPipeTrunk with the reflected elements. """ new_trunk = self.duplicate() new_trunk.branches = [branch.reflect(normal_vec3D, origin_pt3D) for branch in self.branches] new_trunk.pipe_element = self.pipe_element.reflect(normal_vec3D, origin_pt3D) return new_trunk
[docs] def scale(self, factor, origin_pt3D=None): # type: (float, Optional[Point3D]) -> PhHvacPipeTrunk """Scale the pipe's elements by a factor from an origin_pt3D point. Args: factor: A number representing how much the line segment should be scaled. origin_pt3D: A Point3D representing the origin_pt3D from which to scale. If None, it will be scaled from the World origin_pt3D (0, 0, 0). Returns: A new PhHvacPipeTrunk with the scaled elements. """ new_trunk = self.duplicate() new_trunk.branches = [branch.scale(factor, origin_pt3D) for branch in self.branches] new_trunk.pipe_element = self.pipe_element.scale(factor, origin_pt3D) return new_trunk