def gen_cull(self, smx_path=None, window_normals=None): """Generate culled sun sources based on window orientation and climate based sky matrix. The reduced sun sources will significantly speed up the matrix generation. Args: smx_path: sky matrix path, usually the output of gendaymtx window_normals: window normals Returns: Sun receiver primitives strings Corresponding modifier strings """ win_norm = [] if smx_path is not None: cmd = f"rmtxop -ff -c .3 .6 .1 -t {smx_path} " cmd += "| getinfo - | total -if5186 -t," dtot = [float(i) for i in sp.check_output(cmd, shell=True).split(b',')] else: dtot = [1] * self.runlen out_lines = [] mod_str = [] if window_normals is not None: win_norm = window_normals for i in range(1, self.runlen): dirs = radgeom.Vector(*self.rsrc.dir_calc(i)) _mod = 'sol'+str(i) v = 0 if dtot[i - 1] > 0: for norm in win_norm: if norm * dirs < 0: v = 1 mod_str.append(_mod) break line = f"void light sol{i} 0 0 3 {v} {v} {v} sol{i} " line += f"source sun 0 0 4 {dirs.z:.6g} {dirs.x:.6g} {dirs.z:.6g} 0.533" out_lines.append(line) else: for i in range(1, self.runlen): dirs = radgeom.Vector(*self.rsrc.dir_calc(i)) _mod = 'sol'+str(i) v = 0 if dtot[i - 1] > 0: v = 1 mod_str.append(_mod) line = f"void light sol{i} 0 0 3 {v} {v} {v} sol{i} " line += f"source sun 0 0 4 {dirs.z:.6g} {dirs.x:.6g} {dirs.z:.6g} 0.533" out_lines.append(line) logger.debug(out_lines) logger.debug(mod_str) return LSEP.join(out_lines), LSEP.join(mod_str)
def remove_wea_zero_entry(data, metadata, window_normal=None): """Remove data entries with zero solar luminance. If window normal supplied, eliminate entries not seen by window. Solar luminance determined using Treganza sky model. Window field of view is 176 deg with 2 deg tolerance. """ check_window_normal = True if window_normal is not None else False new_dataline = [] zero_dni = lambda row: row.dni!=0 data = filter(zero_dni, data) for row in data: cmd = ['gendaylit', str(row.month), str(row.day), str(row.hours), '-a', str(metadata.latitude), '-o', str(metadata.longitude), '-m', str(metadata.timezone), '-W', str(row.dni), str(row.dhi)] process = sp.run(cmd, stderr=sp.PIPE, stdout=sp.PIPE) primitives = radutil.parse_primitive( process.stdout.decode().splitlines()) light = 0 if process.stderr == b'': light = float(primitives[0].real_arg.split()[2]) dirs = radgeom.Vector(*list(map(float, primitives[1].real_arg.split()[1:4]))) if light > 0: if check_window_normal: for normal in window_normal: if normal * dirs < -0.035: # 2deg tolerance new_dataline.append(row) break else: new_dataline.append(row) return new_dataline
def up_vector(primitives: list) -> radgeom.Vector: """Define the up vector given primitives. Args: primitives: list of dictionary (primitives) Returns: returns a str as x,y,z """ xaxis = radgeom.Vector(1, 0, 0) yaxis = radgeom.Vector(0, 1, 0) norm_dir = samp_dir(primitives) if norm_dir not in (xaxis, xaxis.scale(-1)): upvect = norm_dir.cross(xaxis) else: upvect = norm_dir.cross(yaxis) return upvect
def __init__(self, width, depth, height, origin=radgeom.Vector()): self.width = width self.depth = depth self.height = height self.origin = origin flr_pt2 = origin + radgeom.Vector(width, 0, 0) flr_pt3 = flr_pt2 + radgeom.Vector(0, depth, 0) self.floor = radgeom.Polygon.rectangle3pts(origin, flr_pt2, flr_pt3) extrusion = self.floor.extrude(radgeom.Vector(0, 0, height)) self.clng = extrusion[1] self.wall_south = Surface(extrusion[2], 'wall.south') self.wall_east = Surface(extrusion[3], 'wall.east') self.wall_north = Surface(extrusion[4], 'wall.north') self.wall_west = Surface(extrusion[5], 'wall.west') self.surfaces = [ self.clng, self.floor, self.wall_west, self.wall_north, self.wall_east, self.wall_south ]
def samp_dir(primlist: list) -> radgeom.Vector: """Calculate the primitives' average sampling direction weighted by area.""" primlist = [ p for p in primlist if p.ptype == 'polygon' or p.ptype == 'ring' ] normal_area = radgeom.Vector() for prim in primlist: polygon = parse_polygon(prim.real_arg) normal_area += polygon.normal().scale(polygon.area()) sdir = normal_area.scale(1.0 / len(primlist)) sdir = sdir.normalize() return sdir
def parse_polygon(real_arg: str) -> radgeom.Polygon: """Parse real arguments to polygon. Args: primitive: a dictionary object containing a primitive Returns: modified primitive """ real_args = real_arg.split() coords = [float(i) for i in real_args[1:]] arg_cnt = int(real_args[0]) vertices = [radgeom.Vector(*coords[i:i + 3]) for i in range(0, arg_cnt, 3)] return radgeom.Polygon(vertices)
def assemble_model(config: util.MradConfig) -> Model: """Assemble all the pieces together.""" material_primitives: List[radutil.Primitive] material_primitives = sum( [radutil.unpack_primitives(path) for path in config.material_paths], []) window_groups, _window_normals = get_window_group(config) window_normals = [ item for idx, item in enumerate(_window_normals) if item not in _window_normals[:idx] ] sender_grid = get_sender_grid(config) sender_view, view_dicts = get_sender_view(config) rcvr_sky = radmtx.Receiver.as_sky(basis=config.smx_basis) # Sun CFS _cfs_path = [] for wname, path in config.sun_cfs.items(): ident = util.basename(path) if path.endswith('.xml'): window_primitives = window_groups[wname] upvec = radgeom.Vector(0, 0, 1) bsdf_prim = radutil.bsdf_prim('void', ident, path, upvec, pe=True) if bsdf_prim not in material_primitives: material_primitives.append(bsdf_prim) _tmp_cfs_path = f'tmpcfs{wname}.rad' with open(_tmp_cfs_path, 'w') as wtr: for primitive in window_primitives: new_primitive = radutil.Primitive(ident, primitive.ptype, primitive.identifier, primitive.str_arg, primitive.real_arg) wtr.write(str(new_primitive) + '\n') _cfs_path.append(_tmp_cfs_path) elif path.endswith('.rad'): _cfs_path.append(path) black_mat = radutil.Primitive('void', 'plastic', 'black', '0', '5 0 0 0 0 0') glow_mat = radutil.Primitive('void', 'glow', 'glowing', '0', '4 1 1 1 0') if black_mat not in material_primitives: material_primitives.append(black_mat) if glow_mat not in material_primitives: material_primitives.append(glow_mat) material_path = os.path.join(config.objdir, "all_material.mat") with open(material_path, 'w') as wtr: [wtr.write(str(primitive) + '\n') for primitive in material_primitives] _blackenvpath = os.path.join(config.objdir, 'blackened.rad') with open(_blackenvpath, 'w') as wtr: for path in config.scene_paths: wtr.write(f'\n!xform -m black {path}') return Model(material_path, window_groups, window_normals, sender_grid, sender_view, view_dicts, rcvr_sky, _cfs_path, _blackenvpath)
def make_window(self, dist_left, dist_bot, width, height, wwr=None): if wwr is not None: assert type(wwr) == float, 'WWR must be float' win_polygon = self.polygon.scale(radgeom.Vector(*[wwr] * 3), self.centroid) else: win_pt1 = self.vertices[0]\ + self.vect1.scale(dist_bot)\ + self.vect2.scale(dist_left) win_pt2 = win_pt1 + self.vect1.scale(height) win_pt3 = win_pt1 + self.vect2.scale(width) win_polygon = radgeom.Polygon.rectangle3pts( win_pt3, win_pt1, win_pt2) return win_polygon
def gen_grid(polygon: radgeom.Polygon, height: float, spacing: float) -> list: """Generate a grid of points for orthogonal planar surfaces. Args: polygon: a polygon object height: points' distance from the surface in its normal direction spacing: distance between the grid points Returns: List of the points as list """ vertices = polygon.vertices plane_height = sum([i.z for i in vertices]) / len(vertices) imin, imax, jmin, jmax, _, _ = polygon.extreme() xlen_spc = ((imax - imin) / spacing) ylen_spc = ((jmax - jmin) / spacing) xstart = ((xlen_spc - int(xlen_spc) + 1)) * spacing / 2 ystart = ((ylen_spc - int(ylen_spc) + 1)) * spacing / 2 x0 = [x + xstart for x in util.frange_inc(imin, imax, spacing)] y0 = [x + ystart for x in util.frange_inc(jmin, jmax, spacing)] grid_dir = polygon.normal().reverse() grid_hgt = radgeom.Vector(0, 0, plane_height) + grid_dir.scale(height) raw_pts = [ radgeom.Vector(round(i, 3), round(j, 3), round(grid_hgt.z, 3)) for i in x0 for j in y0 ] scale_factor = 1 - 0.3 / (imax - imin) # scale boundary down .3 meter _polygon = polygon.scale(radgeom.Vector(scale_factor, scale_factor, 0), polygon.centroid()) _vertices = _polygon.vertices if polygon.normal() == radgeom.Vector(0, 0, 1): pt_incls = pt_inclusion(_vertices) else: pt_incls = pt_inclusion(_vertices[::-1]) _grid = [p for p in raw_pts if pt_incls.test_inside(p) > 0] grid = [p.to_list() + grid_dir.to_list() for p in _grid] return grid
def get_port(win_polygon, win_norm, ncs_prims): """ Generate ports polygons that encapsulate the window and NCP geometries. window and NCP geometries are rotated around +Z axis until the area projected onto XY plane is the smallest, thus the systems are facing orthogonal direction. A boundary box is then generated with a slight outward offset. This boundary box is then rotated back the same amount to encapsulate the original window and NCP geomteries. """ ncs_polygon = [radutil.parse_polygon(p.real_arg) for p in ncs_prims if p.ptype=='polygon'] if 1 in [int(abs(i)) for i in win_norm.to_list()]: ncs_polygon.append(win_polygon) bbox = rg.getbbox(ncs_polygon, offset=0.00) bbox.remove([b for b in bbox if b.normal().reverse()==win_norm][0]) return [b.move(win_norm.scale(-.1)) for b in bbox] xax = [1, 0, 0] _xax = [-1, 0, 0] yax = [0, 1, 0] _yax = [0, -1, 0] zaxis = rg.Vector(0, 0, 1) rm_pg = [xax, _yax, _xax, yax] area_list = [] win_normals = [] # Find axiel aligned rotation angle bboxes = [] for deg in range(90): rad = math.radians(deg) win_polygon_r = win_polygon.rotate(zaxis, rad) win_normals.append(win_polygon_r.normal()) ncs_polygon_r = [p.rotate(zaxis, rad) for p in ncs_polygon] ncs_polygon_r.append(win_polygon_r) _bbox = rg.getbbox(ncs_polygon_r, offset=0.001) bboxes.append(_bbox) area_list.append(_bbox[0].area()) # Rotate to position deg = area_list.index(min(area_list)) rrad = math.radians(deg) bbox = bboxes[deg] _win_normal = [round(i, 1) for i in win_normals[deg].to_list()] del bbox[rm_pg.index(_win_normal) + 2] rotate_back = [pg.rotate(zaxis, rrad * -1) for pg in bbox] return rotate_back
def genport(*, wpolys, npolys, depth, scale): """Generate the appropriate aperture for F matrix generation.""" if len(wpolys) > 1: wpoly = merge_windows(wpolys) else: wpoly = wpolys[0] wpoly = radutil.parse_polygon(wpoly.real_arg) wnorm = wpoly.normal() if npolys is not None: all_ports = get_port(wpoly, wnorm, npolys) elif depth is None: raise ValueError('Need to specify (depth and scale) or ncp path') else: # user direct input extrude_vector = wpoly.normal().reverse().scale(depth) scale_vector = rg.Vector(scale, scale, scale) scaled_window = wpoly.scale(scale_vector, wpoly.centroid()) all_ports = scaled_window.extrude(extrude_vector)[1:] port_prims = [] for pi in range(len(all_ports)): new_prim = radutil.polygon2prim(all_ports[pi], 'port', 'portf%s' % str(pi + 1)) logger.debug(str(new_prim)) port_prims.append(new_prim) return port_prims
def analyze_window(window_prim, movedown): """Parse window primitive and prepare for genBSDF.""" window_polygon = window_prim['polygon'] vertices = window_polygon.vertices assert len(vertices) == 4, "4-sided polygon only" window_center = window_polygon.centroid() window_normal = window_polygon.normal() window_normal dim1 = vertices[0] - vertices[1] dim2 = vertices[1] - vertices[2] if dim1.normalize() in (radgeom.Vector(0, 0, 1), radgeom.Vector(0, 0, -1)): height = dim1.length width = dim2.length else: height = dim2.length width = dim1.length _south = radgeom.Vector(0, -1, 0) angle2negY = window_normal.angle_from(_south) rotate_window = window_center.rotate_3d(radgeom.Vector( 0, 0, 1), angle2negY).rotate_3d(radgeom.Vector(1, 0, 0), math.pi / 2) translate = radgeom.Vector(0, 0, -movedown) - rotate_window return height, width, angle2negY, translate
def main(): """Generate a BSDF for macroscopic systems.""" parser = argparse.ArgumentParser() parser.add_argument('-blinds', nargs=3, type=float, metavar=('depth', 'spacing', 'angle')) parser.add_argument('-curve', default='') parser.add_argument('-custom', nargs=3) parser.add_argument('-section', nargs=2) parser.add_argument('-gap', type=float, default=0.0) parser.add_argument('-ext', action='store_true') parser.add_argument('-m', nargs=3, type=float, required=True, metavar=('refl', 'spec', 'rough')) parser.add_argument('-g', type=float) parser.add_argument('-window') parser.add_argument('-env') parser.add_argument('-o', default='default_blinds.rad') args = parser.parse_args() mat_prim = radutil.neutral_plastic_prim('void', 'blindmaterial', *args.m) mat_prim_str = radutil.put_primitive(mat_prim) if args.custom: # Custom periodic geometry pass elif args.section: # Custom section drawing pass elif args.window: primitves = radutil.unpack_primitives(args.window) env_primitives = radutil.unpack_primitives(args.env) env_identifier = [prim['identifier'] for prim in env_primitives] windows = [ p for p in primitves if p['identifier'].startswith('window') ] window = windows[0] # only take the first window primitive depth, spacing, angle = args.blinds movedown = depth * math.cos(math.radians(float(angle))) window_movedown = 0 if args.ext else movedown + args.gap # window_polygon = radutil.parse_polygon(window.real_args) height, width, angle2negY, translate = radutil.analyze_window( window, window_movedown) xform_cmd = f'!xform -rz {math.degrees(angle2negY)} -rx -90 -t {translate.x} {translate.y} {translate.z} {args.env}\n' xform_cmd += f'!xform -rz {math.degrees(angle2negY)} -rx -90 -t {translate.x} {translate.y} {translate.z} {args.window}\n' print(xform_cmd) slat_cmd = radutil.gen_blinds(depth, width, height, spacing, angle, args.curve, movedown) print(slat_cmd) lower_bound = max(movedown, window_movedown) with open("tmp_blinds.rad", 'w') as wtr: wtr.write(mat_prim_str) wtr.write(xform_cmd) wtr.write(slat_cmd) cmd = f"genBSDF -n 4 -f +b -c 500 +geom meter -dim {-width/2} {width/2} {-height/2} {height/2} -{lower_bound} 0 tmp_blinds.rad" print(cmd) _stdout = sp.run(cmd.split(), check=True, stdout=sp.PIPE).stdout.decode() xml_name = "{}_blinds_{}_{}_{}.xml".format(window['identifier'], depth, spacing, angle) with open(xml_name, 'w') as wtr: wtr.write(_stdout) #move back pkgbsdf_cmd = ["pkgBSDF", "-s", xml_name] xform_back_cmd = [ "xform", "-t", str(-translate.x), str(-translate.y), str(-translate.z) ] xform_back_cmd += ["-rx", "90", "-rz", str(-math.degrees(angle2negY))] ps1 = sp.Popen(pkgbsdf_cmd, stdout=sp.PIPE) ps2 = sp.Popen(xform_back_cmd, stdin=ps1.stdout, stdout=sp.PIPE) ps1.stdout.close() _stdout, _ = ps2.communicate() result_primitives = radutil.parse_primitive( _stdout.decode().splitlines()) result_primitives = [ prim for prim in result_primitives if prim['identifier'] not in env_identifier ] with open(args.o, 'w') as wtr: [ wtr.write(radutil.put_primitive(prim)) for prim in result_primitives ] else: width = 10 # default blinds width height = 0.096 # default blinds height depth, spacing, angle = args.blinds if args.ext: glass_z = 0 movedown = args.gap + depth * math.cos(math.radians(angle)) else: glass_z = args.gap + depth * math.cos(math.radians(angle)) movedown = depth * math.cos(math.radians(angle)) lower_bound = min(-glass_z, -movedown) genblinds_cmd = radutil.gen_blinds(depth, width, height, spacing, angle, args.curve, movedown) pt1 = radgeom.Vector(-width / 2, height / 2, -glass_z) pt2 = radgeom.Vector(-width / 2, -height / 2, -glass_z) pt3 = radgeom.Vector(width / 2, -height / 2, -glass_z) tmis = util.tmit2tmis(.38) glass_prim = radutil.glass_prim('void', 'glass1', tmis, tmis, tmis) glazing_polygon = radgeom.Polygon.rectangle3pts(pt1, pt2, pt3) glazing_prim_str = radutil.put_primitive(glass_prim) glazing_prim_str += radutil.put_primitive( radutil.polygon2prim(glazing_polygon, 'glass1', 'window')) with open("tmp_blinds.rad", 'w') as wtr: wtr.write(mat_prim_str) wtr.write(glazing_prim_str) wtr.write(genblinds_cmd) cmd = f"genBSDF -n 4 +f +b -c 500 -geom meter -dim -0.025 0.025 -0.012 0.012 {lower_bound} 0 tmp_blinds.rad" _stdout = sp.run(cmd.split(), check=True, stdout=sp.PIPE).stdout.decode() xml_name = "blinds_{}_{}_{}.xml".format(depth, spacing, angle) with open(xml_name, 'w') as wtr: wtr.write(_stdout) os.remove("tmp_blinds.rad")
def get_zones(self, epjs): """Looping through zones.""" opaque_srfs = epjs['BuildingSurface:Detailed'] construction = self.get_construction(epjs) ext_zones = {} for zone_name in epjs['Zone']: zone_srfs = { k: v for k, v in opaque_srfs.items() if v['zone_name'] == zone_name } if self.check_ext_window(zone_srfs): zone = { 'Wall': {}, 'Floor': {}, 'Ceiling': {}, 'Window': {}, 'Roof': {} } for sn in zone_srfs: surface_name = sn.replace(" ", "_") surface = zone_srfs[sn] srf_type = surface['surface_type'] cnstrct = construction[surface['construction_name']] inner_layer, outer_layer, cthick = self.parse_cnstrct( cnstrct) if self.mat_prims[inner_layer].primitive.identifier == '': inner_layer = 'void' else: inner_layer = inner_layer.replace(' ', '_') srf_polygon = rg.Polygon( [rg.Vector(*v.values()) for v in surface['vertices']]) srf_windows = [] if sn in self.fene_hosts: zone_fsrfs = { n: val for n, val in self.fene_srfs.items() if val['building_surface_name'] == sn } for fn in zone_fsrfs: fsurface = zone_fsrfs[fn] nfvert = int(fsurface['number_of_vertices']) fverts = [[ fsurface[k] for k in fsurface if k.startswith(f'vertex_{n+1}') ] for n in range(nfvert)] wndw_polygon = rg.Polygon( [rg.Vector(*vert) for vert in fverts]) srf_windows.append(wndw_polygon) window_construct = construction[ fsurface['construction_name']] window_material = self.parse_wndw_cnstrct( window_construct) window_name = fn.replace(" ", "_") zone['Window'][window_name] = ru.polygon2prim( wndw_polygon, window_material, window_name) srf_polygon -= wndw_polygon facade = self.thicken(srf_polygon, srf_windows, cthick) zone[srf_type][ f'ext_{surface_name}'] = ru.polygon2prim( facade[1], outer_layer, f"ext_{surface_name}") for idx in range(2, len(facade)): zone[srf_type][ f'sill_{surface_name}.{idx}'] = ru.polygon2prim( facade[idx], inner_layer, f"sill_{surface_name}.{idx}") zone[srf_type][surface_name] = ru.polygon2prim( srf_polygon, inner_layer, surface_name) ext_zones[zone_name] = self.check_srf_normal(zone) return ext_zones