""" The outpost's site: what stands on the ice round the station. See build/outpost.py for the whole. own geometry (ANCH_Site_Works): the landing pad on the landing ground, walkways with marker posts, a tank farm (three tanks on saddles, a pump house, the feed line to the cargo hall), the comms mast, eight floodlight masts, mooring bollards along the berth, cradles for the cargo pods the Sci-Fi Lite pack (kit pieces): cargo pods in the yard, fences on the decks and round the yard, the gate on the way from the pad, terminals at the two doors with a deck polyhaven.com (CC0, build/ph.py): drums, crates, jerrycans, gas bottles, a generator, switch cabinets, road barriers, a tool cart, a hand truck, lifebuoys, two sea buoys; the sky that lights the scene Everything is set out by hand, by its use: fuel beside the cargo hall's north wall, the yard on the way out of the maintenance door, the mast clear of the relay dish's line of sight, lights where people and vehicles go. """ import json import math import bpy from mathutils import Matrix, Vector import own_materials from hs import Frame, clipped, cylinder, loft, ngon, prism, rect, ring, sweep, tube from outpost_more import LITE, aerial, ccw, louvre, roof_unit ICE = -0.62 PAD = (0.0, 60.0, 5.6) SEA = -1.90 def rz(deg): return Matrix.Rotation(math.radians(deg), 4, "Z") def at(x, y, z, yaw=0.0, s=1.0): return Matrix.Translation((x, y, z)) @ rz(yaw) @ Matrix.Diagonal((s, s, s, 1.0)) def circle(cx, cy, r, n): return [(cx + r * math.cos(2.0 * math.pi * k / n), cy + r * math.sin(2.0 * math.pi * k / n)) for k in range(n)] # --------------------------------------------------------------------------------------------------------------- def landing_pad(part): """A round concrete pad on the landing ground: orange ring, white inner ring and bars, lights in its kerb.""" cx, cy, r = PAD g = Frame.ground(0.0, 0.0, ICE - 0.25) prism(part, g, ngon(cx, cy, r, 32), 0.0, 0.41, "slab", cham=0.04, side="concrete", edge="concrete") top = ICE + 0.16 # counter-clockwise round the pad: the outside is to the right sweep(part, [(-0.03, ICE - 0.2), (0.16, ICE - 0.2), (0.16, top + 0.06), (0.10, top + 0.12), (0.03, top + 0.12), (-0.03, top + 0.06)], circle(cx, cy, r, 32), ["concrete", "white", "white", "orange", "white", "white"], closed=True) for rr, w, mat in ((4.25, 0.32, "orange"), (1.55, 0.20, "white")): sweep(part, [(0.0, top - 0.01), (w, top - 0.01), (w, top + 0.012), (0.0, top + 0.012)], circle(cx, cy, rr, 32), [mat] * 4, closed=True) gt = Frame.ground(cx, cy, top) prism(part, gt, ngon(0.0, 0.0, 0.55, 8, math.radians(22.5)), -0.01, 0.014, "orange") for k in range(4): gk = Frame.ground(cx, cy, top, 90.0 * k) prism(part, gk, rect(2.15, 3.85, -0.15, 0.15), -0.01, 0.012, "white") for k in range(12): a = 2.0 * math.pi * (k + 0.5) / 12 x, y = cx + (r + 0.065) * math.cos(a), cy + (r + 0.065) * math.sin(a) prism(part, Frame.ground(x, y, top + 0.11, math.degrees(a)), rect(-0.07, 0.07, -0.10, 0.10), 0.0, 0.06, "cyan", cham=0.015) def walkway(part, pts, w=2.2, lights=True): """A raised way of tread plate with orange edges on a concrete bed, and marker posts along it.""" z = ICE + 0.01 h = w / 2.0 sweep(part, [(-h, -0.20), (h, -0.20), (h, 0.10), (h - 0.05, 0.15), (-h + 0.05, 0.15), (-h, 0.10)], [(x, y, z) for x, y in pts], ["concrete", "orange", "orange", "tread", "orange", "orange"]) if not lights: return for (x0, y0), (x1, y1) in zip(pts[:-1], pts[1:]): d = Vector((x1 - x0, y1 - y0)) length = d.length d /= length n = max(1, int(length // 4.4)) for k in range(n + 1): t = length * k / n for s in (-1, 1): x, y = x0 + d.x * t + d.y * s * (h + 0.22), y0 + d.y * t - d.x * s * (h + 0.22) g = Frame.ground(x, y, ICE - 0.05) prism(part, g, rect(-0.07, 0.07, -0.07, 0.07), 0.0, 0.60, "white", cham=0.02) prism(part, g, rect(-0.05, 0.05, -0.05, 0.05), 0.60, 0.66, "cyan", cham=0.012) def tank(part, x, y, yaw, length=8.0, r=1.35): """A horizontal tank on two concrete saddles, with a walkway and a rail on top.""" zc = ICE + 0.55 + r ax = Vector((math.cos(math.radians(yaw)), math.sin(math.radians(yaw)), 0.0)) c = Vector((x, y, zc)) half = length / 2.0 prof = ((-half, 0.42), (-half + 0.16, 0.80), (-half + 0.45, 1.0), (-half + 0.95, 1.0), (-half + 1.30, 1.0), (half - 1.30, 1.0), (half - 0.95, 1.0), (half - 0.45, 1.0), (half - 0.16, 0.80), (half, 0.42)) rings = [ring(c + ax * a, r * k, 20, ax, ref=(0.0, 0.0, 1.0)) for a, k in prof] mats = ["white", "white", "white", "orange", "white", "orange", "white", "white", "white"] loft(part, rings, "white", smooth=True, mats=mats, cap_mat="darkmetal") side = Vector((-ax.y, ax.x, 0.0)) for a in (-half * 0.55, half * 0.55): p = c + ax * a g = Frame.ground(p.x, p.y, ICE - 0.2, yaw) prism(part, g, clipped(-0.22, 0.22, -r * 0.92, r * 0.92, 0.0, 0.0, 0.0, 0.0), 0.0, 0.2 + 0.55 + r * 0.42, "concrete", cham=0.05) # the walk on top, a rail each side, a hatch g = Frame.ground(c.x, c.y, zc + r - 0.03, yaw) prism(part, g, rect(-half * 0.72, half * 0.72, -0.34, 0.34), 0.0, 0.07, "tread", cham=0.015, side="darkmetal") for s in (-1, 1): pts = [c + ax * a + side * (s * 0.34) + Vector((0, 0, r + 0.02)) for a in (-half * 0.72, half * 0.72)] tube(part, [pts[0] + Vector((0, 0, 0.0)), pts[0] + Vector((0, 0, 0.95)), pts[1] + Vector((0, 0, 0.95)), pts[1]], 0.022, "orange", sides=6, bend=0.12) for k in range(1, 4): q = pts[0] + (pts[1] - pts[0]) * (k / 4.0) cylinder(part, q, q + Vector((0, 0, 0.95)), 0.018, "orange", sides=6) cylinder(part, c + Vector((0, 0, r - 0.05)), c + Vector((0, 0, r + 0.22)), 0.36, "darkmetal", sides=12, cham=0.03) return zc def tank_farm(part): """Three tanks north of the cargo hall, a pump house, and the feed line into the hall's wall.""" x0, x1, y0, y1 = 22.0, 40.5, 13.0, 27.4 g = Frame.ground(0.0, 0.0, ICE - 0.22) prism(part, g, clipped(x0, x1, y0, y1, 0.5, 0.5, 0.5, 0.5), 0.0, 0.34, "slab", cham=0.04, side="concrete", edge="concrete") sweep(part, [(-0.02, ICE - 0.2), (0.20, ICE - 0.2), (0.20, ICE + 0.40), (0.14, ICE + 0.46), (0.04, ICE + 0.46), (-0.02, ICE + 0.40)], ccw([(x0 + 0.3, y0 + 0.3), (x1 - 0.3, y0 + 0.3), (x1 - 0.3, y1 - 0.3), (x0 + 0.3, y1 - 0.3)]), ["concrete", "concrete", "concrete", "orange", "concrete", "concrete"], closed=True) zc = 0.0 for y in (16.0, 20.2, 24.4): zc = tank(part, 32.6, y, 0.0) # pump house, manifold along the tanks' west ends, feed line to the cargo hall roof_unit(part, 24.6, 20.2, ICE + 0.12, 2.0, 2.6, 1.9, fan=True) zm = ICE + 0.95 tube(part, [(27.4, 15.4, zm), (27.4, 25.0, zm)], 0.16, "white", sides=12) for y in (16.0, 20.2, 24.4): tube(part, [(27.4, y, zm), (28.2, y, zm), (28.2, y, zc - 0.3), (28.75, y, zc - 0.3)], 0.11, "orange", sides=8, bend=0.22) cylinder(part, (27.4, y, ICE + 0.1), (27.4, y, zm - 0.14), 0.07, "darkmetal", sides=6) tube(part, [(27.4, 20.2, zm), (25.7, 20.2, zm)], 0.13, "white", sides=10) feed = [(24.6, 18.85, 1.30), (24.6, 9.6, 1.30), (17.2, 9.6, 1.30), (17.2, 6.0, 1.30)] tube(part, feed, 0.15, "white", sides=12, bend=0.5) for (xa, ya, _), (xb, yb, _) in zip(feed[:-1], feed[1:]): n = max(1, int(math.hypot(xb - xa, yb - ya) // 2.6)) for k in range(1, n + 1): t = (k - 0.5) / n px, py = xa + (xb - xa) * t, ya + (yb - ya) * t cylinder(part, (px, py, ICE - 0.05), (px, py, 1.30 - 0.15), 0.05, "darkmetal", sides=6) prism(part, Frame.ground(px, py, ICE - 0.05), rect(-0.16, 0.16, -0.16, 0.16), 0.0, 0.08, "concrete", cham=0.02) cylinder(part, (17.2, 5.92, 1.30), (17.2, 6.12, 1.30), 0.24, "orange", sides=12, cham=0.02) # the collar at the wall def lattice_mast(part, x, y, height=24.0): """The comms mast: four legs tapering to a head with drum aerials, whips and a red lamp, on a slab.""" g = Frame.ground(0.0, 0.0, ICE - 0.2) prism(part, g, clipped(x - 2.1, x + 2.1, y - 2.1, y + 2.1, 0.4, 0.4, 0.4, 0.4), 0.0, 0.42, "slab", cham=0.05, side="concrete", edge="concrete") z0 = ICE + 0.22 bays = 10 def corner(k, level): t = level / float(bays) half = 1.25 + (0.32 - 1.25) * t sx, sy = ((1, 1), (-1, 1), (-1, -1), (1, -1))[k] return Vector((x + sx * half, y + sy * half, z0 + height * 0.92 * t)) for k in range(4): tube(part, [corner(k, 0), corner(k, bays)], 0.065, "white", sides=6) prism(part, Frame.ground(corner(k, 0).x, corner(k, 0).y, z0 - 0.02), rect(-0.2, 0.2, -0.2, 0.2), 0.0, 0.10, "darkmetal", cham=0.02) for lv in range(1, bays + 1): for k in range(4): tube(part, [corner(k, lv), corner((k + 1) % 4, lv)], 0.032, "orange" if lv % 3 == 0 else "white", sides=6) a, c = (corner(k, lv - 1), corner((k + 1) % 4, lv)) if (lv + k) % 2 else (corner((k + 1) % 4, lv - 1), corner(k, lv)) tube(part, [a, c], 0.022, "white", sides=6) top = z0 + height * 0.92 prism(part, Frame.ground(x, y, top), ngon(0.0, 0.0, 0.62, 8, math.radians(22.5)), 0.0, 0.14, "darkmetal", cham=0.03) cylinder(part, (x, y, top + 0.12), (x, y, top + height * 0.08 + 0.3), 0.07, "white", sides=8) for dx, dy, h in ((0.4, 0.0, 2.6), (-0.4, 0.0, 3.4), (0.0, 0.4, 1.9)): aerial(part, x + dx, y + dy, top + 0.14, h) cylinder(part, (x, y, top + height * 0.08 + 0.3), (x, y, top + height * 0.08 + 0.44), 0.07, "red", sides=6, smooth=False) for zz, yaw, r in ((z0 + height * 0.62, 20.0, 0.75), (z0 + height * 0.74, 200.0, 0.6), (z0 + height * 0.50, 110.0, 0.6)): d = Vector((math.cos(math.radians(yaw)), math.sin(math.radians(yaw)), 0.0)) t = (zz - z0) / (height * 0.92) half = 1.25 + (0.32 - 1.25) * t c = Vector((x, y, zz)) + d * (half + 0.12) cylinder(part, c, c + d * 0.34, r, "white", sides=16, cham=0.03, cap_mat="darkmetal") tube(part, [Vector((x, y, zz)), c], 0.04, "darkmetal", sides=6) # a cabinet at its foot roof_unit(part, x + 3.2, y - 0.4, ICE - 0.02, 1.2, 1.9, 2.0, fan=False) tube(part, [(x + 2.6, y - 0.4, ICE + 0.5), (x + 1.9, y - 0.4, ICE + 0.5), (x + 1.25, y - 1.1, ICE + 0.35)], 0.05, "darkmetal", sides=6, bend=0.2) def floodlight(part, x, y, yaw, height=9.0): """A lighting mast: a tapering pole on a concrete block, a cross arm, two lamps looking down along `yaw`.""" g = Frame.ground(x, y, ICE - 0.25) prism(part, g, ngon(0.0, 0.0, 0.46, 8, math.radians(22.5)), 0.0, 0.62, "concrete", cham=0.05) z0 = ICE + 0.35 loft(part, [ring((x, y, z0), 0.13, 8), ring((x, y, z0 + height), 0.075, 8)], "white", smooth=True, cap_mat="darkmetal") prism(part, Frame.ground(x, y, z0 + 0.9), ngon(0.0, 0.0, 0.17, 8, math.radians(22.5)), 0.0, 0.5, "orange", cham=0.02) d = Vector((math.cos(math.radians(yaw)), math.sin(math.radians(yaw)), 0.0)) s = Vector((-d.y, d.x, 0.0)) top = Vector((x, y, z0 + height)) tube(part, [top - s * 0.85 + Vector((0, 0, -0.05)), top + s * 0.85 + Vector((0, 0, -0.05))], 0.04, "darkmetal", sides=6) aim = (d * math.cos(math.radians(38.0)) + Vector((0, 0, -math.sin(math.radians(38.0))))).normalized() heads = [] for k in (-1, 1): c = top + s * (0.6 * k) + Vector((0, 0, -0.16)) + d * 0.12 fr = Frame.facing(c, aim) prism(part, fr, clipped(-0.26, 0.26, -0.18, 0.18, 0.04, 0.04, 0.04, 0.04), -0.20, 0.0, "darkmetal", cham=0.02) prism(part, fr, rect(-0.21, 0.21, -0.13, 0.13), -0.01, 0.015, "light", back=False) heads.append(c) cylinder(part, top, top + Vector((0, 0, 0.10)), 0.045, "red", sides=6, smooth=False) return (top + Vector((0, 0, -0.16)) + d * 0.2), aim def bollard(part, x, y, yaw=0.0): """A pair of mooring bitts on a base plate.""" g = Frame.ground(x, y, ICE - 0.12, yaw) prism(part, g, clipped(-0.62, 0.62, -0.28, 0.28, 0.08, 0.08, 0.08, 0.08), 0.0, 0.22, "concrete", cham=0.03) for s in (-1, 1): p = g.p(0.32 * s, 0.0, 0.0) cylinder(part, (p.x, p.y, ICE + 0.08), (p.x, p.y, ICE + 0.62), 0.10, "darkmetal", sides=10, cham=0.02) cylinder(part, (p.x, p.y, ICE + 0.60), (p.x, p.y, ICE + 0.70), 0.145, "orange", sides=10, cham=0.025) a, c = g.p(-0.40, 0.0, 0.0), g.p(0.40, 0.0, 0.0) tube(part, [(a.x, a.y, ICE + 0.40), (c.x, c.y, ICE + 0.40)], 0.035, "darkmetal", sides=6) def cradle(part, x, y, yaw): """Two concrete sleepers for a cargo pod.""" for a in (-2.0, 2.0): g = Frame.ground(x, y, ICE - 0.15, yaw) prism(part, g, rect(a - 0.25, a + 0.25, -1.45, 1.45), 0.0, 0.50, "concrete", cham=0.04) # --------------------------------------------------------------------------------------------------------------- def add_site(part, b, kit, haven, col, tag): info = {"pods": 0, "fences": 0, "props": 0, "lights": 0} own_materials.EXTRA["tread"] = haven.surface("metal_plate", 1.2) own_materials.EXTRA["snow"] = haven.surface("snow_02", 2.0) landing_pad(part) walkway(part, [(0.0, 51.75), (0.0, PAD[1] - PAD[2] - 0.1)]) walkway(part, [(29.35, 0.0), (58.4, 0.0)]) walkway(part, [(36.0, 1.1), (36.0, 12.9)]) tank_farm(part) lattice_mast(part, -24.0, 33.0) # the yard's cargo pods (the pack's containers) on their cradles for x, y in ((39.2, 5.3), (46.8, 5.3), (39.2, -5.3), (46.8, -5.3), (54.4, -5.3)): cradle(part, x, y, 0.0) ob = kit.place(LITE + "SM_Containers_01", at(x, y, ICE + 0.45), col, name="Cargo pod") if ob is not None: tag(ob, "outpost: cargo pods (Sci-Fi Lite pack)") info["pods"] += 1 # fences: both sides of the landing deck and of the apron, and the yard's east end fences = [(sx * 5.05, y, 90.0, 0.0) for sx in (-1, 1) for y in (43.3, 46.1)] fences += [(23.0, sy * 4.85, 0.0, 0.0) for sy in (-1, 1)] fences += [(59.6, y, 90.0, ICE) for y in (-7.0, -4.2, 4.2, 7.0)] for x, y, yaw, z in fences: ob = kit.place(LITE + "SM_Fence_01", at(x, y, z, yaw), col, name="Fence") if ob is not None: tag(ob, "outpost: fences (Sci-Fi Lite pack)") info["fences"] += 1 # the gate on the way from the pad to the ramp, and a terminal beside each door that has a deck # (the pack's blue trim is the station's orange on these: a per-instance material, the pack's mesh as it is) orange = own_materials.MAT["orange"] ob = kit.place(LITE + "SM_Door_Frame_01", at(0.0, 53.0, ICE + 1.848), col, name="Landing gate", overrides={3: orange}) if ob is not None: tag(ob, "outpost: gate (Sci-Fi Lite pack)") for x, y, yaw in ((4.05, 42.0, 0.0), (22.0, 4.0, -90.0)): ob = kit.place(LITE + "SM_WorkStation_01", at(x, y, 0.0, yaw), col, name="Door terminal", overrides={4: orange}) if ob is not None: tag(ob, "outpost: door terminals (Sci-Fi Lite pack)") # lighting masts; each gets a lamp masts = ((-9.5, 51.0, 60.0), (9.5, 51.0, 120.0), (33.0, 9.0, -50.0), (58.0, -9.2, 140.0), (41.2, 28.2, -135.0), (-46.0, -31.0, 20.0), (46.5, -31.0, 160.0), (-40.5, 17.5, -45.0)) template = None for ob in bpy.data.objects: if ob.type == "LIGHT" and ob.get("ue_light") and "Spot" in str(ob["ue_light"]): template = json.loads(ob["ue_light"]) break for k, (x, y, yaw) in enumerate(masts): head, aim = floodlight(part, x, y, yaw) data = bpy.data.lights.new("Outpost floodlight", "SPOT") data.energy = 9000.0 data.spot_size = math.radians(100.0) data.spot_blend = 0.5 data.color = (0.86, 0.93, 1.0) data.use_shadow = False lamp = bpy.data.objects.new("Outpost floodlight %d" % (k + 1), data) lamp.matrix_world = Matrix.Translation(head) @ aim.to_track_quat("-Z", "Y").to_matrix().to_4x4() col.objects.link(lamp) ue = dict(template or {}) ue.update({"comp_class": "SpotLightComponent", "intensity": 30000.0, "light_color": [219, 237, 255, 255], "attenuation_radius": 3500.0, "inner_cone_angle": 28.0, "outer_cone_angle": 52.0, "cast_shadows": True}) lamp["ue_light"] = json.dumps(ue) lamp["ue_intensity_scale"] = 1.0 tag(lamp, "outpost: floodlights") info["lights"] += 1 # the berth: bitts along the edge, lifebuoys on posts for x in (-36.0, -28.0, -20.0, -12.0, 12.0, 20.0, 28.0, 36.0): bollard(part, x, -33.55) def prop(asset, x, y, z, yaw=0.0, s=1.0, name=None, tilt=None): m = at(x, y, z, yaw, s) if tilt is not None: m = m @ Matrix.Rotation(math.radians(tilt), 4, "X") ob = haven.place(asset, m, col, name=name or asset) if ob is not None: tag(ob, "outpost: site props (polyhaven.com, CC0)") info["props"] += 1 return ob for x in (-24.0, -8.0, 8.0, 24.0): g = Frame.ground(x, -33.3, ICE - 0.1) prism(part, g, rect(-0.09, 0.09, -0.09, 0.09), 0.0, 1.45, "orange", cham=0.02) prism(part, g, clipped(-0.26, 0.26, -0.03, 0.03, 0.0, 0.0, 0.0, 0.0), 1.05, 1.20, "white", cham=0.01) prop("lifebuoy", x, -33.3 + 0.17, ICE + 0.95, 0.0) for x, y in ((-76.0, -48.0), (104.0, -47.0)): prop("ocean_buoy", x, y, SEA + 0.05, 30.0) # the yard: drums by the apron, crates between the pods, barriers along its south side, a cart and a truck for k, (asset, x, y, yaw) in enumerate((("Barrel_01", 31.0, 6.9, 10.0), ("Barrel_01", 31.75, 7.15, 80.0), ("barrel_03", 31.3, 7.75, 0.0), ("barrel_03", 32.4, 7.6, 40.0), ("Barrel_02", 32.25, 6.75, 0.0), ("Barrel_02", 30.5, 7.7, 0.0))): prop(asset, x, y, ICE, yaw) for asset, x, y, z, yaw in (("wooden_military_crate", 43.0, 3.1, ICE, 4.0), ("wooden_military_crate", 43.05, 3.12, ICE + 0.46, -3.0), ("wooden_military_crate", 44.5, 3.2, ICE, 92.0), ("old_military_crate", 50.6, 3.4, ICE, 12.0), ("old_military_crate", 42.8, -2.9, ICE, 185.0), ("plastic_crate_01", 44.6, -2.6, ICE, 15.0), ("plastic_crate_01", 45.05, -2.7, ICE, -8.0), ("plastic_crate_01", 44.8, -2.65, ICE + 0.26, 4.0), ("metal_jerrycan", 31.9, 8.5, ICE, 20.0), ("metal_jerrycan", 32.3, 8.55, ICE, -15.0), ("metal_jerrycan", 51.9, 2.9, ICE, 70.0), ("hand_truck", 49.0, 2.4, ICE, 200.0), ("tool_cart", 26.6, -3.4, ICE, 90.0), ("portable_generator", 27.4, 6.4, ICE, 0.0)): prop(asset, x, y, z, yaw) for k in range(6): prop("concrete_road_barrier", 36.4 + k * 3.9, -9.4, ICE, 2.0 if k % 2 else -3.0) # the tank farm: gas bottles in a row by the kerb, a cabinet at the pump house; the mast: a cabinet and a generator for k in range(4): prop("propane_tank", 38.3 + k * 0.42, 14.0, ICE + 0.12, 25.0 * k) for k in range(3): prop("small_lpg_tank", 36.4 + k * 0.5, 14.05, ICE + 0.12, 40.0 * k) prop("utility_box_01", 23.0, 22.6, ICE + 0.12, 90.0) prop("utility_box_02", 23.0, 17.9, ICE + 0.12, 90.0) prop("utility_box_02", -19.6, 35.4, ICE, 180.0) prop("portable_generator", -21.6, 30.6, ICE + 0.22, 35.0) prop("Barrel_02", -22.6, 30.2, ICE + 0.22, 0.0) prop("utility_box_01", 6.9, 52.4, ICE, 0.0) return info def set_sky(haven): """The scene is lit by Poly Haven's overcast snow field. The world it had stays in the file.""" w = haven.world("snow_field", 1.0, 0.0) if w is None: return None sc = bpy.context.scene if sc.world is not None and sc.world is not w: sc.world.use_fake_user = True sc["anchorage_world_before"] = sc.world.name sc.world = w return w