mirror of
https://github.com/allaunthefox/Research-Stack.git
synced 2026-07-31 03:05:21 +00:00
This squashes all local history (768 commits) onto the scrubbed PR #90 baseline. Individual commits were lost during filter-repo corruption; the working tree content is preserved intact. Build: N/A (working tree state only)
196 lines
6.9 KiB
Python
196 lines
6.9 KiB
Python
"""
|
|
Merkle Jack - A 3D tree structure based on the semi_jack geometry search.
|
|
|
|
This model represents a Merkle tree structure with nodes and struts
|
|
in 3D space, optimized for uniform stress distribution.
|
|
|
|
Based on: /home/allaun/Documents/Research Stack/scratch/exploit_recovery/5-Applications/tools-scripts/semi_jack/
|
|
"""
|
|
|
|
import build123d as bd
|
|
import trimesh
|
|
import numpy as np
|
|
import math
|
|
from typing import List, Tuple, Optional
|
|
|
|
# Geometry parameters from semi_jack search
|
|
DEPTH = 4
|
|
BRANCHING_FACTOR = 2
|
|
BRANCH_ANGLES = [30.0, 25.0, 20.0, 15.0] # degrees from vertical
|
|
AZ_OFFSETS = [0.0, 45.0, 90.0, 135.0] # azimuthal rotation
|
|
TUBULE_RADIUS = 1.5 # mm
|
|
HEIGHT_PER_LEVEL = 6.0 # mm
|
|
|
|
class GNode:
|
|
def __init__(self, id: int, x: float, y: float, z: float, load_frac: float, parent: Optional[int] = None, depth: int = 0):
|
|
self.id = id
|
|
self.x = x
|
|
self.y = y
|
|
self.z = z
|
|
self.load_frac = load_frac
|
|
self.parent = parent
|
|
self.depth = depth
|
|
|
|
def generate_tree(
|
|
depth: int,
|
|
branch_angles: List[float],
|
|
az_offsets: List[float],
|
|
branching_factor: int,
|
|
height_per_level: float = 6.0,
|
|
) -> Tuple[List[GNode], List[Tuple[int, int]]]:
|
|
"""Build a rooted tree in 3D using scaled integer matrices to avoid fractional accumulation."""
|
|
# Scale factor for integer math
|
|
Q_SCALE = 65536
|
|
|
|
nodes = [GNode(id=0, x=0.0, y=0.0, z=0.0, load_frac=1.0, depth=0)]
|
|
edges = []
|
|
node_id = 1
|
|
current_level = [0]
|
|
|
|
# Pre-calculate integer matrices for branch and azimuth
|
|
h_int = int(height_per_level * Q_SCALE)
|
|
|
|
for lv in range(depth):
|
|
angle_deg = branch_angles[min(lv, len(branch_angles)-1)]
|
|
az_base = az_offsets[min(lv, len(az_offsets)-1)]
|
|
|
|
# Integer scaled rotation components
|
|
cos_branch = int(math.cos(math.radians(angle_deg)) * Q_SCALE)
|
|
sin_branch = int(math.sin(math.radians(angle_deg)) * Q_SCALE)
|
|
|
|
step_z_int = (h_int * cos_branch) >> 16
|
|
step_r_int = (h_int * sin_branch) >> 16
|
|
|
|
next_level = []
|
|
|
|
for pid in current_level:
|
|
parent = nodes[pid]
|
|
child_frac = parent.load_frac / branching_factor
|
|
|
|
p_x_int = int(parent.x * Q_SCALE)
|
|
p_y_int = int(parent.y * Q_SCALE)
|
|
p_z_int = int(parent.z * Q_SCALE)
|
|
|
|
for k in range(branching_factor):
|
|
az_deg = az_base + k * (360.0 / branching_factor)
|
|
cos_az = int(math.cos(math.radians(az_deg)) * Q_SCALE)
|
|
sin_az = int(math.sin(math.radians(az_deg)) * Q_SCALE)
|
|
|
|
cx_int = p_x_int + ((step_r_int * cos_az) >> 16)
|
|
cy_int = p_y_int + ((step_r_int * sin_az) >> 16)
|
|
cz_int = p_z_int - step_z_int
|
|
|
|
child = GNode(
|
|
id=node_id,
|
|
x=float(cx_int) / Q_SCALE,
|
|
y=float(cy_int) / Q_SCALE,
|
|
z=float(cz_int) / Q_SCALE,
|
|
load_frac=child_frac,
|
|
parent=pid,
|
|
depth=lv+1,
|
|
)
|
|
nodes.append(child)
|
|
edges.append((pid, node_id))
|
|
next_level.append(node_id)
|
|
node_id += 1
|
|
|
|
current_level = next_level
|
|
|
|
return nodes, edges
|
|
|
|
def gen_part():
|
|
"""Generate the Merkle Jack CAD model for text-to-cad."""
|
|
nodes, edges = generate_tree(
|
|
depth=DEPTH,
|
|
branch_angles=BRANCH_ANGLES,
|
|
az_offsets=AZ_OFFSETS,
|
|
branching_factor=BRANCHING_FACTOR,
|
|
height_per_level=HEIGHT_PER_LEVEL,
|
|
)
|
|
|
|
node_map = {n.id: n for n in nodes}
|
|
|
|
with bd.BuildPart() as jack:
|
|
for p_id, c_id in edges:
|
|
parent = node_map[p_id]
|
|
child = node_map[c_id]
|
|
|
|
# Calculate edge vector and length
|
|
dx = child.x - parent.x
|
|
dy = child.y - parent.y
|
|
dz = child.z - parent.z
|
|
length = math.sqrt(dx**2 + dy**2 + dz**2)
|
|
|
|
if length < 1e-9:
|
|
continue
|
|
|
|
# Create cylinder and position it along the edge
|
|
cylinder = bd.Cylinder(radius=TUBULE_RADIUS, height=length, align=bd.Align.CENTER)
|
|
|
|
# Calculate rotation to align cylinder with edge
|
|
z_dir = bd.Vector(0, 0, 1)
|
|
edge_vec = bd.Vector(dx, dy, dz).normalized()
|
|
|
|
# Rotate cylinder to align with edge direction
|
|
cross_prod = z_dir.cross(edge_vec)
|
|
if cross_prod.length > 1e-9:
|
|
rotation_axis = bd.Axis((0, 0, 0), tuple(cross_prod.normalized()))
|
|
rotation_angle = edge_vec.get_angle(z_dir)
|
|
cylinder = cylinder.rotate(axis=rotation_axis, angle=rotation_angle)
|
|
|
|
# Move cylinder to midpoint of edge
|
|
midpoint = (bd.Vector(parent.x, parent.y, parent.z) +
|
|
bd.Vector(child.x, child.y, child.z)) / 2
|
|
cylinder = cylinder.move(bd.Location(midpoint))
|
|
|
|
return jack
|
|
|
|
if __name__ == "__main__":
|
|
print("Generating Merkle Jack CAD Model...")
|
|
print(f"Depth: {DEPTH}")
|
|
print(f"Branching Factor: {BRANCHING_FACTOR}")
|
|
print(f"Tubule Radius: {TUBULE_RADIUS} mm")
|
|
print(f"Height per Level: {HEIGHT_PER_LEVEL} mm")
|
|
|
|
jack = gen_part()
|
|
print(f"Created CAD model with {len(jack.faces())} faces")
|
|
|
|
# Export as STL using trimesh for physics testing
|
|
output_path = "/home/allaun/Documents/Research Stack/5-Applications/text-to-cad/models/merkle_jack.stl"
|
|
|
|
# Convert build123d shape to trimesh
|
|
# Use tessellation to get mesh data
|
|
mesh = trimesh.creation.cylinder(radius=TUBULE_RADIUS, height=1.0)
|
|
|
|
# For now, create a simple representation with cylinders
|
|
# TODO: Properly convert build123d shape to mesh
|
|
mesh.export(output_path)
|
|
print(f"Exported STL file to: {output_path}")
|
|
|
|
# Also export the tree geometry as JSON for physics simulation
|
|
import json
|
|
output_json = "/home/allaun/Documents/Research Stack/5-Applications/text-to-cad/models/merkle_jack.json"
|
|
nodes, edges = generate_tree(
|
|
depth=DEPTH,
|
|
branch_angles=BRANCH_ANGLES,
|
|
az_offsets=AZ_OFFSETS,
|
|
branching_factor=BRANCHING_FACTOR,
|
|
height_per_level=HEIGHT_PER_LEVEL,
|
|
)
|
|
|
|
geometry_data = {
|
|
"nodes": [{"id": n.id, "x": n.x, "y": n.y, "z": n.z, "load_frac": n.load_frac, "parent": n.parent, "depth": n.depth} for n in nodes],
|
|
"edges": edges,
|
|
"parameters": {
|
|
"depth": DEPTH,
|
|
"branching_factor": BRANCHING_FACTOR,
|
|
"branch_angles": BRANCH_ANGLES,
|
|
"az_offsets": AZ_OFFSETS,
|
|
"tubule_radius": TUBULE_RADIUS,
|
|
"height_per_level": HEIGHT_PER_LEVEL
|
|
}
|
|
}
|
|
|
|
with open(output_json, 'w') as f:
|
|
json.dump(geometry_data, f, indent=2)
|
|
print(f"Exported geometry JSON to: {output_json}")
|