| … | … | @@ -429,6 +429,10 @@ const DEFAULTS: Dictionary = { |
| 429 | 429 | "builder.reset_panels": {"key": KEY_N, "ctrl": true, "alt": true, "label": "Reset panel layout (every panel back to its column)"}, |
| 430 | 430 | # #120 L2 §13: BoxCutter's own start key (its manual: "alt + w with cursor in 3d view"). |
| 431 | 431 | "builder.box_cutter": {"key": KEY_W, "alt": true, "label": "BoxCutter: draw a cutter on the aimed surface"}, |
| 432 | # #120 L2 §13.4–13.5: the Q menu's stack operations (StackOps), each with a key. | |
| 433 | "builder.change_cut": {"key": KEY_X, "alt": true, "label": "Change cut… (subtract / slice / intersect / inset / outset)"}, | |
| 434 | "builder.extract_cut": {"key": KEY_Y, "alt": true, "label": "Extract… (a cut's target ∩ cutter becomes new shapes)"}, | |
| 435 | "builder.apply_up_to": {"key": KEY_A, "ctrl": true, "alt": true, "label": "Apply up to here… (bake the cuts up to one, keep the rest live)"}, | |
| 432 | 436 | "builder.adjust_last": {"key": KEY_F6, "label": "Adjust Last (the last operation's settings)"}, |
| 433 | 437 | "builder.history": {"key": KEY_Z, "ctrl": true, "alt": true, "label": "Undo History"}, |
| 434 | 438 | "builder.repeat_last": {"key": KEY_R, "shift": true, "label": "Repeat the last command (starts the tool again)"}, |
| … | … | @@ -610,6 +610,11 @@ func q_sections() -> Array: |
| 610 | 610 | rows.append(["builder.make_parametric"]) |
| 611 | 611 | if not op_stack(subject).is_empty(): |
| 612 | 612 | rows.append(["builder.cuts_clear"]) |
| 613 | # #120 L2: the stack operations (StackOps); Apply works on shapes, as Make Real does. | |
| 614 | rows.append(["builder.change_cut"]) | |
| 615 | rows.append(["builder.extract_cut"]) | |
| 616 | if is_shape: | |
| 617 | rows.append(["builder.apply_up_to"]) | |
| 613 | 618 | rows.append_array([["builder.hide"], ["builder.hide_unselected"], ["builder.delete"]]) |
| 614 | 619 | return [["Shape" if is_shape else "Selection", rows], cursor_section()] |
| 615 | 620 | |
| … | … | @@ -4353,6 +4358,27 @@ func _builder_key(event: InputEvent) -> void: |
| 4353 | 4358 | rows.append([k.capitalize(), func() -> void: add_modifier(k), MOD_KEYS.get(k, 0)]) |
| 4354 | 4359 | _q_menu.open_choices("Add modifier", rows, get_viewport().get_mouse_position()) |
| 4355 | 4360 | return |
| 4361 | # #120 L2 §13.4–13.5: Change cut / Extract / Apply up to here — each picks one of the | |
| 4362 | # subject's cuts from a list (StackOps). | |
| 4363 | for sa: Array in [["builder.change_cut", "Change cut"], ["builder.extract_cut", "Extract"], | |
| 4364 | ["builder.apply_up_to", "Apply up to here"]]: | |
| 4365 | if not Keymap.tapped(event, String(sa[0])): | |
| 4366 | continue | |
| 4367 | var so := preload("res://scenes/maps/stack_ops.gd") | |
| 4368 | var subj := stack_subject() | |
| 4369 | var srows: Array = [] | |
| 4370 | match String(sa[0]): | |
| 4371 | "builder.change_cut": | |
| 4372 | srows = so.change_rows(self, subj) | |
| 4373 | "builder.extract_cut": | |
| 4374 | srows = so.op_rows(self, subj, so.extract) | |
| 4375 | _: | |
| 4376 | srows = so.op_rows(self, subj, so.apply_up_to) | |
| 4377 | if srows.is_empty(): | |
| 4378 | print("[MapDCLevel] %s: the selection has no cuts" % String(sa[1]).to_lower()) | |
| 4379 | elif _q_menu != null: | |
| 4380 | _q_menu.open_choices(String(sa[1]), srows, get_viewport().get_mouse_position()) | |
| 4381 | return | |
| 4356 | 4382 | if Keymap.tapped(event, "builder.assign_generator"): |
| 4357 | 4383 | if _sel_shape == "": |
| 4358 | 4384 | print("[MapDCLevel] assign generator: select a shape (Make parametric turns a brush into one)") |
| … | … | @@ -39,7 +39,7 @@ func _ready() -> void: |
| 39 | 39 | await get_tree().process_frame |
| 40 | 40 | await _run() |
| 41 | 41 | get_tree().root.get_node("/root/GameMode").set_mode("play") |
| 42 | if checks < 11: | |
| 42 | if checks < 13: | |
| 43 | 43 | print(" **FAIL** only %d checks ran -- setup died before the assertions" % checks) |
| 44 | 44 | fails += 1 |
| 45 | 45 | print("\nGATE q_stack: %s (%d checks, %d failed)" % ["PASS" if fails == 0 else "FAIL", checks, fails]) |
| … | … | @@ -147,6 +147,27 @@ func _run() -> void: |
| 147 | 147 | _ok("Q4 one Ctrl+Z: the shape is live again, both cuts live", (persist.live_shapes() as Dictionary).has(t3) |
| 148 | 148 | and not bool(LevelPersistence.OpFold.resolve_states(persist.records(false))[oa]["applied"])) |
| 149 | 149 | |
| 150 | # ---- Q6 the Q menu and the keys ---- | |
| 151 | map.select_shape(t2) | |
| 152 | await get_tree().process_frame | |
| 153 | var acts: Array = [] | |
| 154 | for sec: Array in (map.q_sections() as Array): | |
| 155 | for row: Array in (sec[1] as Array): | |
| 156 | acts.append(String(row[0])) | |
| 157 | _ok("Q6 Q on a shape with cuts offers Change cut, Extract, Apply up to here", | |
| 158 | acts.has("builder.change_cut") and acts.has("builder.extract_cut") and acts.has("builder.apply_up_to"), acts) | |
| 159 | var ax := InputEventKey.new() | |
| 160 | ax.keycode = KEY_X | |
| 161 | ax.physical_keycode = KEY_X | |
| 162 | ax.alt_pressed = true | |
| 163 | ax.pressed = true | |
| 164 | get_viewport().push_input(ax) | |
| 165 | await get_tree().process_frame | |
| 166 | var q: Node = map.get("_q_menu") | |
| 167 | _ok("Q6 ... Alt+X opens Change cut", q.is_open()) | |
| 168 | (q.get("_menu") as PopupMenu).hide() | |
| 169 | map.select_brushes(PackedInt32Array()) | |
| 170 | ||
| 150 | 171 | # ---- Q5 BoxCutter Y ---- |
| 151 | 172 | map._ensure_ops() |
| 152 | 173 | var op: Node = map.get_node("BoxCutter") |