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

Add Autodesk Viewer integration #160

Merged
merged 44 commits into from
Mar 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
c4b3fcc
Add Autodesk application credentials
jp524 Feb 29, 2024
5f98604
Modify CustomRender to add Autodesk viewer script for 3D files
jp524 Feb 29, 2024
ff9c889
Add service Autodesk and #access_token
jp524 Feb 29, 2024
587575d
Add Autodesk#create_storage_bucket and #upload_file
jp524 Feb 29, 2024
73d129a
Fix bug in #upload_file
jp524 Feb 29, 2024
b13a751
Finalize upload of file for Autodesk viewer
jp524 Mar 4, 2024
9cc2a04
Pin package 'three'
jp524 Mar 4, 2024
0f7191d
Build autodesk-viewer stimulus controller
jp524 Mar 4, 2024
8daa86e
Add buttons to toggle preview in autodesk-viewer stimulus controller
jp524 Mar 4, 2024
bf168c0
Load file inside autodesk-viewer
jp524 Mar 4, 2024
d9a875f
Fix display and hide buttons behaviour
jp524 Mar 4, 2024
4c7ac42
Lint autodesk-view stimulus controller
jp524 Mar 4, 2024
3c9585b
Style autodesk-viewer
jp524 Mar 5, 2024
b504299
Add table and model for AutodekFiles
jp524 Mar 5, 2024
5ae4a0b
Update Autodesk#upload_file_for_viewer to return urn value
jp524 Mar 5, 2024
acd0d4f
Add UploadAutodeskFileJob
jp524 Mar 5, 2024
cf123c1
Remove extra requests from VCR cassette 'upload-3d-file-autodesk'
jp524 Mar 5, 2024
c0674f1
Update provenance of 3D files in fixtures
jp524 Mar 6, 2024
f36fc15
Update UploadAutodeskFileJob to parse repository and find Autodesk files
jp524 Mar 6, 2024
fa0c8db
Rename UploadAutodeskFileJob to be plural
jp524 Mar 6, 2024
f427bc3
Update OctokitHelper#git_clone to use a different fixture repo
jp524 Mar 6, 2024
a82d0d2
Update collections system tests to use VCR cassettes
jp524 Mar 6, 2024
f9cef56
Update UploadAutodeskFilesJob to use build process
jp524 Mar 6, 2024
b88848e
Update system and request tests that relied on Build logs
jp524 Mar 6, 2024
58ae9a1
Update Autodesk service to use build process
jp524 Mar 6, 2024
767d0f5
Modify 3D files to use dash in name instead of underscore
jp524 Mar 6, 2024
84f2e73
Modify custom render to pass URN value of Autodesk file
jp524 Mar 6, 2024
2e55ae0
Modify README to include rendering of 3D files
jp524 Mar 6, 2024
cb895f5
Fix style in autodesk-viewer
jp524 Mar 6, 2024
6da8452
Split Autodesk Service into different classes
jp524 Mar 6, 2024
395d8f5
Modify Autodesk service to handle access token creation error
jp524 Mar 7, 2024
7988dda
Make filepath an instance variable in AutodeskFileUpload service
jp524 Mar 7, 2024
335d76c
Refactor AutodeskFileUpload service
jp524 Mar 7, 2024
f0de0c6
Add logs for errors in AutodeskFileUpload service
jp524 Mar 7, 2024
84a8307
Modify Autodesk bucket data retention policy
jp524 Mar 7, 2024
bc1f0eb
Update Autodesk credentials
jp524 Mar 7, 2024
a864b27
Add scope argument in Autodesk service
jp524 Mar 7, 2024
f82b07d
Modify UploadAutodeskFileJob to find existing AutodeskFiles
jp524 Mar 7, 2024
74d2435
Modify autodesk-viewer controller options to render SVF files
jp524 Mar 11, 2024
fc5c340
Implement proxy for autodesk-viewer to use access token generated in …
jp524 Mar 11, 2024
1559987
Refactor UploadAutodeskFilesJob
jp524 Mar 11, 2024
aaf9e08
Fix VCR for Collections#show system test
jp524 Mar 11, 2024
2fe5045
Fix typo in README
jp524 Mar 11, 2024
1d021f9
Fix bug in CustomRender
jp524 Mar 11, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ Authors use Markdown to write articles on KnewHub. In addition to regular Markdo
- Collapsing elements (HTML details and summary tags)
+ Syntax: `[details Hint]content[/details]`
+ Example: `[details Click Here to Display Content]Content[/details]`
- Viewing 3D files using Autodesk viewer
+ Syntax: `[3d-viewer <relative_path>]`
+ Example: `[3d-viewer ./some-model.stp]`

Note that `relative_path` cannot use underscores (_). Only alphanumeric characters and hyphens (-) are allowed.

