This commit is contained in:
Kseninia Mikhaylova 2025-02-10 16:56:01 +03:00
parent 5ec685374d
commit b84241d8de
5 changed files with 321 additions and 69 deletions

View File

@ -1,15 +1,152 @@
from django.contrib import admin
from django.db import models
from import_export.admin import ImportExportModelAdmin
from .models import ClickableArea, Element3D, Environment, Scene3D
class Scene3DAdmin(ImportExportModelAdmin, admin.ModelAdmin):
filter_horizontal = ("elements",)
# Админ-класс для Scene3D
@admin.register(Scene3D)
class Scene3DAdmin(admin.ModelAdmin):
list_display = ('name', 'description', 'element_count')
search_fields = ('name', 'description')
autocomplete_fields = ('elements',) # Используем автодополнение
class ImportExportBtnAdmin(ImportExportModelAdmin, admin.ModelAdmin):
pass
# Метод для подсчета количества связанных элементов
@admin.display(description='Количество элементов')
def element_count(self, obj):
return obj.elements.count()
admin.site.register(Environment, ImportExportBtnAdmin)
admin.site.register(Scene3D, Scene3DAdmin)
admin.site.register(Element3D, ImportExportBtnAdmin)
admin.site.register(ClickableArea, ImportExportBtnAdmin)
@admin.register(Environment)
class EnvironmentAdmin(admin.ModelAdmin):
list_display = ("id", "clear_color_display", "clear_color_to_display", "file_count")
list_filter = ("clear_color", "clear_color_to")
search_fields = ("id",)
readonly_fields = ("file_preview",)
# Метод для отображения цвета clear_color
@admin.display(description="Цвет очистки (начальный)", empty_value="-")
def clear_color_display(self, obj):
if obj.clear_color:
return f'<div style="background-color:{obj.clear_color}; width:50px; height:15px;"></div>'
return "-"
clear_color_display.allow_tags = True
# Метод для отображения цвета clear_color_to
@admin.display(description="Цвет очистки (конечный)", empty_value="-")
def clear_color_to_display(self, obj):
if obj.clear_color_to:
return f'<div style="background-color:{obj.clear_color_to}; width:50px; height:15px;"></div>'
return "-"
clear_color_to_display.allow_tags = True
# Метод для подсчета количества загруженных файлов
@admin.display(description="Количество файлов", empty_value="0")
def file_count(self, obj):
return sum(
bool(getattr(obj, field.name))
for field in obj._meta.fields
if isinstance(field, models.FileField)
)
# Метод для предпросмотра файлов
@admin.display(description="Предпросмотр файлов")
def file_preview(self, obj):
preview_html = []
if obj.hdr_gainmap:
preview_html.append(
f'<a href="{obj.hdr_gainmap.url}" target="_blank">Просмотр Gainmap</a>'
)
if obj.hdr_json:
preview_html.append(
f'<a href="{obj.hdr_json.url}" target="_blank">Просмотр JSON</a>'
)
if obj.hdr_webp:
preview_html.append(
f'<a href="{obj.hdr_webp.url}" target="_blank">Просмотр WEBP</a>'
)
return "<br>".join(preview_html) or "-"
file_preview.allow_tags = True
@admin.register(Element3D)
class Element3DAdmin(admin.ModelAdmin):
list_display = (
"name",
"model_file_display",
"is_enabled",
"can_not_disable",
"position_display",
"scenes_count",
)
list_filter = ("is_enabled", "can_not_disable")
search_fields = ("name", "description")
readonly_fields = ("model_file_preview",)
# Метод для отображения пути к файлу модели
@admin.display(description="Файл модели", empty_value="-")
def model_file_display(self, obj):
return obj.model_file.name if obj.model_file else "-"
# Метод для отображения позиции элемента
@admin.display(description="Позиция (X, Y, Z)")
def position_display(self, obj):
return f"({obj.x_pos}, {obj.y_pos}, {obj.z_pos})"
# Метод для подсчета количества связанных сцен
@admin.display(description="Количество сцен", empty_value="0")
def scenes_count(self, obj):
return obj.scene3d_set.count()
# Метод для предпросмотра файла модели
@admin.display(description="Предпросмотр файла")
def model_file_preview(self, obj):
if obj.model_file:
return f'<a href="{obj.model_file.url}" target="_blank">Просмотр файла</a>'
return "-"
model_file_preview.allow_tags = True
# Декоратор для регистрации модели ClickableArea
@admin.register(ClickableArea)
class ClickableAreaAdmin(admin.ModelAdmin):
list_display = (
"name",
"description_shortened",
"source_element",
"target_scene",
"object_name",
)
list_filter = ("source__name", "target__name")
search_fields = (
"name",
"description",
"object_name",
"source__name",
"target__name",
)
autocomplete_fields = ("source", "target")
# Метод для отображения укороченного описания
@admin.display(description="Описание", empty_value="-")
def description_shortened(self, obj):
return (
(obj.description[:50] + "...")
if len(obj.description) > 50
else obj.description
)
# Метод для отображения связанного элемента (source)
@admin.display(description="Элемент 3D", empty_value="-")
def source_element(self, obj):
return obj.source.name if obj.source else "-"
# Метод для отображения связанной сцены (target)
@admin.display(description="Сцена", empty_value="-")
def target_scene(self, obj):
return obj.target.name if obj.target else "-"

