Build a scene

Create structures and endpoints, share materials, set reproducible IDs, attach tags and metadata, and look structures up again.

This guide builds a small indoor scene step by step. Each step adds one feature of Scene and Structure.

Create structures#

A Structure takes a geometry and a material. Everything else is optional.

from witwin.core import Box, PhysicalMaterial, Scene, Structure
 
concrete = PhysicalMaterial(eps_r=5.24, sigma_e=0.05, name="concrete")
glass = PhysicalMaterial(eps_r=6.27, sigma_e=0.004, thickness_m=0.006, name="glass")
 
floor = Structure(Box(position=(0.0, 0.0, -0.1), size=(10.0, 8.0, 0.2)), concrete, name="floor")
north = Structure(Box(position=(0.0, 4.0, 1.5), size=(10.0, 0.2, 3.0)), concrete, name="north wall")
window = Structure(Box(position=(5.0, 0.0, 1.5), size=(0.05, 2.0, 1.2)), glass, name="window")
 
scene = Scene(structures=[floor, north, window])
print(len(scene.structures), [int(s.material_id) for s in scene.structures])
3 [0, 0, 1]

floor and north share the same concrete object, so they share one MaterialId. A scene allows that: one material object may cover many structures. It refuses two different material objects with the same ID.

Note

Build structures with Structure(geometry, material). The convenience method geometry.with_material(material) exists on every primitive, but in 0.4.0 it raises ImportError.

Add endpoints#

Endpoints are transmitters and receivers. AntennaState needs an ID, a role ("tx" or "rx") and a position. A scene rejects two endpoints with the same ID.

from witwin.core import AntennaState
 
access_point = AntennaState(1, "tx", (0.0, 0.0, 2.5), power_w=0.1)
laptop = AntennaState(2, "rx", (3.0, -2.0, 0.8))
 
scene = Scene(structures=[floor, north, window], endpoints=[access_point, laptop])

For orientation, polarization, arrays and receiver grids, see Place antennas and receiver grids.

Use reproducible IDs#

Automatic IDs depend on construction order. When the IDs must be stable across runs (for example because results are stored with them), pass them yourself:

concrete = PhysicalMaterial(eps_r=5.24, sigma_e=0.05, name="concrete", material_id=1)
 
floor = Structure(
    Box(position=(0.0, 0.0, -0.1), size=(10.0, 8.0, 0.2)),
    concrete,
    name="floor",
    structure_id=100,
    assignment_id=100,
    surface_id=100,
)
print(int(floor.structure_id), int(floor.material_id), int(floor.surface_id))
100 1 100

The structure takes its material_id from the material. Pass material_id= to Structure only to override it. See Identity and versions.

Attach tags, metadata and priority#

door = Structure(
    Box(position=(-5.0, 0.0, 1.0), size=(0.05, 0.9, 2.0)),
    PhysicalMaterial(eps_r=2.0, name="wood"),
    name="door",
    tags=("openable", "interior"),
    metadata={"source": "floor plan rev B"},
    priority=1,
    enabled=True,
)
print(door.tags, dict(door.metadata), door.priority)
('openable', 'interior') {'source': 'floor plan rev B'} 1

Core stores tags, metadata, priority and enabled for solvers and tools. It does not act on them. A solver may, for example, use priority to decide which structure wins where two overlap; check the solver's documentation. metadata is a read-only mapping. A Scene also takes metadata=.

Look structures up#

scene = Scene(structures=[floor, door])
 
print(scene.structure(100).name)
for assignment in scene.assignments:
    print(int(assignment.surface_id), assignment.material.name)
floor
100 concrete
101 wood

scene.structure(id) raises KeyError for an unknown ID. scene.assignments lists one MaterialAssignment per structure: which material (and phase screen) covers which surface. The door's surface ID is automatic. It is 101 because reserving 100 for the floor moved the counter past it.

Next steps#