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 8ad3a92..5a9eeb0 100755 --- a/manyfold/root/hook.d/experience_updated/300-package_janusxr.rb +++ b/manyfold/root/hook.d/experience_updated/300-package_janusxr.rb @@ -20,7 +20,8 @@ begin end # Change the directory - dir = File.dirname(filename) + dir = File.dirname(filename) + dirPublic = dir.gsub("/mnt/","").gsub("#","%23") Dir.chdir( File.dirname(filename) ) # Read and parse the JSON file data = JSON.parse( File.read( "datapackage.json" ) ) @@ -31,37 +32,12 @@ begin XRForge.log("✅ starting build janusXR XR scene", logfile) - # Extract the desired field (assuming the field is named 'model_file') - thumb_file = data['image'] - - XRForge.log("✅ thumbnail sidecar-file '#{thumb_file}' detected", logfile) - - # Get the base name of the thumbnail file without its extension - base_name = File.basename(thumb_file, File.extname(thumb_file)) - - model_file = nil # Initialize model_file to nil - - # Loop over the list of extensions - XRForge::MODEL_EXT.each do |ext| - # Construct the filename with the current extension - filename = "#{base_name}#{ext}" - - # Check if the file exists - if File.exist?(filename) - XRForge.log("✅ 3D file '#{filename}' detected", logfile) - model_file = "#{dir.gsub("/mnt/","")}/#{filename}" # Store the found filename - break # Stop the loop once a file is found - else - # Log a message for the file that was not found, but don't stop - XRForge.log("⚠️ 3D file '#{filename}' not detected", logfile) - end - end + model_file = XRForge.getExperienceFile(data,dir,logfile) # Check if a model file was found after the loop - if model_file - XRForge.log("✅ Final model file: '#{model_file}'", logfile) - else - XRForge.log("❌ No suitable 3D file found for XR Fragments- / JanusXR-compatible experience", logfile) + if ! model_file + XRForge.log("❌ No suitable 3D file found for JanusXR-compatible experience", logfile) + exit 0 end # Get the value of the environment variable FEDERATE_DRIVE_HOST @@ -70,7 +46,7 @@ begin autogenerate = true if data['description'] && data['description'].match(JMLHeuristic) - if data['description'].match(/autogenerate=['"]false['"]/) + if data['description'].match(/autogenerate=["']false["']/i) XRForge.log("✅ autogenerate='false' found in JML..keeping this JML",logfile) autogenerate = false jmlMatch = data['description'].match(JMLHeuristic) @@ -78,28 +54,62 @@ begin end end if autogenerate + assets = "\n" + objects = "" + + # detect audio track sidecarfile as per XR Fragment spec: https://xrfragment.org/#%F0%9F%93%9C%20level0%3A%20File + xrf_ext = File.extname(model_file) + xrf_base = File.basename(model_file, xrf_ext) + data['resources'].each do |resource| + ext = File.extname(resource['path']) + if ext.match(/mp3/i) + base = File.basename(resource['path'], ext) + if base == xrf_base + XRForge.log("✅ XR Fragments side-car file found: #{resource['path']}",logfile) + assets = assets + " \n" + objects = objects + "\n" + end + end + end + + private = data['keywords'].include?('singleuser') ? "private='true'" : "" + + # tags to JML rooms *REFACTOR PLEASE* + use_local_asset = "" + use_local_asset = data['keywords'].include?('room1') ? "use_local_asset=\"room1\"" : use_local_asset + use_local_asset = data['keywords'].include?('room2') ? "use_local_asset=\"room2\"" : use_local_asset + use_local_asset = data['keywords'].include?('room3') ? "use_local_asset=\"room3\"" : use_local_asset + use_local_asset = data['keywords'].include?('room4') ? "use_local_asset=\"room4\"" : use_local_asset + use_local_asset = data['keywords'].include?('room5') ? "use_local_asset=\"room5\"" : use_local_asset + use_local_asset = data['keywords'].include?('room6') ? "use_local_asset=\"room5\"" : use_local_asset + use_local_asset = data['keywords'].include?('room1_pedestal') ? "use_local_asset=\"room1_pedestal\"" : use_local_asset + use_local_asset = data['keywords'].include?('room2_pedestal') ? "use_local_asset=\"room2_pedestal\"" : use_local_asset + use_local_asset = data['keywords'].include?('room2_narrow') ? "use_local_asset=\"room3_narrow\"" : use_local_asset + + objects = objects + " " + jml = <<~JML - + #{assets} - - + + #{objects} - + JML data['description'] = data['description'] ? data['description'] : "your description here\n" data['description'] = <<~DESCRIPTION#{data['description']} - - - - - - - + + + + + + + #{jml} DESCRIPTION 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 fba5154..c4bb36a 100755 --- a/manyfold/root/hook.d/experience_updated/300-package_mml.rb +++ b/manyfold/root/hook.d/experience_updated/300-package_mml.rb @@ -30,37 +30,12 @@ begin XRForge.log("✅ starting build mml XR scene", logfile) - # Extract the desired field (assuming the field is named 'model_file') - thumb_file = data['image'] - - XRForge.log("✅ thumbnail sidecar-file '#{thumb_file}' detected", logfile) - - # Get the base name of the thumbnail file without its extension - base_name = File.basename(thumb_file, File.extname(thumb_file)) - - model_file = nil # Initialize model_file to nil - - # Loop over the list of extensions - XRForge::MODEL_EXT.each do |ext| - # Construct the filename with the current extension - filename = "#{base_name}#{ext}" - - # Check if the file exists - if File.exist?(filename) - XRForge.log("✅ 3D file '#{filename}' detected", logfile) - model_file = "#{dir.gsub("/mnt/","")}/#{filename}" # Store the found filename - break # Stop the loop once a file is found - else - # Log a message for the file that was not found, but don't stop - XRForge.log("⚠️ 3D file '#{filename}' not detected", logfile) - end - end + model_file = XRForge.getExperienceFile(data,dir,logfile) # Check if a model file was found after the loop - if model_file - XRForge.log("✅ Final model file: '#{model_file}'", logfile) - else - XRForge.log("❌ No suitable 3D file found for XR Fragments- / MML-compatible experience", logfile) + if ! model_file + XRForge.log("❌ No suitable 3D file found for MML-compatible experience", logfile) + exit 0 end # Get the value of the environment variable FEDERATE_DRIVE_HOST diff --git a/manyfold/root/hook.d/experience_updated/300-package_xrf.rb b/manyfold/root/hook.d/experience_updated/300-package_xrf.rb index f8438c1..25b3205 100755 --- a/manyfold/root/hook.d/experience_updated/300-package_xrf.rb +++ b/manyfold/root/hook.d/experience_updated/300-package_xrf.rb @@ -13,36 +13,20 @@ filename = ARGV[0] begin # Change the directory - Dir.chdir( File.dirname(filename) ) + dir = File.dirname(filename) + Dir.chdir( dir ) # Read and parse the JSON file data = JSON.parse( File.read( "datapackage.json" ) ) logfile = File.join( File.dirname(filename), ".xrforge/log.txt" ) XRForge.log("✅ starting XR fragments check", logfile) - # Extract the desired field (assuming the field is named 'model_file') - thumb_file = data['image'] - XRForge.log("✅ thumbnail sidecar-file '#{thumb_file}' detected", logfile) + model_file = XRForge.getExperienceFile(data,dir,logfile) - # Get the base name of the thumbnail file without its extension - base_name = File.basename(thumb_file, File.extname(thumb_file)) - - model_file = nil # Initialize model_file to nil - - # Loop over the list of extensions - XRForge::MODEL_EXT.each do |ext| - # Construct the filename with the current extension - filename = "#{base_name}#{ext}" - - # Check if the file exists - if File.exist?(filename) - XRForge.log("✅ 3D file '#{filename}' detected", logfile) - model_file = filename # Store the found filename - break # Stop the loop once a file is found - else - # Log a message for the file that was not found, but don't stop - XRForge.log("⚠️ 3D file '#{filename}' not detected", logfile) - end + # Check if a model file was found after the loop + if ! model_file + XRForge.log("❌ No suitable 3D file found for XR Fragments-compatible experience", logfile) + exit 0 end # Check if a model file was found after the loop diff --git a/manyfold/root/xrforge.rb b/manyfold/root/xrforge.rb index 5464fb6..3f87fb5 100644 --- a/manyfold/root/xrforge.rb +++ b/manyfold/root/xrforge.rb @@ -106,4 +106,43 @@ module XRForge { root.name => x2j_rec(root) } end + def self.getExperienceFile(datapackage, dir, logfile) + model_file = nil # Initialize model_file to nil + base_name = "" + # Extract the desired field (assuming the field is named 'model_file') + thumb_file = datapackage['image'] + if thumb_file && ! thumb_file.empty? + # HEURISTIC: XR Fragments sidecarfile https://xrfragment.org/#%F0%9F%93%9C%20level0%3A%20File + # Get the base name of the thumbnail file without its extension + base_name = File.basename(thumb_file, File.extname(thumb_file)) + XRForge.log("✅ thumbnail sidecar-file '#{thumb_file}' detected", logfile) + # Loop over the list of extensions + MODEL_EXT.each do |ext| + # Construct the filename with the current extension + filename = "#{base_name}#{ext}" + + # Check if the file exists + if File.exist?(filename) + XRForge.log("✅ 3D file '#{filename}' detected (heuristic: XRFragment sidecarfile)", logfile) + model_file = "#{dir.gsub("/mnt/","")}/#{filename}" # Store the found filename + break # Stop the loop once a file is found + else + # Log a message for the file that was not found, but don't stop + XRForge.log("⚠️ 3D file '#{filename}' not detected", logfile) + end + end + else + # HEURISTIC: get first suitable 3D file + datapackage['resources'].each do |resource| + ext = File.extname(resource['path']) + if MODEL_EXT.include?( ext ) + model_file = resource['path'] + XRForge.log("✅ 3D file '#{model_file}' detected (heuristic: first-found)", logfile) + break + end + end + end + return model_file + end + end diff --git a/manyfold/usr/src/app/app/views/models/_form.html.erb b/manyfold/usr/src/app/app/views/models/_form.html.erb new file mode 100644 index 0000000..8d2cdd9 --- /dev/null +++ b/manyfold/usr/src/app/app/views/models/_form.html.erb @@ -0,0 +1,52 @@ +<%= form_with model: @model do |form| %> + <%= text_input_row form, :name %> + + <%= render "tags_edit", form: form, name: "model[tag_list]", value: (@model.tags.order(taggings_count: :desc, name: :asc).map { |tag| tag.name }).join(","), label: translate(".tags"), tags: @available_tags %> +
+ Available tags:
+ <%= render 'models/tags_info' %> +
+ + <%= collection_select_input_row form, :preview_file, @model.valid_preview_files, :id, :name, help: t(".preview_file.help") %> + + <%= collection_select_input_row form, + :creator, @creators, :id, :name_with_domain, + include_blank: true, + selected: @default_creator&.id, + button: (if policy(:creator).new? + { + path: new_creator_path, + label: t("creators.general.new") + } + end) %> + + <% if SiteSettings.show_libraries || current_user.is_administrator? %> + <%= unless @model.contains_other_models? + collection_select_input_row form, + :library, policy_scope(Library).all, :id, :name, + include_blank: true + end %> + <% end %> + + + <%= collection_select_input_row form, + :collection, @collections, :id, :name_with_domain, + include_blank: true, + button: (if policy(:collection).new? + { + path: new_collection_path, + label: t("collections.general.new") + } + end) %> + + <%= render "links_form", form: form %> + <%= text_input_row form, :caption %> + <%= rich_text_input_row form, :notes, help: t(".notes.help_html") %> + <%= select_input_row form, :license, license_select_options(selected: @model.license), include_blank: true %> + <%= checkbox_input_row form, :sensitive %> + <%= select_input_row form, :indexable, indexable_select_options(form.object) %> + <%= select_input_row form, :ai_indexable, ai_indexable_select_options(form.object) if SiteSettings.allow_ai_bots %> + <%= render "caber_relations_form", form: form %> + + <%= form.submit "Save", class: "btn btn-primary" %> +<% end %> diff --git a/manyfold/usr/src/app/app/views/models/_tags_info.html.erb b/manyfold/usr/src/app/app/views/models/_tags_info.html.erb index 5731212..d7d2e2d 100644 --- a/manyfold/usr/src/app/app/views/models/_tags_info.html.erb +++ b/manyfold/usr/src/app/app/views/models/_tags_info.html.erb @@ -1,5 +1,3 @@ - Features can be toggled via tags:
-
+
@@ -12,21 +10,32 @@
- janusxr + your@mastodon.online - This generates an JanusXR address.
- JanusXR is an Open Immersive Web-layer since 2015, which allows existing webpages to become spatial.
- It is Free and Opensource, and allows you to meet others in this experience (avatars, chat and voice etc).
+ Simply enter your mastodon address as tag, and XRForge will generate a 3D model, based on your last post on mastodon.
+ +
+ JanusXR tags: + - 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 553e2f8..af3064c 100644 --- a/manyfold/usr/src/app/app/views/models/show.html.erb +++ b/manyfold/usr/src/app/app/views/models/show.html.erb @@ -46,19 +46,81 @@ <% end %> <% end %> +
- mml + + room1
+ room2
+ room3
+ room4
+ room5
+ room6
+ room1_pedestal
+ room2_pedestal
+ room2_narrow
- This generates an MML address.
- Metaverse Markup Language (MML) is an open markup language used to define experiences. + This wraps a room around the 3D asset:

