This commit is contained in:
commit e61d535120
11 changed files with 174 additions and 47 deletions

View File

@ -120,6 +120,22 @@
"detail": "django.db", "detail": "django.db",
"documentation": {} "documentation": {}
}, },
{
"label": "migrations",
"importPath": "django.db",
"description": "django.db",
"isExtraImport": true,
"detail": "django.db",
"documentation": {}
},
{
"label": "models",
"importPath": "django.db",
"description": "django.db",
"isExtraImport": true,
"detail": "django.db",
"documentation": {}
},
{ {
"label": "models", "label": "models",
"importPath": "django.db", "importPath": "django.db",
@ -1077,6 +1093,15 @@
"detail": "back.object.migrations.0004_alter_clickablearea_target_name", "detail": "back.object.migrations.0004_alter_clickablearea_target_name",
"documentation": {} "documentation": {}
}, },
{
"label": "Migration",
"kind": 6,
"importPath": "back.object.migrations.0005_scene3d_hdr_gainmap_scene3d_hdr_json_and_more",
"description": "back.object.migrations.0005_scene3d_hdr_gainmap_scene3d_hdr_json_and_more",
"peekOfCode": "class Migration(migrations.Migration):\n dependencies = [\n ('object', '0004_alter_clickablearea_target_name'),\n ]\n operations = [\n migrations.AddField(\n model_name='scene3d',\n name='hdr_gainmap',\n field=models.FileField(blank=True, null=True, upload_to=''),\n ),",
"detail": "back.object.migrations.0005_scene3d_hdr_gainmap_scene3d_hdr_json_and_more",
"documentation": {}
},
{ {
"label": "Scene3DAdmin", "label": "Scene3DAdmin",
"kind": 6, "kind": 6,
@ -1100,7 +1125,7 @@
"kind": 6, "kind": 6,
"importPath": "back.object.models", "importPath": "back.object.models",
"description": "back.object.models", "description": "back.object.models",
"peekOfCode": "class Element3D(models.Model):\n parent = models.ForeignKey(\"self\", on_delete=models.PROTECT, blank=True, null=True)\n model_file = models.FileField(upload_to=group_based_upload_to)\n name = models.CharField(max_length=255)\n description = models.TextField()\n def __str__(self):\n return self.name\nclass Scene3D(models.Model):\n filter_horizontal = ('elements',)\n name = models.CharField(", "peekOfCode": "class Element3D(models.Model):\n parent = models.ForeignKey(\"self\", on_delete=models.PROTECT, blank=True, null=True)\n model_file = models.FileField(upload_to=group_based_upload_to)\n name = models.CharField(max_length=255)\n description = models.TextField()\n def __str__(self):\n return self.name\nclass Scene3D(models.Model):\n filter_horizontal = (\"elements\",)\n name = models.CharField(",
"detail": "back.object.models", "detail": "back.object.models",
"documentation": {} "documentation": {}
}, },
@ -1109,7 +1134,7 @@
"kind": 6, "kind": 6,
"importPath": "back.object.models", "importPath": "back.object.models",
"description": "back.object.models", "description": "back.object.models",
"peekOfCode": "class Scene3D(models.Model):\n filter_horizontal = ('elements',)\n name = models.CharField(\n max_length=120,\n )\n elements = models.ManyToManyField(Element3D)\n min_distance = models.IntegerField(\n validators=[MinValueValidator(1), MaxValueValidator(600)], blank=True, null=True\n )\n max_distance = models.IntegerField(", "peekOfCode": "class Scene3D(models.Model):\n filter_horizontal = (\"elements\",)\n name = models.CharField(\n max_length=120,\n )\n elements = models.ManyToManyField(Element3D)\n min_distance = models.IntegerField(\n validators=[MinValueValidator(1), MaxValueValidator(600)], blank=True, null=True\n )\n max_distance = models.IntegerField(",
"detail": "back.object.models", "detail": "back.object.models",
"documentation": {} "documentation": {}
}, },
@ -1145,7 +1170,7 @@
"kind": 6, "kind": 6,
"importPath": "back.object.serializers", "importPath": "back.object.serializers",
"description": "back.object.serializers", "description": "back.object.serializers",
"peekOfCode": "class Element3DSerializer(serializers.ModelSerializer):\n model_file = serializers.ImageField(use_url=False)\n class Meta:\n model = Element3D\n fields = \"__all__\"\nclass Scene3DSerializer(serializers.ModelSerializer):\n elements = Element3DSerializer(many=True)\n class Meta:\n model = Scene3D\n fields = \"__all__\"", "peekOfCode": "class Element3DSerializer(serializers.ModelSerializer):\n model_file = serializers.ImageField(use_url=False)\n class Meta:\n model = Element3D\n fields = \"__all__\"\nclass Scene3DSerializer(serializers.ModelSerializer):\n elements = Element3DSerializer(many=True)\n hdr_gainmap = serializers.FileField(use_url=False)\n hdr_json = serializers.FileField(use_url=False)\n hdr_webp = serializers.FileField(use_url=False)",
"detail": "back.object.serializers", "detail": "back.object.serializers",
"documentation": {} "documentation": {}
}, },
@ -1154,7 +1179,7 @@
"kind": 6, "kind": 6,
"importPath": "back.object.serializers", "importPath": "back.object.serializers",
"description": "back.object.serializers", "description": "back.object.serializers",
"peekOfCode": "class Scene3DSerializer(serializers.ModelSerializer):\n elements = Element3DSerializer(many=True)\n class Meta:\n model = Scene3D\n fields = \"__all__\"\n depth = 2\nclass ClickableAreaSerializer(serializers.ModelSerializer):\n class Meta:\n model = ClickableArea\n fields = \"__all__\"", "peekOfCode": "class Scene3DSerializer(serializers.ModelSerializer):\n elements = Element3DSerializer(many=True)\n hdr_gainmap = serializers.FileField(use_url=False)\n hdr_json = serializers.FileField(use_url=False)\n hdr_webp = serializers.FileField(use_url=False)\n class Meta:\n model = Scene3D\n fields = \"__all__\"\n depth = 2\nclass ClickableAreaSerializer(serializers.ModelSerializer):",
"detail": "back.object.serializers", "detail": "back.object.serializers",
"documentation": {} "documentation": {}
}, },

