Compare commits
10 Commits
f17bc694cc
...
337b83450f
| Author | SHA1 | Date |
|---|---|---|
|
|
337b83450f | |
|
|
9ba4dee953 | |
|
|
9df43797a1 | |
|
|
c85d762ecf | |
|
|
98565b175c | |
|
|
b9b9fc393c | |
|
|
2e63947569 | |
|
|
cb6bce2ba5 | |
|
|
e96052c286 | |
|
|
ca1dca8414 |
File diff suppressed because it is too large
Load Diff
|
|
@ -1,8 +1,9 @@
|
|||
[workspace]
|
||||
resolver = "2"
|
||||
members = [
|
||||
"lua_modules/filesystem"
|
||||
"lua_modules/filesystem",
|
||||
"lua_modules/rerun_lua"
|
||||
]
|
||||
|
||||
[workspace.dependencies]
|
||||
mlua = { version = "0.9.1", features = ["lua54", "module"] }
|
||||
mlua = { version = "0.9.1", features = ["lua54", "module", "serde", "serialize"] }
|
||||
4
justfile
4
justfile
|
|
@ -5,6 +5,10 @@ run-fs: (lua "fs.example.lua")
|
|||
build-filesystem: (_copy-so "dev" "filesystem")
|
||||
deploy-fs: (_deploy "filesystem")
|
||||
|
||||
run-rr: (lua "rr.example.lua")
|
||||
build-rr: (_copy-so "dev" "rerun_lua")
|
||||
deploy-rr: (_deploy "rerun_lua")
|
||||
|
||||
lua file:
|
||||
LUA_CPATH=c_modules/?.so lua lua/{{file}}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,77 +1,13 @@
|
|||
local fs = require("filesystem")
|
||||
|
||||
local dirs = fs.directory("/Users/kinch/Desktop/Rebuild World")
|
||||
local root = fs.directory(".")
|
||||
|
||||
local FileTree = {foo = "bar"}
|
||||
function FileTree:new(iterator)
|
||||
local file_tree = {
|
||||
iterator = iterator,
|
||||
filters = {}
|
||||
}
|
||||
setmetatable(file_tree, self)
|
||||
self.__index = self
|
||||
return file_tree
|
||||
string.starts_with = function(self, str)
|
||||
return self:find('^' .. str) ~= nil
|
||||
end
|
||||
|
||||
function FileTree:filter(filter_fn)
|
||||
table.insert(self.filters, filter_fn)
|
||||
return self
|
||||
end
|
||||
|
||||
function FileTree:__call()
|
||||
local next = self.iterator()
|
||||
if next == nil then
|
||||
return nil
|
||||
for entry in root do
|
||||
if not entry.rel_path:starts_with("%.") then
|
||||
print(entry.rel_path)
|
||||
end
|
||||
|
||||
for k,filter in pairs(self.filters) do
|
||||
if (not filter(next)) then
|
||||
return self()
|
||||
end
|
||||
end
|
||||
return next
|
||||
end
|
||||
|
||||
function is_dir(entry)
|
||||
return entry:is_dir()
|
||||
end
|
||||
|
||||
function starts_with(prefix)
|
||||
return function(entry)
|
||||
return entry.path:find("^"..prefix)
|
||||
end
|
||||
end
|
||||
|
||||
function does_not(predicate)
|
||||
return function(...) return not predicate(...) end
|
||||
end
|
||||
|
||||
local comic_dir = FileTree:new(fs.directory("."))
|
||||
comic_dir:filter(is_dir)
|
||||
|
||||
for chapter_dir in comic_dir do
|
||||
FileTree:new(fs.directory(chapter_dir)):filter(does_not(starts_with("./")))
|
||||
|
||||
end
|
||||
|
||||
--for dir in dirs do
|
||||
-- if dir:is_dir() then
|
||||
-- for sub_dir in fs.directory(dir.path) do
|
||||
-- print(sub_dir)
|
||||
-- end
|
||||
-- end
|
||||
--end
|
||||
|
||||
--for dir in dirs do
|
||||
-- local files = dir:filter {
|
||||
-- exclude = function(file)
|
||||
-- return file.is_file and not file.basename:starts_with("._")
|
||||
-- end
|
||||
-- }
|
||||
--
|
||||
-- zip {
|
||||
-- from = files,
|
||||
-- into = dir.basename,
|
||||
-- extension = "cbz",
|
||||
-- }
|
||||
--end
|
||||
end
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
local rr = require("rerun_lua")
|
||||
|
||||
|
||||
function bbox(ms_ocr_bbox)
|
||||
return rr.rect_xyxy {
|
||||
ms_ocr_bbox[1],
|
||||
ms_ocr_bbox[2],
|
||||
ms_ocr_bbox[5],
|
||||
ms_ocr_bbox[6],
|
||||
}
|
||||
end
|
||||
|
||||
function paragraphs(ms_form_result)
|
||||
local result = {}
|
||||
for _, paragraph in ipairs(ms_form_result.analyzeResult.paragraphs) do
|
||||
for _, region in ipairs(paragraph.boundingRegions) do
|
||||
table.insert(result, {
|
||||
text = paragraph.content,
|
||||
page = region.pageNumber,
|
||||
bbox = bbox(region.polygon)
|
||||
})
|
||||
end
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
function lines(ms_form_result)
|
||||
local result = {}
|
||||
for _, page in ipairs(ms_form_result.analyzeResult.pages) do
|
||||
for _, line in ipairs(page.lines) do
|
||||
table.insert(result, {
|
||||
text = line.content,
|
||||
page = page.pageNumber,
|
||||
bbox = bbox(line.polygon)
|
||||
})
|
||||
end
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
|
||||
|
||||
function words(ms_form_result)
|
||||
local result = {}
|
||||
for _, page in ipairs(ms_form_result.analyzeResult.pages) do
|
||||
for _, word in ipairs(page.words) do
|
||||
table.insert(result, {
|
||||
text = word.content,
|
||||
page = page.pageNumber,
|
||||
bbox = bbox(word.polygon)
|
||||
})
|
||||
end
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
|
||||
local ms_form = rr.json("/Users/tbr/Desktop/ms_form.json")
|
||||
|
||||
local record = rr.recording("example-2")
|
||||
record:image("54-0", "/Users/tbr/Desktop/00054-0.png")
|
||||
record:text_objects("54-0/ms-forms/paragraphs",paragraphs(ms_form))
|
||||
record:text_objects("54-0/ms-forms/lines",lines(ms_form))
|
||||
record:text_objects("54-0/ms-forms/words",words(ms_form))
|
||||
|
|
@ -7,4 +7,7 @@ edition = "2021"
|
|||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
mlua = { workspace = true }
|
||||
thiserror = "1.0.48"
|
||||
mlua = { workspace = true }
|
||||
zip = "0.6"
|
||||
walkdir = "2.4.0"
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
use crate::driver_filesystem::filesystem_resource::FilesystemResourceLocation;
|
||||
use crate::resource::{DynResource, DynResourceLocation, Resource, ResourceLocation};
|
||||
use crate::source::ResourceSource;
|
||||
use std::fs::rename;
|
||||
use std::io::Sink;
|
||||
use std::path::PathBuf;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
pub struct DirectorySource {
|
||||
location: String,
|
||||
iter: walkdir::IntoIter,
|
||||
}
|
||||
impl DirectorySource {
|
||||
pub fn open_dir(location: String) -> Self {
|
||||
let iter = WalkDir::new(location.clone()).into_iter();
|
||||
DirectorySource { location, iter }
|
||||
}
|
||||
}
|
||||
|
||||
impl ResourceSource for DirectorySource {
|
||||
fn location(&self) -> DynResourceLocation {
|
||||
Box::new(FilesystemResourceLocation::from(PathBuf::from(
|
||||
self.location.clone(),
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for DirectorySource {
|
||||
type Item = DynResourceLocation;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
use crate::resource::ResourceLocation;
|
||||
self.iter
|
||||
.next()
|
||||
.map(|entry| entry.unwrap())
|
||||
.map(|entry| entry.path().strip_prefix("./").unwrap().to_path_buf())
|
||||
.map(|entry| {
|
||||
let loc = FilesystemResourceLocation::from(entry);
|
||||
Box::new(loc) as Box<dyn ResourceLocation>
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
use crate::resource::ResourceLocation;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub struct FilesystemResourceLocation {
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
impl ResourceLocation for FilesystemResourceLocation {
|
||||
fn location(&self) -> String {
|
||||
self.path.display().to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PathBuf> for FilesystemResourceLocation {
|
||||
fn from(path: PathBuf) -> Self {
|
||||
FilesystemResourceLocation { path }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
pub mod directory_source;
|
||||
pub mod filesystem_resource;
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
use crate::sink::ResourceSink;
|
||||
use std::iter::Zip;
|
||||
|
||||
mod zip_zink;
|
||||
|
||||
pub struct ZipZink {}
|
||||
|
||||
impl ResourceSink for ZipZink {}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
use mlua::prelude::{LuaError, LuaResult};
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum FsError {
|
||||
#[error("fs error happened: {msg}")]
|
||||
GenericError { msg: String },
|
||||
}
|
||||
|
||||
pub type Res<T = ()> = Result<T, FsError>;
|
||||
|
||||
impl From<FsError> for LuaError {
|
||||
fn from(fs_error: FsError) -> Self {
|
||||
LuaError::RuntimeError(fs_error.to_string())
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +1,19 @@
|
|||
mod types;
|
||||
mod driver_filesystem;
|
||||
mod driver_zip_archive;
|
||||
mod error;
|
||||
mod lua_bindings;
|
||||
mod operations;
|
||||
mod resource;
|
||||
mod sink;
|
||||
mod source;
|
||||
|
||||
use crate::types::file_tree::*;
|
||||
use crate::source::DynResourceSource;
|
||||
use driver_filesystem::directory_source::DirectorySource;
|
||||
use mlua::prelude::*;
|
||||
|
||||
fn directory(lua: &Lua, path: String) -> LuaResult<DirectoryFileTreeIter> {
|
||||
let read_dir = std::fs::read_dir(path).unwrap();
|
||||
Ok(DirectoryFileTreeIter { read_dir })
|
||||
fn directory(lua: &Lua, path: String) -> LuaResult<DynResourceSource> {
|
||||
let source = DirectorySource::open_dir(path);
|
||||
Ok(Box::new(source))
|
||||
}
|
||||
|
||||
#[mlua::lua_module]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
use crate::resource::DynResourceLocation;
|
||||
use mlua::{MetaMethod, UserData, UserDataFields, UserDataMethods};
|
||||
|
||||
mod resource_bindings;
|
||||
mod source_bindings;
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
use crate::resource::DynResourceLocation;
|
||||
use mlua::{MetaMethod, UserData, UserDataFields, UserDataMethods};
|
||||
|
||||
impl UserData for DynResourceLocation {
|
||||
fn add_fields<'lua, F: UserDataFields<'lua, Self>>(fields: &mut F) {
|
||||
fields.add_field_method_get("rel_path", |lua, this| Ok(this.location()))
|
||||
}
|
||||
|
||||
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
|
||||
methods.add_meta_method_mut(MetaMethod::ToString, |lua, this, _: ()| Ok(this.location()));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
use crate::source::DynResourceSource;
|
||||
use mlua::{MetaMethod, UserData, UserDataMethods};
|
||||
|
||||
impl UserData for DynResourceSource {
|
||||
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
|
||||
methods.add_meta_method_mut(MetaMethod::Call, |lua, this, _: ()| Ok(this.next()));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
mod operation_copy;
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
use crate::sink::DynResourceSink;
|
||||
use crate::source::DynResourceSource;
|
||||
|
||||
pub fn copy_operation(from: DynResourceSource, to: DynResourceSink) {}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
use crate::sink::ResourceSink;
|
||||
use crate::source::ResourceSource;
|
||||
|
||||
/// Points to a resource. Think of it as a path.
|
||||
pub trait ResourceLocation {
|
||||
fn location(&self) -> String;
|
||||
}
|
||||
/// A concrete resource. Think of as a directory or a file
|
||||
pub trait Resource {}
|
||||
// A resource ready to be read. Think of it as a open file for reading
|
||||
pub trait ResourceInputStream: std::io::Read {}
|
||||
// A resource ready to be written. Think of it as a open file for reading
|
||||
pub trait ResourceOutputStream: std::io::Write {}
|
||||
pub type DynResource = Box<dyn Resource>;
|
||||
pub type DynResourceLocation = Box<dyn ResourceLocation>;
|
||||
pub type DynResourceInputStream = Box<dyn ResourceInputStream>;
|
||||
pub type DynResourceOutputStream = Box<dyn ResourceOutputStream>;
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
// mod zip_sink;
|
||||
|
||||
use crate::resource::{Resource, ResourceInputStream};
|
||||
|
||||
pub trait ResourceSink {
|
||||
// fn add_file(&mut self, input_stream: DynResourceInputStream) {}
|
||||
}
|
||||
pub type DynResourceSink = Box<dyn ResourceSink>;
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
use crate::resource::{DynResource, DynResourceLocation};
|
||||
pub trait ResourceSource: Iterator<Item = DynResourceLocation> {
|
||||
fn location(&self) -> DynResourceLocation;
|
||||
}
|
||||
pub type DynResourceSource = Box<dyn ResourceSource<Item = DynResourceLocation>>;
|
||||
|
|
@ -1,87 +0,0 @@
|
|||
use crate::types::file_tree::ResourceType::{Directory, RegularFile};
|
||||
use mlua::{MetaMethod, UserData, UserDataFields, UserDataMethods};
|
||||
use std::fs::{DirEntry, FileType, ReadDir};
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub enum ResourceType {
|
||||
Directory,
|
||||
RegularFile,
|
||||
}
|
||||
|
||||
impl From<FileType> for ResourceType {
|
||||
fn from(file_type: FileType) -> Self {
|
||||
if file_type.is_file() {
|
||||
RegularFile
|
||||
} else {
|
||||
Directory
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Resource {
|
||||
fn path(&self) -> String;
|
||||
fn file_type(&self) -> ResourceType;
|
||||
fn is_dir(&self) -> bool {
|
||||
if let Directory = self.file_type() {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
fn is_file(&self) -> bool {
|
||||
if let RegularFile = self.file_type() {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
pub struct FileSystemResource {
|
||||
path: PathBuf,
|
||||
}
|
||||
impl Resource for FileSystemResource {
|
||||
fn path(&self) -> String {
|
||||
self.path.display().to_string()
|
||||
}
|
||||
|
||||
fn file_type(&self) -> ResourceType {
|
||||
self.path.metadata().unwrap().file_type().into()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait FileTreeIter<RES: Resource>: Iterator<Item = RES> {}
|
||||
|
||||
pub struct DirectoryFileTreeIter {
|
||||
pub read_dir: ReadDir,
|
||||
}
|
||||
|
||||
impl Iterator for DirectoryFileTreeIter {
|
||||
type Item = FileSystemResource;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.read_dir.next().map(|entry| entry.unwrap().into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DirEntry> for FileSystemResource {
|
||||
fn from(value: DirEntry) -> Self {
|
||||
FileSystemResource { path: value.path() }
|
||||
}
|
||||
}
|
||||
|
||||
impl UserData for DirectoryFileTreeIter {
|
||||
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
|
||||
methods.add_meta_method_mut(MetaMethod::Call, |lua, dir_iter, _: ()| Ok(dir_iter.next()));
|
||||
}
|
||||
}
|
||||
impl UserData for FileSystemResource {
|
||||
fn add_fields<'lua, F: UserDataFields<'lua, Self>>(fields: &mut F) {
|
||||
fields.add_field_method_get("path", |lua, fsr| Ok(fsr.path.display().to_string()))
|
||||
}
|
||||
|
||||
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
|
||||
methods.add_meta_method(MetaMethod::ToString, |lua, fsr, _: ()| Ok(fsr.path()));
|
||||
methods.add_method("is_dir", |lua, fsr, _: ()| Ok(fsr.is_dir()));
|
||||
methods.add_method("is_file", |lua, fsr, _: ()| Ok(fsr.is_file()));
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
pub mod file_tree;
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
[package]
|
||||
name = "rerun_lua"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
|
||||
[dependencies]
|
||||
image = { version = "0.24.7", features = ["png", "jpeg", "tiff"] }
|
||||
mlua = { workspace = true }
|
||||
rerun = "0.8.2"
|
||||
serde_json = "1.0.107"
|
||||
serde = { version = "1.0.188", features = ["derive"] }
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
use std::fs::File;
|
||||
use std::io::BufReader;
|
||||
use std::path::PathBuf;
|
||||
use mlua::{AnyUserData, Function, MetaMethod, Table, TableExt, UserData, UserDataRef};
|
||||
use mlua::prelude::*;
|
||||
use rerun::{MsgSender, RecordingStream, RecordingStreamBuilder};
|
||||
use rerun::components::{Rect2D, Tensor, Vec4D};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TextObject {
|
||||
pub text: String,
|
||||
pub bbox: Rect2D,
|
||||
}
|
||||
|
||||
impl UserData for TextObject {
|
||||
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
|
||||
methods.add_meta_method(MetaMethod::ToString, |_lua, this, _:()| {
|
||||
Ok(format!("{:?}", this))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'lua> FromLua<'lua> for TextObject {
|
||||
fn from_lua(value: LuaValue<'lua>, lua: &'lua Lua) -> LuaResult<Self> {
|
||||
let table = value.as_table().ok_or(LuaError::RuntimeError("invalid args for text_object".to_string()))?;
|
||||
let rect = table.get::<_, UserDataRef<Rect2D>>("bbox")?.clone();
|
||||
Ok(TextObject {
|
||||
text: table.get("text")?,
|
||||
bbox: rect
|
||||
})
|
||||
}
|
||||
}
|
||||
fn json(lua: &Lua, path: String) -> LuaResult<LuaValue> {
|
||||
let file = File::open(path.as_str())
|
||||
.map_err(|error| LuaError::RuntimeError(format!("could not open json path: {path}. Error: {error}")))?;
|
||||
let reader = BufReader::new(file);
|
||||
let json_value: serde_json::Value = serde_json::from_reader(reader)
|
||||
.map_err(|error| LuaError::RuntimeError(format!("could not parse json at: {path}. Error: {error}")))?;
|
||||
lua.to_value(&json_value)
|
||||
}
|
||||
|
||||
fn recording(lua: &Lua, name: String) -> LuaResult<AnyUserData> {
|
||||
let stream = RecordingStreamBuilder::new(name)
|
||||
.connect(rerun::default_server_addr(), None)
|
||||
.map_err(|err| LuaError::RuntimeError(err.to_string()))?;
|
||||
lua.create_any_userdata(stream)
|
||||
}
|
||||
|
||||
fn rect_xyxy(lua: &Lua, points: [f32;4]) -> LuaResult<AnyUserData> {
|
||||
let rect = Rect2D::XYXY(Vec4D(points).into());
|
||||
lua.create_any_userdata(rect)
|
||||
}
|
||||
|
||||
fn register_bindings(lua: &Lua) -> LuaResult<()> {
|
||||
|
||||
lua.register_userdata_type::<Rect2D>(|reg| {
|
||||
reg.add_meta_method(MetaMethod::ToString, |lua, this, _:()| Ok(format!("{:?}", this)))
|
||||
})?;
|
||||
|
||||
lua.register_userdata_type::<RecordingStream>(|reg|{
|
||||
reg.add_method("image", |lua, this, (entity_path, image_path): (String,String)| {
|
||||
let image_tensor = Tensor::from_image_file(PathBuf::from(image_path.as_str()).as_path())
|
||||
.map_err(|error| LuaError::RuntimeError(format!("Could not read image file {image_path}. Error: {error}")))?;
|
||||
MsgSender::new(entity_path)
|
||||
.with_timeless(true)
|
||||
.with_component(&[image_tensor])
|
||||
.map_err(|error| LuaError::RuntimeError(format!("Could not send message: {}", error)))?
|
||||
.send(this)
|
||||
.map_err(|error| LuaError::RuntimeError(format!("Could not send message {error}")))?;
|
||||
Ok(())
|
||||
});
|
||||
|
||||
reg.add_method("text_objects", |lua, this, (entity_path, objects): (String, Vec<TextObject>)| {
|
||||
let rects: Vec<Rect2D> = objects.into_iter()
|
||||
.map(|t| t.bbox)
|
||||
.collect();
|
||||
MsgSender::new(entity_path)
|
||||
.with_timeless(true)
|
||||
.with_component(rects.as_slice())
|
||||
.map_err(|error| LuaError::RuntimeError(format!("Could not send message: {}", error)))?
|
||||
.send(this)
|
||||
.map_err(|error| LuaError::RuntimeError(format!("Could not send message {error}")))?;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[mlua::lua_module]
|
||||
fn rerun_lua(lua: &Lua) -> LuaResult<LuaTable> {
|
||||
register_bindings(lua)?;
|
||||
let exports = lua.create_table()?;
|
||||
exports.set("recording", lua.create_function(recording)?)?;
|
||||
exports.set("json", lua.create_function(json)?)?;
|
||||
exports.set("rect_xyxy", lua.create_function(rect_xyxy)?)?;
|
||||
Ok(exports)
|
||||
}
|
||||
Loading…
Reference in New Issue