10 Commits

Author SHA1 Message Date
fe12015d0d Remove stale changes. 2026-02-14 20:40:25 -05:00
9a87c1df10 Reapply "ToolBar buttons to add and remove objects. (#18)"
This reverts commit 0c16b7d879.
2026-02-14 17:31:17 -05:00
9397e3117b Fix linting CI. 2026-02-14 17:29:42 -05:00
a2f849ec72 Install NSIS for windows pt2. 2026-02-14 16:33:18 -05:00
67fcf4619b Update assimp to fix Mac CI.
Zlib no longer uses fdopen.
Zlib previously had redefined fdopen, causing conflicts in apple things.
Zlib has since updated to remove these defines, fixing the issue.
2026-02-14 16:33:10 -05:00
6d9689720d Remove deprecated GLSL. 2026-02-14 14:30:56 -05:00
4f76d37ea0 Install NSIS on windows. 2026-02-14 14:08:35 -05:00
0770b0ea65 Update clang-format action. 2026-02-14 13:54:30 -05:00
0c16b7d879 Revert "ToolBar buttons to add and remove objects. (#18)"
This reverts commit ed604eb655.
2026-02-14 13:41:18 -05:00
ed604eb655 ToolBar buttons to add and remove objects. (#18) 2025-04-12 16:53:40 +00:00
13 changed files with 215 additions and 31 deletions

View File

@@ -43,13 +43,16 @@ jobs:
with:
version: ${{ env.QT_VERSION }}
# Windows
- name: Chocolatey Action
- name: Install pkgconfiglite
if: matrix.os == 'windows-latest'
uses: crazy-max/ghaction-chocolatey@v2
with:
args: install pkgconfiglite --checksum e87b5ea3c9142256af60f2d5b917aa63b571e6a0 --checksum-type sha1
- name: Install nsis
if: matrix.os == 'windows-latest'
uses: crazy-max/ghaction-chocolatey@v2
with:
args: install nsis
- name: Install Debian packaging dependencies
if: matrix.os == 'ubuntu-latest'
@@ -191,6 +194,11 @@ jobs:
uses: crazy-max/ghaction-chocolatey@v2
with:
args: install pkgconfiglite --checksum e87b5ea3c9142256af60f2d5b917aa63b571e6a0 --checksum-type sha1
- name: Install nsis
if: matrix.os == 'windows-latest'
uses: crazy-max/ghaction-chocolatey@v2
with:
args: install nsis
- name: Configure Qtk Library
shell: bash

View File

@@ -39,7 +39,7 @@ jobs:
# Check the entire repo for source files to tidy
files-changed-only: false
# Ignore qtk build and external assimp directories
ignore: '.github|build|extern/assimp/assimp'
ignore: '.github|build|extern'
# Point to compile_commands.json produced by build
database: 'build'
# Use thread comments as feedback
@@ -61,8 +61,12 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: clang-format Check
uses: jidicula/clang-format-action@v4.9.0
- uses: cpp-linter/cpp-linter-action@v2
with:
clang-format-version: '18'
check-path: ${{ matrix.path }}
version: '18'
style: 'file'
tidy-checks: ''
files-changed-only: false
ignore: '.github|build|extern'
extensions: 'cpp,h'
files: ${{ matrix.path }}

View File

@@ -157,8 +157,15 @@ list(APPEND VAR_NAMES QT6_INSTALL_PLUGINS)
# Find Assimp.
if(QTK_SUBMODULES)
# Required to statically link.
add_compile_options(-fPIC)
if(APPLE)
# Avoid zlib redefining fdopen, causing build failures in apple clang.
# https://github.com/assimp/assimp/issues/6118
add_compile_definitions(-Dfdopen=fdopen)
endif()
if(NOT WIN32)
# Required to statically link.
add_compile_options(-fPIC)
endif()
set(BUILD_SHARED_LIBS OFF CACHE STRING "Build static assimp libs" FORCE)
set(ASSIMP_BUILD_ZLIB ON CACHE STRING "Build Zlib with assimp." FORCE)
set(
@@ -175,6 +182,14 @@ if(QTK_SUBMODULES)
"${CMAKE_CURRENT_SOURCE_DIR}/extern/assimp/assimp/"
EXCLUDE_FROM_ALL
)
install(
TARGETS assimp zlibstatic
EXPORT qtk_export
COMPONENT qtk
ARCHIVE DESTINATION lib
LIBRARY DESTINATION lib
RUNTIME DESTINATION bin
)
else()
find_package(assimp REQUIRED)
endif()

View File

@@ -90,7 +90,7 @@ The Ubuntu apt repositories contain all the packages we need to build all target
To build Qtk desktop application with the scene in the screenshots below run the following commands.
```bash
sudo apt update && sudo apt install cmake build-essential git ccache libxkbcommon-dev libassimp-dev qt6-base-dev qt6-tools-dev
sudo apt update && sudo apt install cmake build-essential git ccache libxkbcommon-dev libassimp-dev qt6-base-dev qt6-tools-dev zlib1g-dev
cmake -DQTK_GUI_SCENE=ON -B build
cmake --build build
./build/bin/qtk_gui

View File

@@ -48,6 +48,16 @@ MainWindow::MainWindow(QWidget * parent) : QMainWindow(parent)
&Qtk::ToolBox::updateFocus);
}
connect(ui_->actionDelete_Object,
&QAction::triggered,
this,
&MainWindow::deleteObject);
connect(ui_->actionLoad_Model,
&QAction::triggered,
this,
&MainWindow::loadObject);
// TODO: Fix / use MainWindow in Qt Designer to add these dock widgets.
// For now we will add them manually, but we should be able to do this in the
// designer. At the moment if you edit the UI in designer the dock widget
@@ -104,6 +114,26 @@ void MainWindow::refreshScene(const QString & sceneName)
ui_->qtk__TreeView->updateView(getQtkWidget()->getScene());
}
void MainWindow::deleteObject()
{
if (auto object = ui_->qtk__ToolBox->getObjectFocus(); object != Q_NULLPTR) {
auto scene = getQtkWidget()->getScene();
switch (object->getType()) {
case Qtk::Object::Type::QTK_MESH:
scene->removeObject(dynamic_cast<Qtk::MeshRenderer *>(object));
ui_->qtk__ToolBox->clearFocus();
break;
case Qtk::Object::Type::QTK_MODEL:
scene->removeObject(dynamic_cast<Qtk::Model *>(object));
ui_->qtk__ToolBox->clearFocus();
break;
default:
qDebug() << "Failed to delete model with invalid type";
break;
}
}
}
void MainWindow::setScene(Qtk::Scene * scene)
{
connect(scene,

View File

@@ -11,8 +11,8 @@
#include <unordered_map>
#include <QFileDialog>
#include <QMainWindow>
#include <QPlainTextEdit>
#include "designer-plugins/debugconsole.h"
@@ -144,6 +144,23 @@ class MainWindow : public QMainWindow
*/
void refreshScene(const QString & sceneName);
/**
* Opens a QFileDialog for selecting an object file to load into the scene.
*/
void loadObject()
{
const QUrl file = QFileDialog::getOpenFileName(
this, tr("Load Model"), QDir::homePath(), tr("Object Files (*.obj)"));
getQtkWidget()->getScene()->loadModel(file.fileName().replace(".obj", ""),
file.toString());
}
/**
* Deletes the currently selected object from the scene.
*/
void deleteObject();
private:
/***************************************************************************
* Private Members

View File

@@ -223,7 +223,7 @@
</action>
<action name="actionLoad_Model">
<property name="enabled">
<bool>false</bool>
<bool>true</bool>
</property>
<property name="icon">
<iconset>
@@ -238,7 +238,7 @@
</action>
<action name="actionDelete_Object">
<property name="enabled">
<bool>false</bool>
<bool>true</bool>
</property>
<property name="icon">
<iconset>

View File

@@ -20,7 +20,7 @@ ToolBox::ToolBox(QWidget * parent) :
QDockWidget(parent), objectDetails_(this), transformPanel_(this),
scalePanel_(this), vertex_(this, "Vertex Shader:"),
fragment_(this, "Fragment Shader:"), properiesForm_(new QFormLayout),
shaderForm_(new QFormLayout), ui(new Ui::ToolBox)
shaderForm_(new QFormLayout), ui(new Ui::ToolBox), objectFocus_(Q_NULLPTR)
{
ui->setupUi(this);
setMinimumWidth(350);
@@ -44,10 +44,24 @@ void ToolBox::updateFocus(const QString & name)
{
auto object =
QtkWidget::mWidgetManager.get_widget()->getScene()->getObject(name);
if (object != Q_NULLPTR) {
refreshProperties(object);
refreshShaders(object);
// If we can't find the object show a warning.
if (object == Q_NULLPTR) {
qDebug() << "Failed to find selected object: " << name
<< "; Clearing object panels.";
}
// We should still pass the nullptr here if we failed to find the object
// above.
objectFocus_ = object;
refreshProperties(object);
refreshShaders(object);
}
void ToolBox::clearFocus()
{
objectFocus_ = Q_NULLPTR;
refreshProperties(objectFocus_);
refreshShaders(objectFocus_);
}
ToolBox::SpinBox3D::SpinBox3D(QWidget * parent, const char * l) :
@@ -70,6 +84,13 @@ void ToolBox::SpinBox::disconnect() const
void ToolBox::TransformPanel::setObject(const Qtk::Object * object)
{
// Zero the panel contents if there is no object selected.
if (object == Q_NULLPTR) {
spinBox3D.clear();
return;
}
// Reconnect translation panel controls to the new object.
const std::vector binds = {&Object::setTranslationX,
&Object::setTranslationY,
@@ -90,6 +111,12 @@ void ToolBox::TransformPanel::setObject(const Qtk::Object * object)
void ToolBox::ScalePanel::setObject(const Qtk::Object * object)
{
// Zero the panel contents if there is no object selected.
if (object == Q_NULLPTR) {
spinBox3D.clear();
return;
}
// Reconnect scale panel controls to the new object.
const std::vector binds = {
&Object::setScaleX, &Object::setScaleY, &Object::setScaleZ};
@@ -123,8 +150,21 @@ void ToolBox::refreshProperties(const Object * object)
void ToolBox::refreshShaders(const Object * object)
{
// Zero the panel contents if there is no object selected.
if (object == Q_NULLPTR) {
vertex_.clear();
fragment_.clear();
return;
}
vertex_.path.setValue(object->getVertexShader().c_str());
vertex_.editor->setText(object->getVertexShaderSourceCode().c_str());
fragment_.path.setValue(object->getFragmentShader().c_str());
fragment_.editor->setText(object->getFragmentShaderSourceCode().c_str());
}
void ToolBox::refresh(const Object * object)
{
refreshProperties(object);
refreshShaders(object);
}

View File

@@ -18,8 +18,8 @@
#include <QLabel>
#include <QTextEdit>
#include "qtk/object.h"
#include "qtk/scene.h"
namespace Ui
{
@@ -45,8 +45,13 @@ namespace Qtk
void refreshShaders(const Object * object);
void refresh(const Object * object);
void updateFocus(const QString & name);
[[nodiscard]] Object * getObjectFocus() const { return objectFocus_; }
void clearFocus();
private:
/*************************************************************************
@@ -65,9 +70,9 @@ namespace Qtk
{
}
void setValue(const char * v) { value->setText(tr(v)); }
void setValue(const char * v) const { value->setText(tr(v)); }
void setItem(const char * l, const char * v)
void setItem(const char * l, const char * v) const
{
label->setText(tr(l));
value->setText(tr(v));
@@ -84,8 +89,14 @@ namespace Qtk
}
/// Refresh to display the new object's details
void setObject(const Qtk::Object * object)
void setObject(const Qtk::Object * object) const
{
// Zero contents if there is no object selected.
if (object == Q_NULLPTR) {
name.setValue("");
objectType.setValue("No object selected");
return;
}
name.setItem("Name:", object->getName().toStdString().c_str());
objectType.setItem(
"Type:",
@@ -119,6 +130,15 @@ namespace Qtk
/// Assigning a QWidget to a QLayout also ensures Qt will clean up.
explicit SpinBox3D(QWidget * parent, const char * l = "SpinBox3D:");
/// Zero the SpinBox3D display.
void clear()
{
for (auto & field : fields) {
field->disconnect();
field->spinBox->setValue(0.0);
}
}
/// The main layout for the SpinBox3D widget.
QHBoxLayout * layout;
@@ -176,6 +196,13 @@ namespace Qtk
layout->addWidget(editor);
}
/// Zero the ShaderView display.
void clear() const
{
path.setValue("");
editor->setText("");
}
/// The main layout for the ShaderView widget.
QVBoxLayout * layout;
@@ -190,6 +217,8 @@ namespace Qtk
QFormLayout * properiesForm_;
QFormLayout * shaderForm_;
Object * objectFocus_ {};
Ui::ToolBox * ui;
};
} // namespace Qtk

View File

@@ -55,6 +55,34 @@ template <> Model * Scene::addObject(Model * object)
return object;
}
template <> void Scene::removeObject(MeshRenderer * object)
{
auto it = std::find(mMeshes.begin(), mMeshes.end(), object);
if (it == mMeshes.end()) {
qDebug() << "[Scene::removeObject]: Failed to remove object: "
<< object->getName() << " (" << object << ")";
return;
}
--mObjectCount[object->getName()];
mMeshes.erase(it);
emit sceneUpdated(mSceneName);
}
template <> void Scene::removeObject(Model * object)
{
auto it = std::find(mModels.begin(), mModels.end(), object);
if (it == mModels.end()) {
qDebug() << "[Scene::removeObject]: Failed to remove object: "
<< object->getName() << " (" << object << ")";
return;
}
--mObjectCount[object->getName()];
mModels.erase(it);
emit sceneUpdated(mSceneName);
}
void Scene::draw()
{
if (!mInit) {

View File

@@ -85,16 +85,16 @@ namespace Qtk
void loadModel(const QUrl & url)
{
auto fileName = url.fileName().replace(".obj", "").toStdString();
auto filePath = url.toLocalFile().toStdString();
auto fileName = url.fileName().replace(".obj", "");
auto filePath = url.toLocalFile();
loadModel(fileName, filePath);
}
void loadModel(const std::string & name, const std::string & path)
void loadModel(const QString & name, const QString & path)
{
// Add the dropped model to the load queue.
// This is consumed during rendering of the scene if not empty.
mModelLoadQueue.emplace(name, path);
mModelLoadQueue.emplace(name.toStdString(), path.toStdString());
}
/*************************************************************************
@@ -182,8 +182,9 @@ namespace Qtk
/**
* Adds objects to the scene.
* This template provides explicit specializations for valid types.
* Adding any object other than these types will cause errors.
* This template provides explicit specializations for the valid types:
* MeshRenderer, Model
* Any other object type will cause errors.
* TODO: Refactor to use Object base class container for scene objects.
*
* If creating a new object type for a scene, it must inherit Qtk::Object
@@ -194,6 +195,17 @@ namespace Qtk
*/
template <typename T> T * addObject(T * object);
/**
* Removes an object from the scene.
* This template provides explicit specializations for the valid types:
* MeshRenderer, Model
* Any other object type will cause errors.
* TODO: Refactor to use Object base class container for scene objects.
*
* @param object Pointer to the object to remove from the scene.
*/
template <typename T> void removeObject(T * object);
/**
* @param name The name to use for this scene.
*/

View File

@@ -109,11 +109,12 @@ void main()
#version 330
uniform samplerCube uTexture;
varying vec3 vTexCoord;
in vec3 vTexCoord;
out vec4 FragColor;
void main()
{
gl_FragColor = texture(uTexture, vTexCoord);
FragColor = texture(uTexture, vTexCoord);
}
)"