View File

@ -22,7 +22,7 @@ class Element3D(models.Model):
return self.name return self.name
class Scene3D(models.Model): class Scene3D(models.Model):
filter_horizontal = ('elements',) filter_horizontal = ("elements",)
name = models.CharField( name = models.CharField(
max_length=120, max_length=120,
) )
@ -32,12 +32,22 @@ class Scene3D(models.Model):
validators=[MinValueValidator(1), MaxValueValidator(600)], blank=True, null=True validators=[MinValueValidator(1), MaxValueValidator(600)], blank=True, null=True
) )
max_distance = models.IntegerField( max_distance = models.IntegerField(
validators=[MinValueValidator(2), MaxValueValidator(1000)], blank=True, null=True validators=[MinValueValidator(2), MaxValueValidator(1000)],
blank=True,
null=True,
) )
<<<<<<< HEAD
gainmap = models.FileField(upload_to=group_based_upload_to) gainmap = models.FileField(upload_to=group_based_upload_to)
json = models.FileField() json = models.FileField()
webp = models.FileField() webp = models.FileField()
=======
>>>>>>> 46d2586e693673bf1d8b8a390b297209bdf70ecb
hdr_gainmap = models.FileField(
upload_to=group_based_upload_to, blank=True, null=True
)
hdr_json = models.FileField(upload_to=group_based_upload_to, blank=True, null=True)
hdr_webp = models.FileField(upload_to=group_based_upload_to, blank=True, null=True)
def __str__(self): def __str__(self):
return self.name return self.name

View File

@ -11,8 +11,16 @@ class Element3DSerializer(serializers.ModelSerializer):
class Scene3DSerializer(serializers.ModelSerializer): class Scene3DSerializer(serializers.ModelSerializer):
<<<<<<< HEAD
elements = serializers.ImageField(many=True, use_url=False) elements = serializers.ImageField(many=True, use_url=False)
=======
elements = Element3DSerializer(many=True)
hdr_gainmap = serializers.FileField(use_url=False)
hdr_json = serializers.FileField(use_url=False)
hdr_webp = serializers.FileField(use_url=False)
>>>>>>> 46d2586e693673bf1d8b8a390b297209bdf70ecb
class Meta: class Meta:
model = Scene3D model = Scene3D
fields = "__all__" fields = "__all__"

