Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8294edc1d1 | |||
| 846106a1a8 | |||
| 717ef152f1 | |||
| 859183fe2e | |||
| 9953dff4a0 | |||
| 8ed4e1f679 | |||
| 7e353bee24 | |||
| 383818b6f8 | |||
| 88cd5e4727 | |||
| a2246015c9 | |||
| 840b5e34df | |||
| 9857e77a1a | |||
| ea51793e2f | |||
| 5561038c25 | |||
| 95647069eb | |||
| 4c11d44918 | |||
| 996a62f55a | |||
| 99b84272c6 |
46
README.md
46
README.md
@@ -1,3 +1,47 @@
|
||||
# GlModelViewer
|
||||
|
||||
MediaWiki extension for using model-viewer to display and annotate 3D models
|
||||
MediaWiki extension for using model-viewer to display and annotate 3D models
|
||||
|
||||
## Metadata
|
||||
|
||||
Model-viewer configurations and annotations can be added as TOML in the file's description.
|
||||
|
||||
### Viewer Configuration
|
||||
|
||||
Configuration of the model-viewer instance is contained in the `[viewerConfig]` object.
|
||||
This object can contain any number of child objects each of which describes a different possible configuration.
|
||||
Each configuration can have properties matching the any of the attributes of the model-viewer element except for `src`, `class`, `interpolation-delay`, and `interaction-prompt`.
|
||||
If adding a poster image, the value of the `poster` property must be the name of an image in the Wiki's File namespace (without the namespace header, e.g., `"My poster.jpg"`).
|
||||
|
||||
### Annotations
|
||||
|
||||
Annotations can be configured using the `[annotations]` object.
|
||||
This object can contain any number of child objects each of which describes a different possible annotation.
|
||||
The label for each annotation is the name of the child object (`[annotations."Label here"]`). Each object has two required properties and three optional properties.
|
||||
|
||||
| Property | Required | Description | Example |
|
||||
|----------|----------|-------------|---------|
|
||||
| **data-position** | Yes | String containing the X, Y, and Z coordinates of the annotation. | "0.02297m -0.02544m 0.01780m" |
|
||||
| **data-normal** | Yes | String containing the X, Y, and Z coordinates of the normal vector of the annotation. | "0.78966m -0.53116m 0.30709m" |
|
||||
| **data-orbit** | No | String containing the orbit vector of the camera to view annotation. | "2.31rad 2.01rad 0.65m" |
|
||||
| **data-target** | No | String containing the X, Y, and Z coordinates of the camera target to view annotation. | "-0.01029m 0.00071m 0.01138m" |
|
||||
| **field-of-view** | No | String containing the angle of the field of view in degrees. | "35.25deg" |
|
||||
|
||||
### Annotation sets
|
||||
|
||||
Annotations sets are specific subsets of the listed annotations that can be shown on the model.
|
||||
They are configured using the `[annotationSets]` object.
|
||||
This object can contain any number properties each of which contains an array of strings matching the names of the annotation to be included.
|
||||
The property name is used as the annotation set identifier.
|
||||
By default, all annotations are rendered on a model.
|
||||
|
||||
## Linking
|
||||
|
||||
When adding the model file to a page, use the standard `[[File:Model name.glb]]` internal file link.
|
||||
The link supports three optional properties.
|
||||
|
||||
| Property | Description | Default |
|
||||
|----------|-------------|---------|
|
||||
| **view** | Use one of the non-default configurations specified in the `[viewerConfig]` object. | default viewer configuration |
|
||||
| **set** | Display only the annotations listed in the specified annotation set from the `[annotationSets]` object. | show all annotations |
|
||||
| **transform** | Reorder the displayed annotations. Can be either *rand* or *alpha*. | annotations are numbered in the order they are listed in the `[annotations]` object |
|
||||
5
composer.json
Normal file
5
composer.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"require": {
|
||||
"devium/toml": "*"
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
"author": "Justin Georgi",
|
||||
"url": "https://gitea.azgeorgis.net/jgeorgi/mwModelViewer",
|
||||
"description": "This extension allows .glb and .gltf files to be added, displayed, and annotated in MediaWiki",
|
||||
"version": "0.2.0",
|
||||
"version": "0.2.1",
|
||||
"license-name": "MIT",
|
||||
"type": "media",
|
||||
"manifest_version": 2,
|
||||
@@ -74,6 +74,7 @@
|
||||
],
|
||||
"packageFiles": [
|
||||
"glmv-prev.js",
|
||||
"mini-st.js",
|
||||
"glmv-mvconfig.js",
|
||||
"glmv-hs.js"
|
||||
]
|
||||
|
||||
@@ -44,7 +44,7 @@ class GlModelHooks {
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the config json in a <pre> tag
|
||||
* Render the config toml in a <pre> tag
|
||||
*
|
||||
* @param $input The text inside the custom tag
|
||||
* @param array $args Any attributes given in the tag
|
||||
@@ -170,15 +170,8 @@ class GlModelHooks {
|
||||
$uploadFormObj->mComment .= <<<CONF
|
||||
|
||||
<mvconfig>
|
||||
{
|
||||
"viewerConfig": {
|
||||
"default": {
|
||||
"camera-controls": true
|
||||
}
|
||||
},
|
||||
"annotations": {},
|
||||
"annotationSets": {}
|
||||
}
|
||||
[viewerConfig.default]
|
||||
camera-controls = true
|
||||
</mvconfig>
|
||||
CONF;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
<?php
|
||||
namespace MediaWiki\Extension\GlModelViewer;
|
||||
|
||||
require_once __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
use Devium\Toml\Toml;
|
||||
use MediaWiki\MediaWikiServices;
|
||||
use MediaTransformOutput;
|
||||
use ConfigFactory;
|
||||
@@ -49,7 +52,7 @@ class GlModelTransformOutput extends MediaTransformOutput {
|
||||
public function toHtml($options = []) {
|
||||
$descriptText = $this->file->getDescriptionText();
|
||||
preg_match('/<pre mvconfig.*?>([\S\s]*?)<\/pre>/',$descriptText,$modelDescript);
|
||||
$metadata = json_decode($modelDescript[1], true);
|
||||
$metadata = toml_decode($modelDescript[1], true);
|
||||
|
||||
if ($this->thumb) {
|
||||
$poster = $metadata['viewerConfig'][$this->view]['poster'] ?? false;
|
||||
@@ -180,10 +183,10 @@ class GlModelTransformOutput extends MediaTransformOutput {
|
||||
$attrModelView = array_merge(['src' => $srcUrl, 'class' => 'mv-model', 'interpolation-decay' => '100', 'interaction-prompt' => 'none'], $attrModelView);
|
||||
$attrModelView['style'] = 'width: 100%; height: 100%;';
|
||||
$attrModelView['onload'] = 'modelLoaded(event)';
|
||||
$hotspotHtml = (isset($hotspots)) ? implode($hotspots) : '';
|
||||
$hotspotHtml = (!empty($hotspots)) ? implode($hotspots) : '';
|
||||
|
||||
$elModel = Html::rawElement('model-viewer', $attrModelView, $hotspotHtml);
|
||||
$elMenu = self::buildViewMenu();
|
||||
$elMenu = self::buildViewMenu(!empty($hotspots));
|
||||
|
||||
$elFileLink = '';
|
||||
if (!isset($viewParams['preview']) && $context->getTitle() != $this->file->getTitle()) {
|
||||
@@ -210,9 +213,10 @@ class GlModelTransformOutput extends MediaTransformOutput {
|
||||
/**
|
||||
* Build the button menu used for viewer actions
|
||||
*
|
||||
* @param bool $annotsDrawn true to enable the annotation buttons
|
||||
* @return string
|
||||
*/
|
||||
private static function buildViewMenu() {
|
||||
private static function buildViewMenu($annotsDrawn) {
|
||||
$attrMenu = array(
|
||||
'class' => 'glmv-menu awaiting-model',
|
||||
'style' => 'display: none;'
|
||||
@@ -227,13 +231,15 @@ class GlModelTransformOutput extends MediaTransformOutput {
|
||||
|
||||
$gotoUrl = $mainConfig->get( 'ExtensionAssetsPath' ) . '/GlModelViewer/resources/goto_hs.svg';
|
||||
$attrMenuButtonPrev = array (
|
||||
'class' => 'glmv-menu-button prev-hs disable-on-hide',
|
||||
'class' => 'glmv-menu-button prev-hs disable-on-hide disable-on-none',
|
||||
'disabled' => !$annotsDrawn,
|
||||
'onclick' => 'prevAnnotation(event.target.closest(".glmv-container").querySelector("model-viewer"))',
|
||||
'onmousedown' => 'event.stopPropagation()',
|
||||
'ontouchstart' => 'event.stopPropagation()'
|
||||
);
|
||||
$attrMenuButtonNext = array (
|
||||
'class' => 'glmv-menu-button next-hs disable-on-hide',
|
||||
'class' => 'glmv-menu-button next-hs disable-on-hide disable-on-none',
|
||||
'disabled' => !$annotsDrawn,
|
||||
'onclick' => 'nextAnnotation(event.target.closest(".glmv-container").querySelector("model-viewer"))',
|
||||
'onmousedown' => 'event.stopPropagation()',
|
||||
'ontouchstart' => 'event.stopPropagation()'
|
||||
@@ -241,7 +247,8 @@ class GlModelTransformOutput extends MediaTransformOutput {
|
||||
|
||||
$slideUrl = $mainConfig->get( 'ExtensionAssetsPath' ) . '/GlModelViewer/resources/hs_slideshow.svg';
|
||||
$attrMenuButtonSlides = array (
|
||||
'class' => 'glmv-menu-button disable-on-hide',
|
||||
'class' => 'glmv-menu-button disable-on-hide disable-on-none',
|
||||
'disabled' => !$annotsDrawn,
|
||||
'onclick' => 'event.target.toggleAttribute("toggled"); slideshowAnnotations(event.target.closest(".glmv-container").querySelector("model-viewer"))',
|
||||
'onmousedown' => 'event.stopPropagation()',
|
||||
'ontouchstart' => 'event.stopPropagation()'
|
||||
@@ -249,7 +256,8 @@ class GlModelTransformOutput extends MediaTransformOutput {
|
||||
|
||||
$hideUrl = $mainConfig->get( 'ExtensionAssetsPath' ) . '/GlModelViewer/resources/hs_hide.svg';
|
||||
$attrMenuButtonHide = array (
|
||||
'class' => 'glmv-menu-button',
|
||||
'class' => 'glmv-menu-button disable-on-none',
|
||||
'disabled' => !$annotsDrawn,
|
||||
'onclick' => 'event.target.toggleAttribute("toggled"); toggleAnnotations(event.target.closest(".glmv-container").querySelector("model-viewer"))',
|
||||
'onmousedown' => 'event.stopPropagation()',
|
||||
'ontouchstart' => 'event.stopPropagation()'
|
||||
@@ -264,12 +272,21 @@ class GlModelTransformOutput extends MediaTransformOutput {
|
||||
'ontouchstart' => 'event.stopPropagation()'
|
||||
);
|
||||
|
||||
$resetUrl = $mainConfig->get( 'ExtensionAssetsPath' ) . '/GlModelViewer/resources/reset.svg';
|
||||
$attrMenuButtonReset = array (
|
||||
'class' => 'glmv-menu-button',
|
||||
'onclick' => 'resetView(event.target.closest(".glmv-container").querySelector("model-viewer"))',
|
||||
'onmousedown' => 'event.stopPropagation()',
|
||||
'ontouchstart' => 'event.stopPropagation()'
|
||||
);
|
||||
|
||||
$menuButtons = array(
|
||||
Html::rawElement('div', $attrMenuButtonPrev, '<img class="awaiting-model" src="' . $gotoUrl . '"></image>'),
|
||||
Html::rawElement('div', $attrMenuButtonSlides, '<img class="awaiting-model" src="' . $slideUrl . '"></image>'),
|
||||
Html::rawElement('div', $attrMenuButtonNext, '<img class="awaiting-model" src="' . $gotoUrl . '"></image>'),
|
||||
Html::rawElement('div', $attrMenuButtonHide, '<img class="awaiting-model" src="' . $hideUrl . '"></image>'),
|
||||
Html::rawElement('div', $attrMenuButtonScreen, '<img class="awaiting-model full-hide" src="' . $screenUpUrl . '"></image><img class="awaiting-model full-show" src="' . $screenDownUrl . '"></image>')
|
||||
Html::rawElement('div', $attrMenuButtonScreen, '<img class="awaiting-model full-hide" src="' . $screenUpUrl . '"></image><img class="awaiting-model full-show" src="' . $screenDownUrl . '"></image>'),
|
||||
Html::rawElement('div', $attrMenuButtonReset, '<img class="awaiting-model" src="' . $resetUrl . '"></image>')
|
||||
);
|
||||
|
||||
return Html::rawElement('div', $attrMenu, $menuImg . implode($menuButtons));
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
const TOML = require('./mini-st.js')
|
||||
|
||||
let deleteHotspot = null
|
||||
let grabHotspot = null
|
||||
|
||||
@@ -34,7 +36,7 @@ clickAddHotspot = function(e) {
|
||||
hsOutput['data-target'] = `${targetObj.x.toFixed(5)}m ${targetObj.y.toFixed(5)}m ${targetObj.z.toFixed(5)}m`
|
||||
hsOutput['field-of-view'] = Number.parseFloat(targetModel.getFieldOfView()).toFixed(5) + 'deg'
|
||||
mvconfig.annotations['Hotspot ' + (Object.keys(mvconfig.annotations).length + 1)] = hsOutput
|
||||
let newText = currentText.replace(/(.*?<mvconfig>)[\S\s]*?(<\/mvconfig>.*)/,`$1\n${JSON.stringify(mvconfig, null, 2)}\n$2`)
|
||||
let newText = currentText.replace(/(.*?<mvconfig>)[\S\s]*?(<\/mvconfig>.*)/,`$1\n${TOML.stringify(mvconfig, null, 2)}\n$2`)
|
||||
$('#wpTextbox1').val(newText)
|
||||
}
|
||||
readMvconfig()
|
||||
@@ -67,13 +69,15 @@ clickDeleteHotspot = function (hs) {
|
||||
deleteHotspot = null
|
||||
enableViewer()
|
||||
const anName = hs.target.childNodes[0].innerText
|
||||
let purgeAnnotation = new RegExp('(?<="annotationSets"[\\S\\s]*?)(^.*?' + anName + '.*\n)','gm')
|
||||
hs.target.remove()
|
||||
const editText = $('#wpTextbox1').val()
|
||||
const newText = editText.replace(purgeAnnotation,'')
|
||||
const finalText = newText.replace(/(,)(\n\s+])/gm,'$2')
|
||||
$('#wpTextbox1').val(finalText)
|
||||
writeMvconfig()
|
||||
let currentText = $('#wpTextbox1').val()
|
||||
let [_, mvconfig] = extractMvconfig(currentText)
|
||||
delete mvconfig.annotations[anName]
|
||||
for (anSet in mvconfig.annotationSets) {
|
||||
mvconfig.annotationSets[anSet]=mvconfig.annotationSets[anSet].filter( x => x !== anName )
|
||||
}
|
||||
let newText = currentText.replace(/(.*?<mvconfig>)[\S\s]*?(<\/mvconfig>.*)/,`$1\n${TOML.stringify(mvconfig, null, 2)}\n$2`)
|
||||
$('#wpTextbox1').val(newText)
|
||||
readMvconfig()
|
||||
}
|
||||
|
||||
@@ -91,7 +95,7 @@ isDeleting = function() {
|
||||
*/
|
||||
grabAnnotation = function(e) {
|
||||
if (e.ctrlKey) {
|
||||
grabHotspot = {x: e.x, y: e.y}
|
||||
grabHotspot = {x: e.x, y: e.y, target: e.target}
|
||||
const contEl = $('.glmv-container')[0]
|
||||
contEl.addEventListener('mousemove', moveAnnotation)
|
||||
const mvEl = $('model-viewer')[0]
|
||||
@@ -108,7 +112,7 @@ grabAnnotation = function(e) {
|
||||
moveAnnotation = function(e) {
|
||||
if (grabHotspot) {
|
||||
grabHotspot.move = true
|
||||
e.target.style['transform'] = `translate(${e.x - grabHotspot.x}px, ${e.y - grabHotspot.y}px) scale(1.1,1.1)`
|
||||
grabHotspot.target.style['transform'] = `translate(${e.x - grabHotspot.x}px, ${e.y - grabHotspot.y}px) scale(1.1,1.1)`
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,7 +151,7 @@ releaseAnnotation = function(e) {
|
||||
"data-target": newTarg,
|
||||
"field-of-view": newFov
|
||||
}
|
||||
const newText = currentText.replace(/(.*?<mvconfig>)[\S\s]*?(<\/mvconfig>.*)/,`$1\n${JSON.stringify(mvconfig, null, 2)}\n$2`)
|
||||
const newText = currentText.replace(/(.*?<mvconfig>)[\S\s]*?(<\/mvconfig>.*)/,`$1\n${TOML.stringify(mvconfig, null, 2)}\n$2`)
|
||||
$('#wpTextbox1').val(newText)
|
||||
}
|
||||
grabHotspot = null
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
const TOML = require('./mini-st.js')
|
||||
|
||||
/**
|
||||
* Convert text in the preview text editor to js object
|
||||
*
|
||||
@@ -6,7 +8,7 @@
|
||||
extractMvconfig = function() {
|
||||
const editText = $('#wpTextbox1').val()
|
||||
const extractConfig = editText.match(/<mvconfig>([\S\s]*?)<\/mvconfig>/)
|
||||
let mvconfig = (extractConfig.length >= 2) ? JSON.parse(extractConfig[1]) : {viewerConfig: {}, annotations: {}, annotationSets: {}}
|
||||
let mvconfig = (extractConfig.length >= 2) ? TOML.parse(extractConfig[1]) : {viewerConfig: {}, annotations: {}, annotationSets: {}}
|
||||
if (mvconfig.viewerConfig === undefined) {
|
||||
mvconfig.viewerConfig = {
|
||||
default: {
|
||||
@@ -17,6 +19,12 @@ extractMvconfig = function() {
|
||||
if (mvconfig.annotations === undefined) {
|
||||
mvconfig.annotations = {}
|
||||
}
|
||||
const mView = $('model-viewer')[0]
|
||||
const hsButtons = [...mView.parentElement.querySelectorAll('.disable-on-none')]
|
||||
hsButtons.forEach( mb => {
|
||||
mb.toggleAttribute('disabled',$.isEmptyObject(mvconfig.annotations))
|
||||
})
|
||||
|
||||
if (mvconfig.annotationSets === undefined) {
|
||||
mvconfig.annotationSets = {}
|
||||
}
|
||||
@@ -24,7 +32,7 @@ extractMvconfig = function() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the json string in the edit panel
|
||||
* Reads the TOML string in the edit panel
|
||||
* and updates hotspot elements and menu settings
|
||||
*
|
||||
* @return {bool|object} arrays of view and set names on successful read and update false on failure
|
||||
@@ -34,7 +42,7 @@ readMvconfig = function() {
|
||||
let mvconfig
|
||||
let slotNum = 1
|
||||
|
||||
createHotspot = function(hsLabel, hsSlot, hsTag) {
|
||||
createHotspot = function(hsLabel, hsSlot, hsTag, hsSkip) {
|
||||
let newHs = document.createElement('button')
|
||||
newHs.classList.add('Hotspot')
|
||||
newHs.setAttribute('slot',`hotspot-${hsSlot}`)
|
||||
@@ -42,6 +50,7 @@ readMvconfig = function() {
|
||||
newHs.setAttribute('onclick', 'onAnnotation(event)')
|
||||
newHs.setAttribute('onmousedown', 'grabAnnotation(event)')
|
||||
newHs.setAttribute('onmouseup', 'releaseAnnotation(event)')
|
||||
newHs.toggleAttribute('seq-skip', !!hsSkip)
|
||||
Object.keys(mvconfig.annotations[hsLabel]).forEach((prop) => {
|
||||
newHs.setAttribute(prop, mvconfig.annotations[hsLabel][prop])
|
||||
})
|
||||
@@ -73,7 +82,7 @@ readMvconfig = function() {
|
||||
return
|
||||
}
|
||||
let label = (currentSet != 'default' && mvconfig.annotationSets[currentSet]) ? '-' : null
|
||||
createHotspot(hs, slotNum, label)
|
||||
createHotspot(hs, slotNum, label, true)
|
||||
slotNum += 1
|
||||
})
|
||||
|
||||
@@ -90,8 +99,8 @@ readMvconfig = function() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the current hotspots into json object
|
||||
* and writes the json string to the edit panel
|
||||
* Parses the current hotspots into TOML object
|
||||
* and writes the TOML string to the edit panel
|
||||
*
|
||||
* @return {bool} true on successful write to edit panel
|
||||
*/
|
||||
@@ -109,7 +118,7 @@ writeMvconfig = function () {
|
||||
if (Object.keys(annotationsObj).length === 0) return false
|
||||
const [currentText, mvconfig] = extractMvconfig()
|
||||
mvconfig.annotations = annotationsObj
|
||||
const newText = currentText.replace(/(.*?<mvconfig>)[\S\s]*?(<\/mvconfig>.*)/,`$1\n${JSON.stringify(mvconfig, null, 2)}\n$2`)
|
||||
const newText = currentText.replace(/(.*?<mvconfig>)[\S\s]*?(<\/mvconfig>.*)/,`$1\n${TOML.stringify(mvconfig, null, 2)}\n$2`)
|
||||
$('#wpTextbox1').val(newText)
|
||||
return true
|
||||
}
|
||||
@@ -135,8 +144,9 @@ orb2degree = function(orbString, fix = null) {
|
||||
}
|
||||
if (fix) {
|
||||
degArray = degArray.map((v, idx) => {
|
||||
let fixReg = new RegExp('(\\d*.\\d{' + (((typeof fix) == 'object') ? fix[idx] : fix) + '})(\\d*)([a-z]*)')
|
||||
return v.replace(fixReg,'$1$3')
|
||||
//let fixReg = new RegExp('(-?\\d*\\.\\d{' + (((typeof fix) == 'object') ? fix[idx] : fix) + '})(\\d*)([a-z]*)')
|
||||
//return v.replace(fixReg,'$1$3')
|
||||
return Number.parseFloat(v).toFixed((typeof fix == 'object') ? fix[idx] : fix) + v.substring(v.search(/[a-z]/))
|
||||
})
|
||||
}
|
||||
return degArray.join(' ')
|
||||
@@ -157,7 +167,7 @@ toggleCameraControl = function(view) {
|
||||
} else {
|
||||
delete mvconfig.viewerConfig[currentView]['camera-controls']
|
||||
}
|
||||
const textUpdate = currentText.replace(/(?<=<mvconfig>)([\S\s]*?)(?=<\/mvconfig>)/gm,`\n${JSON.stringify(mvconfig, null, 2)}\n`)
|
||||
const textUpdate = currentText.replace(/(?<=<mvconfig>)([\S\s]*?)(?=<\/mvconfig>)/gm,`\n${TOML.stringify(mvconfig, null, 2)}\n`)
|
||||
$('#wpTextbox1').val(textUpdate)
|
||||
return newControl
|
||||
}
|
||||
@@ -171,7 +181,7 @@ addAnnotationSet = function(newSet) {
|
||||
const mView = $('model-viewer')[0]
|
||||
let [currentText, mvconfig] = extractMvconfig()
|
||||
mvconfig.annotationSets[newSet] = []
|
||||
const textUpdate = currentText.replace(/(?<=<mvconfig>)([\S\s]*?)(?=<\/mvconfig>)/gm,`\n${JSON.stringify(mvconfig, null, 2)}\n`)
|
||||
const textUpdate = currentText.replace(/(?<=<mvconfig>)([\S\s]*?)(?=<\/mvconfig>)/gm,`\n${TOML.stringify(mvconfig, null, 2)}\n`)
|
||||
$('#wpTextbox1').val(textUpdate)
|
||||
selectAnnotationSet(newSet)
|
||||
}
|
||||
@@ -185,7 +195,7 @@ addViewConfig = function(newView) {
|
||||
const mView = $('model-viewer')[0]
|
||||
let [currentText, mvconfig] = extractMvconfig()
|
||||
mvconfig.viewerConfig[newView] = { "camera-controls": true }
|
||||
const textUpdate = currentText.replace(/(?<=<mvconfig>)([\S\s]*?)(?=<\/mvconfig>)/gm,`\n${JSON.stringify(mvconfig, null, 2)}\n`)
|
||||
const textUpdate = currentText.replace(/(?<=<mvconfig>)([\S\s]*?)(?=<\/mvconfig>)/gm,`\n${TOML.stringify(mvconfig, null, 2)}\n`)
|
||||
$('#wpTextbox1').val(textUpdate)
|
||||
selectViewConfig(newView)
|
||||
}
|
||||
@@ -235,6 +245,7 @@ selectViewConfig = function(view) {
|
||||
mView.removeAttribute(s)
|
||||
}
|
||||
})
|
||||
mView.setAttribute('current-view',selectView)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -243,6 +254,7 @@ selectViewConfig = function(view) {
|
||||
*/
|
||||
writeCameraOrbit = function() {
|
||||
const mView = $('model-viewer')[0]
|
||||
const currentView = mView.getAttribute('current-view') ? mView.getAttribute('current-view') : 'default'
|
||||
const newOrbit = orb2degree(mView.getCameraOrbit().toString(),[2,2,5])
|
||||
mView.setAttribute('camera-orbit', newOrbit)
|
||||
const targetObj = mView.getCameraTarget()
|
||||
@@ -251,10 +263,10 @@ writeCameraOrbit = function() {
|
||||
const newField = mView.getFieldOfView().toFixed(5) + 'deg'
|
||||
mView.setAttribute('field-of-view',newField)
|
||||
let [currentText, mvconfig] = extractMvconfig()
|
||||
mvconfig.viewerConfig.default['camera-orbit'] = newOrbit
|
||||
mvconfig.viewerConfig.default['camera-target'] = newTarget
|
||||
mvconfig.viewerConfig.default['field-of-view'] = newField
|
||||
const textUpdate = currentText.replace(/(?<=<mvconfig>)([\S\s]*?)(?=<\/mvconfig>)/gm,`\n${JSON.stringify(mvconfig, null, 2)}\n`)
|
||||
mvconfig.viewerConfig[currentView]['camera-orbit'] = newOrbit
|
||||
mvconfig.viewerConfig[currentView]['camera-target'] = newTarget
|
||||
mvconfig.viewerConfig[currentView]['field-of-view'] = newField
|
||||
const textUpdate = currentText.replace(/(?<=<mvconfig>)([\S\s]*?)(?=<\/mvconfig>)/gm,`\n${TOML.stringify(mvconfig, null, 2)}\n`)
|
||||
$('#wpTextbox1').val(textUpdate)
|
||||
}
|
||||
|
||||
@@ -276,6 +288,6 @@ writeCameraLimit = function(axis, limit) {
|
||||
oldOrbitVals[valueIndex] = newOrbitVals[valueIndex]
|
||||
mvconfig.viewerConfig.default[`${limit}-camera-orbit`] = oldOrbitVals.join(' ')
|
||||
mView.setAttribute(`${limit}-camera-orbit`, oldOrbitVals.join(' '))
|
||||
const textUpdate = currentText.replace(/(?<=<mvconfig>)([\S\s]*?)(?=<\/mvconfig>)/gm,`\n${JSON.stringify(mvconfig, null, 2)}\n`)
|
||||
const textUpdate = currentText.replace(/(?<=<mvconfig>)([\S\s]*?)(?=<\/mvconfig>)/gm,`\n${TOML.stringify(mvconfig, null, 2)}\n`)
|
||||
$('#wpTextbox1').val(textUpdate)
|
||||
}
|
||||
@@ -117,7 +117,7 @@
|
||||
justify-content: flex-start;
|
||||
|
||||
&:hover {
|
||||
width: 196px;
|
||||
width: 232px;
|
||||
|
||||
& .glmv-menu-image {
|
||||
transform: rotate(180deg);
|
||||
@@ -209,7 +209,7 @@
|
||||
border-radius: 9px;
|
||||
|
||||
&:hover {
|
||||
width: 294px;
|
||||
width: 342px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ selectAnnotation = function(mView, annotId) {
|
||||
*/
|
||||
nextAnnotation = function(mView) {
|
||||
let incrAnnotation = 0
|
||||
const numSpots = [...mView.querySelectorAll('Button')].length
|
||||
const numSpots = [...mView.querySelectorAll('Button:not([seq-skip])')].length
|
||||
const currentAnnotation = mView.querySelectorAll('Button:has(.HotspotAnnotation:not(.HiddenAnnotation))')[0]
|
||||
if (!currentAnnotation) {
|
||||
incrAnnotation = 1
|
||||
@@ -95,7 +95,7 @@ nextAnnotation = function(mView) {
|
||||
*/
|
||||
prevAnnotation = function(mView) {
|
||||
let decrAnnotation = 0
|
||||
const numSpots = [...mView.querySelectorAll('Button')].length
|
||||
const numSpots = [...mView.querySelectorAll('Button:not([seq-skip])')].length
|
||||
const currentAnnotation = mView.querySelectorAll('Button:has(.HotspotAnnotation:not(.HiddenAnnotation))')[0]
|
||||
if (!currentAnnotation) {
|
||||
decrAnnotation = numSpots
|
||||
@@ -158,4 +158,18 @@ toggleFullScreen = function(glCont) {
|
||||
} else {
|
||||
glCont.requestFullscreen()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset view to initial position
|
||||
*
|
||||
* @param {ModelViewer} mView
|
||||
*/
|
||||
resetView = function(mView) {
|
||||
const resetOrb = mView.getAttribute('camera-orbit') || 'auto auto auto'
|
||||
const resetTarg = mView.getAttribute('camera-target') || 'auto auto auto'
|
||||
const resetFov = mView.getAttribute('field-of-view') || 'auto'
|
||||
mView.cameraOrbit = resetOrb
|
||||
mView.cameraTarget = resetTarg
|
||||
mView.fieldOfView = resetFov
|
||||
}
|
||||
1150
modules/mini-st.js
Normal file
1150
modules/mini-st.js
Normal file
File diff suppressed because it is too large
Load Diff
1
resources/reset.svg
Normal file
1
resources/reset.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5f6368"><path d="M320-280q-33 0-56.5-23.5T240-360v-240q0-33 23.5-56.5T320-680h40l40-40h160l40 40h40q33 0 56.5 23.5T720-600v240q0 33-23.5 56.5T640-280H320Zm0-80h320v-240H320v240Zm160-40q33 0 56.5-23.5T560-480q0-33-23.5-56.5T480-560q-33 0-56.5 23.5T400-480q0 33 23.5 56.5T480-400ZM342-940q34-11 68.5-15.5T480-960q94 0 177.5 33.5t148 93Q870-774 911-693.5T960-520h-80q-7-72-38-134.5t-79.5-110Q714-812 651-842t-135-36l62 62-56 56-180-180ZM618-20Q584-9 549.5-4.5T480 0q-94 0-177.5-33.5t-148-93Q90-186 49-266.5T0-440h80q8 72 38.5 134.5t79 110Q246-148 309-118t135 36l-62-62 56-56L618-20ZM480-480Z"/></svg>
|
||||
|
After Width: | Height: | Size: 696 B |
Reference in New Issue
Block a user