## Development

Expand Down
1 change: 1 addition & 0 deletions app/assets/stylesheets/application.scss
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
@import "layout/sidebar";
@import "layout/swiper-slide";

@import "components/autodesk-viewer";
@import "components/answer";
@import "components/flash";
@import "components/form-block";
Expand Down
7 changes: 7 additions & 0 deletions app/assets/stylesheets/components/_autodesk-viewer.sass
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// autodesk-viewer

.autodesk-viewer
&:has(div.adsk-viewing-viewer)
position: relative
min-height: 50rem
margin: 2rem 0
14 changes: 14 additions & 0 deletions app/controllers/proxy/autodesk_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
module Proxy
class AutodeskController < ApplicationController
before_action :authenticate_user!

def show
autodesk_service = Autodesk.new
response = autodesk_service.call_viewer(params[:request_type], params[:path], params[:format])

head :bad_request and return unless response.status == 200

send_data response.body
end
end
end
69 changes: 69 additions & 0 deletions app/javascript/controllers/autodesk_viewer_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { Controller } from '@hotwired/stimulus'

export default class extends Controller {
static targets = ['viewerDiv', 'displayBtn', 'hideBtn']

static values = {
urn: String
}

viewer
options = {
shouldInitializeAuth: false,
endpoint: window.location.origin + "/autodesk/viewer-proxy",
env: 'AutodeskProduction',
api: 'derivativeV2'
}

documentId = 'urn:' + this.urnValue

connect () {
this.createViewer()
}

createViewer () {
Autodesk.Viewing.Initializer(this.options, () => {
this.viewer = new Autodesk.Viewing.GuiViewer3D(this.viewerDivTarget)
const startedCode = this.viewer.start()
if (startedCode > 0) {
console.error('Failed to create a Viewer: WebGL not supported.')
return
}
console.log('Initialization complete, loading a model next...')
this.displayBtnTarget.classList.add('hide')
})

this.loadDocument()
}

loadDocument () {
const onDocumentLoadSuccess = (viewerDocument) => {
const defaultModel = viewerDocument.getRoot().getDefaultGeometry()
this.viewer.loadDocumentNode(viewerDocument, defaultModel)
}

const onDocumentLoadFailure = (viewerErrorCode) => {
console.error('Failed fetching Forge manifest. Error code: ' + viewerErrorCode)
}

Autodesk.Viewing.Document.load(this.documentId, onDocumentLoadSuccess, onDocumentLoadFailure)
}

display () {
this.hideBtnTarget.classList.remove('hide')
this.displayBtnTarget.classList.add('hide')
this.createViewer()
}

hide () {
this.displayBtnTarget.classList.remove('hide')
this.hideBtnTarget.classList.add('hide')
this.destroyViewer()
}

destroyViewer () {
this.viewer.finish()
this.viewer = null
Autodesk.Viewing.shutdown()
}
}
69 changes: 69 additions & 0 deletions app/jobs/upload_autodesk_files_job.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
class UploadAutodeskFilesJob
include Sidekiq::Job
include SplitMarkdownHelper

def perform(build_id)
build = Build.find(build_id)
_repository, directory = RepositoryDirectory.define(build.repository.id)

perform_for_each_repository(build, directory)
build.finished_uploading_autodesk_files
end

private

def list_markdown_file_paths(directory)
Dir.glob("#{directory}/**/*.md")
end

def list_autodesk_files_for_markdown_file(markdown_file_path)
markdown_path_name = Pathname.new(markdown_file_path)
directory_path_name = markdown_path_name.dirname

_front_matter, markdown_content = split_markdown(markdown_file_path)
autodesk_file_relative_paths = markdown_content.scan(/\[3d-viewer (.+)\]/).flatten
# ["./3d-files/nist-ctc-01-asme1-rd.stp", "./3d-files/nist-ctc-02-asme1-rc.stp""]

return if autodesk_file_relative_paths.empty?

autodesk_file_relative_paths.map do |relative_path|
directory_path_name.join(relative_path).relative_path_from(Rails.root).to_s
end
# ["repos/author/repo_owner/name/chapter-1/3d-files/nist-ctc-01-asme1-rd.stp",
# "repos/author/repo_owner/name/chapter-1/3d-files/nist-ctc-02-asme1-rc.stp"]
end

def list_autodesk_files_for_directory(directory, autodesk_files = [])
markdown_file_paths = list_markdown_file_paths(directory)
markdown_file_paths.each do |markdown_file_path|
autodesk_files_for_markdown_file = list_autodesk_files_for_markdown_file(markdown_file_path)
autodesk_files << autodesk_files_for_markdown_file if autodesk_files_for_markdown_file
end
autodesk_files = autodesk_files.flatten
end

