mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-11 18:16:40 +00:00
Use object local stacking axis for camera-face layer detection
Replace the camera_looks_up flag (plan vs RCP) and dominant-face- normal heuristic with a unified local-axis projection for all LayerSetDirections: AXIS3 (slabs/roofs) → object local Z (col[2]) AXIS2 (walls) → object local Y (col[1]) AXIS1 (rare walls) → object local X (col[0]) The camera look vector is projected onto the object's world-space stacking axis; a negative dot product means the camera is on the positive (exterior) side of the layer sequence, a positive dot means it is on the negative (interior) side. This correctly handles sloped AXIS3 slabs viewed by two plan-type cameras from opposite sides — the tilted local Z produces different dot products for each camera direction, where the old camera_looks_up flag returned the same result for both. Generated with the assistance of an AI coding tool.
This commit is contained in:
@@ -1940,17 +1940,27 @@ class CreateDrawing(bpy.types.Operator):
|
||||
mats = ifcopenshell.util.element.get_materials(element)
|
||||
return tuple(sorted(m.id() for m in mats)) if mats else ()
|
||||
|
||||
# Camera look direction (unit vector pointing from camera into scene)
|
||||
_cam_look = -self.camera.matrix_world.col[2].to_3d().normalized()
|
||||
|
||||
def get_camera_face_layer_id(guid):
|
||||
"""Return the material ID of the layer the camera sees for this element.
|
||||
|
||||
For IfcMaterialLayerSetUsage elements with AXIS3 (vertical stacking —
|
||||
slabs, roofs), the visible face depends on camera direction:
|
||||
- Plan view (camera looks down): the top layer is visible.
|
||||
- RCP (camera looks up): the bottom layer is visible.
|
||||
DirectionSense POSITIVE means layers stack upward (Layer[0] = bottom,
|
||||
Layer[-1] = top). NEGATIVE means layers stack downward (Layer[0] = top).
|
||||
Returns None for walls (non-AXIS3) or elements without layer usage —
|
||||
these fall back to the generic boundary-ends check.
|
||||
For IfcMaterialLayerSetUsage elements the camera-facing layer depends
|
||||
on the LayerSetDirection and which side of the element the camera is on:
|
||||
|
||||
AXIS3 (slabs/roofs — vertical stacking):
|
||||
Plan view (camera looks down) → top layer visible.
|
||||
RCP (camera looks up) → bottom layer visible.
|
||||
|
||||
AXIS1/AXIS2 (walls — horizontal stacking):
|
||||
The dominant face normal is computed for the element. If the camera
|
||||
look direction is opposite to the face normal the camera sees the
|
||||
face-normal side (the "positive" face). Otherwise it sees the
|
||||
"negative" face (the interior side of the layer sequence).
|
||||
|
||||
DirectionSense POSITIVE: Layer[0] is the negative/interior face,
|
||||
Layer[-1] is the positive/exterior face. NEGATIVE reverses this.
|
||||
"""
|
||||
element = self.get_element_by_guid(guid)
|
||||
if element is None:
|
||||
@@ -1958,8 +1968,6 @@ class CreateDrawing(bpy.types.Operator):
|
||||
material = ifcopenshell.util.element.get_material(element)
|
||||
if material is None or not material.is_a("IfcMaterialLayerSetUsage"):
|
||||
return None
|
||||
if material.LayerSetDirection != "AXIS3":
|
||||
return None
|
||||
layer_set = material.ForLayerSet
|
||||
if layer_set is None:
|
||||
return None
|
||||
@@ -1967,13 +1975,32 @@ class CreateDrawing(bpy.types.Operator):
|
||||
if not layers:
|
||||
return None
|
||||
positive = material.DirectionSense == "POSITIVE"
|
||||
# POSITIVE: Layer[0]=bottom, Layer[-1]=top
|
||||
# NEGATIVE: Layer[0]=top, Layer[-1]=bottom
|
||||
if camera_looks_up:
|
||||
face_layer = layers[0] if positive else layers[-1]
|
||||
else:
|
||||
face_layer = layers[-1] if positive else layers[0]
|
||||
return face_layer.Material.id()
|
||||
layer_dir = material.LayerSetDirection
|
||||
|
||||
# For all LayerSetDirections, determine which side of the element
|
||||
# the camera is on by projecting the camera look vector onto the
|
||||
# object's local stacking axis in world space:
|
||||
# AXIS3 → stacking along local Z (col[2]) — slabs/roofs
|
||||
# AXIS2 → stacking along local Y (col[1]) — most walls
|
||||
# AXIS1 → stacking along local X (col[0]) — rare walls
|
||||
# DirectionSense POSITIVE: Layer[-1] is on the +axis side.
|
||||
# dot < 0 → camera look is opposite to stacking axis
|
||||
# → camera is on the +axis (positive/exterior) side.
|
||||
# This correctly handles sloped slabs seen by two plan-view
|
||||
# cameras from opposite sides, unlike the simpler camera_looks_up
|
||||
# flag which cannot distinguish them.
|
||||
col_idx = {"AXIS1": 0, "AXIS2": 1, "AXIS3": 2}.get(layer_dir)
|
||||
if col_idx is not None:
|
||||
obj = get_obj(guid)
|
||||
if obj is None or obj.type != "MESH":
|
||||
return None
|
||||
stack_axis = obj.matrix_world.col[col_idx].to_3d().normalized()
|
||||
dot = _cam_look.dot(stack_axis)
|
||||
on_positive_side = dot < 0
|
||||
face_layer = (layers[-1] if positive else layers[0]) if on_positive_side else (layers[0] if positive else layers[-1])
|
||||
return face_layer.Material.id()
|
||||
|
||||
return None
|
||||
|
||||
def mat_keys_match(a, b, face_a=None, face_b=None):
|
||||
"""True if two ordered layer-material tuples represent compatible assemblies.
|
||||
|
||||
Reference in New Issue
Block a user