From 7ea1bc7d9807232764f3ba6366e7e28fffe2081d Mon Sep 17 00:00:00 2001 From: Robin Deits Date: Sun, 19 May 2019 15:44:33 -0400 Subject: [PATCH 1/2] Update meshcat to get better .DAE handling --- deps/build.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps/build.jl b/deps/build.jl index 6e6e909..da3f2dc 100644 --- a/deps/build.jl +++ b/deps/build.jl @@ -2,7 +2,7 @@ using Base.Filesystem using BinDeps: unpack_cmd, download_cmd -const meshcat_sha = "efc157436ca4abf5706cf248c51d2e86cae1a0f0" +const meshcat_sha = "c7f9c460f4af249ebefc95a489a2e0f13afb3946" const meshcat_url = "https://github.com/rdeits/meshcat/archive/$meshcat_sha.zip" const assets_dir = normpath(joinpath(@__DIR__, "..", "assets")) From cf3649ddc5d32b90612dcfdc30468b72f87fd6fe Mon Sep 17 00:00:00 2001 From: Robin Deits Date: Mon, 17 Jun 2019 00:30:12 -0400 Subject: [PATCH 2/2] Support loading .obj and .dae files as MeshFileObject --- deps/build.jl | 2 +- src/MeshCat.jl | 5 ++ src/geometry.jl | 21 ------ src/lowering.jl | 19 ++++- src/mesh_files.jl | 114 +++++++++++++++++++++++++++++ src/objects.jl | 3 +- test/data/meshes/cube.dae | 148 ++++++++++++++++++++++++++++++++++++++ test/data/meshes/cube.mtl | 8 +++ test/data/meshes/cube.obj | 38 ++++++++++ test/data/meshes/cube.png | Bin 0 -> 189 bytes test/data/meshes/cube.stl | Bin 0 -> 684 bytes test/visualizer.jl | 36 +++++++--- 12 files changed, 359 insertions(+), 35 deletions(-) create mode 100644 src/mesh_files.jl create mode 100644 test/data/meshes/cube.dae create mode 100644 test/data/meshes/cube.mtl create mode 100644 test/data/meshes/cube.obj create mode 100644 test/data/meshes/cube.png create mode 100644 test/data/meshes/cube.stl diff --git a/deps/build.jl b/deps/build.jl index da3f2dc..c4be771 100644 --- a/deps/build.jl +++ b/deps/build.jl @@ -2,7 +2,7 @@ using Base.Filesystem using BinDeps: unpack_cmd, download_cmd -const meshcat_sha = "c7f9c460f4af249ebefc95a489a2e0f13afb3946" +const meshcat_sha = "3122cecd5da022ad96bb0c8dc0f811a1bc492350" const meshcat_url = "https://github.com/rdeits/meshcat/archive/$meshcat_sha.zip" const assets_dir = normpath(joinpath(@__DIR__, "..", "assets")) diff --git a/src/MeshCat.jl b/src/MeshCat.jl index 4231a3e..977ffce 100644 --- a/src/MeshCat.jl +++ b/src/MeshCat.jl @@ -48,6 +48,7 @@ export Object, Triad, Mesh, MeshFileGeometry, + MeshFileObject, Points, Line, LineLoop, @@ -66,8 +67,12 @@ export Animation, export ArrowVisualizer +abstract type AbstractObject end +abstract type AbstractMaterial end + include("trees.jl") using .SceneTrees +include("mesh_files.jl") include("geometry.jl") include("objects.jl") include("animations.jl") diff --git a/src/geometry.jl b/src/geometry.jl index 7d8a3db..2954991 100644 --- a/src/geometry.jl +++ b/src/geometry.jl @@ -40,27 +40,6 @@ center(geometry::HyperSphere) = origin(geometry) center(geometry::Cylinder) = (origin(geometry) + geometry.extremity) / 2 center(geometry::Cone) = (origin(geometry) + geometry.apex) / 2 -""" -An MeshFileGeometry represents a mesh which is stored as the raw contents -of a file, rather than as a collection of points and vertices. This is useful for -transparently passing mesh files which we can't load in Julia directly to meshcat. -""" -struct MeshFileGeometry{S <: Union{String, Vector{UInt8}}} - contents::S - format::String -end - -function MeshFileGeometry(filename) - ext = lowercase(splitext(filename)[2]) - if ext ∈ (".obj", ".dae") - MeshFileGeometry(open(f -> read(f, String), filename), ext[2:end]) - elseif ext == ".stl" - MeshFileGeometry(open(read, filename), ext[2:end]) - else - throw(ArgumentError("Unsupported extension: $ext. Only .obj, .dae, and .stl meshes can be used to construct MeshFileGeometry")) - end -end - """ $(SIGNATURES) diff --git a/src/lowering.jl b/src/lowering.jl index 4655492..0c3afaf 100644 --- a/src/lowering.jl +++ b/src/lowering.jl @@ -185,11 +185,26 @@ end function lower(geom::MeshFileGeometry) Dict{String, Any}( "uuid" => string(uuid1()), - "type" => "_meshfile", + "type" => "_meshfile_geometry", "format" => geom.format, "data" => pack_mesh_file_data(geom.contents)) end +function lower(obj::MeshFileObject) + Dict{String, Any}( + "metadata" => Dict{String, Any}("version" => 4.5, "type" => "Object"), + "geometries" => [], + "materials" => [], + "object" => Dict{String, Any}( + "uuid" => string(uuid1()), + "type" => "_meshfile_object", + "format" => obj.format, + "data" => pack_mesh_file_data(obj.contents), + "mtl_library" => obj.mtl_library, + "resources" => obj.resources)) +end + + # TODO: Unify these two methods once https://github.com/rdeits/meshcat/issues/50 is resolved pack_mesh_file_data(s::AbstractString) = s pack_mesh_file_data(s::AbstractVector{UInt8}) = PackedVector(s) @@ -228,7 +243,7 @@ end function lower(img::PngImage) Dict{String, Any}( "uuid" => string(uuid1()), - "url" => string("data:image/png;base64,", base64encode(img.data)) + "url" => data_uri(img.data) ) end diff --git a/src/mesh_files.jl b/src/mesh_files.jl new file mode 100644 index 0000000..bd798ba --- /dev/null +++ b/src/mesh_files.jl @@ -0,0 +1,114 @@ + +""" +An MeshFileGeometry represents a mesh which is stored as the raw contents +of a file, rather than as a collection of points and vertices. This is useful for +transparently passing mesh files which we can't load in Julia directly to meshcat. + +Supported formats: + * .stl (ASCII and binary) + * .obj + * .dae (Collada) + +For .obj and .dae files, only a single mesh geometry will be loaded, and any +material or texture properties will be ignored. To load an entire collection of +objects (complete with materials and textures) from an .obj or .dae file, see +MeshFileObject instead. +""" +struct MeshFileGeometry{S <: Union{String, Vector{UInt8}}} + contents::S + format::String +end + +function MeshFileGeometry(filename) + ext = lowercase(splitext(filename)[2]) + if ext ∈ (".obj", ".dae") + MeshFileGeometry(open(f -> read(f, String), filename), ext[2:end]) + elseif ext == ".stl" + MeshFileGeometry(open(read, filename), ext[2:end]) + else + throw(ArgumentError("Unsupported extension: $ext. Only .obj, .dae, and .stl meshes can be used to construct MeshFileGeometry")) + end +end + + +""" +A MeshFileObject is similar to a MeshFileGeometry, but rather than representing +a single geometry, it supports loading an entire object (with geometries, +materials, and textures), or even a collection of objects (for supported file +types). + +Supported formats: + * .obj + * .dae (Collada) + +Since mesh files may include references to other files (such as texture images), +the MeshFileObject also includes a `resources` dictionary mapping relative paths +(as specified in the mesh file) to the contents of the referenced files. Those contents are specified using data URIs, e.g.: + + data:image/png;base64, + +If you construct a MeshFileObject from a `.dae` or `.obj` file, the resources +dictionary will automatically be populated. +""" +struct MeshFileObject <: AbstractObject + contents::String + format::String + mtl_library::Union{String, Nothing} + resources::Dict{String, String} +end + +function MeshFileObject(filename) + ext = lowercase(splitext(filename)[2]) + if ext ∉ (".obj", ".dae") + throw(ArgumentError("Unsupported extension: $ext. Only .obj and .dae meshes can be used to construct MeshFileObject")) + end + contents = open(f -> read(f, String), filename) + format = ext[2:end] + if ext == ".obj" + mtl_library = load_mtl_library(contents, dirname(filename)) + resources = load_mtl_textures(mtl_library, dirname(filename)) + else + mtl_library = nothing + resources = load_dae_textures(contents, dirname(filename)) + end + MeshFileObject(contents, format, mtl_library, resources) +end + +function load_mtl_library(obj_contents, directory=".") + libraries = String[] + for line in eachline(IOBuffer(obj_contents)) + m = match(r"^mtllib (.*)$", line) + if m !== nothing + push!(libraries, open(f -> read(f, String), joinpath(directory, m.captures[1]))) + end + end + join(libraries, '\n') +end + +data_uri(contents::Vector{UInt8}) = string("data:image/png;base64,", base64encode(contents)) + +function load_mtl_textures(mtl_contents, directory=".") + textures = Dict{String, String}() + for line in eachline(IOBuffer(mtl_contents)) + m = match(r"^map_[^ ]* (.*)$", line) + if m !== nothing + name = m.captures[1] + contents = open(read, joinpath(directory, name)) + textures[name] = data_uri(contents) + end + end + textures +end + +function load_dae_textures(dae_contents, directory=".") + # TODO: this is probably not a very robust parsing strategy, + # but I don't have enough examples to work from to decide on a + # better one, so I'm keeping it simple. + r = r"\]*\>\s*([^\<]*)\<\/init_from\>"m + textures = Dict{String, String}() + for match in eachmatch(r, dae_contents) + name = match.captures[1] + textures[name] = data_uri(open(read, joinpath(directory, name))) + end + textures +end diff --git a/src/objects.jl b/src/objects.jl index dc16429..5871043 100644 --- a/src/objects.jl +++ b/src/objects.jl @@ -1,6 +1,4 @@ const GeometryLike = Union{AbstractGeometry, AbstractMesh, MeshFileGeometry} -abstract type AbstractObject end -abstract type AbstractMaterial end struct Object{G <: GeometryLike, M <: AbstractMaterial} <: AbstractObject geometry::G @@ -76,3 +74,4 @@ LineBasicMaterial(;kw...) = GenericMaterial(_type="LineBasicMaterial"; kw...) size::Float32 = 0.002 vertexColors::Int = 2 end + diff --git a/test/data/meshes/cube.dae b/test/data/meshes/cube.dae new file mode 100644 index 0000000..1c12574 --- /dev/null +++ b/test/data/meshes/cube.dae @@ -0,0 +1,148 @@ + + + + + VCGLab + VCGLib | MeshLab + + Y_UP + Tue May 28 03:20:42 2019 + Tue May 28 03:20:42 2019 + + + + cube.png + + + + + + + + + + + + + texture0 + A8R8G8B8 + + + + + texture0-surface + LINEAR + LINEAR + + + + + + 0 0 0 1 + + + 0 0 0 1 + + + + + + 0 0 0 1 + + + 0.3 + + + 0 0 0 1 + + + 0.5 + + + 0 0 0 1 + + + 0 + + + 0 + + + + + + + + + + + 0 0 0 1 0 0 1 1 0 0 1 0 0 0 1 1 0 1 1 1 1 0 1 1 + + + + + + + + + + 0 0 -1 0 0 -1 0 0 1 0 0 1 0 -1 0 0 -1 0 1 0 0 1 0 0 0 1 0 0 1 0 -1 0 0 -1 0 0 + + + + + + + + + + 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 + + + + + + + + + + 0.3 0.375 0.3 0.375 0.3 0.375 0.3 0.375 0.3 0.375 0.3 0.375 0.3 0.875 0.3 0.875 0.3 0.875 0.3 0.875 0.3 0.875 0.3 0.875 0.3 0.625 0.3 0.625 0.3 0.625 0.3 0.625 0.3 0.625 0.3 0.625 0.6 0.625 0.6 0.625 0.6 0.625 0.6 0.625 0.6 0.625 0.6 0.625 0.3 0.125 0.3 0.125 0.3 0.125 0.3 0.125 0.3 0.125 0.3 0.125 0.2 0.625 0.2 0.625 0.2 0.625 0.2 0.625 0.2 0.625 0.2 0.625 + + + + + + + + + + + + + + + +

