Skip to content

Commit aca2fc2

Browse files
committed
Sync from main@102ac01
Automation commits included: 102ac01 fix(roof-seam-generator): v1.1.1 — use centroid proximity for BaseObject face matching ece3c92 feat(roof-seam-generator): v1.1.0 — auto-unwrap ShingledRoof compounds 9a5e982 fix(roof-seam-generator): v1.0.1 — remove sys.exit(), improve selection error 1c8bc20 feat(roof-seam-generator): v1.0.0 — hip caps and valley flashing 4b13136 fix(shingle-generator): v5.0.2 — fix origin selection and clip volume 629e898 fix(brick-generator): v5.1.0 — Report View logging, robust face remap, false bay boundary fix 926df87 feat(brick-generator): v5.0.0 — engraved mortar approach replaces outward extrusion 7c9d017 feat(brick-generator): v4.4.0 — horizontal face support (ledges, caps, sills) 0c5a296 Add push-github.sh: sync non-LFS automation paths to github 569e9e9 Fix radial_brick: translate bricks to cone/cylinder center XY position 6c1eaeb Fix get_global_face: use transformShape not transformGeometry 0f4c132 Fix get_global_face: recover Part.Face after transformGeometry 640d657 Fix radial_brick: apply global placement for objects in Part containers 7db0f3d Add shared freecad_utils library; fix brick placement in Part containers a626634 fix(brick-generator): loud warnings for missing spreadsheet params 002c3ab fix(brick,radial-brick): install geometry to _lib/ not named subdirectory d4c8716 fix(repo): clean up .gitattributes and .gitignore 157751c fix(smart-trim): detect versioned FreeCAD macro dir (v1-2, v1-1, etc.) 154d12f fix(smart-trim): installer uses __file__ dir instead of cwd bc37116 feat(brick-generator): v5.0.0 — queen closers, exact wall width, header alignment 57508ae Add terrain generator: procedural HO scale scenery substrate engine (FCStd/LFS model files not included — stored on gitea)
1 parent 5d59718 commit aca2fc2

1 file changed

Lines changed: 45 additions & 23 deletions

File tree

automation/generators/roof_seam_generator/roof_seam_generator.FCMacro

Lines changed: 45 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
11
"""
2-
Roof Seam Generator for FreeCAD v1.1.0
2+
Roof Seam Generator for FreeCAD v1.1.1
33
Generates hip cap shingles or valley flashing between two selected roof faces.
44

55
Author: Brian (with Claude assistance)
66
Usage: Ctrl+click two adjacent roof faces, run as macro in FreeCAD
77

88
Version History:
9+
- 1.1.1: Fix wrong-seam selection when both ShingledRoof compounds share the
10+
same BaseObject (e.g. two slopes of the same roof solid). The old
11+
brute-force search over all BaseObject face pairs found the first
12+
shared edge in the solid rather than the one near the selected
13+
shingles. Now uses centroid proximity to map each selected shingle
14+
face to its closest BaseObject roof-plane face, then checks only
15+
that specific pair.
916
- 1.1.0: Auto-unwrap ShingledRoof compounds via BaseObject so the macro works
1017
when the user clicks on individual shingle faces rather than the
1118
original roof faces. resolve_shared_edge() tries raw faces first,
@@ -45,7 +52,7 @@ from FreeCAD import Vector
4552
import math
4653
import sys
4754

48-
VERSION = "1.1.0"
55+
VERSION = "1.1.1"
4956
GENERATOR_NAME = "roof_seam_generator"
5057

5158

@@ -162,6 +169,24 @@ def classify_seam(face1, face2, shared_edge):
162169
return 'hip'
163170

164171

172+
def closest_base_face(orig_face, base_faces):
173+
"""
174+
Return the face in base_faces whose centroid is closest to orig_face's
175+
centroid. Used to map a selected shingle face back to the roof-plane
176+
face it was generated from.
177+
"""
178+
orig_center = orig_face.CenterOfMass
179+
best = None
180+
best_dist = float('inf')
181+
for f in base_faces:
182+
d = orig_center.distanceToPoint(f.CenterOfMass)
183+
if d < best_dist:
184+
best_dist = d
185+
best = f
186+
App.Console.PrintMessage(f" → best base face centroid dist: {best_dist:.3f} mm\n")
187+
return best
188+
189+
165190
def resolve_shared_edge(face1, obj1, face2, obj2):
166191
"""
167192
Find the shared edge between two faces, automatically unwrapping
@@ -176,8 +201,11 @@ def resolve_shared_edge(face1, obj1, face2, obj2):
176201
Strategy:
177202
1. Try the raw selected faces (fast path — works if user selected from
178203
the original roof geometry).
179-
2. If that fails and either object is a ShingledRoof compound, collect
180-
all faces of each BaseObject and try every pair.
204+
2. If either object is a ShingledRoof compound, find the single
205+
BaseObject face closest (by centroid) to each selected shingle face,
206+
then check just that pair. Proximity correctly maps each shingle to
207+
the roof plane it sits on — avoiding the wrong-edge problem that
208+
occurs when brute-forcing all face pairs of the same base solid.
181209

182210
Returns:
183211
(shared_edge, resolved_face1, resolved_obj1, resolved_face2, resolved_obj2)
@@ -188,9 +216,8 @@ def resolve_shared_edge(face1, obj1, face2, obj2):
188216
if edge is not None:
189217
return edge, face1, obj1, face2, obj2
190218

191-
# Build candidate face lists, expanding compounds to their BaseObject
192-
def candidates(face, obj):
193-
result = [(face, obj)]
219+
# Unwrap each compound to its single best-matching BaseObject face
220+
def get_base(face, obj):
194221
if (obj.TypeId == 'Part::Compound'
195222
and hasattr(obj, 'BaseObject')
196223
and obj.BaseObject is not None):
@@ -199,22 +226,17 @@ def resolve_shared_edge(face1, obj1, face2, obj2):
199226
f" Unwrapping {obj.Name} → BaseObject={base.Name}"
200227
f" ({len(base.Shape.Faces)} face(s))\n"
201228
)
202-
result += [(f, base) for f in base.Shape.Faces]
203-
return result
204-
205-
cands1 = candidates(face1, obj1)
206-
cands2 = candidates(face2, obj2)
207-
208-
for f1, o1 in cands1:
209-
for f2, o2 in cands2:
210-
if f1 is face1 and f2 is face2:
211-
continue # already tried above
212-
edge = find_shared_edge(f1, f2)
213-
if edge is not None:
214-
App.Console.PrintMessage(
215-
f" Found shared edge via BaseObject resolution\n"
216-
)
217-
return edge, f1, o1, f2, o2
229+
return closest_base_face(face, base.Shape.Faces), base
230+
return face, obj
231+
232+
f1, o1 = get_base(face1, obj1)
233+
f2, o2 = get_base(face2, obj2)
234+
235+
if f1 is not face1 or f2 is not face2:
236+
edge = find_shared_edge(f1, f2)
237+
if edge is not None:
238+
App.Console.PrintMessage(" Found shared edge via BaseObject resolution\n")
239+
return edge, f1, o1, f2, o2
218240

219241
return None, face1, obj1, face2, obj2
220242

0 commit comments

Comments
 (0)