View File

@ -17,6 +17,7 @@ declare module 'vue' {
IMdiHexagonOutline: typeof import('~icons/mdi/hexagon-outline')['default'] IMdiHexagonOutline: typeof import('~icons/mdi/hexagon-outline')['default']
IMdiHome: typeof import('~icons/mdi/home')['default'] IMdiHome: typeof import('~icons/mdi/home')['default']
IMdiMonitorScreenshot: typeof import('~icons/mdi/monitor-screenshot')['default'] IMdiMonitorScreenshot: typeof import('~icons/mdi/monitor-screenshot')['default']
IMdiPagePreviousOutline: typeof import('~icons/mdi/page-previous-outline')['default']
IMdiShop: typeof import('~icons/mdi/shop')['default'] IMdiShop: typeof import('~icons/mdi/shop')['default']
IMdiVideo3d: typeof import('~icons/mdi/video3d')['default'] IMdiVideo3d: typeof import('~icons/mdi/video3d')['default']
Item: typeof import('./src/components/Floorplan/item.vue')['default'] Item: typeof import('./src/components/Floorplan/item.vue')['default']

View File

@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { onMounted } from 'vue'; import { onMounted, watch } from 'vue';
import { PMREMGenerator } from 'three'; import { PMREMGenerator } from 'three';
import { GainMapLoader } from '@monogrid/gainmap-js' import { GainMapLoader } from '@monogrid/gainmap-js'
@ -9,14 +9,20 @@ import hdr_gainmap from '../../assets/promo/hdr/hdr-gainmap.webp'
import hdr_json from '../../assets/promo/hdr/hdr.json?url' import hdr_json from '../../assets/promo/hdr/hdr.json?url'
import hdr_webp from '../../assets/promo/hdr/hdr.webp' import hdr_webp from '../../assets/promo/hdr/hdr.webp'
const { renderer, scene } = useTresContext() const props = defineProps(['hdr_webp', 'hdr_gainmap', 'hdr_json'])
onMounted(async () => { const { renderer, scene } = useTresContext()
const loadEnv = async () => {
console.log(props)
const pmremGenerator = new PMREMGenerator(renderer.value); const pmremGenerator = new PMREMGenerator(renderer.value);
pmremGenerator.compileEquirectangularShader(); pmremGenerator.compileEquirectangularShader();
const loader = new GainMapLoader(renderer.value) const loader = new GainMapLoader(renderer.value)
const result = await loader.loadAsync([hdr_webp, hdr_gainmap, hdr_json,]) const result = await loader.loadAsync([
props.hdr_webp || hdr_webp,
props.hdr_gainmap || hdr_gainmap,
props.hdr_json || hdr_json,
])
const exrCubeRenderTarget = pmremGenerator.fromEquirectangular(result.renderTarget.texture); const exrCubeRenderTarget = pmremGenerator.fromEquirectangular(result.renderTarget.texture);
const exrBackground = exrCubeRenderTarget.texture; const exrBackground = exrCubeRenderTarget.texture;
@ -24,5 +30,9 @@ onMounted(async () => {
scene.value.environment = newEnvMap scene.value.environment = newEnvMap
scene.value.background = exrBackground scene.value.background = exrBackground
result.renderTarget.texture.dispose(); result.renderTarget.texture.dispose();
}
onMounted(async () => {
loadEnv()
}) })
watch(() => props.hdr_webp, loadEnv)
</script> </script>

View File

@ -1,11 +1,14 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, watch } from 'vue'; import { reactive, ref, watch } from 'vue';
import { Box3, Color, Group, Mesh, MeshStandardMaterial, PointLight, SphereGeometry, Vector3 } from 'three'; import { Box3, Color, Group, Mesh, MeshStandardMaterial, PointLight, SphereGeometry, Vector3 } from 'three';
import { useTresContext, useSeek } from '@tresjs/core'; import { useTresContext, useSeek } from '@tresjs/core';
import { useGLTF } from '@tresjs/cientos' import { useGLTF } from '@tresjs/cientos'
import Env from './env.vue'
import { IMAGE_URL, SERVER_URL, } from '../../constants' import { IMAGE_URL, SERVER_URL, } from '../../constants'
import { usePromoSidebar } from '../../stores/promo_sidebar'; import { usePromoSidebar } from '../../stores/promo_sidebar';
import { usePromoScene } from '../../stores/promo_scene';
const props = defineProps(['source', 'loaded', 'loaded_pan']) const props = defineProps(['source', 'loaded', 'loaded_pan'])
@ -22,12 +25,19 @@ const clickable = ref<clickableAreaType[]>([])
const clickable_objects = ref<any[]>([]) const clickable_objects = ref<any[]>([])
const clickable_items = ref<any[]>([]) const clickable_items = ref<any[]>([])
const sidebar = usePromoSidebar(); const sidebar = usePromoSidebar();
const sidebar_scene = usePromoScene()
const { controls, camera, scene } = useTresContext() const { controls, camera, scene } = useTresContext()
const { seekByName } = useSeek() const { seekByName } = useSeek()
const envVars = reactive({}) as { hdr_gainmap?: string, hdr_json?: string, hdr_webp?: string }
const loadModels = async () => { const loadModels = async () => {
const res = await fetch(`${SERVER_URL}/api/obj/scene/${props.source}`) const res = await fetch(`${SERVER_URL}/api/obj/scene/${props.source}`)
const raw_data = await res.json() as scene3D const raw_data = await res.json() as scene3D
if (raw_data.hdr_gainmap) envVars.hdr_gainmap = `${IMAGE_URL}/${raw_data.hdr_gainmap}`
if (raw_data.hdr_json) envVars.hdr_json = `${IMAGE_URL}/${raw_data.hdr_json}`
if (raw_data.hdr_webp) envVars.hdr_webp = `${IMAGE_URL}/${raw_data.hdr_webp}`
const data = raw_data.elements const data = raw_data.elements
if (!controls.value) return; if (!controls.value) return;
@ -39,8 +49,10 @@ const loadModels = async () => {
camera.value?.position.set(1, 1, 1); camera.value?.position.set(1, 1, 1);
camera.value?.lookAt(new Vector3(1, 1, 1)); camera.value?.lookAt(new Vector3(1, 1, 1));
const sidebar_items = []
for (let index = 0; index < data.length; index++) { for (let index = 0; index < data.length; index++) {
const element = data[index]; const element = data[index];
sidebar_items.push({ ...element, is_enabled: true })
const item = {} as model3DType const item = {} as model3DType
item.modelUrl = `${IMAGE_URL}/${element.model_file}` item.modelUrl = `${IMAGE_URL}/${element.model_file}`
@ -55,6 +67,8 @@ const loadModels = async () => {
const clickable_areas = await res.json() const clickable_areas = await res.json()
clickable.value.push(...clickable_areas) clickable.value.push(...clickable_areas)
} }
sidebar_scene.setData(sidebar_items)
sidebar.open()
for (let index = 0; index < clickable.value.length; index++) { for (let index = 0; index < clickable.value.length; index++) {
const element = clickable.value[index]; const element = clickable.value[index];
@ -140,6 +154,9 @@ watch(() => props.source, () => {
}) })
</script> </script>
<template> <template>
<Suspense>
<Env v-bind="envVars" />
</Suspense>
<TresGroup name="loaded"> <TresGroup name="loaded">
<template v-for="item in models"> <template v-for="item in models">
<TresGroup :name="item.name"> <TresGroup :name="item.name">

View File

@ -7,7 +7,6 @@ import { Vector3 } from 'three';
import { TresCanvas } from '@tresjs/core'; import { TresCanvas } from '@tresjs/core';
import { StatsGl, OrbitControls } from '@tresjs/cientos' import { StatsGl, OrbitControls } from '@tresjs/cientos'
import Env from './env.vue'
import LoadModels from './load_models.vue' import LoadModels from './load_models.vue'
import Sidebar from './sidebar.vue' import Sidebar from './sidebar.vue'
import { usePromoSidebar } from '../../stores/promo_sidebar'; import { usePromoSidebar } from '../../stores/promo_sidebar';
@ -62,9 +61,6 @@ watch(() => route.params.target, () => {
</Suspense> </Suspense>
<TresPerspectiveCamera :position="cameraPosition" ref="camera" /> <TresPerspectiveCamera :position="cameraPosition" ref="camera" />
<OrbitControls v-bind="controlsState" @change="onChange" make-default /> <OrbitControls v-bind="controlsState" @change="onChange" make-default />
<Suspense>
<Env />
</Suspense>
<Suspense> <Suspense>
<LoadModels :source="source" :loaded="set_model_load_status" :loaded_pan="loadedPan" /> <LoadModels :source="source" :loaded="set_model_load_status" :loaded_pan="loadedPan" />
</Suspense> </Suspense>
@ -73,9 +69,14 @@ watch(() => route.params.target, () => {
<TresShadowMaterial :opacity="0.2" /> <TresShadowMaterial :opacity="0.2" />
</TresMesh> </TresMesh>
</TresCanvas> </TresCanvas>
<RouterLink to="/promo/main/" class="homelink"> <div class="homelink">
<i-mdi-home /> <a href="#" @click.prevent="sidebar.open" v-if="!sidebar.is_open">
</RouterLink> <i-mdi-page-previous-outline />
</a>
<RouterLink to="/promo/main/">
<i-mdi-home />
</RouterLink>
</div>
</div> </div>
<Sidebar /> <Sidebar />
</div> </div>
@ -96,18 +97,25 @@ watch(() => route.params.target, () => {
filter: blur(10px); filter: blur(10px);
transition: all 300ms linear; transition: all 300ms linear;
} }
.homelink { .homelink {
position: absolute; position: absolute;
right: 0; right: 0;
bottom: 0; bottom: 0;
border-radius: 50%;
border: 1px solid white;
background: white;
padding: 0.5rem;
line-height: 1;
font-size: 0;
svg { svg {
font-size: 5rem; font-size: 3rem;
padding: 1.5rem;
}
a {
margin-bottom: 2rem;
border-radius: 50%;
border: 1px solid white;
background: white;
line-height: 1;
font-size: 0;
display: block;
} }
} }
</style> </style>

View File

@ -3,10 +3,12 @@ import { ref } from 'vue';
import { RouterLink } from 'vue-router'; import { RouterLink } from 'vue-router';
import { onClickOutside } from '@vueuse/core' import { onClickOutside } from '@vueuse/core'
import { usePromoSidebar } from '../../stores/promo_sidebar'; import { usePromoSidebar } from '../../stores/promo_sidebar';
import { usePromoScene } from '../../stores/promo_scene';
const sidebar = usePromoSidebar() const sidebar = usePromoSidebar()
const scene = usePromoScene()
const sidebar_obj = ref() const sidebar_obj = ref()
console.log(scene.list)
// onClickOutside(sidebar_obj, () => sidebar.close()) // onClickOutside(sidebar_obj, () => sidebar.close())
</script> </script>
<template> <template>
@ -14,16 +16,24 @@ const sidebar_obj = ref()
<a href="#" @click.prevent="sidebar.close" class="sidebar-close"> <a href="#" @click.prevent="sidebar.close" class="sidebar-close">
<i-mdi-close /> <i-mdi-close />
</a> </a>
<template v-if="sidebar.is_open"> <div class="sidebar-content">
<h2>{{ sidebar.title }}</h2> <template v-if="!sidebar.is_open"></template>
<template v-for="p in sidebar.description.split('\n')"> <template v-else-if="(sidebar.description && sidebar.title)">
<p>{{ p }}</p> <h2>{{ sidebar.title }}</h2>
<template v-for="p in sidebar.description.split('\n')">
<p>{{ p }}</p>
</template>
<RouterLink class="btn" :to="`/promo/main/${sidebar.target}`" v-if="sidebar.target">
{{ sidebar.target_name }}
</RouterLink>
</template> </template>
<RouterLink class="btn" :to="`/promo/main/${sidebar.target}`" v-if="sidebar.target"> <template v-else>
{{ sidebar.target_name }} <template v-for="item in scene.list">
</RouterLink> <h3>{{ item.name }}</h3>
<p>{{ item.description }}</p>
</template> </template>
</template>
</div>
</div> </div>
</template> </template>
<style scoped lang="scss"> <style scoped lang="scss">
@ -35,8 +45,9 @@ const sidebar_obj = ref()
right: -27vw; right: -27vw;
bottom: 0; bottom: 0;
transition: all 300ms linear; transition: all 300ms linear;
line-height: 1.25;
padding: 2rem; padding: 3rem 2rem 2rem;
&.open { &.open {
right: 0 right: 0
@ -50,17 +61,31 @@ const sidebar_obj = ref()
color: black; color: black;
} }
&-content {
max-height: 100%;
overflow: auto;
}
h2 { h2 {
text-align: center; text-align: center;
font-size: 2rem; font-size: 2rem;
margin: 1rem margin: 1rem
} }
p { p,
h3 {
margin: 1rem 0; margin: 1rem 0;
}
p {
font-size: 1.25rem; font-size: 1.25rem;
} }
h3 {
font-size: 1.5rem;
font-weight: bold
}
.btn { .btn {
display: block; display: block;
margin: 0 auto; margin: 0 auto;
@ -69,21 +94,21 @@ const sidebar_obj = ref()
background: linear-gradient(90deg, rgba(129, 230, 217, 1) 0%, rgba(79, 209, 197, 1) 100%); background: linear-gradient(90deg, rgba(129, 230, 217, 1) 0%, rgba(79, 209, 197, 1) 100%);
color: #313133; color: #313133;
transition: .2s linear; transition: .2s linear;
padding: 1rem 1.5rem; padding: 1rem 1.5rem;
font-size: 1.25rem; font-size: 1.25rem;
font-weight: 700; font-weight: 700;
text-align: center; text-align: center;
text-decoration: none; text-decoration: none;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 1.3px; letter-spacing: 1.3px;
border-radius: 1000px; border-radius: 1000px;
box-shadow: 12px 12px 24px rgba(79, 209, 197, .64); box-shadow: 12px 12px 24px rgba(79, 209, 197, .64);
} }

15
front/src/index.d.ts vendored
View File

@ -14,6 +14,9 @@ interface scene3D {
name: string name: string
min_distance: number min_distance: number
max_distance: number max_distance: number
hdr_gainmap?: string
hdr_json?: string
hdr_webp?: string
elements: element3DType[] elements: element3DType[]
} }
interface element3DType { interface element3DType {
@ -40,12 +43,20 @@ interface clickableAreaType {
target_name?: string target_name?: string
} }
interface PromoSidebarData { interface PromoSidebarData {
title: string title?: string
description: string description?: string
target?: string target?: string
target_name?: string target_name?: string
} }
interface PromoSidebar extends PromoSidebarData { interface PromoSidebar extends PromoSidebarData {
loading: boolean loading: boolean
is_open: boolean is_open: boolean
}
interface PromoScene {
id: number
model_file: string
name: string
description: string
parent?: number
is_enabled: boolean
} }

View File

@ -0,0 +1,12 @@
import { defineStore } from 'pinia'
export const usePromoScene = defineStore('promo_scene', {
state: () => {
return { list: [] as PromoScene[] }
},
actions: {
setData(data: PromoScene[]) {
this.list = data
}
}
})

View File

@ -3,10 +3,10 @@ import { defineStore } from 'pinia'
export const usePromoSidebar = defineStore('promo_sidebar', { export const usePromoSidebar = defineStore('promo_sidebar', {
state: () => { state: () => {
return { return {
title: 'Сайдбар', title: undefined,
description: 'Описание', description: undefined,
target: undefined, target: undefined,
target_name: 'Перейти дальше', target_name: undefined,
loading: true, loading: true,
is_open: false is_open: false
} as PromoSidebar } as PromoSidebar