control gear params from the UI

This commit is contained in:
numzero 2026-10-18 16:48:33 +03:00
parent f35ca37cae
commit f5d578ccf3
11 changed files with 275 additions and 27 deletions

View File

@ -23,11 +23,11 @@ impl BevelGearDesc {
}
}
fn pitch_radius(&self) -> f32 {
pub fn pitch_radius(&self) -> f32 {
0.5 * self.teeth as f32
}
fn pitch_distance(&self) -> f32 {
pub fn pitch_distance(&self) -> f32 {
self.pitch_radius() / self.cone_halfangle.tan()
}
}

View File

@ -10,6 +10,22 @@ mod render;
const BACKGROUND: Vec4 = vec4(0.00, 0.39, 0.52, 1.00);
#[derive(Debug, Clone, Copy)]
#[repr(C)]
pub struct ResetArgs {
pub tooth: u32,
pub cone_halfangle: f32,
}
impl Default for ResetArgs {
fn default() -> Self {
Self {
tooth: 24,
cone_halfangle: std::f32::consts::FRAC_PI_4,
}
}
}
#[derive(Debug, Clone, Copy)]
#[repr(C)]
pub struct RedrawArgs {
@ -72,7 +88,7 @@ fn color_to_wgpu(color: Vec4) -> wgpu::Color {
}
impl Core {
pub fn new(gpu: Gpu, pixel_size: UVec2) -> Self {
pub fn new(gpu: Gpu, pixel_size: UVec2, args: &ResetArgs) -> Self {
let Gpu {
device,
queue,
@ -100,14 +116,14 @@ impl Core {
let bevel_gear_desc = bevel_gear::BevelGearDesc {
profile: Default::default(),
teeth: 24,
cone_halfangle: 0.5,
teeth: args.tooth,
cone_halfangle: args.cone_halfangle,
};
let bevel_gear_mesh = bevel_gear::make_mesh(
&bevel_gear_desc,
&bevel_gear::BevelGearVisual {
inner_distance: 8.0,
outer_distance: 12.0,
inner_distance: bevel_gear_desc.pitch_distance() - 4.0,
outer_distance: bevel_gear_desc.pitch_distance(),
},
&bevel_gear::BevelGearShape::new_blank(&bevel_gear_desc, 60),
)

View File

@ -1,6 +1,6 @@
use std::sync::Arc;
use gear_cutter::{Core, RedrawArgs, init_gpu_inner};
use gear_cutter::{Core, RedrawArgs, ResetArgs, init_gpu_inner};
use glam::uvec2;
use winit::{
application::ApplicationHandler,
@ -26,7 +26,7 @@ impl MainWindow {
instance.create_surface(Arc::clone(&window))
}))
.unwrap();
let core = Core::new(gpu, uvec2(1, 1));
let core = Core::new(gpu, uvec2(1, 1), &ResetArgs::default());
Self { window, core }
}

View File

@ -19,13 +19,13 @@ BoxCore& BoxCore::operator=(BoxCore&& b) {
return *this;
}
BoxCore BoxCore::from_xcb(xcb_connection_t* connection, std::uint32_t window, std::uint32_t width, std::uint32_t height) {
BoxCore BoxCore::from_xcb(xcb_connection_t* connection, std::uint32_t window, std::uint32_t width, std::uint32_t height, const ResetArgs& args) {
if (!connection)
throw std::logic_error("attempt to use a null connection");
if (!window)
throw std::logic_error("attempt to use a null window");
BoxCore out;
out.ptr.ptr = ffi::rt4_viewport_create(connection, window, width, height);
out.ptr.ptr = ffi::rt4_viewport_create(connection, window, width, height, &args);
return out;
}

View File

@ -6,6 +6,7 @@
struct xcb_connection_t;
using ffi::ResetArgs;
using ffi::RedrawArgs;
class MutCore {
@ -36,7 +37,7 @@ public:
void reset();
static BoxCore from_xcb(xcb_connection_t* connection, std::uint32_t window, std::uint32_t width, std::uint32_t height);
static BoxCore from_xcb(xcb_connection_t* connection, std::uint32_t window, std::uint32_t width, std::uint32_t height, const ResetArgs& args);
private:
MutCore ptr;

View File

@ -1,6 +1,6 @@
use std::{ffi::c_void, num::NonZero, ptr::NonNull};
use gear_cutter::{Core, RedrawArgs, init_gpu_inner};
use gear_cutter::{Core, RedrawArgs, ResetArgs, init_gpu_inner};
use glam::{UVec2, uvec2};
use raw_window_handle::{RawDisplayHandle, RawWindowHandle, XcbDisplayHandle, XcbWindowHandle};
@ -8,6 +8,7 @@ unsafe fn create_viewport(
display: impl Into<RawDisplayHandle>,
window: impl Into<RawWindowHandle>,
size: UVec2,
args: &ResetArgs,
) -> Box<Core> {
let target = wgpu::SurfaceTargetUnsafe::RawHandle {
raw_display_handle: display.into(),
@ -17,7 +18,7 @@ unsafe fn create_viewport(
instance.create_surface_unsafe(target)
}))
.unwrap();
Box::new(Core::new(gpu, size))
Box::new(Core::new(gpu, size, args))
}
#[unsafe(no_mangle)]
@ -26,10 +27,11 @@ unsafe extern "C" fn rt4_viewport_create(
window: NonZero<u32>,
width: u32,
height: u32,
args: &ResetArgs,
) -> Box<Core> {
let display = XcbDisplayHandle::new(Some(connection), 0);
let window = XcbWindowHandle::new(window);
unsafe { create_viewport(display, window, uvec2(width, height)) }
unsafe { create_viewport(display, window, uvec2(width, height), args) }
}
#[unsafe(no_mangle)]

View File

@ -6,13 +6,38 @@ GearCutter::GearCutter(QWidget* parent)
: QMainWindow(parent),
m_ui(new Ui::MainWindow) {
m_ui->setupUi(this);
updateParams();
updateView();
}
GearCutter::~GearCutter() = default;
void GearCutter::updateParams() {
const std::uint32_t tooth = m_ui->bevTooth->value();
const std::uint32_t mate_tooth = m_ui->bevMateTooth->value();
const float shaft_angle = qDegreesToRadians((float)m_ui->bevShaftAngle->value());
const float r_gear = 0.5f * tooth;
const float r_mate = 0.5f * mate_tooth;
const float s = std::sin(shaft_angle);
const float c = std::cos(shaft_angle);
const float x_mate = s * r_mate;
const float y_mate = r_gear + c * r_mate;
const float h_gear = x_mate + y_mate * c / s;
const float cone_halfangle = std::atan2(r_gear, h_gear);
m_ui->bevAngle->setValue(qRadiansToDegrees(cone_halfangle));
m_ui->bevMateAngle->setValue(qRadiansToDegrees(shaft_angle - cone_halfangle));
const ResetArgs args{
.tooth = tooth,
.cone_halfangle = cone_halfangle,
};
m_ui->viewport->setParams(args);
}
void GearCutter::updateView() {
RedrawArgs args{
const RedrawArgs args{
.cam_yaw = qDegreesToRadians((float)m_ui->camYaw->value()),
.cam_pitch = qDegreesToRadians((float)m_ui->camPitch->value()),
.cam_view_y = (float)m_ui->camFoVLin->value(),
@ -21,9 +46,4 @@ void GearCutter::updateView() {
m_ui->viewport->setView(args);
}
void GearCutter::updateViewIf(bool update) {
if (update)
updateView();
}
#include "moc_main_window.cpp"

View File

@ -15,8 +15,8 @@ public:
~GearCutter() override;
public slots:
void updateParams();
void updateView();
void updateViewIf(bool update); // for radio buttons
private:
const std::unique_ptr<Ui::MainWindow> m_ui;

View File

@ -26,14 +26,14 @@
<x>0</x>
<y>0</y>
<width>1600</width>
<height>34</height>
<height>33</height>
</rect>
</property>
</widget>
<widget class="QStatusBar" name="statusbar"/>
<widget class="QDockWidget" name="dockWidget">
<property name="features">
<set>QDockWidget::DockWidgetFloatable|QDockWidget::DockWidgetMovable</set>
<set>QDockWidget::DockWidgetFeature::DockWidgetFloatable|QDockWidget::DockWidgetFeature::DockWidgetMovable</set>
</property>
<attribute name="dockWidgetArea">
<number>2</number>
@ -159,10 +159,152 @@
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Bevel gear</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QLabel" name="label_6">
<property name="text">
<string>Tooth</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="bevTooth">
<property name="minimum">
<number>6</number>
</property>
<property name="maximum">
<number>500</number>
</property>
<property name="value">
<number>24</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_9">
<property name="text">
<string>Cone half-angle</string>
</property>
</widget>
</item>
<item>
<widget class="QDoubleSpinBox" name="bevAngle">
<property name="enabled">
<bool>false</bool>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
<property name="suffix">
<string> °</string>
</property>
<property name="decimals">
<number>3</number>
</property>
<property name="minimum">
<double>15.000000000000000</double>
</property>
<property name="maximum">
<double>90.000000000000000</double>
</property>
<property name="singleStep">
<double>5.000000000000000</double>
</property>
<property name="value">
<double>90.000000000000000</double>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_7">
<property name="text">
<string>Mate tooth</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="bevMateTooth">
<property name="minimum">
<number>6</number>
</property>
<property name="maximum">
<number>500</number>
</property>
<property name="value">
<number>32</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Mate half-angle</string>
</property>
</widget>
</item>
<item>
<widget class="QDoubleSpinBox" name="bevMateAngle">
<property name="enabled">
<bool>false</bool>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
<property name="suffix">
<string> °</string>
</property>
<property name="decimals">
<number>3</number>
</property>
<property name="maximum">
<double>90.000000000000000</double>
</property>
<property name="singleStep">
<double>5.000000000000000</double>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_8">
<property name="text">
<string>Shaft angle</string>
</property>
</widget>
</item>
<item>
<widget class="QDoubleSpinBox" name="bevShaftAngle">
<property name="suffix">
<string> °</string>
</property>
<property name="decimals">
<number>1</number>
</property>
<property name="minimum">
<double>15.000000000000000</double>
</property>
<property name="maximum">
<double>150.000000000000000</double>
</property>
<property name="singleStep">
<double>5.000000000000000</double>
</property>
<property name="value">
<double>90.000000000000000</double>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
@ -184,6 +326,17 @@
<container>1</container>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>camYaw</tabstop>
<tabstop>camPitch</tabstop>
<tabstop>camFoVLin</tabstop>
<tabstop>camFoVAng</tabstop>
<tabstop>bevTooth</tabstop>
<tabstop>bevAngle</tabstop>
<tabstop>bevMateTooth</tabstop>
<tabstop>bevMateAngle</tabstop>
<tabstop>bevShaftAngle</tabstop>
</tabstops>
<resources/>
<connections>
<connection>
@ -250,9 +403,57 @@
</hint>
</hints>
</connection>
<connection>
<sender>bevTooth</sender>
<signal>valueChanged(int)</signal>
<receiver>MainWindow</receiver>
<slot>updateParams()</slot>
<hints>
<hint type="sourcelabel">
<x>1557</x>
<y>431</y>
</hint>
<hint type="destinationlabel">
<x>1464</x>
<y>488</y>
</hint>
</hints>
</connection>
<connection>
<sender>bevMateTooth</sender>
<signal>valueChanged(int)</signal>
<receiver>MainWindow</receiver>
<slot>updateParams()</slot>
<hints>
<hint type="sourcelabel">
<x>1509</x>
<y>556</y>
</hint>
<hint type="destinationlabel">
<x>1462</x>
<y>561</y>
</hint>
</hints>
</connection>
<connection>
<sender>bevShaftAngle</sender>
<signal>valueChanged(double)</signal>
<receiver>MainWindow</receiver>
<slot>updateParams()</slot>
<hints>
<hint type="sourcelabel">
<x>1489</x>
<y>681</y>
</hint>
<hint type="destinationlabel">
<x>1461</x>
<y>689</y>
</hint>
</hints>
</connection>
</connections>
<slots>
<slot>updateView()</slot>
<slot>updateViewIf(bool)</slot>
<slot>updateParams()</slot>
</slots>
</ui>

View File

@ -14,6 +14,12 @@ Viewport::Viewport(QWidget* parent, Qt::WindowFlags f)
Viewport::~Viewport() = default;
void Viewport::setParams(ResetArgs new_args) {
setup = new_args;
recreate();
update();
}
void Viewport::setView(RedrawArgs new_args) {
args = new_args;
update();
@ -63,7 +69,7 @@ void Viewport::recreate() try {
const QSize device_size = size() * devicePixelRatio();
core.reset();
core = BoxCore::from_xcb(xcb_connection, x11_window, device_size.width(), device_size.height());
core = BoxCore::from_xcb(xcb_connection, x11_window, device_size.width(), device_size.height(), setup);
} catch (const std::exception& e) {
fprintf(stderr, "failed to recreate the viewport: %s", e.what());
}

View File

@ -12,6 +12,7 @@ public:
~Viewport() override;
QPaintEngine* paintEngine() const override;
void setParams(ResetArgs new_args);
void setView(RedrawArgs new_args);
protected:
@ -21,6 +22,7 @@ protected:
private:
BoxCore core;
ResetArgs setup = {};
RedrawArgs args = {};
void recreate();