2 2 0 0 1 1 0 1 0 0 0 2 0 0 1 3 3 3 1 4 2 2 1 5 4 4 2 6 5 5 2 7 6 6 2 8 6 6 3 9 7 7 3 10 4 4 3 11 0 0 4 12 1 1 4 13 5 5 4 14 5 5 5 15 4 4 5 16 0 0 5 17 1 1 6 18 2 2 6 19 6 6 6 20 6 6 7 21 5 5 7 22 1 1 7 23 2 2 8 24 3 3 8 25 7 7 8 26 7 7 9 27 6 6 9 28 2 2 9 29 3 3 10 30 0 0 10 31 4 4 10 32 4 4 11 33 7 7 11 34 3 3 11 35

+
+
+
+
+ + + + + + + + + + + + + + + + + + +
diff --git a/test/data/meshes/cube.mtl b/test/data/meshes/cube.mtl new file mode 100644 index 0000000..9b6ebf9 --- /dev/null +++ b/test/data/meshes/cube.mtl @@ -0,0 +1,8 @@ +newmtl material_0 +Ka 1.0 1.0 1.0 +Kd 1.000000 1.000000 1.000000 +Ks 1.000000 1.000000 1.000000 +Tr 0.000000 +illum 2 +Ns 0.000000 +map_Kd cube.png diff --git a/test/data/meshes/cube.obj b/test/data/meshes/cube.obj new file mode 100644 index 0000000..edbc07e --- /dev/null +++ b/test/data/meshes/cube.obj @@ -0,0 +1,38 @@ +mtllib ./cube.mtl +usemtl material_0 + +v 0.0 0.0 0.0 +v 1.0 0.0 0.0 +v 1.0 1.0 0.0 +v 0.0 1.0 0.0 +v 0.0 0.0 1.0 +v 1.0 0.0 1.0 +v 1.0 1.0 1.0 +v 0.0 1.0 1.0 + +vt 0.3 0.875 +vt 0.2 0.625 +vt 0.3 0.625 +vt 0.6 0.625 +vt 0.3 0.375 +vt 0.3 0.125 + +vn 0 0 -1 +vn 0 0 1 +vn 0 -1 0 +vn 1 0 0 +vn 0 1 0 +vn -1 0 0 + +f 3/5/1 2/5/1 1/5/1 +f 1/5/1 4/5/1 3/5/1 +f 5/1/2 6/1/2 7/1/2 +f 7/1/2 8/1/2 5/1/2 +f 1/3/3 2/3/3 6/3/3 +f 6/3/3 5/3/3 1/3/3 +f 2/4/4 3/4/4 7/4/4 +f 7/4/4 6/4/4 2/4/4 +f 3/6/5 4/6/5 8/6/5 +f 8/6/5 7/6/5 3/6/5 +f 4/2/6 1/2/6 5/2/6 +f 5/2/6 8/2/6 4/2/6 \ No newline at end of file diff --git a/test/data/meshes/cube.png b/test/data/meshes/cube.png new file mode 100644 index 0000000000000000000000000000000000000000..16a5448f775d3538a9070d7e866316f0357d1037 GIT binary patch literal 189 zcmeAS@N?(olHy`uVBq!ia0vp^EFjFm1|(O0oL2{=I14-?iy0WWg+Z8+Vb&Z8pdfpR zr>`sfV^$evUeo-71#f^tvY8S|xv6<2KrRD=b5UwyNotBhd1gt5g1e`0K#E=} zJ5b!%)5S4_70dKit51*v!==SDr*vzU&=WnkJ#veck3iV@g-#l=4x|5=Xf1 zXga euwhy+`wrTEV0f