From 158abe6426ffc28d67717f856b8a7debe576d184 Mon Sep 17 00:00:00 2001 From: Carlos Villagrasa Date: Sat, 11 Jul 2026 19:30:48 +0200 Subject: [PATCH] Allow for IfcSurfaceStyleShading.Transparency (IFC4+) GetColor only read Transparency for IfcSurfaceStyleRendering; a plain IfcSurfaceStyleShading always came out opaque because only its SurfaceColour was read and IfcColourRgb hardcodes alpha 1. Since IFC4, Transparency belongs to IfcSurfaceStyleShading (Rendering inherits it) and sits at argument index 1 in every schema, so the Rendering case handles both entities: merge the two cases. IFC2X3 Shading has a single argument and safely yields a non-REAL token at offset 1, keeping the colour opaque. Adds a spec with inline IFC4 and IFC2X3 models covering Shading with transparency, Rendering with transparency and IFC2X3 plain Shading. --- .../web-ifc/geometry/IfcGeometryLoader.cpp | 6 +- tests/functional/ShadingTransparency.spec.ts | 144 ++++++++++++++++++ 2 files changed, 145 insertions(+), 5 deletions(-) create mode 100644 tests/functional/ShadingTransparency.spec.ts diff --git a/src/cpp/web-ifc/geometry/IfcGeometryLoader.cpp b/src/cpp/web-ifc/geometry/IfcGeometryLoader.cpp index 0a1896772..943364121 100644 --- a/src/cpp/web-ifc/geometry/IfcGeometryLoader.cpp +++ b/src/cpp/web-ifc/geometry/IfcGeometryLoader.cpp @@ -1026,6 +1026,7 @@ namespace webifc::geometry return {}; } case schema::IFCSURFACESTYLERENDERING: + case schema::IFCSURFACESTYLESHADING: { _loader.MoveToArgumentOffset(expressID, 0); auto outputColor = GetColor(_loader.GetRefArgument()); @@ -1039,11 +1040,6 @@ namespace webifc::geometry return outputColor; } - case schema::IFCSURFACESTYLESHADING: - { - _loader.MoveToArgumentOffset(expressID, 0); - return GetColor(_loader.GetRefArgument()); - } case schema::IFCSTYLEDREPRESENTATION: { _loader.MoveToArgumentOffset(expressID, 3); diff --git a/tests/functional/ShadingTransparency.spec.ts b/tests/functional/ShadingTransparency.spec.ts new file mode 100644 index 000000000..6a172aac6 --- /dev/null +++ b/tests/functional/ShadingTransparency.spec.ts @@ -0,0 +1,144 @@ +import * as WebIFC from '../../dist/web-ifc-api-node.js'; +import {IFCBUILDINGELEMENTPROXY} from '../../dist/web-ifc-api-node.js'; + +import type {IfcAPI} from '../../dist/web-ifc-api-node.js'; + +const SHADING_TRANSPARENCY_IFC4 = `ISO-10303-21; +HEADER; +FILE_DESCRIPTION(('ViewDefinition [CoordinationView]'),'2;1'); +FILE_NAME('','',(''),(''),'','',''); +FILE_SCHEMA(('IFC4')); +ENDSEC; +DATA; +#1=IFCCARTESIANPOINT((0.,0.,0.)); +#2=IFCDIRECTION((0.,0.,1.)); +#3=IFCDIRECTION((1.,0.,0.)); +#4=IFCAXIS2PLACEMENT3D(#1,#2,#3); +#5=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.0000000000000001E-05,#4,$); +#6=IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.); +#7=IFCUNITASSIGNMENT((#6)); +#8=IFCPROJECT('1x7xm5lGf9o8yVrMp7_oY3',$,'Shading transparency repro',$,$,$,$,(#5),#7); +#9=IFCAXIS2PLACEMENT3D(#1,$,$); +#10=IFCLOCALPLACEMENT($,#9); +#11=IFCSITE('2zqnrS0557_gMa3Jzg7KCA',$,'Site',$,$,#10,$,$,.ELEMENT.,$,$,$,$,$); +#12=IFCRELAGGREGATES('3bVA_CHvz6GQIw9DLQws4X',$,$,$,#8,(#11)); +#13=IFCCOLOURRGB($,1.,0.,0.); +#14=IFCSURFACESTYLESHADING(#13,0.80000000000000004); +#15=IFCSURFACESTYLE('shading with transparency',.BOTH.,(#14)); +#16=IFCSURFACESTYLERENDERING(#13,0.80000000000000004,$,$,$,$,$,$,.NOTDEFINED.); +#17=IFCSURFACESTYLE('rendering with transparency',.BOTH.,(#16)); +#18=IFCRECTANGLEPROFILEDEF(.AREA.,$,$,1.,1.); +#19=IFCDIRECTION((0.,0.,1.)); +#20=IFCEXTRUDEDAREASOLID(#18,$,#19,1.); +#21=IFCSTYLEDITEM(#20,(#15),$); +#22=IFCSHAPEREPRESENTATION(#5,'Body','SweptSolid',(#20)); +#23=IFCPRODUCTDEFINITIONSHAPE($,$,(#22)); +#24=IFCCARTESIANPOINT((0.,0.,0.)); +#25=IFCAXIS2PLACEMENT3D(#24,$,$); +#26=IFCLOCALPLACEMENT(#10,#25); +#27=IFCBUILDINGELEMENTPROXY('1$N9WPo3bE6wRF4P$YuzAX',$,'shading box',$,$,#26,#23,$,$); +#28=IFCRECTANGLEPROFILEDEF(.AREA.,$,$,1.,1.); +#29=IFCDIRECTION((0.,0.,1.)); +#30=IFCEXTRUDEDAREASOLID(#28,$,#29,1.); +#31=IFCSTYLEDITEM(#30,(#17),$); +#32=IFCSHAPEREPRESENTATION(#5,'Body','SweptSolid',(#30)); +#33=IFCPRODUCTDEFINITIONSHAPE($,$,(#32)); +#34=IFCCARTESIANPOINT((3.,0.,0.)); +#35=IFCAXIS2PLACEMENT3D(#34,$,$); +#36=IFCLOCALPLACEMENT(#10,#35); +#37=IFCBUILDINGELEMENTPROXY('0wKFqjAy15eQieFk7gc1RM',$,'rendering box',$,$,#36,#33,$,$); +#38=IFCRELCONTAINEDINSPATIALSTRUCTURE('1EGVveoy18y8ZUzzCxJAQ7',$,$,$,(#27,#37),#11); +ENDSEC; +END-ISO-10303-21; +`; + +const SHADING_IFC2X3 = `ISO-10303-21; +HEADER; +FILE_DESCRIPTION(('ViewDefinition [CoordinationView]'),'2;1'); +FILE_NAME('','',(''),(''),'','',''); +FILE_SCHEMA(('IFC2X3')); +ENDSEC; +DATA; +#1=IFCCARTESIANPOINT((0.,0.,0.)); +#2=IFCDIRECTION((0.,0.,1.)); +#3=IFCDIRECTION((1.,0.,0.)); +#4=IFCAXIS2PLACEMENT3D(#1,#2,#3); +#5=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.0000000000000001E-05,#4,$); +#6=IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.); +#7=IFCUNITASSIGNMENT((#6)); +#8=IFCPERSON($,$,$,$,$,$,$,$); +#9=IFCORGANIZATION($,'org',$,$,$); +#10=IFCPERSONANDORGANIZATION(#8,#9,$); +#11=IFCORGANIZATION($,'org',$,$,$); +#12=IFCAPPLICATION(#11,'1','app','app'); +#13=IFCOWNERHISTORY(#10,#12,$,.ADDED.,$,$,$,0); +#14=IFCPROJECT('1pHRBOsmT8txw9xBa7JqLd',#13,'IFC2X3 shading repro',$,$,$,$,(#5),#7); +#15=IFCAXIS2PLACEMENT3D(#1,$,$); +#16=IFCLOCALPLACEMENT($,#15); +#17=IFCSITE('29FHeh_Yr3af5Ouh7msCzT',#13,'Site',$,$,#16,$,$,.ELEMENT.,$,$,$,$,$); +#18=IFCRELAGGREGATES('0$9Z_B8ATFjPYckHxMI5Yk',#13,$,$,#14,(#17)); +#19=IFCCOLOURRGB($,0.,1.,0.); +#20=IFCSURFACESTYLESHADING(#19); +#21=IFCSURFACESTYLE('plain IFC2X3 shading',.BOTH.,(#20)); +#22=IFCCARTESIANPOINT((0.,0.)); +#23=IFCAXIS2PLACEMENT2D(#22,$); +#24=IFCRECTANGLEPROFILEDEF(.AREA.,$,#23,1.,1.); +#25=IFCAXIS2PLACEMENT3D(#1,$,$); +#26=IFCDIRECTION((0.,0.,1.)); +#27=IFCEXTRUDEDAREASOLID(#24,#25,#26,1.); +#28=IFCPRESENTATIONSTYLEASSIGNMENT((#21)); +#29=IFCSTYLEDITEM(#27,(#28),$); +#30=IFCSHAPEREPRESENTATION(#5,'Body','SweptSolid',(#27)); +#31=IFCPRODUCTDEFINITIONSHAPE($,$,(#30)); +#32=IFCAXIS2PLACEMENT3D(#1,$,$); +#33=IFCLOCALPLACEMENT(#16,#32); +#34=IFCBUILDINGELEMENTPROXY('2RLsNYqGPD4wePf6E53no2',#13,'ifc2x3 shading box',$,$,#33,#31,$,$); +#35=IFCRELCONTAINEDINSPATIALSTRUCTURE('2DCzxJhfT8GvuOYkp9pSos',#13,$,$,(#34),#17); +ENDSEC; +END-ISO-10303-21; +`; + +let ifcApi: IfcAPI; + +function colorOf(modelID: number, nameFragment: string) { + const ids = ifcApi.GetLineIDsWithType(modelID, IFCBUILDINGELEMENTPROXY); + for (let i = 0; i < ids.size(); i++) { + const id = ids.get(i); + if (!ifcApi.GetLine(modelID, id).Name.value.includes(nameFragment)) continue; + return ifcApi.GetFlatMesh(modelID, id).geometries.get(0).color; + } + throw new Error(`no element matching "${nameFragment}"`); +} + +function open(content: string): number { + return ifcApi.OpenModel(new TextEncoder().encode(content)); +} + +beforeAll(async () => { + ifcApi = new WebIFC.IfcAPI(); + await ifcApi.Init(); + ifcApi.SetLogLevel(WebIFC.LogLevel.LOG_LEVEL_OFF); +}); + +describe('IfcSurfaceStyleShading transparency', () => { + test('IFC4 plain Shading Transparency is applied to the mesh color alpha', () => { + const modelID = open(SHADING_TRANSPARENCY_IFC4); + const color = colorOf(modelID, 'shading box'); + expect(color.w).toBeCloseTo(0.2, 5); + ifcApi.CloseModel(modelID); + }); + + test('IFC4 Rendering Transparency keeps working the same way', () => { + const modelID = open(SHADING_TRANSPARENCY_IFC4); + const color = colorOf(modelID, 'rendering box'); + expect(color.w).toBeCloseTo(0.2, 5); + ifcApi.CloseModel(modelID); + }); + + test('IFC2X3 Shading (no Transparency attribute) stays opaque', () => { + const modelID = open(SHADING_IFC2X3); + const color = colorOf(modelID, 'ifc2x3 shading box'); + expect(color.w).toBeCloseTo(1.0, 5); + ifcApi.CloseModel(modelID); + }); +});