# Mako: objects, rig, sockets, normals, collision, report (exec'd by build_mako.py last) REPORT["pruned_faces"] = prune_inside(att, inside_depth, keep_depth=0.02) def finish(mb, parent=root, coll=C_ROOT, origin=None): return mb.build(coll, parent=parent, recalc=False, origin=origin) def pivot_at(o, p): o.matrix_parent_inverse.identity() o.location = p o_hull = finish(hull) o_glass = finish(glass) o_cab = finish(cab) o_frames = finish(frames) o_att = finish(att) o_clip = finish(clip) o_hatch = finish(hatch, origin=HATCH_HINGE); pivot_at(o_hatch, HATCH_HINGE) o_rotor = finish(rotor, origin=ROT_C); pivot_at(o_rotor, ROT_C) o_arms = {} for side, (mb_a, A0) in ARMS.items(): oa = finish(mb_a, origin=A0); pivot_at(oa, A0) o_arms[side] = oa def drive(o, path, index, prop, expr, lo, hi, desc, default=0.0): if prop not in root.keys(): root[prop] = default root.id_properties_ui(prop).update(min=lo, max=hi, description=desc) fc = o.driver_add(path, index) dr = fc.driver; dr.type = 'SCRIPTED' v = dr.variables.new(); v.name = "p"; v.type = 'SINGLE_PROP' v.targets[0].id = root; v.targets[0].data_path = '["%s"]' % prop dr.expression = expr # rig (simple-expression drivers: this Blender runs no Python in files) drive(o_hatch, "rotation_euler", 0, "hatch_open_deg", "-p*0.0174533", 0.0, HATCH_MAX, "hatch inside the docking clips: 0 shut .. %d deg open, hinged across its aft edge (further, its corners would reach the aft clips)" % HATCH_MAX) drive(o_rotor, "rotation_euler", 1, "rotor_turns", "p*6.283185", -1e6, 1e6, "stern thruster rotor: turns about the long axis") for side, oa in o_arms.items(): drive(oa, "rotation_euler", 2, "arms_out_deg", ("-p" if side > 0 else "p") + "*0.0174533", 0.0, 90.0, "stern manipulators: 0 stowed aft beside the duct .. 90 deg swung out sideways") # sockets for the game SOCK = {"SOCKET_Clamp": CL_C + Vector((0, 0, 1.94)), "SOCKET_Pilot_Eye": PILOT_EYE, "SOCKET_Hatch": Vector((0, Y_CL, 1.20)), "SOCKET_Thrust": TH_C + Vector((0, 0.13, 0)), "SOCKET_Lamp_L": LAMPS[0][0], "SOCKET_Lamp_R": LAMPS[1][0]} for side, (cx, cz) in PODS.items(): tag = "L" if side > 0 else "R" SOCK["SOCKET_Jet_" + tag] = Vector((cx, Yi(POD["x1"]) + 0.02, cz)) SOCK["SOCKET_Intake_" + tag] = Vector((cx, Yi(POD["x0"]) - 0.02, cz)) for side, (oa, A0) in ((s, (o_arms[s], ARMS[s][1])) for s in o_arms): SOCK["SOCKET_Grip_" + ("L" if side > 0 else "R")] = A0 + Vector((side * 0.03, 0.10 + 0.085 + 0.04 + 0.06, 0)) for nm, p in SOCK.items(): e = bpy.data.objects.new(nm, None); C_SOCK.objects.link(e); e.parent = root; e.location = p e.empty_display_size = 0.15 # one static exterior mesh: skin, fittings, clamp saddle and clips, canopy mullions (glass, cabin and the moving parts stay apart) with bpy.context.temp_override(active_object=o_hull, selected_editable_objects=[o_hull, o_att, o_clip, o_frames]): bpy.ops.object.join() o_hull.name = "SM_Mako_Hull" # the skin's normals from the analytic surface (custom normals): faces the hull pass flagged on_skin take, at each corner, # the normal the build stored on the vertex (an_x/an_y/an_z); frames, steps, sleeves and fittings keep their own. me = o_hull.data cn = [Vector(c.vector) for c in me.corner_normals] fl = me.attributes.get("on_skin") an = [me.attributes.get(n) for n in ("an_x", "an_y", "an_z")] n_set = 0 if fl is not None and all(a is not None for a in an): onf = [d.value for d in fl.data] ax_ = [[d.value for d in a.data] for a in an] lv = [l.vertex_index for l in me.loops] for p in me.polygons: if onf[p.index] != 1: continue for li in range(p.loop_start, p.loop_start + p.loop_total): vi = lv[li] n = Vector((ax_[0][vi], ax_[1][vi], ax_[2][vi])) if n.length > 0.5 and n.dot(p.normal) > 0.5: cn[li] = n.normalized() n_set += 1 me.normals_split_custom_set(cn) for nm in ("on_skin", "an_x", "an_y", "an_z"): # build-time data: not exported a = me.attributes.get(nm) if a is not None: me.attributes.remove(a) REPORT["custom_normal_corners"] = n_set exec(open(os.path.join(HERE, "mk_collision.py")).read()) REPORT["ucx"] = REPORT_UCX allp = [] for o in C_ROOT.all_objects: if o.type == 'MESH': allp += [o.matrix_world @ v.co for v in o.data.vertices] mn = Vector((min(p.x for p in allp), min(p.y for p in allp), min(p.z for p in allp))) mx = Vector((max(p.x for p in allp), max(p.y for p in allp), max(p.z for p in allp))) REPORT["bounds_min"] = [round(c, 3) for c in mn]; REPORT["bounds_max"] = [round(c, 3) for c in mx] REPORT["dimensions"] = [round(c, 3) for c in (mx - mn)] REPORT["audit"] = [audit(o) for o in C_ROOT.all_objects if o.type == 'MESH'] # the cabin stays inside: no vertex of it may reach the skin (or the glass, which is sunk 22 mm) by less than 5 mm _out = [v.co for v in o_cab.data.vertices if inside_depth(o_cab.matrix_world @ v.co) < GLASS_D + 0.005] REPORT["cabin_verts_near_or_outside_skin"] = len(_out) REPORT["cabin_worst_points"] = [[round(c, 3) for c in p] for p in sorted(_out, key=lambda q: inside_depth(q))[:6]] REPORT["build_seconds"] = round(time.time() - t_start, 2) json.dump(REPORT, open(os.path.join(HERE, "build_report.json"), "w"), indent=1) for ob in C_ROOT.all_objects: # drivers made in this run can stay flagged invalid until re-set if ob.animation_data: for fc in ob.animation_data.drivers: fc.driver.expression = fc.driver.expression fc.driver.is_valid = True