port: SVG edge classification (#3668) onto wgpu [worklist #114-120]

Ports the 7-commit v0.8.0 SVG edge-classification feature (f0970b90b +
6 follow-ups) onto wgpu's heavily-diverged serializer. Reconstructed
block-by-block rather than merged, because both sides rewrote
SvgSerializer (v0.8.0 +419, wgpu +778) and git's conflict alignment was
misleading.

Key wgpu adaptations reasoned per block:
- IfcUtil::IfcBaseEntity* (pointer identity) -> express::Base (value),
  incl. as a std::map key in draw_hlr (express::Base has operator<);
  nullptr fallback -> express::Base{}
- boost::optional -> std::optional (css_class, dash_array)
- hlr_calc::result_type pair -> 3-tuple (adds per-edge class label)
- draw_hlr restructured with a group_by_product map: ONE path_object per
  product so multiple class buckets share a group and per-path classes
  survive Bonsai's merge (NOT naive per-item threading, which fragments
  groups -- caught during visual verification)
- settings wired into wgpu's apply_settings() (ctor-called), NOT the
  feature's added ready() call which wgpu already solved differently
- logger_.Warning -> logger().warning in write(geometry_data)

Verified end-to-end via IfcConvert on a curved-geometry stress scene
(Suzanne/Torus/Sphere/Cube/Cone/...): edges classify into outline/sharp/
crease/boundary/flush with correct per-product grouping and CSS styling.
Requires the model's storey to carry an Elevation and --svg-project to
trigger the HLR projection.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Dion Moult
2026-07-25 20:49:05 +10:00
parent 43fc405b58
commit a341ad29f3
11 changed files with 635 additions and 71 deletions
+45
View File
@@ -112,6 +112,51 @@ class TestImportCameraProps(NewFile):
assert camera.shift_x == 0.0
assert camera.shift_y == 0.0
def test_defaults_edge_classification_props_when_pset_is_absent(self):
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
drawing = ifc.createIfcAnnotation(ObjectType="DRAWING")
camera = bpy.data.cameras.new("Camera")
subject.import_camera_props(drawing, camera)
props = subject.get_camera_props(camera)
assert props.use_edge_classification is False
assert props.render_creases is True
assert props.valley_angle_min_degrees == pytest.approx(12.0)
assert props.render_sharp is True
assert props.ridge_angle_min_degrees == pytest.approx(45.0)
assert props.render_flush is False
def test_imports_edge_classification_props_from_drawing_pset(self):
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
drawing = ifc.createIfcAnnotation(ObjectType="DRAWING")
pset = ifcopenshell.api.pset.add_pset(ifc, product=drawing, name="EPset_Drawing")
ifcopenshell.api.pset.edit_pset(
ifc,
pset=pset,
properties={
"UseEdgeClassification": True,
"RenderCreases": False,
"ValleyAngleMinDegrees": 8.0,
"RenderSharp": False,
"RidgeAngleMinDegrees": 30.0,
"RenderFlush": True,
},
)
camera = bpy.data.cameras.new("Camera")
subject.import_camera_props(drawing, camera)
props = subject.get_camera_props(camera)
assert props.use_edge_classification is True
assert props.render_creases is False
assert props.valley_angle_min_degrees == pytest.approx(8.0)
assert props.render_sharp is False
assert props.ridge_angle_min_degrees == pytest.approx(30.0)
assert props.render_flush is True
class TestSyncPerspectiveCameraShifts(NewFile):
def test_round_trips_perspective_camera_shifts_through_drawing_pset(self):