Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

C++ & Python API: add helpers for constructing an entity path #4595

Merged
merged 10 commits into from
Dec 20, 2023
2 changes: 1 addition & 1 deletion crates/rerun_c/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -731,7 +731,7 @@ pub unsafe extern "C" fn _rr_free_string(str: *mut c_char) {

// Free the string:
unsafe {
// SAFETY: `_rr_free_string` should
// SAFETY: `_rr_free_string` should only be called on strings allocated by `_rr_escape_entity_path_part`.
let _ = CString::from_raw(str);
}
}
Expand Down
3 changes: 3 additions & 0 deletions docs/code-examples/entity_path.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,7 @@ int main() {
rerun::new_entity_path({"world", std::to_string(42), "unescaped string!"}),
rerun::TextDocument("This entity path was provided as a list of unescaped strings")
);

assert(rerun::escape_entity_path_part("my string!") == R"(my\ string\!)");
assert(rerun::new_entity_path({"world", "42", "my string!"}) == R"(/world/42/my\ string\!)");
Wumpf marked this conversation as resolved.
Show resolved Hide resolved
}
3 changes: 3 additions & 0 deletions docs/code-examples/entity_path.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,6 @@
rr.log(
["world", 42, "unescaped string!"], rr.TextDocument("This entity path was provided as a list of unescaped strings")
)

assert rr.escape_entity_path_part("my string!") == r"my\ string\!"
assert rr.new_entity_path(["world", 42, "my string!"]) == r"/world/42/my\ string\!"
2 changes: 2 additions & 0 deletions docs/code-examples/entity_path.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//! Example of different ways of constructing an entity path.

fn main() -> Result<(), Box<dyn std::error::Error>> {
let rec = rerun::RecordingStreamBuilder::new("rerun_example_text_document").spawn()?;

Expand Down
1 change: 0 additions & 1 deletion docs/code-examples/roundtrips.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@
opt_out_compare = {
"arrow3d_simple": ["cpp", "py", "rust"], # TODO(#3206): examples use different RNGs
"asset3d_out_of_tree": ["cpp", "py", "rust"], # float issues since calculation is done slightly differently (also, Python uses doubles)
"entity_path": ["cpp"], # C++ doesn't have helpers for escaping an entity path yet
"mesh3d_partial_updates": ["cpp", "py", "rust"], # float precision issues
"pinhole_simple": ["cpp", "py", "rust"], # TODO(#3206): examples use different RNGs
"point2d_random": ["cpp", "py", "rust"], # TODO(#3206): examples use different RNGs
Expand Down
27 changes: 15 additions & 12 deletions rerun_cpp/src/rerun/entity_path.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,19 @@
#include "string_utils.hpp"

namespace rerun {
std::string escape_entity_path_part(std::string_view unescaped) {
auto escaped_c_str = _rr_escape_entity_path_part(detail::to_rr_string(unescaped));

if (escaped_c_str == nullptr) {
Error(ErrorCode::InvalidStringArgument, "Failed to escape entity path part").handle();
return std::string(unescaped);
} else {
std::string result = escaped_c_str;
_rr_free_string(escaped_c_str);
return result;
}
}

std::string new_entity_path(const std::vector<std::string_view>& path) {
if (path.empty()) {
return "/";
Expand All @@ -13,18 +26,8 @@ namespace rerun {
std::string result;

for (const auto& part : path) {
auto escaped_c_str = _rr_escape_entity_path_part(detail::to_rr_string(part));

if (escaped_c_str == nullptr) {
Error(ErrorCode::InvalidStringArgument, "Failed to escape entity path part")
.handle();
} else {
if (!result.empty()) {
result += "/"; // leading slash would also have be fine
}
result += escaped_c_str;
_rr_free_string(escaped_c_str);
}
result += "/";
result += escape_entity_path_part(part);
}

return result;
Expand Down
6 changes: 6 additions & 0 deletions rerun_cpp/src/rerun/entity_path.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@
#include <vector>

namespace rerun {

/// Escape an individual part of an entity path.
///
/// For instance, `escape_entity_path_path("my image!")` will return `"my\ image\!"`.
std::string escape_entity_path_part(std::string_view str);

/// Construct an entity path by escaping each part of the path.
///
/// For instance, `rerun::new_entity_path({"world", 42, "unescaped string!"})` will return
Expand Down
2 changes: 2 additions & 0 deletions rerun_py/docs/gen_common_index.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,8 @@ class Section:
"set_global_data_recording",
"set_thread_local_data_recording",
"start_web_viewer_server",
"escape_entity_path_part",
"new_entity_path",
],
class_list=["RecordingStream", "LoggingHandler", "MemoryRecording"],
),
Expand Down
14 changes: 12 additions & 2 deletions rerun_py/rerun_sdk/rerun/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,16 +65,18 @@
"datatypes",
"disable_timeline",
"disconnect",
"escape_entity_path_part",
"experimental",
"get_application_id",
"get_data_recording",
"get_global_data_recording",
"get_recording_id",
"get_thread_local_data_recording",
"is_enabled",
"log",
"log_components",
"log",
"memory_recording",
"new_entity_path",
"reset_time",
"save",
"script_add_args",
Expand All @@ -92,7 +94,15 @@
import rerun_bindings as bindings # type: ignore[attr-defined]

from ._image import ImageEncoded, ImageFormat
from ._log import AsComponents, ComponentBatchLike, IndicatorComponentBatch, log, log_components
from ._log import (
AsComponents,
ComponentBatchLike,
IndicatorComponentBatch,
escape_entity_path_part,
log,
log_components,
new_entity_path,
)
from .any_value import AnyValues
from .archetypes import (
AnnotationContext,
Expand Down
21 changes: 21 additions & 0 deletions rerun_py/rerun_sdk/rerun/_log.py
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,27 @@ def log_components(
)


def escape_entity_path_part(part: str) -> str:
r"""
Escape an individual part of an entity path.

For instance, `escape_entity_path_path("my image!")` will return `"my\ image\!"`.

See <https://www.rerun.io/docs/concepts/entity-path> for more on entity paths.

Parameters
----------
part:
An unescaped string

Returns
-------
str:
The escaped entity path.
"""
return str(bindings.escape_entity_path_part(part))


def new_entity_path(entity_path: list[Any]) -> str:
r"""
Construct an entity path, defined by a list of (unescaped) parts.
Expand Down
6 changes: 6 additions & 0 deletions rerun_py/src/python_bridge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ fn rerun_bindings(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_pyfunction!(version, m)?)?;
m.add_function(wrap_pyfunction!(get_app_url, m)?)?;
m.add_function(wrap_pyfunction!(start_web_viewer_server, m)?)?;
m.add_function(wrap_pyfunction!(escape_entity_path_part, m)?)?;
m.add_function(wrap_pyfunction!(new_entity_path, m)?)?;

// blueprint
Expand Down Expand Up @@ -939,6 +940,11 @@ fn start_web_viewer_server(port: u16) -> PyResult<()> {
}
}

#[pyfunction]
fn escape_entity_path_part(part: &str) -> String {
EntityPathPart::from(part).to_string()
}

#[pyfunction]
fn new_entity_path(parts: Vec<&str>) -> String {
let path = EntityPath::from(parts.into_iter().map(EntityPathPart::from).collect_vec());
Expand Down
Loading