View File

@ -1,23 +1,69 @@
from django.core.management.base import BaseCommand, CommandError
from django.core.files import File
import glob
import os
import glob
from django.core.management.base import BaseCommand
from django.core.files import File
import logging
from object.models import Element3D, Scene3D
logger = logging.getLogger("root")
from object.models import Scene3D, Element3D
logger = logging.getLogger(__name__) # Инициализация логгера
class Command(BaseCommand):
def handle(self, *args, **options):
root_directory = "object/management/commands/data"
help = "Import GLB files into the database and associate them with a specific Scene3D object."
def handle(self, *args, **options):
logger.info("Starting the import process...")
# Определение корневой директории
base_dir = os.path.dirname(os.path.abspath(__file__)) # Получаем путь к текущему файлу команды
root_directory = os.path.join(base_dir, "../../data") # Переходим на уровень выше и указываем папку data
root_directory = os.path.normpath(root_directory) # Нормализуем путь для кроссплатформенности
if not os.path.exists(root_directory):
logger.error(f"The directory {root_directory} does not exist.")
return
logger.info(f"Using root directory: {root_directory}")
# Поиск файлов .glb
try:
files = glob.glob("*.glb", recursive=True, root_dir=root_directory)
hv = Scene3D.objects.get(id=48)
if not files:
logger.warning("No .glb files found in the specified directory.")
return
logger.info(f"Found {len(files)} .glb files: {', '.join(files)}")
except Exception as e:
logger.error(f"Error while searching for .glb files: {e}")
return
# Получение объекта Scene3D
try:
hv = Scene3D.objects.get(id=56)
logger.info(f"Retrieved Scene3D object with ID 56: {hv}")
except Scene3D.DoesNotExist:
logger.error("Scene3D object with ID 56 does not exist.")
return
except Exception as e:
logger.error(f"Error while retrieving Scene3D object: {e}")
return
# Обработка каждого файла
for f in files:
with open(os.path.join(root_directory, f), 'rb') as file:
file_path = os.path.join(root_directory, f)
logger.info(f"Processing file: {file_path}")
try:
with open(file_path, 'rb') as file:
el = Element3D(name=f)
el.model_file = File(file, f)
el.save()
logger.info(el)
hv.elements.add(el)
logger.info(hv.elements.count())
logger.info(f"Successfully saved Element3D object: {el}")
hv.elements.add(el)
logger.info(f"Added Element3D {el} to Scene3D {hv}. Total elements count: {hv.elements.count()}")
except FileNotFoundError:
logger.error(f"File not found: {file_path}")
except Exception as e:
logger.error(f"Error while processing file {f}: {e}")
logger.info("Import process completed successfully.")

View File

