TagStudio icon indicating copy to clipboard operation
TagStudio copied to clipboard

[Feature Request]: Render STL thumbnails

Open CJones-Optics opened this issue 1 year ago • 24 comments

Checklist

  • [X] I am using an up-to-date version.
  • [X] I have read the documentation.
  • [X] I have searched existing issues.

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.,

CJones-Optics avatar Aug 21 '24 01:08 CJones-Optics

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

thibmaek avatar Aug 22 '24 08:08 thibmaek

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)

JonatanNevo avatar Aug 25 '24 16:08 JonatanNevo

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

CyanVoxel avatar Aug 25 '24 16:08 CyanVoxel

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.

JonatanNevo avatar Aug 25 '24 16:08 JonatanNevo

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.

CyanVoxel avatar Aug 25 '24 17:08 CyanVoxel

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

JonatanNevo avatar Aug 25 '24 17:08 JonatanNevo

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 avatar Aug 27 '24 14:08 rfletchr

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.

rfletchr avatar Aug 27 '24 15:08 rfletchr

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)

JonatanNevo avatar Aug 27 '24 15:08 JonatanNevo

@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?

rfletchr avatar Sep 03 '24 16:09 rfletchr

@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

JonatanNevo avatar Sep 03 '24 18:09 JonatanNevo

@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 ;)

rfletchr avatar Sep 03 '24 18:09 rfletchr

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

JonatanNevo avatar Sep 03 '24 18:09 JonatanNevo

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

svgPhoenix avatar Nov 03 '24 02:11 svgPhoenix

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

seakrueger avatar Nov 03 '24 02:11 seakrueger

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.

svgPhoenix avatar Nov 03 '24 02:11 svgPhoenix

I will throw in my 2 cents here as well.

In terms of static 2D thumbnails, I have been using headless Blender via Python to generate 2D thumbnails of my STLs for a while; I tend to like the more polished look over the generic monotone output from manyfold, stl-thumb, etc. Not sure if that could be an alternative or optional plugin somehow. I can share my script and associated .blend files for that if anyone wishes.

An interactive preview would also be awesome, but definitely wouldn't need any fancy shading capabilities from Blender.

calculuschild avatar Nov 22 '24 16:11 calculuschild

Just wanted to chime in that I'd also love to see this happen!

I've been following the development for a while, and I think this would be absolutely PERFECT for organizing my STL library for 3D Printing.

trebory6 avatar Jan 08 '25 15:01 trebory6

Looks like (as of 8 hours ago!) Open3D finally released a build with Python 3.12 support, so we can start looking into using that for this!

CyanVoxel avatar Jan 08 '25 15:01 CyanVoxel

Nice! I'll try taking a look at it later today

JonatanNevo avatar Jan 08 '25 15:01 JonatanNevo

Looks like (as of 8 hours ago!) Open3D finally released a build with Python 3.12 support, so we can start looking into using that for this!

Woah, what a coincidence! Totally didn't plan that. Awesome.

trebory6 avatar Jan 08 '25 16:01 trebory6

@CyanVoxel is there some persistent cache system? It take quite a while to render all the images, especially because open3d does not really like multithreading so I need to render it one at a time

JonatanNevo avatar Jan 08 '25 19:01 JonatanNevo

I made a quick and dirty poc, the angle is way off and it takes a lot of time to render but you can see the WIP pr here: https://github.com/TagStudioDev/TagStudio/pull/693

JonatanNevo avatar Jan 08 '25 20:01 JonatanNevo

@CyanVoxel is there some persistent cache system? It take quite a while to render all the images, especially because open3d does not really like multithreading so I need to render it one at a time

Not at the moment, but that's the next system for v9.5 I'll be developing now that I'm not as tied up with #655

CyanVoxel avatar Jan 09 '25 02:01 CyanVoxel

Hi again. I was wondering if there was any movement on this issue or an expected release date?

I would also be happy to mess around with any test builds if that helps at all.

CJones-Optics avatar Jun 10 '25 01:06 CJones-Optics