diff --git a/back/object/admin.py b/back/object/admin.py index c39d1a8..625f10b 100644 --- a/back/object/admin.py +++ b/back/object/admin.py @@ -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'
' + 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'
' + 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'Просмотр Gainmap' + ) + if obj.hdr_json: + preview_html.append( + f'Просмотр JSON' + ) + if obj.hdr_webp: + preview_html.append( + f'Просмотр WEBP' + ) + return "
".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'Просмотр файла' + 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 "-" diff --git a/back/object/management/commands/glb_import.py b/back/object/management/commands/glb_import.py index 8b6e923..03a0fc8 100644 --- a/back/object/management/commands/glb_import.py +++ b/back/object/management/commands/glb_import.py @@ -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): + help = "Import GLB files into the database and associate them with a specific Scene3D object." + def handle(self, *args, **options): - root_directory = "object/management/commands/data" - - files = glob.glob("*.glb", recursive=True, root_dir=root_directory) - hv = Scene3D.objects.get(id=48) + 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) + 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: - el = Element3D(name=f) - el.model_file = File(file, f) - el.save() - logger.info(el) - hv.elements.add(el) - logger.info(hv.elements.count()) - \ No newline at end of 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(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.") \ No newline at end of file diff --git a/back/object/models.py b/back/object/models.py index e80e3e0..2532e26 100644 --- a/back/object/models.py +++ b/back/object/models.py @@ -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( "Название", diff --git a/front/package-lock.json b/front/package-lock.json index dd8b980..6649ae5 100644 --- a/front/package-lock.json +++ b/front/package-lock.json @@ -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", diff --git a/front/src/components/Promo/load_models.vue b/front/src/components/Promo/load_models.vue index 782b8ed..dcfdc91 100644 --- a/front/src/components/Promo/load_models.vue +++ b/front/src/components/Promo/load_models.vue @@ -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