+
+ <% if ENV['FEDERATE_DRIVE_HOST'].present? %> + + <% if @model.tags.where(name: "xrfragments" ).any? %> + + + + + + <% + # lets generate a JanusXR spatial layer for the current scene + jmlfile = @model.library.path+"/"+@model.path+"/.xrforge/scene.jml" + jml = "" + if File.exist?(jmlfile) + jml = File.read(jmlfile) + end + %> + + + + <%== jml %> + + <% end %> + + <% if @model.tags.where(name: "xrfragments" ).any? %> + + + + + <% end %> + <% if SiteSettings.federation_enabled? %> <% end %> - <% if @model.creator %> - - - - - <% end %> - <% if ENV['FEDERATE_DRIVE_HOST'].present? %> - - <% if @model.tags.where(name: "xrfragments" ).any? %> - - - - - - <% - # lets generate a JanusXR spatial layer for the current scene - jmlfile = @model.library.path+"/"+@model.path+"/.xrforge/scene.jml" - jml = "" - if File.exist?(jmlfile) - jml = File.read(jmlfile) - end - %> - - - -<%== jml %> - - <% end %> <% if @model.tags.where(name: "xrfragments" ).any? %> - + <% end %> + <% end %> +
+ + + <% + fediURL = ENV['FEDERATE_DRIVE_HOST']+"/"+@model.library.name+"/"+@model.path.gsub("#","%23")+"/.xrforge/janusxr.html" + %> + <%= link_to "JanusXR", fediURL, target:"_blank" %> + + +
+ +
+   + + Click to view this page in a JanusXR (JML) browser like JanusWeb.
+ JanusXR is an Open XR content-weblayer since 2013, which allows existing webpages to become spatial.
+ It is Free and Opensource, and allows you to meet others in this experience (avatars, chat and voice etc).
+
+
+
+
+ <%= link_to "XR Fragments", ENV['FEDERATE_DRIVE_HOST']+"/"+@model.library.name+"/"+@model.path.gsub("#","%23")+"/.xrforge/scene.gltf" %> + +
+ +
+   + + This is the glTF JSON URL with embedded hyperlinks for immersive navigation to other 3D scene-files, as per the XR Fragments spec.
+
+
+
+
<% if @model.remote? %> - <%= link_to @model.federails_actor.at_address, @model.federails_actor.profile_url, target: "new" %> + <%= link_to @model.federails_actor.at_address, @model.federails_actor.profile_url, target: "new" %> <% else %> - <%= @model.federails_actor.short_at_address %> <%= render Components::CopyButton.new(text: @model.federails_actor.at_address) %> - <% end %>
@@ -74,62 +136,12 @@
<%= icon "person", Creator.model_name.human %><%= link_to @model.creator.name, @model.creator, itemprop: "author" %>
- - - <% - fediURL = ENV['FEDERATE_DRIVE_HOST']+"/"+@model.library.name+"/"+@model.path.gsub("#","%23")+"/.xrforge/janusxr.html" - %> - <%= link_to "JanusXR address", fediURL, target:"_blank" %> - - -
- -
-   - - This is the JanusXR address.
- JanusXR is an Open Immersive Web-layer since 2015, which allows existing webpages to become spatial.
- It is Free and Opensource, and allows you to meet others in this experience (avatars, chat and voice etc).
-
-
-
-
- - - <%= link_to "MML address", "https://viewer.mml.io/main/v1/?url="+URI.encode_www_form_component(ENV['FEDERATE_DRIVE_HOST']+"/"+@model.library.name+"/"+@model.path.gsub("#","%23")+"/.xrforge/scene.mml"), target:"_blank" %> + <%= link_to "MML", "https://viewer.mml.io/main/v1/?url="+URI.encode_www_form_component(ENV['FEDERATE_DRIVE_HOST']+"/"+@model.library.name+"/"+@model.path.gsub("#","%23")+"/.xrforge/scene.mml"), target:"_blank" %> " target="_blank">
@@ -145,7 +157,20 @@
+ <% end %> + + <%= card :secondary, t(".model_details") do %> + + + <% if @model.creator %> + + + + + <% end %> - - <% end %> <% if @model.collection %> @@ -282,6 +288,7 @@
<%= render Components::DownloadButton.new(model: @model) %>
+ <%= card :secondary, t("layouts.card_list_page.actions_heading") do %> <%= render Components::ReportButton.new(object: @model, path: new_model_report_path(@model)) %> diff --git a/manyfold/usr/src/app/config/initializers/xrforge.rb b/manyfold/usr/src/app/config/initializers/xrforge.rb index 4dae60e..bd21b36 100644 --- a/manyfold/usr/src/app/config/initializers/xrforge.rb +++ b/manyfold/usr/src/app/config/initializers/xrforge.rb @@ -45,3 +45,64 @@ Rails.application.config.to_prepare do end end + +#class XRForgeRoutes +# # Rack endpoints must implement a #call method that accepts the Rack environment hash (env). +# def call(env) +# # Wrap the environment in a Rails Request object for easier parameter access +# request = ActionDispatch::Request.new(env) +# +# # 1. Access the model ID from the route parameters +# # The route path parameters are stored in the routing information +# model_id = request.path_parameters[:id] +# +# begin +# # 2. Find the model +# # Note: We are outside the controller, so we access Model directly. +# model = Model.find(model_id) +# +# puts "-------------------------" +# Rails.logger.info("build process for Model ID: #{model.id}") +# url_helpers = Rails.application.routes.url_helpers +# redirect_path = url_helpers.model_path(model) +# +# # 302 Found response for a temporary redirect +# [ +# 302, +# { +# 'Content-Type' => 'text/html', +# 'Location' => redirect_path +# }, +# ['Build process started successfully.'] +# ] +# +# rescue ActiveRecord::RecordNotFound +# # Handle model not found error +# [ +# 404, +# { 'Content-Type' => 'text/plain' }, +# ["Model with ID #{model_id} not found."] +# ] +# rescue => e +# # Handle other errors +# Rails.logger.error("Build Rack App Error: #{e.message}") +# [ +# 500, +# { 'Content-Type' => 'text/plain' }, +# ["Internal Server Error: Could not initiate build."] +# ] +# end +# end +#end +# +#Rails.configuration.to_prepare do +# Rails.application.routes.draw do +# resources :models, only: [] do +# member do +# # Point the route directly to an instance of the Rack application class. +# # The route is: POST /models/:id/build +# match 'build', to: XRForgeRoutes.new, via: :post +# end +# end +# end +#end diff --git a/manyfold/usr/src/app/public/assets/jml_templates.png b/manyfold/usr/src/app/public/assets/jml_templates.png new file mode 100644 index 0000000..be6108a Binary files /dev/null and b/manyfold/usr/src/app/public/assets/jml_templates.png differ
<%= icon "person", Creator.model_name.human %><%= link_to @model.creator.name, @model.creator, itemprop: "author" %>
@@ -160,6 +185,8 @@ This is the build log of XR Forge.
When you add files, they are processed, validated (for XR Fragment compliance).
+ Features can be toggled via tags:
+
<%= render 'models/tags_info' %>
@@ -187,27 +214,6 @@