From 8b8d02f0ede118e163df64efc4113a9d1e3f32a2 Mon Sep 17 00:00:00 2001 From: Hugh Carson Date: Tue, 10 Mar 2026 15:17:29 -0400 Subject: [PATCH] Add preextrusion_ops to SolidModelTarget and cavity etch test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a preextrusion_ops field to SolidModelTarget that allows operations (e.g., 2D metal patterning) to run before layer extrusions. This prevents OCC recursive deletion from cascading across dimensions when 3D boolean operations consume surfaces that were shared with 2D groups. Pipeline order is now: preextrusion_ops → extrusion_ops → postrender_ops → intersection_ops Also adds a "Cavity Etch" test that exercises the substrate/cavity/vacuum domain decomposition workflow with overlapping 2D surfaces extruded to different depths, boolean cuts, and 3D meshing. --- src/schematics/solidmodels.jl | 11 ++++- test/test_schematic_solidmodel.jl | 81 +++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+), 2 deletions(-) diff --git a/src/schematics/solidmodels.jl b/src/schematics/solidmodels.jl index ab48f62f..7f2ee46e 100644 --- a/src/schematics/solidmodels.jl +++ b/src/schematics/solidmodels.jl @@ -59,6 +59,7 @@ struct SolidModelTarget <: Target ignored_layers::Vector{Symbol} retained_physical_groups::Vector{Tuple{String, Int}} rendering_options + preextrusion_ops postrenderer end @@ -70,6 +71,7 @@ SolidModelTarget( substrate_layers=[], wave_port_layers=[], ignored_layers=[], + preextrusion_ops=[], postrender_ops=[], retained_physical_groups=[], kwargs... @@ -83,6 +85,7 @@ SolidModelTarget( ignored_layers, retained_physical_groups, (; solidmodel=true, retained_physical_groups=retained_physical_groups, kwargs...), + preextrusion_ops, postrender_ops ) @@ -246,8 +249,12 @@ function render!(sm::SolidModel, sch::Schematic, target::Target; strict=:error, # Extrusions # Target specific actions # Intersections with rendered volume - postrender_ops = - vcat(extrusion_ops(target, sch), target.postrenderer, intersection_ops(target, sch)) + postrender_ops = vcat( + target.preextrusion_ops, + extrusion_ops(target, sch), + target.postrenderer, + intersection_ops(target, sch) + ) reopen_logfile(sch, :render_solidmodel) with_logger(sch.logger) do return render!( diff --git a/test/test_schematic_solidmodel.jl b/test/test_schematic_solidmodel.jl index 32a20350..c8728f2e 100644 --- a/test/test_schematic_solidmodel.jl +++ b/test/test_schematic_solidmodel.jl @@ -1189,3 +1189,84 @@ end ) end end + +@testitem "Schematic + SolidModel + Cavity Etch" setup = [CommonTestSetup] begin + using .SchematicDrivenLayout + + # Reproducer for Gmsh booleanOperator preserve-numbering bugs: + # Mixed-dimension BooleanFragments with overlapping 2D surfaces extruded + # into volumes at different heights, followed by boolean domain decomposition. + # Tests that physical groups survive correctly through the pipeline and that + # 3D meshing produces a valid mesh. + cavity_depth = 20μm + + cs = CoordinateSystem("test") + place!(cs, MeshSized(148 * cavity_depth)(centered(Rectangle(1mm, 1mm))), :cavity) + place!(cs, centered(Rectangle(2mm, 2mm)), :metal_negative) + place!(cs, centered(Rectangle(3mm, 3mm)), :chip_area) + place!(cs, centered(Rectangle(3mm, 3mm)), :simulated_area) + place!(cs, centered(Rectangle(3mm, 3mm)), :writeable_area) + + tech = ProcessTechnology( + (;), + (; + height=(; simulated_area=-1mm), + thickness=(; simulated_area=2mm, chip_area=525μm, cavity=cavity_depth) + ) + ) + + g = SchematicGraph("test") + add_node!(g, BasicComponent(cs)) + sch = plan(g) + check!(sch) + + target = SolidModelTarget( + tech; + simulation=true, + bounding_layers=[], + substrate_layers=[:chip_area, :cavity], + indexed_layers=[], + wave_port_layers=[], + ignored_layers=[], + postrender_ops=[ + ( + "substrate", + SolidModels.difference_geom!, + ("chip_area_extrusion", "cavity_extrusion", 3, 3), + :remove_object => true, + :remove_tool => true + ), + ( + "vacuum", + SolidModels.difference_geom!, + ("simulated_area_extrusion", "substrate", 3, 3) + ) + ], + retained_physical_groups=[ + ("vacuum", 3), + ("substrate", 3), + ("cavity", 3), + ("metal", 2) + ] + ) + + sm = SolidModel("test"; overwrite=true) + @testset "Cavity etch render" begin + @test_nowarn render!(sm, sch, target) + end + + @testset "Physical groups" begin + @test SolidModels.hasgroup(sm, "vacuum", 3) + @test SolidModels.hasgroup(sm, "substrate", 3) + @test length(SolidModels.entitytags(sm["vacuum", 3])) >= 1 + @test length(SolidModels.entitytags(sm["substrate", 3])) >= 1 + end + + @testset "3D meshing" begin + @test_nowarn SolidModels.gmsh.model.mesh.generate(3) + ntets = SolidModels.gmsh.option.get_number("Mesh.NbTetrahedra") + @test ntets > 0 + end + + SolidModels.gmsh.finalize() +end