extends LevelOperator
## CABLES FROM EDGES (#120 L3, docs/node-tree-and-stacks.md §14.5): the edge set (§14.4) ->
## one cable per chain (EdgeCables). The preview draws the cables' curves; Enter writes ONE
## gesture (a curve + a cable per chain, map.cables_from_edges). Adjust Last re-runs it.
## Keys (Cablerator's from-edges where the convention allows): P points at vertices /
## centres, C corners round / sharp (Cablerator's H is every tool's help here), [ ] / Tab
## the offset and the radius.
const EdgeCablesScript := preload("res://scenes/maps/curves/edge_cables.gd")
const POINTS_AT := ["Vertices", "Centres"]
const CORNERS := ["Round", "Sharp"]
var centres := false
var sharp := false
var offset := 0.05 # metres off the edge, along its outward normal
var radius := 0.05
var _edges: Array = [] # selected_edges() at activation (Adjust Last: stored)
var _frozen := false
func op_name() -> String:
return "cable from edges"
## The mouse drags the focused value (§17.7 Q2): this tool works on the selection.
func drags_value() -> bool:
return true
func gesture_label() -> String:
return "Cable from edges"
func preview_color() -> Color:
return Color(1.0, 0.85, 0.3, 0.9) # the curve view's amber
func setup_modal(m: ModalControls) -> void:
m.confirm_label = "add the cables"
m.table([["P", "points at", func() -> String: return POINTS_AT[1 if centres else 0]],
["C", "corners", func() -> String: return CORNERS[1 if sharp else 0]],
["", "chains", func() -> String: return "%d" % EdgeCablesScript.chains(_edges).size()]])
m.bind("offset", "off the edge", func() -> float: return offset,
func(v: float) -> void:
offset = maxf(0.0, v)
update_preview(),
0.01, " m", 0.002)
m.bind("radius", "radius", func() -> float: return radius,
func(v: float) -> void:
radius = clampf(v, 0.005, 2.0)
update_preview(),
0.01, " m", 0.002)
func status() -> String:
return modal().status_text()
func on_activate() -> bool:
if not _frozen:
_edges = map.selected_edges() if map != null and map.has_method("selected_edges") else []
if _edges.is_empty():
print("[cable from edges] select edges first (edge mode, Shift-click to add)")
return false
update_preview()
return true
func on_deactivate() -> void:
clear_preview()
func handle_key(event: InputEvent) -> bool:
if not (event is InputEventKey) or not (event as InputEventKey).pressed:
return false
match KeyRules.key_of(event as InputEventKey):
KEY_P:
centres = not centres
update_preview()
return true
KEY_C:
sharp = not sharp
update_preview()
return true
return modal().handle_key(event)
func update_preview() -> void:
clear_preview()
var lines := PackedVector3Array()
for ch: Dictionary in EdgeCablesScript.chains(_edges):
var pts: Array = EdgeCablesScript.curve_points(ch, centres, offset, sharp)
if pts.size() < 2:
continue
var closed := bool(ch["closed"])
var fs := CurveMath.sample(pts, closed, 64)
for i in range(1, fs.size()):
lines.append(fs[i - 1]["p"])
lines.append(fs[i]["p"])
if closed and fs.size() > 2:
lines.append(fs[fs.size() - 1]["p"])
lines.append(fs[0]["p"])
draw_lines(lines)
func confirm() -> bool:
var dots: Array = map.cables_from_edges(_edges, centres, offset, sharp, {"radius": radius})
if dots.is_empty():
return false
ModalControls.summary("Added %d cable%s from %d edge%s" % [dots.size(), "" if dots.size() == 1 else "s",
_edges.size(), "" if _edges.size() == 1 else "s"])
return true
func tool_settings() -> Array:
return [
{"id": "points_at", "label": "Points at", "type": "enum", "value": 1 if centres else 0,
"choices": POINTS_AT, "tip": "P: the chain's vertices, or its edge centres (rounds corners off)"},
{"id": "corners", "label": "Corners", "type": "enum", "value": 1 if sharp else 0,
"choices": CORNERS, "tip": "C: round (auto handles) or sharp (vector)"},
{"id": "offset", "label": "Offset", "type": "float", "value": offset, "min": 0.0, "max": 2.0,
"step": 0.01, "tip": "Off the edge along its outward normal (the radius sits the cable on the edge)"},
{"id": "radius", "label": "Radius", "type": "float", "value": radius, "min": 0.005, "max": 2.0,
"step": 0.01},
]
func set_tool_setting(sid: String, value: Variant) -> bool:
match sid:
"points_at":
centres = int(value) == 1
"corners":
sharp = int(value) == 1
"offset":
offset = maxf(0.0, float(value))
"radius":
radius = clampf(float(value), 0.005, 2.0)
_:
return false
if not _frozen:
update_preview()
return true
func adjust_ids() -> Array:
return ["points_at", "corners", "offset", "radius"]
func adjust_snapshot() -> Dictionary:
return {"edges": _edges.duplicate(true), "centres": centres, "sharp": sharp, "offset": offset, "radius": radius}
func adjust_restore(snap: Dictionary) -> void:
_edges = (snap.get("edges", []) as Array).duplicate(true)
centres = bool(snap.get("centres", centres))
sharp = bool(snap.get("sharp", sharp))
offset = float(snap.get("offset", offset))
radius = float(snap.get("radius", radius))
_frozen = true
func adjust_end() -> void:
_frozen = false
clear_preview()