Spark AR Native UI Picker - spark-ar-studio

In Documentation page of Native UI Picker it says:
"The NativeUI Picker is an interface you can add to effects. It allows people using your effect to choose different options within it by selecting icons.
For example the user can tap different icons to change the texture shown in the effect"
I've made several effects with NativeUI texture "switcher", and was trying to implement NativeUI to switch not between textures, but effects (objects) themselves.
For example if I would make a FaceMesh with some texture on it, that would change with a screen tap and a particle system with another texture also with a change when the screen is tapped. How could I switch between those two using NativeUI?
I don't really like my current solution to this because it's not user friendly. I've used PatchEditor and FaceFinder, turning on/off visibility of the faceMesh and Emitter when eyebrows are raised. See the screenshot bellow:
I would really appreciate any suggestions,
Thank you!

// Load in modules
const NativeUI = require("NativeUI");
const Textures = require("Textures");
const Patches = require("Patches");
export const Diagnostics = require("Diagnostics");
// Create a number
let userNumber = 0;
const texture0 = Textures.get("number1");
const texture1 = Textures.get("number2");
const texture2 = Textures.get("number3");
const index = 0;
const configuration = {
selectedIndex: index,
items: [
{ image_texture: texture0 },
{ image_texture: texture1 },
{ image_texture: texture2 }
]
};
const picker = NativeUI.picker;
picker.configure(configuration);
picker.visible = true;
picker.selectedIndex.monitor().subscribe(function(index) {
userNumber = index.newValue;
// Send the number to the Patch Editor under the name 'userNumber'
Patches.setScalarValue("userNumber", userNumber);
// Debugging
Diagnostics.log("user selection = " + index.newValue);
});
Using NativeUI picker. The code above will output index value as userNumber(scalar) to patch editor.
Examples:
If user pick number 1 the output of userNumber will be 0,
If user pick number 2 the output of userNumber will be 1,
If user pick number 3 the output of userNumber will be 2,
etc.
You can use userNumber as switch combine with equal exactly
NativeUI Picker as Switch Picture

Related

Can I embed RSS feed within a json file

