Question-and-Answer Resource for the Building Energy Modeling Community
Get started with the Help page
Ask Your Question
2

... Python OpenStudio bindings: surface.vertices() -> tuple?

asked 2025-06-30 16:26:49 -0500

updated 2025-08-21 06:57:23 -0500

To OpenStudio SDK devs working with Python bindings ...

As a Python-SDK training exercise, I'm in the process of converting existing Ruby gems to Python packages/modules. It's going fairly smoothly (e.g. Ruby vs Python unit test results are lining up). Yet I'm hitting something weird when I try the following (here, using the interactive Python interpreter):

vtx = openstudio.Point3dVector()
vtx.append(openstudio.Point3d( 0, 0,10))
vtx.append(openstudio.Point3d( 0, 0, 0))
vtx.append(openstudio.Point3d(10, 0, 0))
vtx.append(openstudio.Point3d(10, 0,10))

model = openstudio.model.Model()
surface = openstudio.model.Surface(vtx, model)
print(surface.vertices().__class__.__name__)

>>> tuple

Tuple? Shouldn't it be an openstudio.model.Point3dVector? I can work around this, e.g. converting to a list, or generating a new Point3dVector (point by point). But it would be better to stick to SDK documented return types. This is the first snag of this nature I've come across.

Is this a bug, or expected behaviour? Am I missing something really obvious (documented somewhere)? I haven't found anything on UMH or the OpenStudio GitHub repo. Working with SDK v3.9.0 & Python 3.10, BTW.

Thanks in advance,

EDIT:

The following isn't really answering "why?" or "where can one find documentation on this?". If someone does provide a suitable answer, I'll gladly accept it, and edit the original question with my comments below.


In a nutshell, an OpenStudio getter method returns a copy of an OpenStudio model object. Lower-level objects, like 3D points or materials, are returned as other instances of the same class. 1D collections of objects (e.g. floor.vertices(), model.getSpaces()) are also returned as copies. Yet often as simpler, built-in class instances, like Python tuples or Ruby Arrays. I've only come across this with 1D collections, when using either Python or Ruby. There's nothing inherently wrong with this, but it isn't initially obvious. I'd suggest relying on the online OpenStudio SDK documentation with a pinch of caution, and test return variables of interest (particularly 1D collections) when coding (via interactive shells, unit tests, etc.). More of a head's up for beginners ...


... a few observations:

#1. This is not unique to Python. The Ruby bindings offer similar behaviour. I just hadn't paid attention to this until recently - my bad. Revisiting the example in the question:

Python:

import openstudio

vx1 = openstudio.Point3dVector()
vx1.append(openstudio.Point3d(10, 0, 0))
vx1.append(openstudio.Point3d(10,10, 0))
vx1.append(openstudio.Point3d( 0,10, 0))
vx1.append(openstudio.Point3d( 0, 0, 0))

vx2 = openstudio.Point3dVector()
vx2.append(openstudio.Point3d(20, 0, 0))
vx2.append(openstudio.Point3d(20,10, 0))
vx2.append(openstudio.Point3d(10,10, 0))
vx2.append(openstudio.Point3d(10, 0, 0))

vtx = openstudio.Point3dVectorVector()
vtx.append(vx1)
vtx.append(vx2)

print(len(vtx))
> 2

print(vtx.__class__.__name__)
> Point3dVectorVector

print(vtx[0].__class__.__name__)
> tuple

print(vtx[0][0].__class__.__name__)
> Point3d

Ruby ... (more)

edit retag flag offensive close merge delete

1 Answer

Sort by ยป oldest newest most voted
1

answered 2025-08-21 03:36:26 -0500

updated 2025-08-21 03:49:40 -0500

I can answer as to the why.

OpenStudio SDK code is C++, period. The Ruby and Python bindings are (almost) automagically generated via SWIG (Simplified Wrapper Interface Generator).

SWIG will create a target language shallow wrapper class for class objects. This is the case for Point3d.

TL;DR: it creates a Python Point3d class that will basically forward to native C++ library, so it "acts" like it.

Your confusion here is that Point3dVector is generally NOT a class. It's an alias (typedef) to std::vector<Point3d>.

https://github.com/NREL/OpenStudio/bl...

// vector of Point3d
using Point3dVector = std::vector<Point3d>;

SWIG has logic to handle return type collections such as std::vector<T>, and will result in a Ruby Array[wrapper<T>] or Python tuple[wrapper<T>] for convenience (see Note 1)

We still tell SWIG to handle it here, meaning that you also have a shallow wrapper class in your target language that acts like a std::vector.


Note 1: With python, tuples are immutable, and you've probably hit a TypeError: 'tuple' object does not support item assignment already.

What you may not have noticed is that the ruby Array returned is frozen. Try this for eg:

m = exampleModel
s = m.getSurfaces.first
vertices = s.vertices
raise unless vertices.class == Array
raise unless vertices.frozen?
vertices[0] = "hello"

FrozenError: can't modify frozen Array: [#<OpenStudio::Point3d:0x00007d30e8f42360 @__swigtype__="_p_openstudio__Point3d">, #<OpenStudio::Point3d:0x00007d30e8f42338 @__swigtype__="_p_openstudio__Point3d">, #<OpenStudio::Point3d:0x00007d30e8f42310 @__swigtype__="_p_openstudio__Point3d">, #<OpenStudio::Point3d:0x00007d30e8f422e8 @__swigtype__="_p_openstudio__Point3d">] (FrozenError)
edit flag offensive delete link more

Your Answer

Please start posting anonymously - your entry will be published after you log in or create a new account.

Add Answer

Training Workshops

Question Tools

1 follower

Stats

Asked: 2025-06-30 16:26:49 -0500

Seen: 974 times

Last updated: Aug 21