From 54efe0da3d283f3706f5f1fd23a7a2ea113d394a Mon Sep 17 00:00:00 2001 From: Leon van Kammen Date: Tue, 18 Nov 2025 17:45:59 +0100 Subject: [PATCH] cleanup hooks + added gltf-JSON section in show-template + added mastodonpost2image hook --- .../hook.d/experience_updated/200-to-gltf.rb | 60 ++--------- .../experience_updated/250-to-textures.rb | 101 ++++++++++++++++++ .../experience_updated/300-package_janusxr.rb | 9 +- .../experience_updated/300-package_mml.rb | 5 + .../500-mastodon-post2image.rb | 96 +++++++++++++++++ .../src/app/app/views/models/show.html.erb | 22 ++++ 6 files changed, 238 insertions(+), 55 deletions(-) create mode 100755 manyfold/root/hook.d/experience_updated/250-to-textures.rb create mode 100755 manyfold/root/hook.d/experience_updated/500-mastodon-post2image.rb diff --git a/manyfold/root/hook.d/experience_updated/200-to-gltf.rb b/manyfold/root/hook.d/experience_updated/200-to-gltf.rb index f99541e..076c998 100755 --- a/manyfold/root/hook.d/experience_updated/200-to-gltf.rb +++ b/manyfold/root/hook.d/experience_updated/200-to-gltf.rb @@ -14,36 +14,6 @@ filename = ARGV[0] require 'base64' require 'json' -## Updates the 'uri' of an image in a GLTF hash when a matching PNG filename is found. -## -## gltf: a parsed JSON hash from a .gltf file -## png_path: path to the PNG file to embed -## -## returns: true if updated successfully, false if not found -#def update_gltf_image(gltf, png_path) -# # Get base name (without extension) -# name = File.basename(png_path, '.png') -# -# # Find image entry with the same name -# image_entry = gltf['images']&.find { |img| img['name'] == name } -# -# unless image_entry -# warn "No image named '#{name}' found in GLTF" -# return false -# end -# -# # Read and base64-encode the PNG file -# data = File.binread(png_path) -# encoded = Base64.strict_encode64(data) -# -# # Update the URI field with the base64-encoded PNG data URI -# image_entry['uri'] = "data:image/png;base64,#{encoded}" -# -# puts "Updated image '#{name}' in GLTF" -# true -#end - - begin # Change the directory @@ -55,36 +25,20 @@ begin ext = File.extname(filename) filenameWithoutExt = File.basename(filename, ext) - if ! XRForge::MODEL_EXT.any?( ext ) + if ! XRForge::MODEL_EXT.any?( ext ) || ext == ".gltf" exit(0) # not a 3d file end logfile = File.join( File.dirname(filename), ".xrforge/log.txt" ) XRForge.log("✅ generating gltf", logfile) - gltf_path = ".xrforge/#{filenameWithoutExt}.gltf" + gltf_path = ".xrforge/scene.gltf" system("assimp export #{filename} #{gltf_path}") system("assimp extract #{filename} | sed 's|/.*/||g'") - - # Read and parse the GLTF JSON - gltf = JSON.parse(File.read(gltf_path)) - - # Ensure images section exists - unless gltf['images'] && gltf['images'].is_a?(Array) - abort("No 'images' array found in #{gltf_path}") - end - - # Iterate through the images array - gltf['images'].each_with_index do |img, i| - name = img['name'] - next unless name && !name.empty? - - old_filename = "website_img#{i}.png" - new_filename = "#{name}.png" - - if File.exist?(old_filename) - XRForge.log("✅ Renaming #{old_filename} -> #{new_filename}", logfile) - File.rename(old_filename, new_filename) - end + + # tag it! + if ! data['keywords'].include?('gltf') + data['keywords'].push('gltf') + File.write("datapackage.json", JSON.pretty_generate(data) ) end XRForge.log(" ", logfile) diff --git a/manyfold/root/hook.d/experience_updated/250-to-textures.rb b/manyfold/root/hook.d/experience_updated/250-to-textures.rb new file mode 100755 index 0000000..7dd3c6e --- /dev/null +++ b/manyfold/root/hook.d/experience_updated/250-to-textures.rb @@ -0,0 +1,101 @@ +#!/usr/bin/env ruby + +require 'json' +require_relative './../../xrforge.rb' + +# Check if a filename is provided +if ARGV.length != 1 + puts "Usage: #{$0} " + exit 1 +end + +puts "TODO" +exit 0 + +filename = ARGV[0] + +require 'base64' +require 'json' + +# Updates the 'uri' of an image in a GLTF hash when a matching PNG filename is found. +# +# gltf: a parsed JSON hash from a .gltf file +# png_path: path to the PNG file to embed +# +# returns: true if updated successfully, false if not found +def update_gltf_image(gltf, png_path) + # Get base name (without extension) + name = File.basename(png_path, '.png') + + # Find image entry with the same name + image_entry = gltf['images']&.find { |img| img['name'] == name } + + unless image_entry + warn "No image named '#{name}' found in GLTF" + return false + end + + # Read and base64-encode the PNG file + data = File.binread(png_path) + encoded = Base64.strict_encode64(data) + + # Update the URI field with the base64-encoded PNG data URI + image_entry['uri'] = "data:image/png;base64,#{encoded}" + + puts "Updated image '#{name}' in GLTF" + true +end + +begin + + # Change the directory + dir = File.dirname(filename) + Dir.chdir( File.dirname(filename) ) + # Read and parse the JSON file + data = JSON.parse( File.read( "datapackage.json" ) ) + + ext = File.extname(filename) + filenameWithoutExt = File.basename(filename, ext) + + if ! XRForge::MODEL_EXT.any?( ext ) + exit(0) # not a 3d file + end + + logfile = File.join( File.dirname(filename), ".xrforge/log.txt" ) + XRForge.log("✅ unpacking textures", logfile) + gltf_path = ".xrforge/#{filenameWithoutExt}.gltf" + system("assimp export #{filename} #{gltf_path}") + system("assimp extract #{filename} | sed 's|/.*/||g'") + + # Read and parse the GLTF JSON + gltf = JSON.parse(File.read(gltf_path)) + + # Ensure images section exists + unless gltf['images'] && gltf['images'].is_a?(Array) + abort("No 'images' array found in #{gltf_path}") + end + + # Iterate through the images array + gltf['images'].each_with_index do |img, i| + name = img['name'] + next unless name && !name.empty? + + old_filename = "website_img#{i}.png" + new_filename = "#{name}.png" + + if File.exist?(old_filename) + XRForge.log("✅ Renaming #{old_filename} -> #{new_filename}", logfile) + File.rename(old_filename, new_filename) + end + end + + XRForge.log(" ", logfile) + +rescue Errno::ENOENT + puts "File #{filename} not found" +rescue JSON::ParserError + puts "Error parsing JSON from #{filename}" +rescue => e + puts "An error occurred: #{e.message}" +end + diff --git a/manyfold/root/hook.d/experience_updated/300-package_janusxr.rb b/manyfold/root/hook.d/experience_updated/300-package_janusxr.rb index fd15ef8..653e7f6 100755 --- a/manyfold/root/hook.d/experience_updated/300-package_janusxr.rb +++ b/manyfold/root/hook.d/experience_updated/300-package_janusxr.rb @@ -5,7 +5,7 @@ require_relative './../../xrforge.rb' # Check if a filename is provided if ARGV.length != 1 - puts "Usage: #{$0} " + puts "Usage: #{$0} " exit 1 end @@ -13,6 +13,11 @@ filename = ARGV[0] begin + # dont run for each file-update + if ! filename.end_with?("datapackage.json") + exit 0 + end + # Change the directory dir = File.dirname(filename) Dir.chdir( File.dirname(filename) ) @@ -68,7 +73,7 @@ begin - + diff --git a/manyfold/root/hook.d/experience_updated/300-package_mml.rb b/manyfold/root/hook.d/experience_updated/300-package_mml.rb index b220507..fba5154 100755 --- a/manyfold/root/hook.d/experience_updated/300-package_mml.rb +++ b/manyfold/root/hook.d/experience_updated/300-package_mml.rb @@ -11,6 +11,11 @@ end filename = ARGV[0] +# dont run for each file-update +if ! filename.end_with?("datapackage.json") + exit 0 +end + begin # Change the directory diff --git a/manyfold/root/hook.d/experience_updated/500-mastodon-post2image.rb b/manyfold/root/hook.d/experience_updated/500-mastodon-post2image.rb new file mode 100755 index 0000000..f9c3d2e --- /dev/null +++ b/manyfold/root/hook.d/experience_updated/500-mastodon-post2image.rb @@ -0,0 +1,96 @@ +#!/usr/bin/env ruby + +require 'json' +require 'rss' +require 'open-uri' +require 'cgi' +require_relative './../../xrforge.rb' + +# Check if a filename is provided +if ARGV.length != 1 + puts "Usage: #{$0} " + exit 1 +end + +filename = ARGV[0] + +# dont run for each file-update +if ! filename.end_with?("datapackage.json") + exit 0 +end + +begin + # Change the directory + Dir.chdir( File.dirname(filename) ) + # Read and parse the JSON file + data = JSON.parse( File.read( "datapackage.json" ) ) + + data['keywords'].any? do |tag| + if tag.match?(/@.*@.*\./) # scan for activitypub handle (@foo@mastodon.online e.g.) + APHandle = tag + end + end + + if ! APHandle # nothing to do + exit 0 + end + + APHandle = "@vladh@merveilles.town" + + logfile = File.join( File.dirname(filename), ".xrforge/log.txt" ) + XRForge.log("✅ starting Mastodon post2image", logfile) + + parts = APHandle.split("@") + server = parts[2].sub(/@/,"") + rssUrl = "https://#{server}/@#{parts[1]}.rss" + XRForge.log("✅ checking #{rssUrl}", logfile) + + feed = RSS::Parser.parse(URI.open(rssUrl, 'User-Agent' => 'Ruby-RSS-Client')) + + puts feed.channel.title + puts feed.channel.link + + first_item = feed.items.first + if first_item + description = CGI.unescapeHTML(first_item.description.to_s).gsub(/<[^>]*>/, '') + description = description.length > 150 ? description = description[0,200] + " (..)" : description + else + XRForge.log("❌ did not find post", logfile) + exit 0 + end + + puts description + + # look for first image + MEDIA_REGEX = / e + # Handle HTTP errors (e.g., 404 not found, 403 forbidden) + puts "Error fetching feed: #{e.message}" + exit +rescue => e + # Handle other parsing or connection errors + puts "An error occurred: #{e.message}" +rescue Errno::ENOENT + puts "File #{filename} not found" +rescue JSON::ParserError + puts "Error parsing JSON from #{filename}" +rescue => e + puts "An error occurred: #{e.message}" +end diff --git a/manyfold/usr/src/app/app/views/models/show.html.erb b/manyfold/usr/src/app/app/views/models/show.html.erb index 6b1dbc8..e3dae16 100644 --- a/manyfold/usr/src/app/app/views/models/show.html.erb +++ b/manyfold/usr/src/app/app/views/models/show.html.erb @@ -187,6 +187,28 @@ + + + + + + <%= link_to "glTF JSON", ENV['FEDERATE_DRIVE_HOST']+"/"+@model.library.name+"/"+@model.path.gsub("#","%23")+"/.xrforge/scene.gltf" %> + +
+ +
+   + + This is the glTF JSON URL.
+ This is an opensource fallback-mechanism for developers/engine's which don't support XR Fragments. + deeplinks.

+ Instead of https://my.org/foo.glb#chair they can do: +
$ curl  | jq '.meshes[] | select(.name == "chair")'
+
+
+
+ + <% end %> <% if @model.collection %>