# Spearfish: objects, rig, sockets, collision, report (exec'd by build_spearfish.py) def finish(mb, parent=root, coll=C_ROOT, origin=None): return mb.build(coll, parent=parent, recalc=False, origin=origin) 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) o_hatch.matrix_parent_inverse.identity() o_hatch.location = HATCH_HINGE # rig: the two whip masts fold aft for stowing (0 raised as drawn, 1 folded), one axis-angle each root["masts_folded"] = 0.0 root.id_properties_ui("masts_folded").update(min=0.0, max=1.0, description="whip masts: 0 raised (the blueprint), 1 folded aft along the stern for the carousel") for side, (mb_m, base, d_r, ln) in MASTS.items(): om = finish(mb_m, origin=base) om.matrix_parent_inverse.identity() om.location = base om.rotation_mode = 'AXIS_ANGLE' axis, ang, _ = MAST_FOLD[side] om.rotation_axis_angle = (0.0, axis.x, axis.y, axis.z) fcm = om.driver_add("rotation_axis_angle", 0) drm = fcm.driver; drm.type = 'SCRIPTED' vm = drm.variables.new(); vm.name = "p"; vm.type = 'SINGLE_PROP' vm.targets[0].id = root; vm.targets[0].data_path = '["masts_folded"]' drm.expression = "p*%.6f" % ang # rig: the hatch swings up about its aft edge (the X axis through the hinge); simple-expression driver root["hatch_open_deg"] = 0.0 root.id_properties_ui("hatch_open_deg").update(min=0.0, max=100.0, description="top hatch: 0 shut .. 100 deg open, hinged on its aft edge") fc = o_hatch.driver_add("rotation_euler", 0) 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 = '["hatch_open_deg"]' dr.expression = "-p*0.0174533" # sockets for the game for nm, p in (("SOCKET_Clamp", CL_C + Vector((0, 0, 1.94))), ("SOCKET_Lamp_L", Vector((POD_L * S, Yi(POD_X0) - 0.01, Zi(BIGFIN_Y) - math.tan(math.radians(BIGFIN_ANHEDRAL)) * (POD_L - BIG_LS[0]) * S))), ("SOCKET_Lamp_R", Vector((-POD_L * S, Yi(POD_X0) - 0.01, Zi(BIGFIN_Y) - math.tan(math.radians(BIGFIN_ANHEDRAL)) * (POD_L - BIG_LS[0]) * S))), ("SOCKET_Pilot_Eye", Vector((0, SY - 0.05, ZF + 0.86))), ("SOCKET_Thrust", Vector((0, Y_TAIL + 0.05, ZMf(Y_TAIL)))), ("SOCKET_Hatch", HATCH_HINGE + Vector((0, -0.30, 0.05)))): 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, clips, window frames (glass, cabin and hatch stay their own) 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_Spearfish_Hull" # the skin's normals from the analytic surface (custom normals): the grid bends round the openings, and normals averaged # from its uneven faces ripple in reflections. Corners of a face that lies on the surface and faces like it get the # surface's normal; every other corner keeps its own (frames, sleeves, fittings, their hard edges). me = o_hull.data cn = [Vector(c.vector) for c in me.corner_normals] co = [v.co for v in me.vertices] lv = [l.vertex_index for l in me.loops] n_set = 0 for p in me.polygons: pts = [co[lv[li]] for li in range(p.loop_start, p.loop_start + p.loop_total)] if all(abs(_F(q) - 1.0) < 2e-3 for q in pts) and p.normal.dot(normal_at(p.center)) > 0.9: for li in range(p.loop_start, p.loop_start + p.loop_total): cn[li] = normal_at(co[lv[li]]) n_set += 1 me.normals_split_custom_set(cn) REPORT["custom_normal_corners"] = n_set # collision: low-poly convex hulls from the hull's exact shape and the parts' own vertices (sf_collision.py) exec(open(os.path.join(HERE, "sf_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'] REPORT["build_seconds"] = round(time.time() - t_start, 2) json.dump(REPORT, open(os.path.join(HERE, "build_report.json"), "w"), indent=1) # drivers created in this run can stay flagged invalid until re-set (dump_spearfish.refresh_drivers does the same) for ob in C_ROOT.all_objects: if ob.animation_data: for fc in ob.animation_data.drivers: fc.driver.expression = fc.driver.expression fc.driver.is_valid = True