What I am asking is similar to Can I serve RSS in JSON? but I am not sure he is trying to achieve the same thing as me. I want to create a globe news app where a user can hover over different countries on a 3D globe and they can see the top news stories from the country that day, I am using three js’ globe gl for the 3D globe and the country labels, I have found some good news widgets I want to embed under each country label: https://rss.app/feed/GtCHIcNstmLwXE13 but data for the countries is in a json file and I am not sure if I can put an rss embed link into a json file, is this possible? Here is an editable example of what I have made so far: https://codepen.io/forerunrun/pen/YzayQbg ${d.agency} - ${d.program} Program
Landing on ${new Date(d.date).toLocaleDateString()}.
`)
.onLabelClick(d => window.open(d.url, '_blank'))
(elem);
fetch('https://shange- fagan.github.io/globe.news/moon_landings.json') // make the request to fetch https://raw.githubusercontent.com/eesur/country-codes-lat-long/e20f140202afbb65addc13cad120302db26f119a/country-codes-lat-long-alpha3.json
// fetch('https://raw.githubusercontent.com/eesur/country-codes-lat-long/e20f140202afbb65addc13cad120302db26f119a/country-codes-lat-long-alpha3.json')
.then(r => r.json()) //then get the returned json request header if and when the request value returns true
.then(landingSites => { // then use the request result as a callback
console.log(landingSites)
// moon.labelsData(landingSites.ref_country_codes);
moon.labelsData(landingSites);
console.log(moon.labelLabel)
// custom globe material
const globeMaterial = moon.globeMaterial();
globeMaterial.bumpScale = 10;
new THREE.TextureLoader().load('//unpkg.com/three-globe/example/img/earth-water.png', texture => {
globeMaterial.specularMap = texture;
globeMaterial.specular = new THREE.Color('grey');
globeMaterial.shininess = 15;
});
setTimeout(() => { // wait for scene to be populated (asynchronously)
const directionalLight = moon.scene().children.find(obj3d => obj3d.type === 'DirectionalLight');
directionalLight && directionalLight.position.set(1, 1, 1); // change light position to see the specularMap's effect
});
});
//moon.controls().autoRotate = false;
//moon.controls().autoRotateSpeed = 0.85;
//const animate = () => {
//requestAnimationFrame(animate);
//moon.rotation.y += 0.01;
//}
//animate();
ignore the current labels on the 3D globe they are just locations of different moon landing sites as I used the https://globe.gl/example/moon-landing-sites/index.html Example as the basic structure for my project
Any advice would be greatly appreciated.
Edit: here is a link to the live example and repo on github incase you can’t see all the files being used I.e json files- in the code pen example: repo: https://github.com/Shange-Fagan/globe.news GitHub pages live example: https://shange-fagan.github.io/globe.news/

Update Google Calendar UI after changing visability setting via Workspace Add-On

I have a very basic Google Workspace Add-on that uses the CalendarApp class to toggle the visabilty of a calendar’s events when a button is pressed, using the setSelected() method
The visabilty toggling works, but the change in only reflected in the UI when the page is refreshed. Toggling the checkbox manually in the UI reflects the change immediately without needing to refresh the page.
Is there a method to replicate this immediate update behaviour via my Workspace Add-On?
A mwe is below.
function onDefaultHomePageOpen() {
// create button
var action = CardService.newAction().setFunctionName('toggleCalVis')
var button = CardService.newTextButton()
.setText("TOGGLE CAL VIS")
.setOnClickAction(action)
.setTextButtonStyle(CardService.TextButtonStyle.FILLED)
var buttonSet = CardService.newButtonSet().addButton(button)
// create CardSection
var section = CardService.newCardSection()
.addWidget(buttonSet)
// create card
var card = CardService.newCardBuilder().addSection(section)
// call CardBuilder.call() and return card
return card.build()
}
function toggleCalVis() {
// fetch calendar with UI name "foo"
var calendarName = "foo"
var calendarsByName = CalendarApp.getCalendarsByName(calendarName)
var namedCalendar = calendarsByName[0]
// Toggle calendar visabilty in the UI
if (namedCalendar.isSelected()) {
namedCalendar.setSelected(false)
}
else {
namedCalendar.setSelected(true)
}
}
In short: Create a chrome extension
(2021-sep-2)Reason: The setSelected() method changes ONLY the data on server. To apply the effect of it, you need to refresh the page. But Google Workspace Extension "for security reason" does not allow GAS to do that. However in an Chrome Extension you can unselect the checkbox of visibility by plain JS. (the class name of the left list is encoded but stable for me.) I have some code for Chrome Extension to select the nodes although I didn't worked it out(see last part).
(2021-jul-25)Worse case: Default calendars won't be selected by getAllCalendars(). I just tried the same thing as you mentioned, and the outcome is worse. I wanted to hide all calendars, and I am still pretty sure the code is correct, since I can see the calendar names in the console.
const allCals = CalendarApp.getAllCalendars()
allCals.forEach(cal => {console.log(`unselected ${cal.setSelected(false).getName()}`)})
Yet, the principle calendar, reminder calendar, and task calendar are not in the console.
And google apps script dev should ask themselves: WHY DO PEOPLE USE Calendar.setSelected()? We don't want to hide the calendar on the next run.
In the official document, none of these two behaviour is mentioned.
TL;DR part (My reason for not using GAS)
GAS(google-apps-script) has less functionality. For what I see, google is trying to build their own eco-system, but everything achievable in GAS is also available via javascript. I can even use typescript and do whatever I want by creating an extension.
GAS is NOT easy to learn. The learning was also painful, I spent 4 hours to build the first sample card, and I can interact correctly with the opened event after 9 hours. The documentation is far from finished.
GAS is poorly supported. The native web-based code editor (https://script.google.com/) is not build for coding real apps, it loses the version control freedom in new interface. And does not support cross-file search. Instead of import, codes run from top to bottom in the list, which you need to find that by yourself. (pass along no extension, no prettier, I can tolerate these)
In comparison with other online JS code editors, like codepen / code sandbox / etcetera it does so less function. Moreover, VSCode also has a online version now(github codespaces).
I hope my 13 hours in GAS are not totally wasted. As least whoever read this can just avoid suffering the same painful test.
Here's the code(typescript) for disable all the checks in Chrome.
TRACKER_CAL_ID_ENCODED is the calendar ID of which I don't want to uncheck. Since it is not the major part of this question, it is not very carefully commented.
(line update: 2022-jan-31) Aware that the mutationsList.length >= 3 is not accurate, I cannot see how mutationsList.length works.
Extension:
getSelectCalendarNode()
.then(unSelectCalendars)
function getSelectCalendarNode() {
return new Promise((resolve) => {
document.onreadystatechange = function () {
if (document.readyState == "complete") {
const leftSidebarNode = document.querySelector(
"div.QQYuzf[jsname=QA0Szd]"
)!;
new MutationObserver((mutationsList, observer) => {
for (const mutation of mutationsList) {
if (mutation.target) {
let _selectCalendarNode = document.querySelector("#dws12b.R16x0");
// customized calendars will start loading on 3th+ step, hence 3, but when will they stop loading? I didn't work this out
if (mutationsList.length >= 3) {
// The current best workaround I saw is setTimeout after loading event... There's no event of loading complete.
setTimeout(() => {
observer.disconnect();
resolve(_selectCalendarNode);
}, 1000);
}
}
}
}).observe(leftSidebarNode, { childList: true, subtree: true });
}
};
});
}
function unSelectCalendars(selectCalendarNode: unknown) {
const selcar = selectCalendarNode as HTMLDivElement;
const calwrappers = selcar.firstChild!.childNodes; // .XXcuqd
for (const calrow of calwrappers) {
const calLabel = calrow.firstChild!.firstChild as HTMLLabelElement;
const calSelectWrap = calLabel.firstChild!;
const calSelcted =
(calSelectWrap.firstChild!.firstChild! as HTMLDivElement).getAttribute(
"aria-checked"
) == "true"
? true
: false;
// const calNameSpan = calSelectWrap.nextSibling!
// .firstChild! as HTMLSpanElement;
// const calName = calNameSpan.innerText;
const encodedCalID = calLabel.getAttribute("data-id")!; // const decodedCalID = atob(encodedCalID);
if ((encodedCalID === TRACKER_CAL_ID_ENCODED) !== calSelcted) {
//XOR
calLabel.click();
}
}
console.log(selectCalendarNode);
return;
}
There is no way to make a webpage refresh with Google Apps Script
Possible workarounds:
From the sidebar, provide users a link that redirects them to the Calendar UI webpage (thus a new, refreshed version of it will be opened)
Install a Goole Chrome extension that refreshes the tab in specified intervals

Set transparent for specific element

I want set transparent for specific element, i follow this code:
var instanceTree = this.viewer.model.getInstanceTree();
var fragList = this.viewer.model.getFragmentList();
this.listElement.forEach(element => {
instanceTree.enumNodeFragments(element, (fragId) => {
console.log(element.material)
var material = fragList.getMaterial(fragId)
if (material) {
material.opacity = value;
material.transparent = true;
material.needsUpdate = true;
}
});
});
this.viewer.impl.invalidate(true, true, true);
but it overide for all elements have that material. How can i set for selected element?
Appreciate any comments.
UPDATE 1:
i found go around way is clone main material than register it with different name:
var newMaterial = material.clone();
const materials = this.viewer.impl.matman();
materials.addMaterial('mymaterial',newMaterial,true);
fragList.setMaterial(fragId,newMaterial);
newMaterial.opacity = value;
newMaterial.transparent = true;
newMaterial.needsUpdate = true;
but effect is not what i want, it has different color and when set transparent i can only see a couple object behind it
You can create your own, custom THREE.js material and assign it to the fragment using fragList.setMaterial(fragId, material).
For more information on using custom materials or shaders, see https://forge.autodesk.com/blog/custom-shader-materials-forge-viewer.
EDIT:
Regarding the visual anomalies (for example, when you only see some of the objects behind something semi-transparent), this is a known problem, unfortunately with no clear solution. When the Forge Model Derivative service creates an SVF file to be viewed in Forge Viewer, the individual scene elements are stored in a data structure optimized for fast traversal, depending on whether they are semi-transparent or fully opaque. This data structure is fixed, and so unfortunately, when you take an object that was originally fully opaque, and you make it semi-transparent, it will most likely be rendered in a wrong order...

Forge Viewer: changing fragment material

Is there a way to change the material of an existing fragment? I see there is a changing fragment material function in the documentation, but this seems to be for adding custom models, rather than manipulating existing fragments in the viewer.
I specifically wish to change the material so I can manipulate the linewidth of the fragment. I've tried manipulating the fragments of viewer.model.getFragmentList(), specifically Viewer.model.getFragmentList().getMaterial(i) and its properties (such as linewidth) to no avail, even after updating via viewer.impl.invalidate
While this is not part of the officially supported Viewer APIs, you can change the material of existing fragments using the fragment list's setMaterial method, for example like so:
function changeSelectedObjects(viewer, customMaterial) {
const materialManager = viewer.impl.matman();
materialManager.addMaterial('myCustomMaterial', customMaterial, true /* skip material heuristics */);
const model = viewer.model;
model.unconsolidate(); // If the model is consolidated, material changes won't have any effect
const tree = model.getInstanceTree();
const frags = model.getFragmentList();
const dbids = viewer.getSelection();
for (const dbid of dbids) {
tree.enumNodeFragments(dbid, (fragid) => {
frags.setMaterial(fragid, customMaterial);
});
}
}
See https://forge.autodesk.com/blog/custom-shader-materials-forge-viewer for a more specific example.
With that said, changing the line width might be trickier, especially if it's about modifying 2D drawings. I don't think that kind of customization has been explored very well.

Set instanceTree to a custom node in Forge 3D viewer

Lets say I'm working with a 3D file which is the combination of one Architectural model and one Structural model.
The instance tree or Model Browser looks like this
root/
Arch/
Level 01/
Level 02/
...
Str/
Level 01/
Level 02/
...
I want to display only the Level 01.
So I:
Followed the steps in the Viewer tutorial
Add an event listener to both Autodesk.Viewing.GEOMETRY_LOADED_EVENT & Autodesk.Viewing.OBJECT_TREE_CREATED_EVENT
When the 2 are fired, I use the code in this article to display only the Level 01 without ghosting.
I have 2 problem with this approach
I have to wait until the entire model is loaded before I can filter the level
After filtering the level, if I click on Model Browser, I can still see the entire model structure (but with everything as hidden except Level 01). How can I set the instance tree to only have what's below?
root/
Arch/
Level 01/
Str/
Level 01/
EDIT
At what point am I supposed to override the shouldInclude() function?
I've tried this and put a breakpoint but it seems it never gets called... I also tried to move it around but in vain.
const start = Date.now();
Autodesk.Viewing.UI.ModelStructurePanel.shouldInclude = (node) => {
Logger.log(node);
return true;
};
Autodesk.Viewing.Initializer(options, () => {
Logger.log(`Viewer initialized in ${Date.now() - start}ms`);
const config = {};
// prettier-ignore
Autodesk.Viewing.theExtensionManager.registerExtension('MyAwesomeExtension', MyAwesomeExtension);
viewerApp = new Autodesk.Viewing.ViewingApplication('MyViewerDiv');
viewerApp.registerViewer(viewerApp.k3D, Autodesk.Viewing.Private.GuiViewer3D, config);
loadDocumentStart = Date.now();
// prettier-ignore
viewerApp.loadDocument(documentId, onDocumentLoadSuccess, onDocumentLoadFailure);
});
Regarding #1: the object tree is stored in the file's internal database which - for performance reasons - is only loaded after the actual geometry.
Regarding #2: you can subclass the ModelStructurePanel class and add your own behavior, for example, by overriding the ModelStructurePanel#shouldInclude method.
Since I wasn't able to understand how to use ModelStructurePanel, I overrode Autodesk.Viewing.ViewingApplication.selectItem to only modify the options which are either passed to loadDocumentNode or startWithDocumentNode as below:
const options = {
ids: leafIDs.length > 0 ? leafIDs : null, // changed this line
acmSessionId: this.myDocument.acmSessionId,
loadOptions,
useConsolidation: this.options.useConsolidation,
consolidationMemoryLimit: this.options.consolidationMemoryLimit || 100 * 1024 * 1024, // 100 MB
};
With leafIDs being an array of objectIDs to display. I was able to build it by:
querying the ModelDerivativeAPI using GET :urn/metadata/:guid
going through the tree to find the ids which I am interested in.
There's probably a more elegant way to do this but that's the best I could do so far.