From 9605d52009e2e04ac3cd00de6be68a77a3e90939 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Sun, 15 Dec 2024 21:49:58 -0800 Subject: [PATCH 1/7] first cut of parsing a sample street object --- src/components/managed-street.js | 170 ++++++++++++++++++++++++++++++- 1 file changed, 167 insertions(+), 3 deletions(-) diff --git a/src/components/managed-street.js b/src/components/managed-street.js index d70cdabc..afb030d4 100644 --- a/src/components/managed-street.js +++ b/src/components/managed-street.js @@ -5,14 +5,137 @@ const { segmentVariants } = require('../segments-variants.js'); const streetmixUtils = require('../tested/streetmix-utils'); const streetmixParsersTested = require('../tested/aframe-streetmix-parsers-tested'); -// invoking from js console +// invoking from js console - load from streetmix /* userLayersEl = document.getElementById('street-container'); newStreetEl = document.createElement('a-entity'); -newStreetEl.setAttribute('managed-street', 'sourceType: streetmix-url; sourceValue: https://streetmix.net/kfarr/3/; synchronize: true'); +newStreetEl.setAttribute('managed-street', { + sourceType: 'streetmix-url', + sourceValue: 'https://streetmix.net/kfarr/3/', + synchronize: true +}); +userLayersEl.append(newStreetEl); +*/ + +// invoking from js console - load from blob +/* +userLayersEl = document.getElementById('street-container'); +newStreetEl = document.createElement('a-entity'); +newStreetEl.setAttribute('managed-street', { + sourceType: 'json-blob', + sourceValue: window.STREET.SAMPLE_STREET, + synchronize: true +}); userLayersEl.append(newStreetEl); */ +// Example Street object +const COLORS = window.STREET.colors; +window.STREET.SAMPLE_STREET = { + // some of this is redundant, many of the segment attributes defined below can be inferred by type + id: 'aaaaaaaa-0123-4678-9000-000000000000', // this is a real v4 UUID! https://everyuuid.com/ + name: "Kieran's Awesome Street", + width: 40, // this is the user-specified RoW width, not cumulative width of segments + length: 100, + justifyWidth: 'center', + justifyLength: 'start', + segments: [ + { + id: 'aaaaaaaa-0123-4678-9000-000000000001', + name: 'Sidewalk for walking', + type: 'sidewalk', + surface: 'sidewalk', + color: COLORS.white, + level: 1, + width: 3, + direction: 'none', + generated: { + pedestrians: [ + { + density: 'normal' + } + ] + } + }, + { + id: 'aaaaaaaa-0123-4678-9000-000000000002', + name: 'Sidewalk for trees and stuff', + type: 'sidewalk', + surface: 'sidewalk', + color: COLORS.white, + level: 1, + width: 1, + direction: 'none', + generated: { + clones: [ + { + mode: 'fixed', + model: 'tree3', + spacing: 15 + } + ] + } + }, + { + id: 'aaaaaaaa-0123-4678-9000-000000000003', + name: 'Parking for cars', + type: 'parking-lane', + surface: 'concrete', + color: COLORS.lightGray, + level: 0, + width: 3, + direction: 'inbound', + generated: { + clones: [ + { + mode: 'random', + modelsArray: 'sedan-rig, self-driving-waymo-car, suv-rig', + spacing: 6, + count: 6 + } + ], + stencil: [ + { + model: 'parking-t', + cycleOffset: 1, + spacing: 6 + } + ] + } + }, + { + id: 'aaaaaaaa-0123-4678-9000-000000000004', + name: 'Drive Lane for cars and stuff', + type: 'drive-lane', + color: COLORS.white, + surface: 'asphalt', + level: 0, + width: 3, + direction: 'inbound', + generated: { + clones: [ + { + mode: 'random', + modelsArray: + 'sedan-rig, box-truck-rig, self-driving-waymo-car, suv-rig, motorbike', + spacing: 7.3, + count: 4 + } + ] + } + }, + { + id: 'aaaaaaaa-0123-4678-9000-000000000005', + name: 'A beautiful median', + type: 'divider', + surface: 'sidewalk', + color: COLORS.white, + level: 1, + width: 0.5 + } + ] +}; + AFRAME.registerComponent('managed-street', { schema: { width: { @@ -98,9 +221,10 @@ AFRAME.registerComponent('managed-street', { if (data.sourceType === 'streetmix-url') { this.loadAndParseStreetmixURL(data.sourceValue); } else if (data.sourceType === 'streetplan-url') { + // this function is not yet implemented this.refreshFromStreetplanURL(data.sourceValue); } else if (data.sourceType === 'json-blob') { - this.refreshFromJSONBlob(data.sourceValue); + this.parseStreetObject(data.sourceValue); } }, applyLength: function () { @@ -174,6 +298,8 @@ AFRAME.registerComponent('managed-street', { this.justifiedDirtBox = dirtBox; dirtBox.setAttribute('material', `color: ${window.STREET.colors.brown};`); dirtBox.setAttribute('data-layer-name', 'Underground'); + dirtBox.setAttribute('data-no-transform', ''); + dirtBox.setAttribute('data-ignore-raycaster', ''); } this.justifiedDirtBox.setAttribute('height', 2); // height is 2 meters from y of -0.1 to -y of 2.1 this.justifiedDirtBox.setAttribute('width', streetWidth); @@ -202,6 +328,42 @@ AFRAME.registerComponent('managed-street', { `${xPosition} -1 ${zPosition}` ); }, + parseStreetObject: function (streetObject) { + // reset and delete all existing entities + this.remove(); + + // given an object streetObject, create child entities with 'street-segment' component + this.el.setAttribute( + 'data-layer-name', + 'Managed Street • ' + streetObject.name + ); + this.el.setAttribute('managed-street', 'width', streetObject.width); + this.el.setAttribute('managed-street', 'length', streetObject.length); + + for (let i = 0; i < streetObject.segments.length; i++) { + const segment = streetObject.segments[i]; + const segmentEl = document.createElement('a-entity'); + this.el.appendChild(segmentEl); + + segmentEl.setAttribute('street-segment', { + type: segment.type, // this is the base type, it won't load its defaults since we are changing more than just the type value + width: segment.width, + length: streetObject.length, + level: segment.level, + direction: segment.direction, + color: segment.color || window.STREET.types[segment.type]?.color, + surface: segment.surface || window.STREET.types[segment.type]?.surface // no error handling for segmentPreset not found + }); + segmentEl.setAttribute('data-layer-name', segment.name); + // wait for street-segment to be loaded, then generate components from segment object + segmentEl.addEventListener('loaded', () => { + segmentEl.components[ + 'street-segment' + ].generateComponentsFromSegmentObject(segment); + this.applyJustification(); + }); + } + }, loadAndParseStreetmixURL: async function (streetmixURL) { const data = this.data; const streetmixAPIURL = streetmixUtils.streetmixUserToAPI(streetmixURL); @@ -293,6 +455,8 @@ AFRAME.registerComponent('managed-street', { } }); +// Helper functions for Streetmix to A-Frame conversion + function getSeparatorMixinId(previousSegment, currentSegment) { if (previousSegment === undefined || currentSegment === undefined) { return null; From 9eda177477c11e2b072484bc93f2080343a7259d Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Wed, 18 Dec 2024 16:28:19 -0800 Subject: [PATCH 2/7] accept hash #managed-street-json:[url-encoded-json-blob] --- src/components/managed-street.js | 39 ++++++++++++++----- .../AddLayerPanel/createLayerFunctions.js | 20 ++++++++-- .../components/AddLayerPanel/layersData.js | 20 +++++++--- src/json-utils_1.1.js | 17 ++++++++ 4 files changed, 78 insertions(+), 18 deletions(-) diff --git a/src/components/managed-street.js b/src/components/managed-street.js index afb030d4..b371d5d0 100644 --- a/src/components/managed-street.js +++ b/src/components/managed-street.js @@ -30,12 +30,12 @@ userLayersEl.append(newStreetEl); */ // Example Street object -const COLORS = window.STREET.colors; +// 'width' this is the user-specified RoW width, not cumulative width of segments +// some of this is redundant, many of the segment attributes defined below can be inferred by type window.STREET.SAMPLE_STREET = { - // some of this is redundant, many of the segment attributes defined below can be inferred by type - id: 'aaaaaaaa-0123-4678-9000-000000000000', // this is a real v4 UUID! https://everyuuid.com/ + id: 'aaaaaaaa-0123-4678-9000-000000000000', name: "Kieran's Awesome Street", - width: 40, // this is the user-specified RoW width, not cumulative width of segments + width: 40, length: 100, justifyWidth: 'center', justifyLength: 'start', @@ -45,7 +45,7 @@ window.STREET.SAMPLE_STREET = { name: 'Sidewalk for walking', type: 'sidewalk', surface: 'sidewalk', - color: COLORS.white, + color: '#ffffff', level: 1, width: 3, direction: 'none', @@ -62,7 +62,7 @@ window.STREET.SAMPLE_STREET = { name: 'Sidewalk for trees and stuff', type: 'sidewalk', surface: 'sidewalk', - color: COLORS.white, + color: '#ffffff', level: 1, width: 1, direction: 'none', @@ -81,7 +81,7 @@ window.STREET.SAMPLE_STREET = { name: 'Parking for cars', type: 'parking-lane', surface: 'concrete', - color: COLORS.lightGray, + color: '#dddddd', level: 0, width: 3, direction: 'inbound', @@ -107,7 +107,7 @@ window.STREET.SAMPLE_STREET = { id: 'aaaaaaaa-0123-4678-9000-000000000004', name: 'Drive Lane for cars and stuff', type: 'drive-lane', - color: COLORS.white, + color: '#ffffff', surface: 'asphalt', level: 0, width: 3, @@ -129,7 +129,7 @@ window.STREET.SAMPLE_STREET = { name: 'A beautiful median', type: 'divider', surface: 'sidewalk', - color: COLORS.white, + color: '#ffffff', level: 1, width: 0.5 } @@ -147,7 +147,7 @@ AFRAME.registerComponent('managed-street', { }, sourceType: { type: 'string', - oneOf: ['streetmix-url', 'streetplan-url', 'json-blob'] + oneOf: ['streetmix-url', 'streetplan-url', 'json-blob', 'json-hash'] }, sourceValue: { type: 'string' @@ -225,6 +225,25 @@ AFRAME.registerComponent('managed-street', { this.refreshFromStreetplanURL(data.sourceValue); } else if (data.sourceType === 'json-blob') { this.parseStreetObject(data.sourceValue); + } else if (data.sourceType === 'json-hash') { + // url.com/page#managed-street-json:{"data":"value"} + const fragment = window.location.hash; + const prefix = '#managed-street-json:'; + try { + const encodedJsonStr = fragment.substring(prefix.length); + const jsonStr = decodeURIComponent(encodedJsonStr); + const streetObjectFromHash = JSON.parse(jsonStr); + this.parseStreetObject(streetObjectFromHash); + this.el.setAttribute('managed-street', 'synchronize', false); + this.el.setAttribute('managed-street', 'sourceType', 'json-blob'); + this.el.setAttribute( + 'managed-street', + 'sourceValue', + streetObjectFromHash + ); + } catch (err) { + console.error('Error parsing fragment:', err); + } } }, applyLength: function () { diff --git a/src/editor/components/components/AddLayerPanel/createLayerFunctions.js b/src/editor/components/components/AddLayerPanel/createLayerFunctions.js index 7a92c83b..1589afd2 100644 --- a/src/editor/components/components/AddLayerPanel/createLayerFunctions.js +++ b/src/editor/components/components/AddLayerPanel/createLayerFunctions.js @@ -50,7 +50,7 @@ export function createMapbox() { }); } -export function createManagedStreet(position) { +export function createManagedStreetFromStreetmix(position) { // This creates a new Managed Street let streetmixURL = prompt( 'Please enter a Streetmix URL', @@ -65,8 +65,6 @@ export function createManagedStreet(position) { 'managed-street': { sourceType: 'streetmix-url', sourceValue: streetmixURL, - showVehicles: true, - showStriping: true, synchronize: true } } @@ -76,6 +74,22 @@ export function createManagedStreet(position) { } } +export function createManagedStreetFromHash(position) { + // This creates a new Managed Street from a JSON passed after the hash (#) symbol of the URL. + const definition = { + id: createUniqueId(), + components: { + position: position ?? '0 0 0', + 'managed-street': { + sourceType: 'json-hash', + synchronize: true + } + } + }; + + AFRAME.INSPECTOR.execute('entitycreate', definition); +} + export function createStreetmixStreet(position, streetmixURL, hideBuildings) { // This code snippet allows the creation of an additional Streetmix street // in your 3DStreet scene without replacing any existing streets. diff --git a/src/editor/components/components/AddLayerPanel/layersData.js b/src/editor/components/components/AddLayerPanel/layersData.js index 42278192..630b0e6f 100644 --- a/src/editor/components/components/AddLayerPanel/layersData.js +++ b/src/editor/components/components/AddLayerPanel/layersData.js @@ -10,7 +10,8 @@ import { create94ftRightOfWay, create150ftRightOfWay, createImageEntity, - createManagedStreet + createManagedStreetFromStreetmix, + createManagedStreetFromHash } from './createLayerFunctions'; export const streetLayersData = [ @@ -74,14 +75,23 @@ export const streetLayersData = [ handlerFunction: createIntersection }, { - name: 'Create Managed Street (Beta)', + name: 'Create Managed Street From Streetmix (Beta)', img: '', requiresPro: true, icon: '', - description: - 'Create a new street from Streetmix using the Managed Street component.', + description: 'Create an Auto-Managed Street from a Streetmix URL.', id: 8, - handlerFunction: createManagedStreet + handlerFunction: createManagedStreetFromStreetmix + }, + { + name: 'Create Managed Street From JSON in URL Hash (Beta)', + img: '', + requiresPro: true, + icon: '', + description: + 'Create an Auto-Managed Street from JSON passed after the hash (#) symbol of the URL.', + id: 9, + handlerFunction: createManagedStreetFromHash } ]; diff --git a/src/json-utils_1.1.js b/src/json-utils_1.1.js index ff3fe553..30764b16 100644 --- a/src/json-utils_1.1.js +++ b/src/json-utils_1.1.js @@ -487,6 +487,23 @@ AFRAME.registerComponent('set-loader-from-hash', { if (!streetURL) { return; } + if (streetURL.startsWith('managed-street-json:')) { + // use set timeout + setTimeout(() => { + const definition = { + components: { + 'managed-street': { + sourceType: 'json-hash', + synchronize: true + } + } + }; + AFRAME.INSPECTOR.execute('entitycreate', definition); + // street notify + STREET.notify.successMessage('Loading Managed Street JSON from URL'); + }, 1000); + return; + } if (streetURL.includes('//streetmix.net')) { console.log( '[set-loader-from-hash]', From 592045a3e5d0d0ea7d56f2e3043a938d62529c30 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Thu, 19 Dec 2024 08:52:26 -0800 Subject: [PATCH 3/7] revert adding new button for create from hash --- .../AddLayerPanel/createLayerFunctions.js | 20 +++---------------- .../components/AddLayerPanel/layersData.js | 20 +++++-------------- 2 files changed, 8 insertions(+), 32 deletions(-) diff --git a/src/editor/components/components/AddLayerPanel/createLayerFunctions.js b/src/editor/components/components/AddLayerPanel/createLayerFunctions.js index 1589afd2..7a92c83b 100644 --- a/src/editor/components/components/AddLayerPanel/createLayerFunctions.js +++ b/src/editor/components/components/AddLayerPanel/createLayerFunctions.js @@ -50,7 +50,7 @@ export function createMapbox() { }); } -export function createManagedStreetFromStreetmix(position) { +export function createManagedStreet(position) { // This creates a new Managed Street let streetmixURL = prompt( 'Please enter a Streetmix URL', @@ -65,6 +65,8 @@ export function createManagedStreetFromStreetmix(position) { 'managed-street': { sourceType: 'streetmix-url', sourceValue: streetmixURL, + showVehicles: true, + showStriping: true, synchronize: true } } @@ -74,22 +76,6 @@ export function createManagedStreetFromStreetmix(position) { } } -export function createManagedStreetFromHash(position) { - // This creates a new Managed Street from a JSON passed after the hash (#) symbol of the URL. - const definition = { - id: createUniqueId(), - components: { - position: position ?? '0 0 0', - 'managed-street': { - sourceType: 'json-hash', - synchronize: true - } - } - }; - - AFRAME.INSPECTOR.execute('entitycreate', definition); -} - export function createStreetmixStreet(position, streetmixURL, hideBuildings) { // This code snippet allows the creation of an additional Streetmix street // in your 3DStreet scene without replacing any existing streets. diff --git a/src/editor/components/components/AddLayerPanel/layersData.js b/src/editor/components/components/AddLayerPanel/layersData.js index 630b0e6f..42278192 100644 --- a/src/editor/components/components/AddLayerPanel/layersData.js +++ b/src/editor/components/components/AddLayerPanel/layersData.js @@ -10,8 +10,7 @@ import { create94ftRightOfWay, create150ftRightOfWay, createImageEntity, - createManagedStreetFromStreetmix, - createManagedStreetFromHash + createManagedStreet } from './createLayerFunctions'; export const streetLayersData = [ @@ -75,23 +74,14 @@ export const streetLayersData = [ handlerFunction: createIntersection }, { - name: 'Create Managed Street From Streetmix (Beta)', - img: '', - requiresPro: true, - icon: '', - description: 'Create an Auto-Managed Street from a Streetmix URL.', - id: 8, - handlerFunction: createManagedStreetFromStreetmix - }, - { - name: 'Create Managed Street From JSON in URL Hash (Beta)', + name: 'Create Managed Street (Beta)', img: '', requiresPro: true, icon: '', description: - 'Create an Auto-Managed Street from JSON passed after the hash (#) symbol of the URL.', - id: 9, - handlerFunction: createManagedStreetFromHash + 'Create a new street from Streetmix using the Managed Street component.', + id: 8, + handlerFunction: createManagedStreet } ]; From 7d5eb690fbc1dff39619b816e27518be1c782bbb Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Thu, 19 Dec 2024 20:42:42 -0800 Subject: [PATCH 4/7] bugfix for last segment incorrect x position --- src/components/managed-street.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/managed-street.js b/src/components/managed-street.js index b371d5d0..3cac88a1 100644 --- a/src/components/managed-street.js +++ b/src/components/managed-street.js @@ -446,6 +446,7 @@ AFRAME.registerComponent('managed-street', { // When all entities are loaded, do something with them this.allLoadedPromise.then(() => { + this.refreshManagedEntities(); this.applyJustification(); this.createOrUpdateJustifiedDirtBox(); AFRAME.INSPECTOR.selectEntity(this.el); From 79f5432dafab8640cc772259b0d8fc9f3fe80856 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Thu, 19 Dec 2024 21:36:45 -0800 Subject: [PATCH 5/7] move url hash parsing out of managed-street --- src/components/managed-street.js | 35 ++++++++++++++++++-------------- src/json-utils_1.1.js | 29 ++++++++++++++++++-------- 2 files changed, 41 insertions(+), 23 deletions(-) diff --git a/src/components/managed-street.js b/src/components/managed-street.js index 3cac88a1..7f2c168d 100644 --- a/src/components/managed-street.js +++ b/src/components/managed-street.js @@ -29,6 +29,12 @@ newStreetEl.setAttribute('managed-street', { userLayersEl.append(newStreetEl); */ +// invoking from js console - load from hash +/* +url to copy / paste: +localhost:3333#managed-street-json:{"id":"aaaaaaaa-0123-4678-9000-000000000000","name":"Kieran's Awesome Street","width":40,"length":100,"justifyWidth":"center","justifyLength":"start","segments":[{"id":"aaaaaaaa-0123-4678-9000-000000000001","name":"Sidewalk for walking","type":"sidewalk","surface":"sidewalk","color":"#ffffff","level":1,"width":3,"direction":"none","generated":{"pedestrians":[{"density":"normal"}]}},{"id":"aaaaaaaa-0123-4678-9000-000000000002","name":"Sidewalk for trees and stuff","type":"sidewalk","surface":"sidewalk","color":"#ffffff","level":1,"width":1,"direction":"none","generated":{"clones":[{"mode":"fixed","model":"tree3","spacing":15}]}},{"id":"aaaaaaaa-0123-4678-9000-000000000003","name":"Parking for cars","type":"parking-lane","surface":"concrete","color":"#dddddd","level":0,"width":3,"direction":"inbound","generated":{"clones":[{"mode":"random","modelsArray":"sedan-rig, self-driving-waymo-car, suv-rig","spacing":6,"count":6}],"stencil":[{"model":"parking-t","cycleOffset":1,"spacing":6}]}},{"id":"aaaaaaaa-0123-4678-9000-000000000004","name":"Drive Lane for cars and stuff","type":"drive-lane","color":"#ffffff","surface":"asphalt","level":0,"width":3,"direction":"inbound","generated":{"clones":[{"mode":"random","modelsArray":"sedan-rig, box-truck-rig, self-driving-waymo-car, suv-rig, motorbike","spacing":7.3,"count":4}]}},{"id":"aaaaaaaa-0123-4678-9000-000000000005","name":"A beautiful median","type":"divider","surface":"sidewalk","color":"#ffffff","level":1,"width":0.5}]} +*/ + // Example Street object // 'width' this is the user-specified RoW width, not cumulative width of segments // some of this is redundant, many of the segment attributes defined below can be inferred by type @@ -224,25 +230,24 @@ AFRAME.registerComponent('managed-street', { // this function is not yet implemented this.refreshFromStreetplanURL(data.sourceValue); } else if (data.sourceType === 'json-blob') { - this.parseStreetObject(data.sourceValue); - } else if (data.sourceType === 'json-hash') { - // url.com/page#managed-street-json:{"data":"value"} - const fragment = window.location.hash; - const prefix = '#managed-street-json:'; - try { - const encodedJsonStr = fragment.substring(prefix.length); - const jsonStr = decodeURIComponent(encodedJsonStr); - const streetObjectFromHash = JSON.parse(jsonStr); - this.parseStreetObject(streetObjectFromHash); - this.el.setAttribute('managed-street', 'synchronize', false); - this.el.setAttribute('managed-street', 'sourceType', 'json-blob'); + // if data.sourceValue is a string convert string to object for parsing but keep string for saving + if (typeof data.sourceValue === 'string') { + const streetObjectFromBlob = JSON.parse(data.sourceValue); + this.parseStreetObject(streetObjectFromBlob); + } + + // if data.sourceValue is an object, then parse and rewrite it as a json string for saving + if (typeof data.sourceValue === 'object') { + const streetObjectFromBlob = data.sourceValue; + + this.parseStreetObject(streetObjectFromBlob); + + const stringifiedStreetObject = JSON.stringify(streetObjectFromBlob); this.el.setAttribute( 'managed-street', 'sourceValue', - streetObjectFromHash + stringifiedStreetObject ); - } catch (err) { - console.error('Error parsing fragment:', err); } } }, diff --git a/src/json-utils_1.1.js b/src/json-utils_1.1.js index 3f9e2b62..86725be2 100644 --- a/src/json-utils_1.1.js +++ b/src/json-utils_1.1.js @@ -488,16 +488,29 @@ AFRAME.registerComponent('set-loader-from-hash', { return; } if (streetURL.startsWith('managed-street-json:')) { + // url.com/page#managed-street-json:{"data":"value"} + const fragment = window.location.hash; + const prefix = '#managed-street-json:'; + + let streetObjectFromHash = {}; + try { + const encodedJsonStr = fragment.substring(prefix.length); + const jsonStr = decodeURIComponent(encodedJsonStr); + streetObjectFromHash = JSON.parse(jsonStr); + } catch (err) { + console.error('Error parsing fragment:', err); + } + const definition = { + components: { + 'managed-street': { + sourceType: 'json-blob', + sourceValue: streetObjectFromHash, + synchronize: true + } + } + }; // use set timeout setTimeout(() => { - const definition = { - components: { - 'managed-street': { - sourceType: 'json-hash', - synchronize: true - } - } - }; AFRAME.INSPECTOR.execute('entitycreate', definition); // street notify STREET.notify.successMessage('Loading Managed Street JSON from URL'); From 015a1fd8a961ec9288408fb9e2b65beaba3f5707 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Thu, 19 Dec 2024 21:56:53 -0800 Subject: [PATCH 6/7] do not allow object to be passed to managed-street, only json string --- src/components/managed-street.js | 20 +++++--------------- src/json-utils_1.1.js | 7 +++---- 2 files changed, 8 insertions(+), 19 deletions(-) diff --git a/src/components/managed-street.js b/src/components/managed-street.js index 7f2c168d..5fac6727 100644 --- a/src/components/managed-street.js +++ b/src/components/managed-street.js @@ -23,7 +23,7 @@ userLayersEl = document.getElementById('street-container'); newStreetEl = document.createElement('a-entity'); newStreetEl.setAttribute('managed-street', { sourceType: 'json-blob', - sourceValue: window.STREET.SAMPLE_STREET, + sourceValue: JSON.stringify(window.STREET.SAMPLE_STREET), synchronize: true }); userLayersEl.append(newStreetEl); @@ -153,7 +153,7 @@ AFRAME.registerComponent('managed-street', { }, sourceType: { type: 'string', - oneOf: ['streetmix-url', 'streetplan-url', 'json-blob', 'json-hash'] + oneOf: ['streetmix-url', 'streetplan-url', 'json-blob'] }, sourceValue: { type: 'string' @@ -234,19 +234,9 @@ AFRAME.registerComponent('managed-street', { if (typeof data.sourceValue === 'string') { const streetObjectFromBlob = JSON.parse(data.sourceValue); this.parseStreetObject(streetObjectFromBlob); - } - - // if data.sourceValue is an object, then parse and rewrite it as a json string for saving - if (typeof data.sourceValue === 'object') { - const streetObjectFromBlob = data.sourceValue; - - this.parseStreetObject(streetObjectFromBlob); - - const stringifiedStreetObject = JSON.stringify(streetObjectFromBlob); - this.el.setAttribute( - 'managed-street', - 'sourceValue', - stringifiedStreetObject + } else { + console.log( + '[managed-street]: ERROR parsing json-blob, sourceValue must be a string' ); } } diff --git a/src/json-utils_1.1.js b/src/json-utils_1.1.js index 86725be2..dac6e2cb 100644 --- a/src/json-utils_1.1.js +++ b/src/json-utils_1.1.js @@ -492,11 +492,10 @@ AFRAME.registerComponent('set-loader-from-hash', { const fragment = window.location.hash; const prefix = '#managed-street-json:'; - let streetObjectFromHash = {}; + let jsonStr = {}; try { const encodedJsonStr = fragment.substring(prefix.length); - const jsonStr = decodeURIComponent(encodedJsonStr); - streetObjectFromHash = JSON.parse(jsonStr); + jsonStr = decodeURIComponent(encodedJsonStr); } catch (err) { console.error('Error parsing fragment:', err); } @@ -504,7 +503,7 @@ AFRAME.registerComponent('set-loader-from-hash', { components: { 'managed-street': { sourceType: 'json-blob', - sourceValue: streetObjectFromHash, + sourceValue: jsonStr, synchronize: true } } From cee039a1504ab40fa5b4a842afa57d64adb15c65 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Thu, 19 Dec 2024 23:41:39 -0800 Subject: [PATCH 7/7] add sample managed streets to add layer panel remove lengthy examples from managed street copy dev info into readme (remove from user facing docs) --- src/DEV-NOTES.md | 5 + src/README.md | 12 + src/components/managed-street.js | 137 -------- .../AddLayerPanel/createLayerFunctions.js | 37 +- .../AddLayerPanel/defaultStreets.js | 324 ++++++++++++++++++ .../components/AddLayerPanel/layersData.js | 51 ++- ui_assets/cards/icons/3dst24.png | Bin 0 -> 2558 bytes 7 files changed, 400 insertions(+), 166 deletions(-) create mode 100644 src/editor/components/components/AddLayerPanel/defaultStreets.js create mode 100644 ui_assets/cards/icons/3dst24.png diff --git a/src/DEV-NOTES.md b/src/DEV-NOTES.md index da88f63b..d9299b06 100644 --- a/src/DEV-NOTES.md +++ b/src/DEV-NOTES.md @@ -2,6 +2,11 @@ These is a place to save random notes like code snippets, links to assets, and other references. This doc might not be useful to anyone else :) +### Example managed street load from hash +``` +localhost:3333#managed-street-json:{"id":"aaaaaaaa-0123-4678-9000-000000000000","name":"Kieran's Awesome Street","width":40,"length":100,"justifyWidth":"center","justifyLength":"start","segments":[{"id":"aaaaaaaa-0123-4678-9000-000000000001","name":"Sidewalk for walking","type":"sidewalk","surface":"sidewalk","color":"#ffffff","level":1,"width":3,"direction":"none","generated":{"pedestrians":[{"density":"normal"}]}},{"id":"aaaaaaaa-0123-4678-9000-000000000002","name":"Sidewalk for trees and stuff","type":"sidewalk","surface":"sidewalk","color":"#ffffff","level":1,"width":1,"direction":"none","generated":{"clones":[{"mode":"fixed","model":"tree3","spacing":15}]}},{"id":"aaaaaaaa-0123-4678-9000-000000000003","name":"Parking for cars","type":"parking-lane","surface":"concrete","color":"#dddddd","level":0,"width":3,"direction":"inbound","generated":{"clones":[{"mode":"random","modelsArray":"sedan-rig, self-driving-waymo-car, suv-rig","spacing":6,"count":6}],"stencil":[{"model":"parking-t","cycleOffset":1,"spacing":6}]}},{"id":"aaaaaaaa-0123-4678-9000-000000000004","name":"Drive Lane for cars and stuff","type":"drive-lane","color":"#ffffff","surface":"asphalt","level":0,"width":3,"direction":"inbound","generated":{"clones":[{"mode":"random","modelsArray":"sedan-rig, box-truck-rig, self-driving-waymo-car, suv-rig, motorbike","spacing":7.3,"count":4}]}},{"id":"aaaaaaaa-0123-4678-9000-000000000005","name":"A beautiful median","type":"divider","surface":"sidewalk","color":"#ffffff","level":1,"width":0.5}]} +``` + ### Audio Notes ``` var entity = document.querySelector('.playme'); diff --git a/src/README.md b/src/README.md index 28e88af0..76b5f749 100644 --- a/src/README.md +++ b/src/README.md @@ -106,5 +106,17 @@ I learned a few things: * This UUID is not shown in the UI. It can be found by going to this URL and supplying the nameSpacedId and creatorId, such as: https://streetmix.net/api/v1/streets?namespacedId=3&creatorId=kfarr . This will redirect to the UUID API endpoint * I wrote a quick JS helper function that takes a user facing URL on Streetmix (such as https://streetmix.net/kfarr/3/a-frame-city-builder-street-only) and transforms it into the API Redirect to find the UUID endpoint. You can find the [helper function docs here](https://github.com/kfarr/3dstreet/tree/master/src#streetmix-utilsjs). +# Possibly Accepted URL Input hash schemes + +3DStreet can import third-party street data in a variety of formats. The following URL input hash schemes are experimental and not guaranteed to be supported in future versions: + +| Scheme | Description | Usage Example | +| --------- | -- |-- | +| `streetmix-url` | Streetmix User-Facing Street URL | `https://3dstreet.app/#https://streetmix.net/kfarr/3/3dstreet-demo-street` | +| `streetplan-url` | StreetPlan API URL | `https://3dstreet.app/#https://streetplan.net/3dstreet/89241` | +| `managed-street-json` | Managed Street JSON Blob | `https://3dstreet.app/#managed-street-json:{"data":"value"}` | +| `cloud-uuid-legacy` | 3DStreet Scene JSON Format from Cloud UUID with .json Extension | `https://3dstreet.app/#scenes/bc72ab26-891d-417b-a50f-0cf84621a54c.json` | +| `cloud-uuid` | 3DStreet Scene JSON Format from Cloud UUID | `https://3dstreet.app/#scenes/bc72ab26-891d-417b-a50f-0cf84621a54c` | + ### More Notes See [DEV-NOTES](DEV-NOTES.md) for additional notes on future features and work in progress. diff --git a/src/components/managed-street.js b/src/components/managed-street.js index 5fac6727..dc75e9db 100644 --- a/src/components/managed-street.js +++ b/src/components/managed-street.js @@ -5,143 +5,6 @@ const { segmentVariants } = require('../segments-variants.js'); const streetmixUtils = require('../tested/streetmix-utils'); const streetmixParsersTested = require('../tested/aframe-streetmix-parsers-tested'); -// invoking from js console - load from streetmix -/* -userLayersEl = document.getElementById('street-container'); -newStreetEl = document.createElement('a-entity'); -newStreetEl.setAttribute('managed-street', { - sourceType: 'streetmix-url', - sourceValue: 'https://streetmix.net/kfarr/3/', - synchronize: true -}); -userLayersEl.append(newStreetEl); -*/ - -// invoking from js console - load from blob -/* -userLayersEl = document.getElementById('street-container'); -newStreetEl = document.createElement('a-entity'); -newStreetEl.setAttribute('managed-street', { - sourceType: 'json-blob', - sourceValue: JSON.stringify(window.STREET.SAMPLE_STREET), - synchronize: true -}); -userLayersEl.append(newStreetEl); -*/ - -// invoking from js console - load from hash -/* -url to copy / paste: -localhost:3333#managed-street-json:{"id":"aaaaaaaa-0123-4678-9000-000000000000","name":"Kieran's Awesome Street","width":40,"length":100,"justifyWidth":"center","justifyLength":"start","segments":[{"id":"aaaaaaaa-0123-4678-9000-000000000001","name":"Sidewalk for walking","type":"sidewalk","surface":"sidewalk","color":"#ffffff","level":1,"width":3,"direction":"none","generated":{"pedestrians":[{"density":"normal"}]}},{"id":"aaaaaaaa-0123-4678-9000-000000000002","name":"Sidewalk for trees and stuff","type":"sidewalk","surface":"sidewalk","color":"#ffffff","level":1,"width":1,"direction":"none","generated":{"clones":[{"mode":"fixed","model":"tree3","spacing":15}]}},{"id":"aaaaaaaa-0123-4678-9000-000000000003","name":"Parking for cars","type":"parking-lane","surface":"concrete","color":"#dddddd","level":0,"width":3,"direction":"inbound","generated":{"clones":[{"mode":"random","modelsArray":"sedan-rig, self-driving-waymo-car, suv-rig","spacing":6,"count":6}],"stencil":[{"model":"parking-t","cycleOffset":1,"spacing":6}]}},{"id":"aaaaaaaa-0123-4678-9000-000000000004","name":"Drive Lane for cars and stuff","type":"drive-lane","color":"#ffffff","surface":"asphalt","level":0,"width":3,"direction":"inbound","generated":{"clones":[{"mode":"random","modelsArray":"sedan-rig, box-truck-rig, self-driving-waymo-car, suv-rig, motorbike","spacing":7.3,"count":4}]}},{"id":"aaaaaaaa-0123-4678-9000-000000000005","name":"A beautiful median","type":"divider","surface":"sidewalk","color":"#ffffff","level":1,"width":0.5}]} -*/ - -// Example Street object -// 'width' this is the user-specified RoW width, not cumulative width of segments -// some of this is redundant, many of the segment attributes defined below can be inferred by type -window.STREET.SAMPLE_STREET = { - id: 'aaaaaaaa-0123-4678-9000-000000000000', - name: "Kieran's Awesome Street", - width: 40, - length: 100, - justifyWidth: 'center', - justifyLength: 'start', - segments: [ - { - id: 'aaaaaaaa-0123-4678-9000-000000000001', - name: 'Sidewalk for walking', - type: 'sidewalk', - surface: 'sidewalk', - color: '#ffffff', - level: 1, - width: 3, - direction: 'none', - generated: { - pedestrians: [ - { - density: 'normal' - } - ] - } - }, - { - id: 'aaaaaaaa-0123-4678-9000-000000000002', - name: 'Sidewalk for trees and stuff', - type: 'sidewalk', - surface: 'sidewalk', - color: '#ffffff', - level: 1, - width: 1, - direction: 'none', - generated: { - clones: [ - { - mode: 'fixed', - model: 'tree3', - spacing: 15 - } - ] - } - }, - { - id: 'aaaaaaaa-0123-4678-9000-000000000003', - name: 'Parking for cars', - type: 'parking-lane', - surface: 'concrete', - color: '#dddddd', - level: 0, - width: 3, - direction: 'inbound', - generated: { - clones: [ - { - mode: 'random', - modelsArray: 'sedan-rig, self-driving-waymo-car, suv-rig', - spacing: 6, - count: 6 - } - ], - stencil: [ - { - model: 'parking-t', - cycleOffset: 1, - spacing: 6 - } - ] - } - }, - { - id: 'aaaaaaaa-0123-4678-9000-000000000004', - name: 'Drive Lane for cars and stuff', - type: 'drive-lane', - color: '#ffffff', - surface: 'asphalt', - level: 0, - width: 3, - direction: 'inbound', - generated: { - clones: [ - { - mode: 'random', - modelsArray: - 'sedan-rig, box-truck-rig, self-driving-waymo-car, suv-rig, motorbike', - spacing: 7.3, - count: 4 - } - ] - } - }, - { - id: 'aaaaaaaa-0123-4678-9000-000000000005', - name: 'A beautiful median', - type: 'divider', - surface: 'sidewalk', - color: '#ffffff', - level: 1, - width: 0.5 - } - ] -}; - AFRAME.registerComponent('managed-street', { schema: { width: { diff --git a/src/editor/components/components/AddLayerPanel/createLayerFunctions.js b/src/editor/components/components/AddLayerPanel/createLayerFunctions.js index 7a92c83b..eed592dd 100644 --- a/src/editor/components/components/AddLayerPanel/createLayerFunctions.js +++ b/src/editor/components/components/AddLayerPanel/createLayerFunctions.js @@ -1,5 +1,6 @@ import { loadScript, roundCoord } from '../../../../../src/utils.js'; import { createUniqueId } from '../../../lib/entity.js'; +import * as defaultStreetObjects from './defaultStreets.js'; export function createSvgExtrudedEntity(position) { // This component accepts a svgString and creates a new entity with geometry extruded @@ -50,7 +51,7 @@ export function createMapbox() { }); } -export function createManagedStreet(position) { +export function createManagedStreetFromStreetmixURLPrompt(position) { // This creates a new Managed Street let streetmixURL = prompt( 'Please enter a Streetmix URL', @@ -76,7 +77,29 @@ export function createManagedStreet(position) { } } +export function createManagedStreetFromStreetObject(position, streetObject) { + // This creates a new Managed Street + if (streetObject && streetObject !== '') { + const definition = { + id: createUniqueId(), + components: { + position: position ?? '0 0 0', + 'managed-street': { + sourceType: 'json-blob', + sourceValue: JSON.stringify(streetObject), + showVehicles: true, + showStriping: true, + synchronize: true + } + } + }; + + AFRAME.INSPECTOR.execute('entitycreate', definition); + } +} + export function createStreetmixStreet(position, streetmixURL, hideBuildings) { + // legacy // This code snippet allows the creation of an additional Streetmix street // in your 3DStreet scene without replacing any existing streets. if (streetmixURL === undefined) { @@ -116,6 +139,18 @@ export function create60ftRightOfWay(position) { true ); } + +export function create60ftRightOfWayManagedStreet(position) { + console.log( + 'create60ftRightOfWayManagedStreet', + defaultStreetObjects.stroad60ftROW + ); + createManagedStreetFromStreetObject( + position, + defaultStreetObjects.stroad60ftROW + ); +} + export function create80ftRightOfWay(position) { createStreetmixStreet( position, diff --git a/src/editor/components/components/AddLayerPanel/defaultStreets.js b/src/editor/components/components/AddLayerPanel/defaultStreets.js new file mode 100644 index 00000000..098f9f30 --- /dev/null +++ b/src/editor/components/components/AddLayerPanel/defaultStreets.js @@ -0,0 +1,324 @@ +// Define some example streets in Managed Street object format + +export const stroad60ftROW = { + id: '2d729802-6d80-45fa-89bd-f6d6b120d936', + name: '60ft Right of Way 36ft Road Width', + width: 18.288, // Keep in meters + length: 100, + justifyWidth: 'center', + justifyLength: 'start', + segments: [ + { + id: 'JCWzsLQHmyfDHzQhi9_pU', + name: 'Dense Sidewalk', + type: 'sidewalk', + surface: 'sidewalk', + color: '#ffffff', + level: 1, + width: 1.829, + direction: 'none', + generated: { + pedestrians: [ + { + density: 'dense' + } + ] + } + }, + { + id: 'RsLZFtSi3oJH7uufQ5rc4', + name: 'Tree Planting Strip', + type: 'sidewalk', + surface: 'sidewalk', + color: '#ffffff', + level: 1, + width: 0.914, + direction: 'none', + generated: { + clones: [ + { + mode: 'fixed', + model: 'tree3', + spacing: 15 + } + ] + } + }, + { + id: 'Xf2CNmHkMaGkTM8EaJn6h', + name: 'Modern Street Lamp', + type: 'sidewalk', + surface: 'sidewalk', + color: '#ffffff', + level: 1, + width: 0.914, + direction: 'none', + generated: { + clones: [ + { + mode: 'fixed', + model: 'lamp-modern', + spacing: 30, + facing: 0 + } + ] + } + }, + { + id: 'GbEHhCMPmVom_IJK-xIn3', + name: 'Inbound Parking', + type: 'parking-lane', + surface: 'concrete', + color: '#dddddd', + level: 0, + width: 2.438, + direction: 'inbound', + generated: { + clones: [ + { + mode: 'random', + modelsArray: 'sedan-rig, self-driving-waymo-car, suv-rig', + spacing: 6, + count: 6 + } + ], + stencil: [ + { + model: 'parking-t', + cycleOffset: 1, + spacing: 6 + } + ] + } + }, + { + id: 'z4gZgzYoM7sQ7mzIV01PC', + name: 'Inbound Drive Lane', + type: 'drive-lane', + surface: 'asphalt', + color: '#ffffff', + level: 0, + width: 3.048, + direction: 'inbound', + generated: { + clones: [ + { + mode: 'random', + modelsArray: + 'sedan-rig, box-truck-rig, self-driving-waymo-car, suv-rig, motorbike', + spacing: 7.3, + count: 4 + } + ] + } + }, + { + id: 'myp8_d3x_-hwuhTyH8ux1', + name: 'Outbound Drive Lane', + type: 'drive-lane', + surface: 'asphalt', + color: '#ffffff', + level: 0, + width: 3.048, + direction: 'outbound', + generated: { + clones: [ + { + mode: 'random', + modelsArray: + 'sedan-rig, box-truck-rig, self-driving-waymo-car, suv-rig, motorbike', + spacing: 7.3, + count: 4 + } + ] + } + }, + { + id: 'ARosTXeWGXp17QyfZgSKB', + name: 'Outbound Parking', + type: 'parking-lane', + surface: 'concrete', + color: '#dddddd', + level: 0, + width: 2.438, + direction: 'outbound', + generated: { + clones: [ + { + mode: 'random', + modelsArray: 'sedan-rig, self-driving-waymo-car, suv-rig', + spacing: 6, + count: 6 + } + ], + stencil: [ + { + model: 'parking-t', + cycleOffset: 1, + spacing: 6 + } + ] + } + }, + { + id: 'oweuZgwBHUbt65Ep7GZhU', + name: 'Modern Street Lamp', + type: 'sidewalk', + surface: 'sidewalk', + color: '#ffffff', + level: 1, + width: 0.914, + direction: 'none', + generated: { + clones: [ + { + mode: 'fixed', + model: 'lamp-modern', + spacing: 30, + facing: 180 + } + ] + } + }, + { + id: 'vL9qDNp5neZt32zlZ9ExG', + name: 'Tree Planting Strip', + type: 'sidewalk', + surface: 'sidewalk', + color: '#ffffff', + level: 1, + width: 0.914, + direction: 'none', + generated: { + clones: [ + { + mode: 'fixed', + model: 'tree3', + spacing: 15 + } + ] + } + }, + { + id: 'RClRRZoof9_BYnqQm7mz-', + name: 'Normal Sidewalk', + type: 'sidewalk', + surface: 'sidewalk', + color: '#ffffff', + level: 1, + width: 1.829, + direction: 'none', + generated: { + pedestrians: [ + { + density: 'normal' + } + ] + } + } + ] +}; + +export const exampleStreet = { + id: 'aaaaaaaa-0123-4678-9000-000000000000', + name: "Kieran's Basic Street", + width: 40, + length: 100, + justifyWidth: 'center', + justifyLength: 'start', + segments: [ + { + id: 'aaaaaaaa-0123-4678-9000-000000000001', + name: 'Sidewalk for walking', + type: 'sidewalk', + surface: 'sidewalk', + color: '#ffffff', + level: 1, + width: 3, + direction: 'none', + generated: { + pedestrians: [ + { + density: 'normal' + } + ] + } + }, + { + id: 'aaaaaaaa-0123-4678-9000-000000000002', + name: 'Sidewalk for trees and stuff', + type: 'sidewalk', + surface: 'sidewalk', + color: '#ffffff', + level: 1, + width: 1, + direction: 'none', + generated: { + clones: [ + { + mode: 'fixed', + model: 'tree3', + spacing: 15 + } + ] + } + }, + { + id: 'aaaaaaaa-0123-4678-9000-000000000003', + name: 'Parking for cars', + type: 'parking-lane', + surface: 'concrete', + color: '#dddddd', + level: 0, + width: 3, + direction: 'inbound', + generated: { + clones: [ + { + mode: 'random', + modelsArray: 'sedan-rig, self-driving-waymo-car, suv-rig', + spacing: 6, + count: 6 + } + ], + stencil: [ + { + model: 'parking-t', + cycleOffset: 1, + spacing: 6 + } + ] + } + }, + { + id: 'aaaaaaaa-0123-4678-9000-000000000004', + name: 'Drive Lane for cars and stuff', + type: 'drive-lane', + color: '#ffffff', + surface: 'asphalt', + level: 0, + width: 3, + direction: 'inbound', + generated: { + clones: [ + { + mode: 'random', + modelsArray: + 'sedan-rig, box-truck-rig, self-driving-waymo-car, suv-rig, motorbike', + spacing: 7.3, + count: 4 + } + ] + } + }, + { + id: 'aaaaaaaa-0123-4678-9000-000000000005', + name: 'A beautiful median', + type: 'divider', + surface: 'sidewalk', + color: '#ffffff', + level: 1, + width: 0.5 + } + ] +}; diff --git a/src/editor/components/components/AddLayerPanel/layersData.js b/src/editor/components/components/AddLayerPanel/layersData.js index 42278192..c20b2b37 100644 --- a/src/editor/components/components/AddLayerPanel/layersData.js +++ b/src/editor/components/components/AddLayerPanel/layersData.js @@ -1,17 +1,4 @@ -import { - createSvgExtrudedEntity, - createStreetmixStreet, - createCustomModel, - createPrimitiveGeometry, - createIntersection, - create40ftRightOfWay, - create60ftRightOfWay, - create80ftRightOfWay, - create94ftRightOfWay, - create150ftRightOfWay, - createImageEntity, - createManagedStreet -} from './createLayerFunctions'; +import * as createFunctions from './createLayerFunctions'; export const streetLayersData = [ { @@ -21,7 +8,7 @@ export const streetLayersData = [ description: 'Create an additional Streetmix street in your 3DStreet scene without replacing any existing streets.', id: 1, - handlerFunction: createStreetmixStreet + handlerFunction: createFunctions.createStreetmixStreet }, { name: '40ft RoW / 24ft Roadway Width', @@ -29,7 +16,7 @@ export const streetLayersData = [ icon: 'ui_assets/cards/icons/streetmix24.png', description: 'Premade Street 40ft Right of Way / 24ft Roadway Width', id: 2, - handlerFunction: create40ftRightOfWay + handlerFunction: createFunctions.create40ftRightOfWay }, { name: '60ft RoW / 36ft Roadway Width', @@ -37,7 +24,7 @@ export const streetLayersData = [ icon: 'ui_assets/cards/icons/streetmix24.png', description: 'Premade Street 60ft Right of Way / 36ft Roadway Width', id: 3, - handlerFunction: create60ftRightOfWay + handlerFunction: createFunctions.create60ftRightOfWay }, { name: '80ft RoW / 56ft Roadway Width', @@ -45,7 +32,7 @@ export const streetLayersData = [ icon: 'ui_assets/cards/icons/streetmix24.png', description: 'Premade Street 80ft Right of Way / 56ft Roadway Width', id: 4, - handlerFunction: create80ftRightOfWay + handlerFunction: createFunctions.create80ftRightOfWay }, { name: '94ft RoW / 70ft Roadway Width', @@ -53,7 +40,7 @@ export const streetLayersData = [ icon: 'ui_assets/cards/icons/streetmix24.png', description: 'Premade Street 94ft Right of Way / 70ft Roadway Width', id: 5, - handlerFunction: create94ftRightOfWay + handlerFunction: createFunctions.create94ftRightOfWay }, { name: '150ft RoW / 124ft Roadway Width', @@ -61,7 +48,7 @@ export const streetLayersData = [ icon: 'ui_assets/cards/icons/streetmix24.png', description: 'Premade Street 150ft Right of Way / 124ft Roadway Width', id: 6, - handlerFunction: create150ftRightOfWay + handlerFunction: createFunctions.create150ftRightOfWay }, { name: 'Create intersection', @@ -71,17 +58,25 @@ export const streetLayersData = [ description: 'Create intersection entity. Parameters of intersection component could be changed in properties panel.', id: 7, - handlerFunction: createIntersection + handlerFunction: createFunctions.createIntersection }, { - name: 'Create Managed Street (Beta)', + name: '(Beta) Managed Street from Streetmix URL', img: '', requiresPro: true, icon: '', description: - 'Create a new street from Streetmix using the Managed Street component.', + 'Create a new street from Streetmix URL using the Managed Street component.', id: 8, - handlerFunction: createManagedStreet + handlerFunction: createFunctions.createManagedStreetFromStreetmixURLPrompt + }, + { + name: '(Beta) Managed Street 60ft RoW / 36ft Roadway Width', + img: 'ui_assets/cards/street-preset-60-36.jpg', + icon: 'ui_assets/cards/icons/3dst24.png', + description: 'Premade Street 60ft Right of Way / 36ft Roadway Width', + id: 9, + handlerFunction: createFunctions.create60ftRightOfWayManagedStreet } ]; @@ -94,7 +89,7 @@ export const customLayersData = [ description: 'Create entity with svg-extruder component, that accepts a svgString and creates a new entity with geometry extruded from the svg and applies the default mixin material grass.', id: 1, - handlerFunction: createSvgExtrudedEntity + handlerFunction: createFunctions.createSvgExtrudedEntity }, { name: 'glTF model from URL', @@ -104,7 +99,7 @@ export const customLayersData = [ description: 'Create entity with model from path for a glTF (or Glb) file hosted on any publicly accessible HTTP server.', id: 2, - handlerFunction: createCustomModel + handlerFunction: createFunctions.createCustomModel }, { name: 'Create primitive geometry', @@ -114,7 +109,7 @@ export const customLayersData = [ description: 'Create entity with A-Frame primitive geometry. Geometry type could be changed in properties panel.', id: 3, - handlerFunction: createPrimitiveGeometry + handlerFunction: createFunctions.createPrimitiveGeometry }, { name: 'Place New Image Entity', @@ -124,6 +119,6 @@ export const customLayersData = [ description: 'Place an image such as a sign, reference photo, custom map, etc.', id: 4, - handlerFunction: createImageEntity + handlerFunction: createFunctions.createImageEntity } ]; diff --git a/ui_assets/cards/icons/3dst24.png b/ui_assets/cards/icons/3dst24.png new file mode 100644 index 0000000000000000000000000000000000000000..c56ce444499945b0ae04bd3a5675b47603c8a7ce GIT binary patch literal 2558 zcmZ`*3pkT~8-Fk?Gg0J}*PP-*+bGGJF-FE5lC!+oSY}SMjZLR_Iph#If4setC>JR& z$(tmnN^*J`A#J5k<#36RM9R0(SH7;^|8xDH`+k1+{kwnn|M~x)>w1!1oDM>zRHOg^ zfa2|Nt^%thoL~vTeUxSXQD8tpSVt@XRHs8$iQNnpu zF&F@S#hnCJIP3&dn73iYibTFwDH8pbtyCoT-51iJ70*-zW(&>En*jij?ZPPnTqqE% zP&6yp&5P;fNI(Tdgu#h{5hOC49Yzzf05lsVaKp$Afjtb5QMhcDi zcHS^jbh0EB03=-TIINo;G|B@)bM%z=TjGEEvfQzT*Dk~<;9n)$=Yq>MHSG`SP%5-? zupvy8Pa8oMVtRCTV0M}Fu_N9XGb(%_aQ{|mdk{erAwfAbj9{qdybD*Y+K!98RclzwEkB? zgv%Hx*yFf-W}wz=Ww7yR1V{A_koat3h zTfC$~S)}4pW#U@OO88~(sv6}aqZI;%!3av)wymRU{Kc8FILlO-M04ljGm-J7?M_ek zhxEARORSW-+0JRcXFkS#=@Z`nRCznogBp1_1nW&eOug{PTrmBzidG0-URY*h4GQ4u$#k zgLu~)|Lgpl;5+18WF2)y3~7jxvZ*MRNgPxjpj6DZoi#Z(=l;gF03EH;=?S+5x7~k9 zyD3hCa5c~^@h`sc&UET{%I;$#)1t8&Tcu7kP^83Q+Lmq=z0axlQwJXNUQP@ZZ_$=y zMXKqOKf^OC6F)5~8wpG`es;ASqgSt+BhW9TY)_L`QTbFfg-@tIHrF2{QwBv9S zLM;r+M`r|4Ai4eJ&02)?kts=V`p)y$d{0Eu)||^K>$?}~b;tCM@KfJ?m}^~L?Vu##*x0hw+H0}tT;icuoPfmo^2)?0HQ`JYEN%~Kjk7DtV#DKwP{dEgkiOxlQZt@+- zG0oGfN?qRepvoDJlk*YXg~f$dZVcqzl{**vv(%>+rl;dF^W|9<^yVjgFg|NPLq*jm zBeO7}V7nr+xf2SNpL_hrftTa6oe1I-Z_@6vzsJ2bv#Cmx`v)G>dNzJq{Sfs)_C-?n z4EFdD3*W@DjGw(1b z&s!I~bMVvgJGZqieAe3Mkh2Ri**oZ`>|!=iHwnIlf{Jc2G^T=L4?>$yI6QAzje+39 z#Ap*sB_Ap7DqW>Xg@1g?dXJP=nDe;fHxNG%HK&K@Z0Ysv&d+$KLD==1e8JuJF|Zo- z%?+m^h6KFQcExd$OMI;F8eHtRedZ?>2P{M|BV~hsaST7sSV*c?3}h*PQ2pZ zMYjV-dcf1_3IDN7_HncBZFfT{+%k#Y;-sGtjzLk%Wxen2EXwM@>d=SD)eEX!R6C0? z7jv5zwl?b3B*)K9Tdl-w)$%kF&BAUb54dxcb!=v@$>sl5)1=##58BgBk~GNjp;IyH pW}hD79+kG!8)3xo=*NxRHGt!kUolhx?-Ksu;BB06l~(?-{{=gc5P$#x literal 0 HcmV?d00001