MORPH INTO: Gear Cutter

This commit is contained in:
numzero 2026-10-17 21:27:16 +03:00
parent 068e2fb2e4
commit 42b37369f5
22 changed files with 50 additions and 223 deletions

View File

@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 4.2) cmake_minimum_required(VERSION 4.2)
project(PROJECT-NAME VERSION 1.0.0 LANGUAGES CXX) project(gear-cutter VERSION 1.0.0 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)

44
Cargo.lock generated
View File

@ -2,28 +2,6 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 4 version = 4
[[package]]
name = "PROJECT-NAME"
version = "0.1.0"
dependencies = [
"bytemuck",
"glam",
"pollster",
"wgpu",
"winit",
]
[[package]]
name = "PROJECT-NAME-impl"
version = "0.1.0"
dependencies = [
"PROJECT-NAME",
"glam",
"pollster",
"raw-window-handle",
"wgpu",
]
[[package]] [[package]]
name = "ab_glyph" name = "ab_glyph"
version = "0.2.32" version = "0.2.32"
@ -489,6 +467,28 @@ dependencies = [
"slab", "slab",
] ]
[[package]]
name = "gear-cutter"
version = "0.1.0"
dependencies = [
"bytemuck",
"glam",
"pollster",
"wgpu",
"winit",
]
[[package]]
name = "gear-cutter-impl"
version = "0.1.0"
dependencies = [
"gear-cutter",
"glam",
"pollster",
"raw-window-handle",
"wgpu",
]
[[package]] [[package]]
name = "gethostname" name = "gethostname"
version = "1.1.0" version = "1.1.0"

View File

@ -2,7 +2,7 @@
members = ["ui"] members = ["ui"]
[package] [package]
name = "PROJECT-NAME" name = "gear-cutter"
version = "0.1.0" version = "0.1.0"
edition = "2024" edition = "2024"

View File

