It seems as though you arbitrarily take the first placement of the first product and use this as the model origin. My advice is not to do this. The reason is that there are often models (typically civil models) that will use a placement of 0,0,0 whilst their representation are at map coordinates. Also the first product might be anything, including 2D alignment segments in IFC4X3. Here are the various scenarios you will encounter:
Small local coordinates for placement, small local coordinates for representations. Hooray!
Large map coordinates for placement, small local coordinates for representation. Annoying, but easy to offset.
Small local coordinates for placement, large map coordinates for representations. Yikes!
Large coordinates for placement, large coordinates for representation. (Yes, crazy stuff like -1000000 in the placement, then +3000000 in the representation - I've come across this more than I'd like to admit)
Notice we also use create_generic_shape - this handles context prioritisation. There can be multiple representations and so it's important to prioritise visualised bodies over others. I'm not sure how IFCJS handles this but it's something to consider.
In these lines of code:
https://github.com/tudelft3d/ifcgref/blob/58822cad6b7ac50a90b336d9a740ec752383ccc7/app.py#L573-L581
It seems as though you arbitrarily take the first placement of the first product and use this as the model origin. My advice is not to do this. The reason is that there are often models (typically civil models) that will use a placement of 0,0,0 whilst their representation are at map coordinates. Also the first product might be anything, including 2D alignment segments in IFC4X3. Here are the various scenarios you will encounter:
So in general I'd advise using the same approach that we do in the BlenderBIM Add-on when detecting an arbitrary false origin to use: we check the first absolute coordinates of vertex of three meaningful representations prioritised by context. See https://github.com/IfcOpenShell/IfcOpenShell/blob/v0.8.0/src/blenderbim/blenderbim/tool/loader.py#L645-L654 and https://github.com/IfcOpenShell/IfcOpenShell/blob/v0.8.0/src/blenderbim/blenderbim/tool/loader.py#L645-L654
Notice we also use create_generic_shape - this handles context prioritisation. There can be multiple representations and so it's important to prioritise visualised bodies over others. I'm not sure how IFCJS handles this but it's something to consider.