def test_assign_values(): geo = Polygon('test_polygon', vertices) geo.identifier = 'new_polygon' geo.vertices = tuple(tuple(pt) for pt in alt_vertices) assert geo.to_radiance(minimal=True) == \ 'void polygon new_polygon 0 0 12 6.0 -10.0 20.0 6.0 -5.0 20.0 ' \ '6.0 -5.0 10.0 6.0 -10.0 10.0'
def test_polygon(): geo = Polygon('test_polygon', vertices) assert geo.identifier == 'test_polygon' assert geo.vertices == tuple(tuple(pt) for pt in vertices) assert geo.to_radiance( minimal=True) == \ 'void polygon test_polygon 0 0 12 3.0 -7.0 15.0 3.0 -1.0 15.0 ' \ '3.0 -1.0 0.0 3.0 -7.0 0.0'
def _add_geo_and_modifier(hb_obj): """Add a honeybee object to the geometry and modifier strings.""" mod_name = '%s_mod' % hb_obj.identifier mod_names.append(mod_name) white_plastic.identifier = mod_name rad_poly = Polygon(hb_obj.identifier, hb_obj.vertices, white_plastic) geo_strs.append(rad_poly.to_radiance(False, False, False)) mod_strs.append(white_plastic.to_radiance(True, False, False))
def test_from_and_to_dict(): modifier = Plastic('polygon_material') polygon = Polygon('default_polygon', vertices, modifier=modifier) polygon_dict = polygon.to_dict() # check values in dictionary assert polygon_dict['identifier'] == 'default_polygon' assert polygon_dict['modifier'] == modifier.to_dict() assert polygon_dict['vertices'] == tuple(tuple(pt) for pt in vertices) polygon_from_dict = Polygon.from_dict(polygon_dict) assert polygon_from_dict.to_radiance() == polygon.to_radiance()
def test_from_string(): geometry_str = metal_polygon geo = Polygon.from_string(geometry_str) assert geo.identifier == 'polygon_one' assert geo.vertices == tuple(tuple(pt) for pt in vertices) assert ' '.join(geo.to_radiance(minimal=True).split()) == \ ' '.join(geometry_str.split())
def formQuad(triangleA : Polygon, triangleB : Polygon) -> Polygon: """ Forming a quad out of two complementary triangles """ vertices = [[], [], [], []] vertices[0] = triangleA.vertices[0] vertices[1] = triangleA.vertices[1] vertices[3] = triangleA.vertices[2] # First we search for the unique vertex in triangle B for i in range(len(triangleB.vertices)): isDuplicate = False vertexB = triangleB.vertices[i] for j in range(len(triangleA.vertices)): vertexA = triangleA.vertices[j] if listsSame(vertexB, vertexA): isDuplicate = True break # Once we find the vertex unique to the second triangle, we assign it and break out if not isDuplicate: vertices[2] = vertexB break # Next we check if the vertex ordering needs to be switched # This is necessary when two or more elements switch from one vertex to the next for i in range(3): differentElements = 0 for j in range(3): if vertices[i][j] < vertices[i+1][j] - SIGMA or vertices[i][j] > vertices[i+1][j] + SIGMA: differentElements += 1 # Swap if differentElements > 1: # Note that this could throw an index error for the first and last vertices, but during my testing this has been a sufficient workaround tmp = vertices[i-1] vertices[i-1] = vertices[i] vertices[i] = tmp break quad = Polygon(triangleA.identifier, vertices) quad.modifier = triangleA.modifier return quad
def create_view_factor_modifiers(model_file, exclude_sky, exclude_ground, individual_shades, triangulate, folder, name): """Translate a Model into an Octree and corresponding modifier list for view factors. \b Args: model_file: Full path to a Model JSON file (HBJSON) or a Model pkl (HBpkl) file. """ try: # create the directory if it's not there if not os.path.isdir(folder): preparedir(folder) # load the model and ensure the properties align with the energy model model = Model.from_file(model_file) original_units = None if model.units != 'Meters': original_units = model.units model.convert_to_units('Meters') for room in model.rooms: room.remove_colinear_vertices_envelope(tolerance=0.01, delete_degenerate=True) if original_units is not None: model.convert_to_units(original_units) # triangulate the sub-faces if requested if triangulate: apertures, parents_to_edit = model.triangulated_apertures() for tri_aps, edit_infos in zip(apertures, parents_to_edit): if len(edit_infos) == 3: for room in model._rooms: if room.identifier == edit_infos[2]: break for face in room._faces: if face.identifier == edit_infos[1]: break for i, ap in enumerate(face._apertures): if ap.identifier == edit_infos[0]: break face._apertures.pop(i) # remove the aperture to replace face._apertures.extend(tri_aps) doors, parents_to_edit = model.triangulated_doors() for tri_drs, edit_infos in zip(doors, parents_to_edit): if len(edit_infos) == 3: for room in model._rooms: if room.identifier == edit_infos[2]: break for face in room._faces: if face.identifier == edit_infos[1]: break for i, dr in enumerate(face._doors): if dr.identifier == edit_infos[0]: break face._doors.pop(i) # remove the doors to replace face._doors.extend(tri_drs) # set values to be used throughout the modifier assignment offset = model.tolerance * -1 white_plastic = Plastic('white_plastic', 1, 1, 1) geo_strs, mod_strs, mod_names = [], [], [] def _add_geo_and_modifier(hb_obj): """Add a honeybee object to the geometry and modifier strings.""" mod_name = '%s_mod' % hb_obj.identifier mod_names.append(mod_name) white_plastic.identifier = mod_name rad_poly = Polygon(hb_obj.identifier, hb_obj.vertices, white_plastic) geo_strs.append(rad_poly.to_radiance(False, False, False)) mod_strs.append(white_plastic.to_radiance(True, False, False)) # loop through all geometry in the model and get radiance strings for room in model.rooms: for face in room.faces: if not isinstance(face.type, AirBoundary): if isinstance(face.boundary_condition, Surface): face.move(face.normal * offset) _add_geo_and_modifier(face) for ap in face.apertures: _add_geo_and_modifier(ap) for dr in face.doors: _add_geo_and_modifier(dr) all_shades = model.shades + model._orphaned_faces + \ model._orphaned_apertures + model._orphaned_doors if individual_shades: for shade in all_shades: _add_geo_and_modifier(shade) else: white_plastic.identifier = 'shade_plastic_mod' mod_names.append(white_plastic.identifier) mod_strs.append(white_plastic.to_radiance(True, False, False)) for shade in all_shades: rad_poly = Polygon(shade.identifier, shade.vertices, white_plastic) geo_strs.append(rad_poly.to_radiance(False, False, False)) # add the ground and sky domes if requested if not exclude_sky: mod_names.append('sky_glow_mod') mod_strs.append('void glow sky_glow_mod 0 0 4 1 1 1 0') geo_strs.append('sky_glow_mod source sky_dome 0 0 4 0 0 1 180') if not exclude_ground: mod_names.append('ground_glow_mod') mod_strs.append('void glow ground_glow_mod 0 0 4 1 1 1 0') geo_strs.append( 'ground_glow_mod source ground_dome 0 0 4 0 0 -1 180') # write the radiance strings to the output folder geo_file = os.path.join(folder, '{}.rad'.format(name)) mod_file = os.path.join(folder, '{}.mod'.format(name)) oct_file = os.path.join(folder, '{}.oct'.format(name)) with open(geo_file, 'w') as gf: gf.write('\n\n'.join(mod_strs + geo_strs)) with open(mod_file, 'w') as mf: mf.write('\n'.join(mod_names)) # use the radiance files to create an octree cmd = Oconv(output=oct_file, inputs=[geo_file]) cmd.options.f = True run_command(cmd.to_radiance(), env=folders.env) except Exception as e: _logger.exception('Model translation failed.\n{}'.format(e)) sys.exit(1) else: sys.exit(0)
def main(): argc = len(sys.argv) if argc < 2: print("Error: .rad file not specified, usage: python3 genParallelViews.py <file.rad>") return -1 filePath = sys.argv[1] if not filePath.endswith(".rad"): print("Error: .rad file not specified, usage: python3 genParallelViews.py <file.rad>") return -1 print("Scene up direction: [{0}, {1}, {2}]".format(SCENE_UP[0], SCENE_UP[1], SCENE_UP[2])) # Read in the RAD file stringObjects = reader.parse_from_file(filePath) polygons = [] materials = [] currentModifier = None for stringObject in stringObjects: if not "polygon" in stringObject: validMaterial = False for material in VALID_MATERIALS: if material in stringObject: validMaterial = True break # This is a bit hacky right now. We get an exception if we try and parse a non-material or non-polygon if not validMaterial: print("Error: Can't parse '{0}' from RAD file. If this is a material try manually adding it to the script, else ignore.".format(stringObject)) continue primitiveDict = reader.string_to_dict(stringObject) if primitiveDict["type"] == "polygon": primitiveDict["modifier"] = None polygon = Polygon.from_primitive_dict(primitiveDict) polygon.modifier = currentModifier polygons.append(polygon) elif primitiveDict["type"] == "plastic": plastic = Plastic.from_primitive_dict(primitiveDict) currentModifier = plastic materials.append(plastic) elif primitiveDict["type"] == "metal": metal = Metal.from_primitive_dict(primitiveDict) currentModifier = metal materials.append(metal) elif primitiveDict["type"] == "glass": glass = Glass.from_primitive_dict(primitiveDict) currentModifier = glass materials.append(glass) else: print("Error: Unable to assign material from '{0}'.".format(stringObject)) # Loop through all the polygons read in from the RAD file and classify them as triangles or quads triangles = [] quads = [] for polygon in polygons: if len(polygon.vertices) == 3: triangles.append(polygon) elif len(polygon.vertices) == 4: quads.append(polygon) # Loop through all the triangles read in from the RAD file and attempt to form quads from them trianglesMissed = [] i = 0 while True: if i >= len(triangles) - 1: break triangleA = triangles[i] triangleB = triangles[i+1] if formsQuad(triangleA, triangleB): quad = formQuad(triangleA, triangleB) quads.append(quad) i += 2 else: trianglesMissed.append(triangleA) i += 1 if len(trianglesMissed) != 0: print("The following triangles from the RAD file couldn't be formed into quads: ", end="") for triangle in trianglesMissed: print("{0}".format(triangle.identifier), end=" ") print() # Loop through all the quads and generate a Radiance parallel projection view for it viewDict = {} for quad in quads: # type 'l' defines this view as a parallel projection view = View(quad.identifier, type='l') # Get the dimensions of the quad. # One of these should be approximately 0.0 because a quad is two dimensional dimensions = [0, 0, 0] for i in range(3): dimensions[i] = getDimensionLength(quad, i) # Set the view's horizontal and vertical size based on the dimensions of the quad # The projection will contain the entire quad horizontalSet = False verticalSet = False for i in range(3): if dimensions[i] > SIGMA: if not horizontalSet: view.h_size = dimensions[i] horizontalSet = True else: view.v_size = dimensions[i] verticalSet = True break if not horizontalSet or not verticalSet: print("Error: " + view.identifier + " vh and/or vv not set") continue # Set view direction normal = getQuadNormal(quad) if len(normal) == 0: print("Error: " + view.identifier + " vn not set") continue direction = (-normal[0], -normal[1], -normal[2]) view.direction = direction # Set view position position = getViewPosition(quad, dimensions, normal) view.position = position # Set view up view.up_vector = SCENE_UP if listsSame(SCENE_UP, direction) or listsSame(SCENE_UP, normal): if not listsSame(SCENE_UP, [0.0, 0.0, 1.0]): view.up_vector = [0.0, 0.0, 1.0] elif not listsSame(SCENE_UP, [0.0, 1.0, 0.0]): view.up_vector = [0.0, 1.0, 0.0] else: view.up_vector = [1.0, 0.0, 0.0] viewDict[quad.identifier] = view print("\n-----Radiance Parallel Views-----") for view in viewDict.values(): print("view=" + view.identifier + " " + view.to_radiance()) print("----------\n\nTotal view count: {0}, Total quad count: {1}".format(len(viewDict.values()), len(quads))) writeOBJFile(BASE_FILE_NAME, quads, viewDict) writeMTLFile(BASE_FILE_NAME, quads) return 0