309 lines
9.2 KiB
Python
309 lines
9.2 KiB
Python
import csv
|
|
import math
|
|
from pathlib import Path
|
|
|
|
# -----------------------
|
|
# CONFIG
|
|
# -----------------------
|
|
CSV_PATH = "conveyors.csv"
|
|
OUT_TSCN = "generated_conveyors.tscn"
|
|
|
|
SCALE = 0.0254
|
|
FIXED_Y = 2.5
|
|
|
|
STRAIGHT_BELT_ID = "3_38ygf"
|
|
CURVED_BELT_ID = "1_ef28r"
|
|
|
|
# Spur / curve tuning
|
|
SPUR_TANGENT_NUDGE = 0.0
|
|
MIN_CURVE_DEG = 1.0
|
|
|
|
# Geometry (must match asset)
|
|
CURVE_INNER_RADIUS = 1.815
|
|
BELT_WIDTH = 1.524
|
|
|
|
# -----------------------
|
|
# HELPERS
|
|
# -----------------------
|
|
def transform_from_points(x1, y1, x2, y2):
|
|
dx = x2 - x1
|
|
dy = y2 - y1
|
|
|
|
length = math.hypot(dx, dy) * SCALE
|
|
|
|
mid_x = (x1 + x2) / 2 * SCALE
|
|
mid_z = -(y1 + y2) / 2 * SCALE # AutoCAD Y → Godot -Z
|
|
|
|
rot_y = math.atan2(-dy, dx)
|
|
|
|
return {
|
|
"length": length,
|
|
"pos": (mid_x, FIXED_Y, mid_z),
|
|
"rot_y": rot_y,
|
|
}
|
|
|
|
def transform3d(rot_y, x, y, z):
|
|
c = math.cos(rot_y)
|
|
s = math.sin(rot_y)
|
|
return (
|
|
f"Transform3D({c}, 0, {-s}, "
|
|
f"0, 1, 0, "
|
|
f"{s}, 0, {c}, "
|
|
f"{x}, {y}, {z})"
|
|
)
|
|
|
|
def parse_key(key):
|
|
p = key.split("_")
|
|
return p[0], int(p[1])
|
|
|
|
def unit_fwd(rot_y):
|
|
return math.cos(rot_y), math.sin(rot_y)
|
|
|
|
def unit_right(rot_y):
|
|
return -math.sin(rot_y), math.cos(rot_y)
|
|
|
|
def end_point(conv):
|
|
dx, dz = unit_fwd(conv["rot_y"])
|
|
half = conv["length"] / 2
|
|
return (
|
|
conv["pos"][0] + dx * half,
|
|
conv["pos"][2] + dz * half
|
|
)
|
|
|
|
def start_point(conv):
|
|
dx, dz = unit_fwd(conv["rot_y"])
|
|
half = conv["length"] / 2
|
|
return (
|
|
conv["pos"][0] - dx * half,
|
|
conv["pos"][2] - dz * half
|
|
)
|
|
|
|
def add_straight_node(lines, name, rot_y, cx, cz, length):
|
|
t = transform3d(rot_y, cx, FIXED_Y, cz)
|
|
lines.append(f'[node name="{name}" parent="." instance=ExtResource("{STRAIGHT_BELT_ID}")]')
|
|
lines.append(f"transform = {t}")
|
|
lines.append("right_side_guards_enabled = false")
|
|
lines.append("left_side_guards_enabled = false")
|
|
lines.append("head_end_leg_enabled = false")
|
|
lines.append("tail_end_leg_enabled = false")
|
|
lines.append("enable_comms = true")
|
|
lines.append(f'speed_tag_name = "{name}_OIP"')
|
|
lines.append(f"size = Vector3({length:.6f}, 0.5, 1.524)")
|
|
lines.append("")
|
|
|
|
def add_curved_node(lines, name, rot_y, px, pz, angle_deg):
|
|
t = transform3d(rot_y, px, FIXED_Y, pz)
|
|
lines.append(f'[node name="{name}" parent="." instance=ExtResource("{CURVED_BELT_ID}")]')
|
|
lines.append(f"transform = {t}")
|
|
lines.append(f"inner_radius = {CURVE_INNER_RADIUS}")
|
|
lines.append(f"conveyor_angle = {angle_deg:.3f}")
|
|
lines.append("enable_comms = true")
|
|
lines.append(f'speed_tag_name = "{name}_OIP"')
|
|
lines.append("")
|
|
|
|
# -----------------------
|
|
# READ CSV
|
|
# -----------------------
|
|
straight = {}
|
|
vfd_only = [] # included=0 candidates (some will become spurs, others will be chain-continued)
|
|
|
|
with open(CSV_PATH, newline="") as f:
|
|
reader = csv.DictReader(f)
|
|
for row in reader:
|
|
key = row["conveyor_key"].strip()
|
|
included = row["included"].strip()
|
|
|
|
prefix, sec = parse_key(key)
|
|
|
|
if included == "1":
|
|
if not all(row[c].strip() for c in ("start_x", "start_y", "end_x", "end_y")):
|
|
continue
|
|
|
|
conv = transform_from_points(
|
|
float(row["start_x"]),
|
|
float(row["start_y"]),
|
|
float(row["end_x"]),
|
|
float(row["end_y"])
|
|
)
|
|
|
|
conv["name"] = key
|
|
conv["prefix"] = prefix
|
|
conv["sec"] = sec
|
|
|
|
straight[key] = conv
|
|
else:
|
|
vfd_only.append({"name": key, "prefix": prefix, "sec": sec})
|
|
|
|
# Precompute endpoints & directions for geometry straights
|
|
for c in straight.values():
|
|
c["start"] = start_point(c)
|
|
c["end"] = end_point(c)
|
|
c["fwd"] = unit_fwd(c["rot_y"])
|
|
|
|
# -----------------------
|
|
# WRITE TSCN
|
|
# -----------------------
|
|
lines = []
|
|
lines.append('[gd_scene load_steps=3 format=3]')
|
|
lines.append('')
|
|
lines.append('[ext_resource type="PackedScene" path="res://parts/assemblies/BeltConveyorAssembly.tscn" id="3_38ygf"]')
|
|
lines.append('[ext_resource type="PackedScene" path="res://parts/assemblies/CurvedBeltConveyorAssembly.tscn" id="1_ef28r"]')
|
|
lines.append('')
|
|
lines.append('[node name="GeneratedConveyors" type="Node3D"]')
|
|
lines.append('')
|
|
|
|
# -----------------------
|
|
# 1) Geometry straight conveyors (included=1)
|
|
# -----------------------
|
|
for c in straight.values():
|
|
cx, _, cz = c["pos"]
|
|
add_straight_node(lines, c["name"], c["rot_y"], cx, cz, c["length"])
|
|
|
|
# -----------------------
|
|
# 2) Spur conveyors (use vfd_only list, but mark those we actually place)
|
|
# -----------------------
|
|
placed_spurs = set()
|
|
|
|
for spur in vfd_only:
|
|
prefix = spur["prefix"]
|
|
sec = spur["sec"]
|
|
|
|
prev_key = f"{prefix}_{sec-1}"
|
|
next_key = f"{prefix}_{sec+1}"
|
|
|
|
# spur needs both neighbors with geometry (or already-added straights later, but at this stage it's fine)
|
|
if prev_key not in straight or next_key not in straight:
|
|
continue
|
|
|
|
prev = straight[prev_key]
|
|
nxt = straight[next_key]
|
|
|
|
pfx, pfz = prev["fwd"]
|
|
nfx, nfz = nxt["fwd"]
|
|
|
|
cross = pfx * nfz - pfz * nfx
|
|
dot = pfx * nfx + pfz * nfz
|
|
delta = math.atan2(cross, dot)
|
|
|
|
angle_deg = abs(delta) * 180.0 / math.pi
|
|
if angle_deg < MIN_CURVE_DEG:
|
|
continue
|
|
|
|
turn_sign = 1.0 if delta > 0 else -1.0
|
|
|
|
rx, rz = unit_right(prev["rot_y"])
|
|
end_x, end_z = prev["end"]
|
|
|
|
# place along side edge then inward by inner radius (your current spur logic)
|
|
edge_x = end_x + rx * (BELT_WIDTH / 2) * turn_sign
|
|
edge_z = end_z + rz * (BELT_WIDTH / 2) * turn_sign
|
|
|
|
inward_x = -rx * turn_sign
|
|
inward_z = -rz * turn_sign
|
|
|
|
place_x = edge_x + inward_x * CURVE_INNER_RADIUS + pfx * SPUR_TANGENT_NUDGE
|
|
place_z = edge_z + inward_z * CURVE_INNER_RADIUS + pfz * SPUR_TANGENT_NUDGE
|
|
|
|
add_curved_node(lines, spur["name"], prev["rot_y"], place_x, place_z, angle_deg)
|
|
placed_spurs.add(spur["name"])
|
|
|
|
# -----------------------
|
|
# 3) VFD-only straight conveyors (CHAIN CONTINUATION)
|
|
# Run AFTER spurs. Only place leftovers (not in placed_spurs).
|
|
# Also: after we place one, add it to "straight" so multiple missing sections can chain.
|
|
# -----------------------
|
|
# Build quick lookup for vfd_only rows by key
|
|
vfd_by_key = {s["name"]: s for s in vfd_only}
|
|
|
|
placed_chain = set()
|
|
|
|
made_progress = True
|
|
while made_progress:
|
|
made_progress = False
|
|
|
|
for spur in vfd_only:
|
|
name = spur["name"]
|
|
if name in placed_spurs or name in placed_chain:
|
|
continue
|
|
if name in straight:
|
|
continue
|
|
|
|
prefix = spur["prefix"]
|
|
sec = spur["sec"]
|
|
prev_key = f"{prefix}_{sec-1}"
|
|
next_key = f"{prefix}_{sec+1}"
|
|
|
|
# --- Case A: place AFTER previous (if previous exists, next does NOT exist/placed) ---
|
|
if prev_key in straight and next_key not in straight:
|
|
ref = straight[prev_key]
|
|
dx, dz = ref["fwd"]
|
|
length = ref["length"]
|
|
|
|
# start at previous end, same direction, same length
|
|
sx, sz = ref["end"]
|
|
ex, ez = (sx + dx * length, sz + dz * length)
|
|
cx, cz = (sx + dx * (length / 2), sz + dz * (length / 2))
|
|
|
|
add_straight_node(lines, name, ref["rot_y"], cx, cz, length)
|
|
|
|
# register as a new straight so the chain can continue
|
|
newc = {
|
|
"name": name,
|
|
"prefix": prefix,
|
|
"sec": sec,
|
|
"length": length,
|
|
"pos": (cx, FIXED_Y, cz),
|
|
"rot_y": ref["rot_y"],
|
|
}
|
|
newc["start"] = (sx, sz)
|
|
newc["end"] = (ex, ez)
|
|
newc["fwd"] = (dx, dz)
|
|
|
|
straight[name] = newc
|
|
placed_chain.add(name)
|
|
made_progress = True
|
|
continue
|
|
|
|
# --- Case B: place BEFORE next (if next exists, prev does NOT exist/placed) ---
|
|
if next_key in straight and prev_key not in straight:
|
|
ref = straight[next_key]
|
|
dx, dz = ref["fwd"]
|
|
length = ref["length"]
|
|
|
|
# end at next start, same direction, same length (going backwards)
|
|
ex, ez = ref["start"]
|
|
sx, sz = (ex - dx * length, ez - dz * length)
|
|
cx, cz = (sx + dx * (length / 2), sz + dz * (length / 2))
|
|
|
|
add_straight_node(lines, name, ref["rot_y"], cx, cz, length)
|
|
|
|
newc = {
|
|
"name": name,
|
|
"prefix": prefix,
|
|
"sec": sec,
|
|
"length": length,
|
|
"pos": (cx, FIXED_Y, cz),
|
|
"rot_y": ref["rot_y"],
|
|
}
|
|
newc["start"] = (sx, sz)
|
|
newc["end"] = (ex, ez)
|
|
newc["fwd"] = (dx, dz)
|
|
|
|
straight[name] = newc
|
|
placed_chain.add(name)
|
|
made_progress = True
|
|
continue
|
|
|
|
# -----------------------
|
|
# WRITE FILE
|
|
# -----------------------
|
|
Path(OUT_TSCN).write_text("\n".join(lines), encoding="utf-8")
|
|
|
|
leftovers = [s["name"] for s in vfd_only if s["name"] not in placed_spurs and s["name"] not in placed_chain]
|
|
print(
|
|
f"Generated {len([k for k in straight.keys() if k not in vfd_by_key])} geometry straights, "
|
|
f"{len(placed_spurs)} spurs, "
|
|
f"{len(placed_chain)} chain-continued VFD-only straights. "
|
|
f"Leftovers not placed: {len(leftovers)}"
|
|
)
|