Scene¶
Resolving a model's geometry into one frame, without changing the model.
get_scene(doc) returns every surface it could place, in world coordinates with
the building rotation applied, alongside what it could not place and what it did
not attempt. It reads; it does not author, validate or mutate. Writing the
document before and after yields the same bytes.
The resolution it applies was measured against EnergyPlus's own surface vertex
report rather than reasoned from the input reference, which is why
translate_to_world now agrees with it.
Resolve a model's geometry into one frame, without changing the model.
get_scene(doc) reads a document and returns a :class:Scene: every surface it could place, in
world coordinates with the building rotation applied, plus what it could not place and what it did
not attempt. It modifies nothing.
WHY THIS IS A NEW MODULE AND NOT AN ADDITION TO geometry.py
geometry.py is the authoring and calculation surface, and it is already long. Nothing here
authors. The one function there this module is related to is translate_to_world, which mutates
the document by design and is corrected to the same rule this module establishes.
THE RULE, WHICH WAS MEASURED RATHER THAN REASONED
Three clauses, in this order, against the engine's own vertex report over 17 models and 532 surfaces:
- Only when the model declares the relative system, rotate each surface by its zone's
direction_of_relative_northand then translate it by the zone's origin. The condition is not a nicety: twelve of the example models declareWorldand carry a non-zero zone origin anyway, and applying it displaces every surface in them. - Rotate the whole resolved building by
Building.north_axis, about the world origin, in the engine's sense. That sense is clockwise seen from above, so it is the negation of a counter-clockwise rotation. Applying it per surface inside its own zone frame instead leaves the zone layout unrotated, which is whattranslate_to_worlddoes today: every surface correctly oriented and every zone in the wrong place, a drawing that passes an eyeball test at up to 201.98 m of error.Shading:Site:Detailedis the one exception, and it is the engine's: site shading is fixed in space and does not turn with the building, measured by entering one square under both detached shading types in a model declaring a north axis of 158.434 and reading back which of the two moved. - Reverse the vertex order when the model declares clockwise entry, so that the right-hand rule gives the outward normal in every model.
Candidate rules and their agreement with the engine:
=================================================== ==================== ===========
candidate models within 0.01 m worst error
=================================================== ==================== ===========
the rule in _resolve_surfaces 5/17 201.98 m
the rule in translate_to_world 5/17 201.98 m
coordinate system read, north axis applied per 8/17 201.98 m
surface
coordinate system read, north axis applied to the 14/17 17.59 m
building
all three clauses, compared as rings 17/17 0.0045 m
=================================================== ==================== ===========
WHAT IS NOT DONE, AND IS NOT AN OVERSIGHT
The starting vertex is not renormalised. The engine's report begins every surface at its upper-left corner, and matching that would mean discarding the author's ordering to reproduce a reporting convention. The corpus check carries a fixture whose whole purpose is to fail if someone adds it.
The simplified surface family (Wall:Exterior, Window, Roof and their siblings) is
not read. Those types are keyed on origin, width, height and tilt rather than on vertices and need
their own rule. They are reported in :attr:Scene.unattempted so that a model made of them looks
different from a model with no geometry at all.
The zone multiplier is not expanded. It is a simulation instruction, and the engine's own report does not repeat those surfaces either.
Reason = Literal['too-few-vertices', 'zone-not-found', 'parent-surface-not-found', 'no-vertices']
module-attribute
¶
AppliedRules
dataclass
¶
What resolution read from the model, and what it had to assume.
Source code in idfkit/scene.py
coordinate_system
instance-attribute
¶
defaulted = ()
class-attribute
instance-attribute
¶
is_clockwise
property
¶
Whether the author entered vertices clockwise, which clause three reverses.
is_relative
property
¶
Whether zone origins and zone rotations apply, which is clause one's condition.
north_axis
instance-attribute
¶
starting_vertex_position
instance-attribute
¶
vertex_entry_direction
instance-attribute
¶
ResolvedSurface
dataclass
¶
One surface, placed.
object_type together with name is the address. A name alone is not unique across types,
and a consumer that must search the document by name to find what its user selected has been
handed a picture rather than a view of the model.
Source code in idfkit/scene.py
area
property
¶
The area of the resolved polygon.
boundary = ''
class-attribute
instance-attribute
¶
construction = ''
class-attribute
instance-attribute
¶
is_shading = False
class-attribute
instance-attribute
¶
name
instance-attribute
¶
normal
property
¶
The outward normal, signed by the declared entry direction.
object_type
instance-attribute
¶
parent_surface = None
class-attribute
instance-attribute
¶
polygon
instance-attribute
¶
surface_type = ''
class-attribute
instance-attribute
¶
zone = ''
class-attribute
instance-attribute
¶
Scene
dataclass
¶
A model's geometry, resolved into one frame.
All three lists are in document order. The corpus compares unresolved and unattempted as
sets, because neither carries a semantically meaningful order; that is a statement about what
counts as equal and not permission for the producer to vary. A list that reorders between runs
is a flickering interface and an unreadable diff, and ordering costs nothing here because
resolution already walks the document in order.
Source code in idfkit/scene.py
SceneBounds
dataclass
¶
The box enclosing every resolved vertex, and no more.
Source code in idfkit/scene.py
UnattemptedType
dataclass
¶
A geometry type present in the model that this slice does not read.
Source code in idfkit/scene.py
UnresolvedObject
dataclass
¶
A geometry object that could not be placed, and why.
The reason is an enumeration rather than a message, so that a consumer can group on it and a reworded string does not change behaviour.
missing_reference names what the object pointed at and the model does not hold, for the two
reasons that are a dangling reference. The reason says how to group the failure; it does not say
which wall to go and find, and a reader fixing the model needs the name rather than a second
search through the document. Absent when nothing was referenced, as for an object whose vertex
list is too short.
Source code in idfkit/scene.py
get_scene(doc)
¶
Resolve a model's geometry into one frame, leaving the model untouched.
One argument, one return, no options. There is no include_shading, no zones= filter and
no color_by: a filter is a list operation the caller already has, and a colour is a viewing
decision this function has no business making.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
doc
|
IDFDocument
|
the document to read. It is not modified, and a preserving write before and after yields identical bytes. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
A |
Scene
|
class: |
Scene
|
resolved surface, an unresolved object with a reason, or a count under an unattempted type. |
Examples:
>>> from idfkit import new_document, get_scene
>>> model = new_document()
>>> scene = get_scene(model)
>>> scene.surfaces, scene.bounds
((), None)
An empty model and a model of surfaces this slice cannot read are different answers: