Skip to content

Mcap writer layout metadata#857

Draft
surbina wants to merge 11 commits intomainfrom
cursor/mcap-writer-layout-metadata-0e7b
Draft

Mcap writer layout metadata#857
surbina wants to merge 11 commits intomainfrom
cursor/mcap-writer-layout-metadata-0e7b

Conversation

@surbina
Copy link
Contributor

@surbina surbina commented Feb 3, 2026

Changelog

Added write_layout method to the Python MCAP writer for storing Foxglove layouts in file metadata.

Docs

Updated MCAPWriter and open_mcap documentation to reflect the new write_layout method and the Python wrapper class.

Description

The Python SDK's MCAP writer previously lacked a direct method to embed Foxglove Layout objects into the MCAP file's metadata. This PR introduces a write_layout method to the MCAPWriter class, allowing users to easily serialize a foxglove.layouts.Layout object to JSON and store it under the "foxglove.layout" metadata key.

To achieve this, the underlying Rust-backed _MCAPWriter is now wrapped by a new Python MCAPWriter class. The foxglove.open_mcap function has been updated to return this Python wrapper, ensuring that all existing MCAPWriter functionality remains available while adding the new write_layout capability. This change provides a more consistent and convenient API for embedding visualization layouts directly into MCAP files, improving their self-describing nature for Foxglove Studio.

Verified by running existing MCAP tests and a new dedicated test case for write_layout that confirms correct serialization and metadata storage.


Open in Cursor Open in Web

This adds a write_layout() method to the MCAPWriter class that allows
writing a Layout object to the MCAP file's metadata. The layout is
serialized to JSON using the Layout.to_json() method and stored under
the 'foxglove.layout' metadata key with an empty string key.

Changes:
- Created a Python wrapper class for MCAPWriter in mcap.py that adds
  the write_layout() method while delegating all other methods to the
  underlying Rust implementation
- Updated foxglove.__init__.py to wrap the open_mcap function so it
  returns the Python MCAPWriter wrapper
- Added a test for the write_layout functionality

Co-authored-by: sebastian <sebastian@foxglove.dev>
@cursor
Copy link

cursor bot commented Feb 3, 2026

Cursor Agent can help with this pull request. Just @cursor in comments and I'll start working on changes in this branch.
Learn more about Cursor Agents

Use fully qualified name foxglove.open_mcap in the docstring to allow
Sphinx to properly resolve the reference.

Co-authored-by: sebastian <sebastian@foxglove.dev>
Co-authored-by: sebastian <sebastian@foxglove.dev>
Co-authored-by: sebastian <sebastian@foxglove.dev>
Instead of creating a Python wrapper class for MCAPWriter, add the
write_layout method directly to the Rust PyMcapWriter implementation.
This is cleaner and more consistent with the existing codebase.

The method calls to_json() on the Layout object using PyO3 and stores
the result in metadata under the 'foxglove.layout' key.

Co-authored-by: sebastian <sebastian@foxglove.dev>
Comment on lines 313 to 315
// Store in metadata with name "foxglove.layout" and empty key
let mut metadata = std::collections::BTreeMap::new();
metadata.insert(String::new(), json_str);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cursor let's add a layout_name parameter to the write_layout function. Use layout_name as the key here instead of an empty string.

Copy link

@cursor cursor bot Feb 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done! I've added the layout_name parameter to the write_layout function.

Changes

Rust (python/foxglove-sdk/src/mcap.rs)

fn write_layout(&self, py: Python<'_>, layout: Py<PyAny>, layout_name: String) -> PyResult<()> {
    let json_str: String = layout.call_method0(py, "to_json")?.extract(py)?;
    
    let mut metadata = std::collections::BTreeMap::new();
    metadata.insert(layout_name, json_str);  // Uses layout_name as the key
    // ...
}

Type stub (mcap.pyi)

def write_layout(self, layout: "Layout", layout_name: str) -> None:
    """
    :param layout: A Layout object from foxglove.layouts.
    :param layout_name: The name to use as the key in the metadata record.
    """

Test updated

layout_name = "my_test_layout"
writer.write_layout(layout, layout_name)
# Verifies that layout_name is used as the key in the metadata
assert layout_name in record.metadata

Open in Cursor Open in Web

The layout_name parameter is used as the key in the metadata record
instead of an empty string.

Co-authored-by: sebastian <sebastian@foxglove.dev>
Changed write_layout to store layouts in an internal BTreeMap within
PyMcapWriter instead of writing immediately. All accumulated layouts
are written as a single metadata record with the name 'foxglove.layout'
when the writer is closed. This allows multiple layouts to be stored
in the same file with different names.

- Added layouts field to PyMcapWriter struct
- Updated close() to write accumulated layouts before closing
- Updated test to verify multiple layouts are preserved

Co-authored-by: sebastian <sebastian@foxglove.dev>
- Changed layout parameter type from Layout object to string
- Reordered parameters: layout_name first, layout second
- Changed metadata name from 'foxglove.layout' to 'foxglove.layouts'
- Updated test to pass layout.to_json() as string
- Removed unused Layout type import from mcap.pyi

Co-authored-by: sebastian <sebastian@foxglove.dev>
Co-authored-by: sebastian <sebastian@foxglove.dev>
Adds a write_layout method that stores a layout JSON string as MCAP
metadata under the 'foxglove.layouts' key with layout_name as the key
within the metadata record.

Co-authored-by: sebastian <sebastian@foxglove.dev>
/// ```
pub fn write_layout(&self, layout_name: &str, layout: &str) -> Result<(), FoxgloveError> {
let mut metadata = std::collections::BTreeMap::new();
metadata.insert(layout_name.to_string(), layout.to_string());
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cursor this creates a new metadata object each time we call write_layout, so only the last layout will be saved. Can you ensure no layouts are lost if there are multiple calls to write_layout?

Copy link

@cursor cursor bot Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done! I've updated McapWriterHandle to accumulate layouts and write them all when closing.

Changes

rust/foxglove/src/mcap_writer.rs:

Added a layouts field with interior mutability to accumulate layouts:

pub struct McapWriterHandle<W: Write + Seek + Send + 'static> {
    sink: Arc<McapSink<W>>,
    context: Weak<Context>,
    layouts: Mutex<BTreeMap<String, String>>,  // New field
}

Updated write_layout to add to the internal map instead of writing immediately:

pub fn write_layout(&self, layout_name: &str, layout: &str) {
    self.layouts
        .lock()
        .unwrap()
        .insert(layout_name.to_string(), layout.to_string());
}

Updated finish() to write all accumulated layouts before closing:

fn finish(&self) -> Result<Option<W>, FoxgloveError> {
    if let Some(context) = self.context.upgrade() {
        context.remove_sink(self.sink.id());
    }
    // Write all accumulated layouts as a single metadata record
    let layouts = std::mem::take(&mut *self.layouts.lock().unwrap());
    if !layouts.is_empty() {
        self.sink.write_metadata("foxglove.layouts", layouts)?;
    }
    self.sink.finish()
}

Now multiple calls to write_layout will accumulate all layouts and write them together as a single metadata record when the writer is closed.

Open in Cursor Open in Web

Added a layouts field (Mutex<BTreeMap>) to McapWriterHandle to store
layouts internally. The write_layout method now adds to the internal
map instead of writing immediately. All accumulated layouts are written
as a single metadata record when the writer is closed, ensuring no
layouts are lost when write_layout is called multiple times.

Co-authored-by: sebastian <sebastian@foxglove.dev>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

4 participants