Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion barmesh/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@ Julian’s 3-axis tool-surface mesh. **All geometry here is Z-up** (CAD).
| `tool_contact.gd` | Ball-nose drop along **tool axis −Z** from a point above |
| `draw.gd` | ImmediateMesh; **only** place that converts CAD Z-up → Godot Y-up `(x, z, y)` |

**Conditions** live in `subdiv.gd` (shared ε / stepover / a). Split a live `Bar` with `InsertNodeIntoBarF` when **XY > epsilon** (default 0.01 mm) **and** (**3D length > stepover** 1 mm **or** contact-normal angle **> a** 15°). Cell splits (`MakeBarBetweenNodesF`): walk right-hand rings via `GetBarBackRight`; pick the cell with worst residual to the plane through avg(contact points) with normal avg(contact normals); split with the chord that minimises the worse child residual. Tol: **coplanar_tol** (⊥) + max pairwise normal angle **a**. Insertion XY defaults to midpoint bisection; optional plane-intersect guess brackets z/normal discontinuities along the bar.
**Conditions** live in `subdiv.gd` (shared ε / stepover / a). Split a live `Bar` with `InsertNodeIntoBarF` when **XY > epsilon** (default 0.01 mm) **and** (**3D length > stepover** 1 mm **or** contact-normal angle **> a** 15°). Cell splits (`MakeBarBetweenNodesF`): walk right-hand rings via `GetBarBackRight`; pick the cell with worst residual to the plane through avg(contact points) with normal avg(contact normals); split with the chord that minimises the worse child residual. Tol: **coplanar_tol** (⊥) + max pairwise normal angle **a**. Interactive builds leave cell splits to **Subdivide next N cells** (default N=10); headless/CI still auto-runs up to `max_refine_passes`. Insertion XY defaults to midpoint bisection; optional plane-intersect guess brackets z/normal discontinuities along the bar.

Do not assign `Node.p` to a `Node3D.transform` without `draw.cad_to_godot`.
78 changes: 58 additions & 20 deletions barmesh/draw.gd
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ const Contact = preload("res://barmesh/tool_contact.gd")
@export var stepover_mm: float = 6.0
@export var angle_deg: float = 15.0
@export var max_refine_passes: int = 12
## Auto cell splits after bar refine. Interactive default 0 (Julian 149: button + N).
@export var auto_cell_refine_passes: int = 0
@export var show_bars: bool = true
@export var show_normals: bool = true
@export var include_base_plane: bool = true
Expand All @@ -22,6 +24,7 @@ var _playing: bool = false
var _run_id: int = 0
var _last_bm: BarMesh
var _last_tris: Array = []
var _last_params: Subdiv.Params
var _normals_mi: MeshInstance3D


Expand Down Expand Up @@ -57,6 +60,8 @@ func _ready() -> void:
add_child(_normals_mi)
if DisplayServer.get_name() == "headless":
row_delay_s = 0.0
# CI still needs progressive cell splits without a UI.
auto_cell_refine_passes = max_refine_passes
call_deferred("play_over_part")


