TagStudioDev / TagStudio

A User-Focused Photo & File Management System
https://docs.tagstud.io/
GNU General Public License v3.0
5.17k stars 371 forks source link

[Feature Request]: Render STL thumbnails #351

Open CJones-Optics opened 2 months ago

CJones-Optics commented 2 months ago

Checklist

Description

It would be awesome eventually to get thumbnails for STL files. The tagging system works great for a 3D printing workflow. (And I imagine other CAD workflows)

Solution

I found a github repo for rendering STL file thumbnails for use in Windows or Gnome like DE: REPO

Some kind of fork of this could be used. It is all written in rust which I am no expert in.

Alternatives

Issue #97 may provide an equivalent method of getting STL files. I am not sure which implmenetation would be easier.

I don't know any Rust, and am not very good at software in general lmao. I might have a play with the repo and see if I can implement it, but opening the issue in case anyone with more experiance wants to tackle it.,

thibmaek commented 2 months ago

Could act similar to manyfold for example. TagStudio would be a great alternative to manage a large library of 3D printing files (STL, OBJ, 3MF, SCAD, F3D) without the complexity of selfhosting or resource heavy usage of manyfold.

macOS renders STL thumbs natively, is there no way to tap into QuickLook (what's used for the thumbnail) here? image

JonatanNevo commented 2 months ago

I found a python library that handles rendering of 3D objects and scenes, open3d The issue is that it does not support python 3.12 yet

@CyanVoxel what python version is the project pinned to? if we can use 3.11 I can add the STL implementation (and potentially more 3D file formats as well)

CyanVoxel commented 2 months ago

I found a python library that handles rendering of 3D objects and scenes, open3d The issue is that it does not support python 3.12 yet

@CyanVoxel what python version is the project pinned to? if we can use 3.11 I can add the STL implementation (and potentially more 3D file formats as well)

It's pinned to 3.11 and 3.12, with 3.12 being the sticking point - I'm also aware open3d and have been hoping to use it for rendering here once they finally bring support for 3.12. I believe isl-org/Open3D#6433 is the issue over there to watch for, with isl-org/Open3D#6717 being a PR for it that looks to be pretty far along. I've been keeping tabs on it every now and again for a while, but fingers crossed that 3.12 support isn't too far off

JonatanNevo commented 2 months ago

I have been looking into rendering it myself using OpenGL support in QT like this repo but that requires the usage of QWindow instead of QWidget for the PreviewPanel

I don't know QT enough to know the ramifications of such a change, and if it's even possible.

CyanVoxel commented 2 months ago

I have been looking into rendering it myself using OpenGL support in QT like this repo but that requires the usage of QWindow instead of QWidget for the PreviewPanel

I don't know QT enough to know the ramifications of such a change, and if it's even possible.

I'm also unfortunately not familiar enough with Qt to know the consequences off hand, but I feel it would either be a quick "well this doesn't work it's a separate window" or "okay this'll work I just need to shuffle around some object types". It's up to you if you wanna take a stab at it or just wait for open3d to roll around to 3.12. I couldn't exactly tell you the pros and cons of either implementation, but I feel open3d would be easier to implement for and expand upon.

JonatanNevo commented 2 months ago

I'll take a stab at it, Using OpenGL with QT can allow advanced stuff such as interactive preview (rotating the model and such) which, from what I saw, will be harder using open3d

rfletchr commented 2 months ago

It shouldn't be an issue. You should be able to take their QGLControllerWidget from engine.py and add it to a layout.

https://github.com/alonrubintec/3DViewer/blob/d30bc2da629944b715a7823515fab56fda5b7391/engine.py#L28

I would add though that PySide has a module called QT3D which provides all the APIs for doing this stuff without adding a requirement on a third party project.

https://doc.qt.io/qt-6/qt3d-simple-cpp-example.html

rfletchr commented 2 months ago

Heres the python version of the above example.

# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

"""PySide6 port of the qt3d/simple-cpp example from Qt v5.x"""

import sys
from PySide6.QtCore import (Property, QObject, QPropertyAnimation, Signal)
from PySide6.QtGui import (QGuiApplication, QMatrix4x4, QQuaternion, QVector3D)
from PySide6.Qt3DCore import (Qt3DCore)
from PySide6.Qt3DExtras import (Qt3DExtras)

class OrbitTransformController(QObject):
    def __init__(self, parent):
        super().__init__(parent)
        self._target = None
        self._matrix = QMatrix4x4()
        self._radius = 1
        self._angle = 0

    def setTarget(self, t):
        self._target = t

    def getTarget(self):
        return self._target

    def setRadius(self, radius):
        if self._radius != radius:
            self._radius = radius
            self.updateMatrix()
            self.radiusChanged.emit()

    def getRadius(self):
        return self._radius

    def setAngle(self, angle):
        if self._angle != angle:
            self._angle = angle
            self.updateMatrix()
            self.angleChanged.emit()

    def getAngle(self):
        return self._angle

    def updateMatrix(self):
        self._matrix.setToIdentity()
        self._matrix.rotate(self._angle, QVector3D(0, 1, 0))
        self._matrix.translate(self._radius, 0, 0)
        if self._target is not None:
            self._target.setMatrix(self._matrix)

    angleChanged = Signal()
    radiusChanged = Signal()
    angle = Property(float, getAngle, setAngle, notify=angleChanged)
    radius = Property(float, getRadius, setRadius, notify=radiusChanged)

class Window(Qt3DExtras.Qt3DWindow):
    def __init__(self):
        super().__init__()

        # Camera
        self.camera().lens().setPerspectiveProjection(45, 16 / 9, 0.1, 1000)
        self.camera().setPosition(QVector3D(0, 0, 40))
        self.camera().setViewCenter(QVector3D(0, 0, 0))

        # For camera controls
        self.createScene()
        self.camController = Qt3DExtras.QOrbitCameraController(self.rootEntity)
        self.camController.setLinearSpeed(50)
        self.camController.setLookSpeed(180)
        self.camController.setCamera(self.camera())

        self.setRootEntity(self.rootEntity)

    def createScene(self):
        # Root entity
        self.rootEntity = Qt3DCore.QEntity()

        # Material
        self.material = Qt3DExtras.QPhongMaterial(self.rootEntity)

        # Torus
        self.torusEntity = Qt3DCore.QEntity(self.rootEntity)
        self.torusMesh = Qt3DExtras.QTorusMesh()
        self.torusMesh.setRadius(5)
        self.torusMesh.setMinorRadius(1)
        self.torusMesh.setRings(100)
        self.torusMesh.setSlices(20)

        self.torusTransform = Qt3DCore.QTransform()
        self.torusTransform.setScale3D(QVector3D(1.5, 1, 0.5))
        self.torusTransform.setRotation(QQuaternion.fromAxisAndAngle(QVector3D(1, 0, 0), 45))

        self.torusEntity.addComponent(self.torusMesh)
        self.torusEntity.addComponent(self.torusTransform)
        self.torusEntity.addComponent(self.material)

        # Sphere
        self.sphereEntity = Qt3DCore.QEntity(self.rootEntity)
        self.sphereMesh = Qt3DExtras.QSphereMesh()
        self.sphereMesh.setRadius(3)

        self.sphereTransform = Qt3DCore.QTransform()
        self.controller = OrbitTransformController(self.sphereTransform)
        self.controller.setTarget(self.sphereTransform)
        self.controller.setRadius(20)

        self.sphereRotateTransformAnimation = QPropertyAnimation(self.sphereTransform)
        self.sphereRotateTransformAnimation.setTargetObject(self.controller)
        self.sphereRotateTransformAnimation.setPropertyName(b"angle")
        self.sphereRotateTransformAnimation.setStartValue(0)
        self.sphereRotateTransformAnimation.setEndValue(360)
        self.sphereRotateTransformAnimation.setDuration(10000)
        self.sphereRotateTransformAnimation.setLoopCount(-1)
        self.sphereRotateTransformAnimation.start()

        self.sphereEntity.addComponent(self.sphereMesh)
        self.sphereEntity.addComponent(self.sphereTransform)
        self.sphereEntity.addComponent(self.material)

if __name__ == '__main__':
    app = QGuiApplication(sys.argv)
    view = Window()
    view.show()
    sys.exit(app.exec())

If you want to generate thumbnails you'd probably need to find something which supports rendering to an off-screen surface QT has QOffscreenSurface but I'm not sure if QT3D can write to it.

JonatanNevo commented 2 months ago

Thanks @rfletchr. I'm planning on working on it on the weekend when ill have some time.

There are two things I want to tackle here. One is to generate the static images for the thumbnails and one is to generate an interactive preview for the selected file (something basic like arcball)

rfletchr commented 2 months ago

@JonatanNevo the example code I posted does everything you need for the interactive preview, did you have any luck getting it to write to an offscreen surface?

JonatanNevo commented 2 months ago

@rfletchr life happened and it seems like I won't have time to work on this in the foreseeable future Feel free to take over, I'll try to help if I can

rfletchr commented 2 months ago

@JonatanNevo I'm in a similar position myself. I use allot of QT in my work life though so I try to offer little bits and pieces when I can to try and save people hassle. I've held off of writing code for this project though as its code style doesn't run with mine and I know I'd just keep trying to change things and cause more trouble than good ;)

JonatanNevo commented 2 months ago

Then hopefully someone else picks this up, or I will in a few months when I'll have the time :)

svgPhoenix commented 1 hour ago

open3d now supports python 3.12. I don't need to be able to rotate the models in TagStudio, I just need to be able to see them at all. Without stl/3mf support, I cannot use TagStudio at all, and I badly need it to organize all the models on my NAS lol

seakrueger commented 1 hour ago

While Open3D did merge 3.12 support, they have not yet published a release that supports that version yet. Hopefully, it happens soon but until then, we'll have to wait

svgPhoenix commented 1 hour ago

oh my bad not checking if that had been released but I mostly wanted to give my 2c that implementing something is a lot more important than a fancy feature (manipulating 3d objects) that I see as probably being beyond the scope of this app.