@ -1,24 +1 @@
# Qt6+WGPU skeleton program # Gear Cutter
This example combines Qt6 GUI (C++) with WGPU rendering (Rust). It doesn’t do much of either but it’s the bridging what’s relevant.
Currently only X11 (XCB) is supported but it should be easy to extend to other platforms.
![Screenshot](screenshot.png)
## Usage
This program is intended as a starting point rather than a dependency, so just copy it.
1. Fetch this code:
* clone the repo: `git clone https://gitea.noteuclid.ru/noteuclid/qt-wgpu-skel.git`
* or, download and unpack the [tarball](https://gitea.noteuclid.ru/noteuclid/qt-wgpu-skel/archive/master.tar.gz).
2. Rename the directory to your liking
3. Run `init.sh` with the project name, like:
* `./init.sh 'My FANCY Project'`, or
* `./init.sh 'My FANCY Project' 'fancy-project'`.
Running `init.sh` without arguments prints the help.
If you want to use Qt5, change the version in both `CMakeLists.txt`, as well as the KF version.
If you don’t want to depend on KDE, remove the ECM and KF5 dependencies, as well as `KColorCombo inBackground` from the UI. You’ll need to replace `inBackground->color()` with something else though.

83
init.sh
View File

@ -1,83 +0,0 @@
#!/bin/sh
# set -x # debug
set -e # abort on errors
cd "${0%/*}" # where this script is located
usage() {
{
echo "Usage:"
echo
echo " ./init.sh 'Project Name' [ internal-name ]"
echo
echo "Project name may be anything you like, in any language, with spaces (but not other control characters), etc."
echo
echo "Technical name must be ASCII alphanumeric (a-z, 0-9, also _ and - are allowed), preferably all-lowercase. If omitted, it will be generated from the project name (but that only works if the latter is ASCII too)."
echo
echo "Running this script with no arguments shows this message"
} | fold -s 2>&1
}
name="$1"
[ -n "$name" ] || { usage; exit 0; } 2>&1
echo -n "$name" | LC_ALL=C grep '[[:cntrl:]]' && { echo 'Project name must not contain any control characters'; exit 2; } 2>&1
name="$(echo -n $name)" # normalize whitespace
techname="$2"
if [ -z "$techname" ]; then
techname=$(
echo -n $name |
LC_ALL=C tr ' ' '-' |
LC_ALL=C tr -d -c '[:alnum:]-_' |
LC_ALL=C tr '[:upper:]' '[:lower:]' |
cat
)
echo "Technical name not specified; autogenerated: \"$techname\"" >&2
fi
echo -n $techname | LC_ALL=C grep '[^0-9A-Za-z_-]' && { echo 'Technical name must be ASCII alphanumeric'; exit 2; } 2>&1
echo -n $techname | LC_ALL=C grep -q '^[A-Za-z]' || { echo 'Technical name must start with an ASCII letter'; exit 2; } 2>&1
echo -n $techname | LC_ALL=C grep -q '[A-Za-z0-9]$' || { echo 'Technical name must end with an ASCII letter or digit'; exit 2; } 2>&1
echo "Project name: $name"
echo "Internal name: $techname"
if ! [ -e .git ]; then
alias git='echo skipping:'
fi
# detach from the skeleton repository
# oldurl="$(git remote -v get-url origin)"
git remote remove origin
# remove this script from the project (but keep on disk for now)
git rm --cached init.sh
git rm -r skel-test
# replace the README with a stub
echo "# $name" >| README.md
git rm screenshot.png
# escape HTML special characters (&, <, and >), but also sed special characters (& again, \, and /)
htmlname=$(echo -n "$name" | sed 's/&/\&amp;/g; s/</\&lt;/g; s/>/\&gt;/g; s:[\\/&]:\\&:g')
sed -i "s/PROJECT NAME/$htmlname/" ui/src/main_window.ui
# escape Rust special characters (" and \; don't worry of the others as they are unlikely enough and easy to fix manually), and also sed special characters (&, \, and /)
quotname=$(echo -n "$name" | sed 's/"/\\"/g; s:[\\/&]:\\&:g')
sed -i "s/PROJECT NAME/$quotname/" src/main.rs
# $techname is alphanumeric, nothing to escape
grep -l -r PROJECT-NAME | xargs sed -i "s/PROJECT-NAME/$techname/g"
# replace - with _ to form a valid snake_case identifier
ident=$(echo -n $techname | LC_ALL=C tr '-' '_')
grep -l -r PROJECT_NAME | xargs sed -i "s/PROJECT_NAME/$ident/g"
# black magic to convert snake_case to PascalCase
pascal=$(echo -n $ident | LC_ALL=C tr '_' '\n' | LC_ALL=C awk -v FIELDWIDTHS='1 1000' -v ORS='' '{print toupper($1) $2}')
grep -l -r PROJECTNAME | xargs sed -i "s/PROJECTNAME/$pascal/g"
# another hack, to update Cargo.lock
cargo tree >/dev/null
git commit -a -m "MORPH INTO: $name"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

View File

@ -1,3 +0,0 @@
# Self-tests
This directory contains self-tests for the skeleton itself. It should be removed when starting a new project.

View File

@ -1,5 +0,0 @@
#!/bin/sh
set -E
exec cargo check

View File

@ -1,8 +0,0 @@
#!/bin/sh
set -x
set -E
mkdir build
cmake -S . -B build -G Ninja -D CMAKE_BUILD_TYPE=Debug
ninja -C build

View File

@ -1,17 +0,0 @@
#!/bin/zsh
set -e
trap 'echo Test failed!' ERR
name='<Test/+> "Example"'
dir=${0%/*}
root=$dir/..
tmp=$(mktemp -d)
trap 'rm -rf $tmp' EXIT
rsync -a --exclude=/.git --exclude-from=$root/.gitignore $root/. $tmp/
cd $tmp
echo "Initializing in $tmp"
./init.sh $name
echo "Starting the shell in $tmp"
echo "Review the files, then exit the shell"
${SHELL:-/bin/sh} -i
echo "Review successful, cleaning up"

View File

@ -1,16 +0,0 @@
#!/bin/zsh
set -e
trap 'echo Test failed!' ERR
name='<Test/+> "Example"'
root=$(realpath ${0%/*}/..)
tmp=$(mktemp -d)
trap 'rm -rf $tmp' EXIT
cd $tmp
git -C $root archive HEAD | tar -x
echo "Initializing in $tmp"
./init.sh $name
echo "Starting the shell in $tmp"
echo "Review the files, then exit the shell"
${SHELL:-/bin/sh} -i
echo "Review successful, cleaning up"

View File

@ -1,18 +0,0 @@
#!/bin/zsh
set -e
trap 'echo Test failed!' ERR
name='<Test/+> "Example"'
dir=${0%/*}
root=$dir/..
tmp=$(mktemp -d)
trap 'rm -rf $tmp' EXIT
rsync -a --exclude-from=$root/.gitignore $root/. $tmp/
cd $tmp
git clean -fxdq
echo "Initializing in $tmp"
./init.sh $name
echo "Starting the shell in $tmp"
echo "Review the files, then exit the shell"
${SHELL:-/bin/sh} -i
echo "Review successful, cleaning up"

View File

@ -1,6 +1,6 @@
use std::sync::Arc; use std::sync::Arc;
use PROJECT_NAME::{Core, RedrawArgs, init_gpu_inner}; use gear_cutter::{Core, RedrawArgs, init_gpu_inner};
use glam::uvec2; use glam::uvec2;
use winit::{ use winit::{
application::ApplicationHandler, application::ApplicationHandler,
@ -9,7 +9,7 @@ use winit::{
window::Window, window::Window,
}; };
const TITLE: &str = "PROJECT NAME"; const TITLE: &str = "Gear Cutter";
struct MainWindow { struct MainWindow {
window: Arc<Window>, window: Arc<Window>,

View File

@ -1,6 +1,6 @@
include(impl.cmake) include(impl.cmake)
qt_add_executable(PROJECT-NAME qt_add_executable(gear-cutter
src/api.cxx src/api.cxx
src/main.cxx src/main.cxx
src/main_window.cxx src/main_window.cxx
@ -8,6 +8,6 @@ qt_add_executable(PROJECT-NAME
src/viewport.cxx src/viewport.cxx
) )
target_link_libraries(PROJECT-NAME PRIVATE Qt6::Gui Qt6::Widgets) target_link_libraries(gear-cutter PRIVATE Qt6::Gui Qt6::Widgets)
target_link_libraries(PROJECT-NAME PRIVATE PROJECT_NAME_impl) target_link_libraries(gear-cutter PRIVATE gear_cutter_impl)
target_include_directories(PROJECT-NAME PRIVATE src) target_include_directories(gear-cutter PRIVATE src)

View File

@ -1,5 +1,5 @@
[package] [package]
name = "PROJECT-NAME-impl" name = "gear-cutter-impl"
version = "0.1.0" version = "0.1.0"
edition = "2024" edition = "2024"
@ -7,7 +7,7 @@ edition = "2024"
crate-type = ["staticlib"] crate-type = ["staticlib"]
[dependencies] [dependencies]
PROJECT-NAME = {path = "../"} gear-cutter = {path = "../"}
glam = { version = "0.30" } glam = { version = "0.30" }
pollster = "0.4.0" pollster = "0.4.0"

View File

@ -8,7 +8,7 @@ language = "C++"
[parse] [parse]
parse_deps = true parse_deps = true
include = ["PROJECT-NAME"] include = ["gear-cutter"]
[export] [export]
exclude = ["Box"] exclude = ["Box"]

View File

@ -1,9 +1,9 @@
# build the Rust side as a static library # build the Rust side as a static library
set(impl_basename "${CARGO_TARGET_DIR}/release/libPROJECT_NAME_impl") set(impl_basename "${CARGO_TARGET_DIR}/release/libgear_cutter_impl")
add_custom_command( add_custom_command(
OUTPUT ${impl_basename}.a OUTPUT ${impl_basename}.a
COMMAND env CARGO_TARGET_DIR=${CARGO_TARGET_DIR} ${CARGO} build --release --package PROJECT-NAME-impl COMMAND env CARGO_TARGET_DIR=${CARGO_TARGET_DIR} ${CARGO} build --release --package gear-cutter-impl
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
DEPFILE ${impl_basename}.d DEPFILE ${impl_basename}.d
USES_TERMINAL USES_TERMINAL
@ -17,9 +17,9 @@ add_custom_target(build_impl
DEPENDS ${impl_basename}.a DEPENDS ${impl_basename}.a
) )
add_library(PROJECT_NAME_impl STATIC IMPORTED) add_library(gear_cutter_impl STATIC IMPORTED)
set_target_properties(PROJECT_NAME_impl PROPERTIES set_target_properties(gear_cutter_impl PROPERTIES
IMPORTED_LOCATION ${impl_basename}.a IMPORTED_LOCATION ${impl_basename}.a
) )
@ -37,9 +37,9 @@ add_custom_command(
DEPENDS_EXPLICIT_ONLY DEPENDS_EXPLICIT_ONLY
) )
target_sources(PROJECT_NAME_impl INTERFACE target_sources(gear_cutter_impl INTERFACE
${bindings_basename}.hxx ${bindings_basename}.hxx
) )
target_include_directories(PROJECT_NAME_impl INTERFACE target_include_directories(gear_cutter_impl INTERFACE
"${CMAKE_CURRENT_BINARY_DIR}/include" "${CMAKE_CURRENT_BINARY_DIR}/include"
) )

View File

@ -1,6 +1,6 @@
use std::{ffi::c_void, num::NonZero, ptr::NonNull}; use std::{ffi::c_void, num::NonZero, ptr::NonNull};
use PROJECT_NAME::{Core, RedrawArgs, init_gpu_inner}; use gear_cutter::{Core, RedrawArgs, init_gpu_inner};
use glam::{UVec2, uvec2}; use glam::{UVec2, uvec2};
use raw_window_handle::{RawDisplayHandle, RawWindowHandle, XcbDisplayHandle, XcbWindowHandle}; use raw_window_handle::{RawDisplayHandle, RawWindowHandle, XcbDisplayHandle, XcbWindowHandle};

View File

@ -8,7 +8,7 @@ int main(int argc, char* argv[]) {
QApplication app(argc, argv); QApplication app(argc, argv);
auto w = new PROJECTNAME; auto w = new GearCutter;
w->show(); w->show();
return app.exec(); return app.exec();

View File

@ -2,16 +2,16 @@
#include "ui_main_window.h" #include "ui_main_window.h"
PROJECTNAME::PROJECTNAME(QWidget* parent) GearCutter::GearCutter(QWidget* parent)
: QMainWindow(parent), : QMainWindow(parent),
m_ui(new Ui::MainWindow) { m_ui(new Ui::MainWindow) {
m_ui->setupUi(this); m_ui->setupUi(this);
updateView(); updateView();
} }
PROJECTNAME::~PROJECTNAME() = default; GearCutter::~GearCutter() = default;
void PROJECTNAME::updateView() { void GearCutter::updateView() {
RedrawArgs args{ RedrawArgs args{
.cam_yaw = qDegreesToRadians((float)m_ui->camYaw->value()), .cam_yaw = qDegreesToRadians((float)m_ui->camYaw->value()),
.cam_pitch = qDegreesToRadians((float)m_ui->camPitch->value()), .cam_pitch = qDegreesToRadians((float)m_ui->camPitch->value()),
@ -21,7 +21,7 @@ void PROJECTNAME::updateView() {
m_ui->viewport->setView(args); m_ui->viewport->setView(args);
} }
void PROJECTNAME::updateViewIf(bool update) { void GearCutter::updateViewIf(bool update) {
if (update) if (update)
updateView(); updateView();
} }

View File

@ -7,12 +7,12 @@ namespace Ui {
class MainWindow; class MainWindow;
} }
class PROJECTNAME : public QMainWindow { class GearCutter : public QMainWindow {
Q_OBJECT Q_OBJECT
public: public:
explicit PROJECTNAME(QWidget* parent = nullptr); explicit GearCutter(QWidget* parent = nullptr);
~PROJECTNAME() override; ~GearCutter() override;
public slots: public slots:
void updateView(); void updateView();

View File

@ -11,7 +11,7 @@
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
<string>PROJECT NAME</string> <string>Gear Cutter</string>
</property> </property>
<widget class="QWidget" name="centralwidget"> <widget class="QWidget" name="centralwidget">
<layout class="QHBoxLayout" name="horizontalLayout_2"> <layout class="QHBoxLayout" name="horizontalLayout_2">