Expand Down Expand Up @@ -145,36 +150,69 @@ func _refine_barmesh(bm: BarMesh, R: float, tris: Array, z_plane: float, z_above
_draw_barmesh(bm)
if row_delay_s > 0.0:
await get_tree().create_timer(row_delay_s).timeout
# Cells after bars (Julian 144): worst planar residual → MakeBarBetweenNodesF.
await _refine_cells(bm, params, my_run)
_last_params = params
# Cells after bars (Julian 144/149): optional auto; interactive uses button + N.
await _refine_cells(bm, params, my_run, auto_cell_refine_passes)


## Split up to `count` worst out-of-tolerance cells (Julian CNC 149).
func subdivide_next_cells(count: int) -> int:
if _playing or _last_bm == null or count <= 0:
return 0
var params: Subdiv.Params = _last_params
if params == null:
params = _make_params()
_last_params = params
var done := 0
for _i in range(count):
if not _try_one_cell_split(_last_bm, params):
break
done += 1
_draw_barmesh(_last_bm)
return done


func _refine_cells(bm: BarMesh, params: Subdiv.Params, my_run: int) -> void:
for _pass in range(max_refine_passes):
func _make_params() -> Subdiv.Params:
var params := Subdiv.Params.new()
params.epsilon_m = epsilon_mm * 0.001
params.stepover_m = stepover_mm * 0.001
params.angle_deg = angle_deg
params.coplanar_tol_m = epsilon_mm * 0.001
return params


func _refine_cells(bm: BarMesh, params: Subdiv.Params, my_run: int, passes: int) -> void:
for _pass in range(maxi(passes, 0)):
if my_run != _run_id:
return
if bm.nodes.size() > 8000:
break
var worst: Dictionary = Subdiv.find_worst_cell_seed(bm, params)
if not bool(worst.get("ok", false)):
break
var pick: Dictionary = Subdiv.pick_cell_split_for_make_bar(worst["nodes"], worst["bars"], params)
if not bool(pick.get("ok", false)):
if not _try_one_cell_split(bm, params):
break
var n1: BarMesh.BMNode = pick["node1"]
var n2: BarMesh.BMNode = pick["node2"]
var b1: BarMesh.BMBar = pick["bar1"]
var b2: BarMesh.BMBar = pick["bar2"]
if b1.bbardeleted or b2.bbardeleted:
break
if bm.d_test_colinearity_f(n1, b1, n2, b2) or bm.d_test_colinearity_f(n2, b2, n1, b1):
break
bm.make_bar_between_nodes_f(n1, b1, n2, b2)
_draw_barmesh(bm)
if row_delay_s > 0.0:
await get_tree().create_timer(row_delay_s).timeout


func _try_one_cell_split(bm: BarMesh, params: Subdiv.Params) -> bool:
if bm.nodes.size() > 8000:
return false
var worst: Dictionary = Subdiv.find_worst_cell_seed(bm, params)
if not bool(worst.get("ok", false)):
return false
var pick: Dictionary = Subdiv.pick_cell_split_for_make_bar(worst["nodes"], worst["bars"], params)
if not bool(pick.get("ok", false)):
return false
var n1: BarMesh.BMNode = pick["node1"]
var n2: BarMesh.BMNode = pick["node2"]
var b1: BarMesh.BMBar = pick["bar1"]
var b2: BarMesh.BMBar = pick["bar2"]
if b1.bbardeleted or b2.bbardeleted:
return false
if bm.d_test_colinearity_f(n1, b1, n2, b2) or bm.d_test_colinearity_f(n2, b2, n1, b1):
return false
bm.make_bar_between_nodes_f(n1, b1, n2, b2)
return true


func _grid_parts(lo: float, hi: float) -> int:
var gs: float = maxf(stepover_mm * 0.001, 0.0002)
return clampi(int(ceil((hi - lo) / gs)), 1, 48)
Expand Down
17 changes: 16 additions & 1 deletion scenes/main.tscn
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ script = ExtResource("6")
offset_left = 16.0
offset_top = 16.0
offset_right = 400.0
offset_bottom = 420.0
offset_bottom = 520.0

[node name="VBox" type="VBoxContainer" parent="ToolpathUI/Panel"]
layout_mode = 2
Expand Down Expand Up @@ -156,6 +156,21 @@ text = "Debug folded 2-tris (dihedral)"
layout_mode = 2
text = "Build tool surface"

[node name="CellSplitLabel" type="Label" parent="ToolpathUI/Panel/VBox"]
layout_mode = 2
text = "Next cell splits"

[node name="CellSplitCount" type="SpinBox" parent="ToolpathUI/Panel/VBox"]
layout_mode = 2
min_value = 1.0
max_value = 200.0
step = 1.0
value = 10.0

[node name="CellSplit" type="Button" parent="ToolpathUI/Panel/VBox"]
layout_mode = 2
text = "Subdivide next N cells"

[node name="Status" type="Label" parent="ToolpathUI/Panel/VBox"]
layout_mode = 2
text = "BarMesh only. MMB orbit / Shift+MMB pan."
Expand Down
16 changes: 15 additions & 1 deletion scripts/toolpath_ui.gd
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ signal bake_requested(
sample_step: float,
z_stepdown: float
)
signal cell_subdivide_requested(count: int)

@onready var strategy: OptionButton = $Panel/VBox/Strategy
@onready var tool_radius: SpinBox = $Panel/VBox/ToolRadius
Expand All @@ -16,6 +17,7 @@ signal bake_requested(
@onready var sample_step: SpinBox = $Panel/VBox/SampleStep
@onready var z_stepdown_label: Label = $Panel/VBox/ZStepdownLabel
@onready var z_stepdown: SpinBox = $Panel/VBox/ZStepdown
@onready var cell_split_count: SpinBox = $Panel/VBox/CellSplitCount
@onready var status: Label = $Panel/VBox/Status

func _ready() -> void:
Expand Down Expand Up @@ -45,8 +47,13 @@ func _ready() -> void:
sample_step.value = 0.01
$Panel/VBox/Title.text = "Tool surface (CAD Z-up)"
$Panel/VBox/Bake.text = "Build tool surface"
status.text = "BarMesh refine: epsilon / stepover / angle."
status.text = "BarMesh refine: epsilon / stepover / angle. Cell splits: button."
$Panel/VBox/Bake.pressed.connect(_on_bake)
cell_split_count.min_value = 1
cell_split_count.max_value = 200
cell_split_count.step = 1
cell_split_count.value = 10
$Panel/VBox/CellSplit.pressed.connect(_on_cell_split)
var bars := $Panel/VBox/ShowBars as CheckBox
var norms := $Panel/VBox/ShowNormals as CheckBox
var ortho := $Panel/VBox/Ortho as CheckBox
Expand Down Expand Up @@ -90,5 +97,12 @@ func _on_bake() -> void:
status.text = "Building…"
bake_requested.emit("barmesh", tool_radius.value, stepover.value, sample_step.value, z_stepdown.value)


func _on_cell_split() -> void:
var n := int(cell_split_count.value)
status.text = "Subdividing next %d cells…" % n
cell_subdivide_requested.emit(n)


func set_status(text: String) -> void:
status.text = text
17 changes: 17 additions & 0 deletions scripts/toolsurface_host.gd
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ func _ready() -> void:
var ui := get_node_or_null(ui_path)
if ui and ui.has_signal("bake_requested"):
ui.bake_requested.connect(_on_bake_requested)
if ui and ui.has_signal("cell_subdivide_requested"):
ui.cell_subdivide_requested.connect(_on_cell_subdivide_requested)
if DisplayServer.get_name() != "headless":
call_deferred("_on_bake_requested", "barmesh", 5.0, 6.0, 0.01, 15.0)

Expand All @@ -27,6 +29,21 @@ func _on_bake_requested(
await bake_strategy("barmesh", tool_radius_mm, stepover_mm, epsilon_mm, angle_deg)


func _on_cell_subdivide_requested(count: int) -> void:
var ui := get_node_or_null(ui_path)
var viz := get_node_or_null(barmesh_preview_path)
if viz == null or not viz.has_method("subdivide_next_cells"):
if ui:
ui.set_status("No BarMesh to subdivide yet.")
return
var done: int = int(viz.subdivide_next_cells(count))
if ui:
if done <= 0:
ui.set_status("No more out-of-tolerance cells to split.")
else:
ui.set_status("Split %d cell(s)." % done)


func bake_strategy(
strategy: String,
tool_radius_mm: float = 5.0,
Expand Down