def perform_for_each_repository(build, directory)
autodesk_files = list_autodesk_files_for_directory(directory)

if autodesk_files.empty?
build.logs.create(content: 'No Autodesk files were found in this repository.')
else
build.logs.create(content: 'Autodesk files found in this repository. Uploading...')
autodesk_files.each do |filepath|
perform_for_each_file(build, build.repository.id, filepath)
end
end
end

def perform_for_each_file(build, repository_id, filepath)
autodesk_file = AutodeskFile.find_by(repository_id:, filepath:)
autodesk_file ||= AutodeskFile.create!(repository_id:, filepath:)

autodesk_service = AutodeskFileUpload.new(filepath, build)
urn = autodesk_service.upload_file_for_viewer

return if urn.nil?

autodesk_file.update(urn:)
end
end
5 changes: 5 additions & 0 deletions app/models/autodesk_file.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class AutodeskFile < ApplicationRecord
belongs_to :repository

validates :filepath, presence: true
end
15 changes: 14 additions & 1 deletion app/models/build.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ def repository_author
:getting_repo_description,
:parsing_questions,
:creating_repo_index,
:uploading_autodesk_files,
:completed

event :receive_webhook, after_commit: :schedule_receive_webhook do
Expand All @@ -47,8 +48,12 @@ def repository_author
transitions from: :parsing_questions, to: :creating_repo_index
end

event :upload_autodesk_files, after_commit: :schedule_upload_autodesk_files do
transitions from: :creating_repo_index, to: :uploading_autodesk_files
end

event :complete, after_commit: :complete_build do
transitions from: :creating_repo_index, to: :completed
transitions from: :uploading_autodesk_files, to: :completed
end
end

Expand Down Expand Up @@ -99,6 +104,10 @@ def schedule_create_repo_index
CreateRepoIndexJob.perform_async(id)
end

def schedule_upload_autodesk_files
UploadAutodeskFilesJob.perform_async(id)
end

def complete_build
update(status: 'Complete', completed_at: DateTime.current)
end
Expand Down Expand Up @@ -130,6 +139,10 @@ def finished_parsing_questions
end

def finished_creating_repo_index
upload_autodesk_files!
end

def finished_uploading_autodesk_files
complete!
end

Expand Down
1 change: 1 addition & 0 deletions app/models/repository.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ class Repository < ApplicationRecord
belongs_to :github_installation
has_many :builds, dependent: :destroy
has_many :questions, dependent: :destroy
has_many :autodesk_files, dependent: :destroy

before_save :set_branch

Expand Down
52 changes: 52 additions & 0 deletions app/services/autodesk.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
class Autodesk
attr_reader :access_token, :access_token_error_msg

def initialize(scope: 'viewables:read')
@conn = Faraday.new(url: 'https://developer.api.autodesk.com')
@bucket_key = Rails.application.credentials.dig(:autodesk, :upload, :bucket_key)
@scope = scope
create_access_token
end

def call_viewer(request_type, path, format)
@conn.get(
"derivativeservice/v2/#{request_type}/#{request_path(path, format)}",
nil,
{ Authorization: "Bearer #{@access_token}" }
)
end

private

def create_access_token
response = @conn.post(
'/authentication/v2/token',
{ grant_type: 'client_credentials', scope: @scope },
{ 'Content-Type': 'application/x-www-form-urlencoded',
Accept: 'application/json',
Authorization: "Basic #{base64_client_info}" }
)
handle_response(response)
end

def handle_response(response)
response_body = JSON.parse(response.body)
if response.status == 200
@access_token = response_body['access_token']
else
@access_token_error_msg = response_body.values.join('. ')
end
end

def base64_client_info
client_id = Rails.application.credentials.dig(:autodesk, :upload, :client_id)
client_secret = Rails.application.credentials.dig(:autodesk, :upload, :client_secret)
Base64.strict_encode64("#{client_id}:#{client_secret}")
end

def request_path(path, format)
result = CGI.escape(path)
result.concat(".#{format}") unless format.nil?
result
end
end
24 changes: 24 additions & 0 deletions app/services/autodesk_bucket.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
class AutodeskBucket < Autodesk
def initialize
super(scope: 'data:read bucket:create')
end

def query_storage_bucket_objects
response = @conn.get(
"/oss/v2/buckets/#{@bucket_key}/objects",
nil,
{ 'Content-Type': 'application/json', Authorization: "Bearer #{@access_token}" }
)
JSON.parse(response.body)
end

def create_storage_bucket
request_params = { bucketKey: @bucket_key, access: 'full', policyKey: 'persistent' }
response = @conn.post(
'/oss/v2/buckets',
request_params.to_json,
{ 'Content-Type': 'application/json', Authorization: "Bearer #{@access_token}" }
)
JSON.parse(response.body)
end
end
Loading