@ -15,50 +15,6 @@ def group_based_upload_to(instance, filename):
)
class Environment(models.Model):
clear_color = ColorField(blank=True, null=True)
clear_color_to = ColorField(blank=True, null=True)
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)
class Element3D(models.Model):
model_file = models.FileField(upload_to=group_based_upload_to)
name = models.CharField(max_length=255)
description = models.TextField(blank=True, null=True)
is_enabled = models.BooleanField(default=True)
can_not_disable = models.BooleanField(default=False)
x_pos = models.IntegerField(default=0)
y_pos = models.IntegerField(default=0)
z_pos = models.IntegerField(default=0)
def __str__(self):
return self.name
class Scene3D(models.Model):
name = models.CharField(max_length=120)
description = models.TextField(blank=True, null=True)
elements = models.ManyToManyField(Element3D)
env = models.ForeignKey(Environment, models.RESTRICT, blank=True, null=True)
min_distance = models.IntegerField(
default=10,
validators=[MinValueValidator(1), MaxValueValidator(600)],
)
max_distance = models.IntegerField(
default=20,
validators=[MinValueValidator(2), MaxValueValidator(1000)],
)
def __str__(self):
return self.name
def maximum_size_validator(image):
max_width = 512
max_height = 512
@ -68,6 +24,117 @@ def maximum_size_validator(image):
raise ValidationError("Height or Width is larger than what is allowed")
class Environment(models.Model):
clear_color = ColorField(
blank=True,
null=True,
verbose_name="Цвет очистки (начальный)"
)
clear_color_to = ColorField(
blank=True,
null=True,
verbose_name="Цвет очистки (конечный)"
)
hdr_gainmap = models.FileField(
upload_to=group_based_upload_to,
blank=True,
null=True,
verbose_name="HDR Gainmap файл"
)
hdr_json = models.FileField(
upload_to=group_based_upload_to,
blank=True,
null=True,
verbose_name="HDR JSON файл"
)
hdr_webp = models.FileField(
upload_to=group_based_upload_to,
blank=True,
null=True,
verbose_name="HDR WEBP файл"
)
def __str__(self):
return f"Среда #{self.id}"
class Meta:
verbose_name = "Среда"
verbose_name_plural = "Среды"
class Element3D(models.Model):
model_file = models.FileField(
upload_to=group_based_upload_to,
verbose_name="Файл модели"
)
name = models.CharField(
max_length=255,
verbose_name="Название элемента"
)
description = models.TextField(
blank=True,
null=True,
verbose_name="Описание элемента"
)
is_enabled = models.BooleanField(
default=True,
verbose_name="Включен"
)
can_not_disable = models.BooleanField(
default=False,
verbose_name="Невозможно отключить"
)
x_pos = models.IntegerField(
default=0,
verbose_name="Позиция X"
)
y_pos = models.IntegerField(
default=0,
verbose_name="Позиция Y"
)
z_pos = models.IntegerField(
default=0,
verbose_name="Позиция Z"
)
def __str__(self):
return self.name
class Meta:
verbose_name = "Элемент 3D"
verbose_name_plural = "Элементы 3D"
class Scene3D(models.Model):
name = models.CharField(max_length=120, verbose_name="Название сцены")
description = models.TextField(blank=True, null=True, verbose_name="Описание сцены")
elements = models.ManyToManyField("Element3D", verbose_name="Элементы 3D")
env = models.ForeignKey(
"Environment",
on_delete=models.RESTRICT,
blank=True,
null=True,
verbose_name="Среда",
)
min_distance = models.IntegerField(
default=10,
validators=[MinValueValidator(1), MaxValueValidator(600)],
verbose_name="Минимальное расстояние",
)
max_distance = models.IntegerField(
default=20,
validators=[MinValueValidator(2), MaxValueValidator(1000)],
verbose_name="Максимальное расстояние",
)
def __str__(self):
return self.name
class Meta:
verbose_name = "Сцена 3D"
verbose_name_plural = "Сцены 3D"
class ClickableArea(models.Model):
name = models.CharField(
"Название",

View File

@ -807,6 +807,7 @@
"version": "3.9.0",
"resolved": "https://registry.npmjs.org/@tresjs/cientos/-/cientos-3.9.0.tgz",
"integrity": "sha512-LAtMveKlecKvWh7TNWdwEs3nQUYMLqz9DZy0YhSZ6OVTfL2vevx2K4sH9744UME8OedUf4fkFFkX4OWQRHaDRQ==",
"license": "MIT",
"dependencies": {
"@tresjs/core": "3.9.0",
"@vueuse/core": "^10.9.0",

View File

@ -130,6 +130,7 @@ const loadModels = async () => {
item.modelFile = loaded_scene
item.id = element.id
item.name = element.name
console.log(item)
if (!element.is_enabled) {
item.modelFile.visible = false