折腾笔记[6]-使用rust绘制三维画面

news/2025/1/18 23:37:11/文章来源:https://www.cnblogs.com/qsbye/p/18679035

摘要

使用rust绘制界面;界面包含一个三维坐标轴,使用鼠标旋转坐标轴,左侧显示对应的旋转向量,四元数等信息.
Use Rust to draw the interface; the interface contains a three-dimensional coordinate axis, which can be rotated using the mouse, and the corresponding rotation vector, quaternion and other information are displayed on the left side.

关键词

rust;nalgebra;egui;three-d;

关键信息

[package]
name = "exp65-rust-ziglang-slambook2"
version = "0.1.0"
edition = "2021"[dependencies]
nalgebra = { version = "0.33.2",features = ["rand"]}
rand = "0.8.5"
wgpu = "23.0.1"
winit = "0.30.8"eframe = "0.30.0"
egui = { version = "0.30.0", features = ["default"
]}
egui_extras = {version = "0.30.0",features = ["default", "image"]}three-d = {path = "./static/three-d" , features=["egui-gui"] }
three-d-asset = {version = "0.9",features = ["hdr", "http"] }env_logger = { version = "0.11.6", default-features = false, features = ["auto-color","humantime",
] }

原理简介

rust打印变量的类型

在Rust中,类型推断是一个强大的特性,但在某些情况下,编译器可能无法推断出具体的类型。你提供的两个代码片段就展示了这种情况。
第一个代码片段:

(|view_matrix| {let type_name = std::any::type_name::<_>(&view_matrix);println!("typeof view_matrix is {:?}", type_name);
})(&view_matrix);

在这个片段中,编译器无法推断出view_matrix的类型,因为它被用作一个闭包的参数。在这个上下文中,view_matrix的类型是未知的,因此编译器无法为std::any::type_name::<_>(&view_matrix)推断出具体的类型参数。
第二个代码片段:

fn type_name_of<T>(_: &T) -> &'static str {std::any::type_name::<T>()
}
(|view_matrix| println!("typeof view_matrix is {:?}", type_name_of(view_matrix)))(&view_matrix);

在这个片段中,type_name_of函数是一个泛型函数,它接受一个引用作为参数,并返回一个静态字符串。当调用type_name_of(view_matrix)时,编译器可以推断出T的类型,因为view_matrix被传递给了这个函数。这样,std::any::type_name::<T>()就可以正确地推断出T的类型。
总结来说,第一个代码片段中,编译器无法推断出view_matrix的类型,因为它被用作一个闭包的参数,且没有足够的信息来确定其类型。而在第二个代码片段中,通过使用泛型函数type_name_of,编译器可以推断出view_matrix的类型,因为函数的泛型参数T提供了所需的信息。

rust的交互界面GUI库:egui和three_d简介

[https://github.com/asny/three-d]
[https://www.egui.rs/#demo]
[https://github.com/emilk/egui]
egui(发音为“e-gooey”)是一个简单、快速且高度可移植的Rust即时模式GUI库。egui可以在网页、本地以及您喜欢的游戏引擎中运行。
egui旨在成为最容易使用的Rust GUI库,也是用Rust制作网页应用的最简单方式。
只要您能绘制纹理三角形,就可以使用egui,这意味着您可以轻松将其集成到您选择的游戏引擎中。
eframe是官方的egui框架,支持为Web、Linux、Mac、Windows和Android编写应用。
一个OpenGL/WebGL/OpenGL ES渲染器,旨在使图形简单化,同时仍具有高效绘制您想要的内容的能力。
three-d
适用于那些只想绘制东西的人和那些想避免繁琐设置但仍希望获得低级访问权限的人。
使得结合高级功能与自定义低级实现成为可能,例如自定义着色器。
尝试用几行简单的代码完成工作。
力求尽可能明确,以便对您来说没有意外——没有隐藏的魔法。
适用于桌面、网页和移动设备。
three-d可以用于例如:
数据可视化
图像处理
UI渲染
工具(2D或3D)
游戏(2D或3D)

egui (pronounced "e-gooey") is a simple, fast, and highly portable immediate mode GUI library for Rust. egui runs on the web, natively, and in your favorite game engine.

egui aims to be the easiest-to-use Rust GUI library, and the simplest way to make a web app in Rust.

egui can be used anywhere you can draw textured triangles, which means you can easily integrate it into your game engine of choice.

eframe is the official egui framework, which supports writing apps for Web, Linux, Mac, Windows, and Android.

A OpenGL/WebGL/OpenGL ES renderer which seeks to make graphics simple but still have the power to efficiently draw exactly what you want.

three-d

targets those who just want to draw something and those who want to avoid the tedious setup but still wants low-level access.
makes it possible to combine high-level features with custom low-level implementations for example custom shaders.
tries to do stuff in a few simple lines of code.
aims to be as explicit as possible so there is no surprises for you - no hidden magic.
targets desktop, web and mobile.
three-d can for example be used for

data visualization
image processing
UI rendering
tools (2D or 3D)
games (2D or 3D)

实现

原始代码:[https://github.com/gaoxiang12/slambook2/blob/master/ch3/visualizeGeometry/visualizeGeometry.cpp]

#include <iostream>
#include <iomanip>using namespace std;#include <Eigen/Core>
#include <Eigen/Geometry>using namespace Eigen;#include <pangolin/pangolin.h>struct RotationMatrix {Matrix3d matrix = Matrix3d::Identity();
};ostream &operator<<(ostream &out, const RotationMatrix &r) {out.setf(ios::fixed);Matrix3d matrix = r.matrix;out << '=';out << "[" << setprecision(2) << matrix(0, 0) << "," << matrix(0, 1) << "," << matrix(0, 2) << "],"<< "[" << matrix(1, 0) << "," << matrix(1, 1) << "," << matrix(1, 2) << "],"<< "[" << matrix(2, 0) << "," << matrix(2, 1) << "," << matrix(2, 2) << "]";return out;
}istream &operator>>(istream &in, RotationMatrix &r) {return in;
}struct TranslationVector {Vector3d trans = Vector3d(0, 0, 0);
};ostream &operator<<(ostream &out, const TranslationVector &t) {out << "=[" << t.trans(0) << ',' << t.trans(1) << ',' << t.trans(2) << "]";return out;
}istream &operator>>(istream &in, TranslationVector &t) {return in;
}struct QuaternionDraw {Quaterniond q;
};ostream &operator<<(ostream &out, const QuaternionDraw quat) {auto c = quat.q.coeffs();out << "=[" << c[0] << "," << c[1] << "," << c[2] << "," << c[3] << "]";return out;
}istream &operator>>(istream &in, const QuaternionDraw quat) {return in;
}int main(int argc, char **argv) {pangolin::CreateWindowAndBind("visualize geometry", 1000, 600);glEnable(GL_DEPTH_TEST);pangolin::OpenGlRenderState s_cam(pangolin::ProjectionMatrix(1000, 600, 420, 420, 500, 300, 0.1, 1000),pangolin::ModelViewLookAt(3, 3, 3, 0, 0, 0, pangolin::AxisY));const int UI_WIDTH = 500;pangolin::View &d_cam = pangolin::CreateDisplay().SetBounds(0.0, 1.0, pangolin::Attach::Pix(UI_WIDTH), 1.0, -1000.0f / 600.0f).SetHandler(new pangolin::Handler3D(s_cam));// uipangolin::Var<RotationMatrix> rotation_matrix("ui.R", RotationMatrix());pangolin::Var<TranslationVector> translation_vector("ui.t", TranslationVector());pangolin::Var<TranslationVector> euler_angles("ui.rpy", TranslationVector());pangolin::Var<QuaternionDraw> quaternion("ui.q", QuaternionDraw());pangolin::CreatePanel("ui").SetBounds(0.0, 1.0, 0.0, pangolin::Attach::Pix(UI_WIDTH));while (!pangolin::ShouldQuit()) {glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);d_cam.Activate(s_cam);pangolin::OpenGlMatrix matrix = s_cam.GetModelViewMatrix();Matrix<double, 4, 4> m = matrix;RotationMatrix R;for (int i = 0; i < 3; i++)for (int j = 0; j < 3; j++)R.matrix(i, j) = m(j, i);rotation_matrix = R;TranslationVector t;t.trans = Vector3d(m(0, 3), m(1, 3), m(2, 3));t.trans = -R.matrix * t.trans;translation_vector = t;TranslationVector euler;euler.trans = R.matrix.eulerAngles(2, 1, 0);euler_angles = euler;QuaternionDraw quat;quat.q = Quaterniond(R.matrix);quaternion = quat;glColor3f(1.0, 1.0, 1.0);pangolin::glDrawColouredCube();// draw the original axisglLineWidth(3);glColor3f(0.8f, 0.f, 0.f);glBegin(GL_LINES);glVertex3f(0, 0, 0);glVertex3f(10, 0, 0);glColor3f(0.f, 0.8f, 0.f);glVertex3f(0, 0, 0);glVertex3f(0, 10, 0);glColor3f(0.2f, 0.2f, 1.f);glVertex3f(0, 0, 0);glVertex3f(0, 0, 10);glEnd();pangolin::FinishFrame();}
}

注意:three-d库需要使用[https://github.com/asny/three-d]的最新版本,参考toml方式引用本地库文件.

#![allow(dead_code)]
#![allow(unused_variables)]
#![allow(unused_imports)]
#![allow(unused_mut)]
#![allow(unused_assignments)]
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] 
#![allow(rustdoc::missing_crate_level_docs)]
#![allow(unsafe_code)]
#![allow(clippy::undocumented_unsafe_blocks)]use nalgebra::{Matrix3, Vector3, UnitQuaternion, Quaternion, Isometry3, Rotation3, UnitComplex, Rotation2, Unit,Translation3, Perspective3, Orthographic3, Vector4, Point3, Const,ArrayStorage, Matrix4, ViewStorage
};// 绘图和界面库
use three_d::*; 
use three_d::egui::*;use env_logger::init;use std::fmt;
use std::sync::Arc;
use std::any::type_name;// 1. 定义一个结构体,用于表示旋转矩阵
#[derive(Debug)]
#[derive(PartialEq)]
#[derive(Default)]
struct RotationMatrix {matrix: Matrix3<f64>,
}// 实现Display trait,以便输出旋转矩阵
impl fmt::Display for RotationMatrix {fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {write!(f, "{:?}", self.matrix)}
}// 2. 定义一个结构体,用于表示平移向量
#[derive(Debug)]
#[derive(PartialEq)]
#[derive(Default)]
struct TranslationVector {trans: Vector3<f64>,
}
// 实现Display trait,以便输出平移向量
impl fmt::Display for TranslationVector {fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {write!(f, "=[{}, {}, {}]", self.trans.x, self.trans.y, self.trans.z)}
}
// 3. 定义一个结构体,用于表示四元数
#[derive(Debug)]
#[derive(PartialEq)]
#[derive(Default)]
struct QuaternionDraw {q: UnitQuaternion<f64>,
}
// 实现Display trait,以便输出四元数
impl fmt::Display for QuaternionDraw {fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {let i = self.q.quaternion().i;let j = self.q.quaternion().j;let k = self.q.quaternion().k;let w = self.q.quaternion().w;write!(f, "=[{}, {}, {}, {}]", i, j, k, w)}
}/* start 测试 */ 
fn test() {// 创建一个单位旋转矩阵let rotation_matrix = RotationMatrix {matrix: Matrix3::identity(),};// 测试矩阵是否为单位矩阵assert_eq!(rotation_matrix.matrix, Matrix3::identity(), "The matrix should be the identity matrix.");// 测试矩阵的行列式是否为1assert_eq!(rotation_matrix.matrix.determinant(), 1.0, "The determinant of the identity matrix should be 1.");// 打印旋转矩阵println!("{}", rotation_matrix);// 创建一个平移向量let translation_vector = TranslationVector {trans: Vector3::new(1.0, 2.0, 3.0),};// 定义一个预期的平移向量let expected_translation_vector = TranslationVector {trans: Vector3::new(1.0, 2.0, 3.0),};// 测试平移向量是否相等assert_eq!(translation_vector, expected_translation_vector, "The translation vectors should be equal.");// 打印平移向量println!("{}", translation_vector);// 创建一个单位四元数let quaternion_draw = QuaternionDraw {q: UnitQuaternion::identity(),};// 定义一个预期的单位四元数let expected_quaternion_draw = QuaternionDraw {q: UnitQuaternion::identity(),};// 测试四元数是否相等assert_eq!(quaternion_draw, expected_quaternion_draw, "The quaternions should be equal.");// 打印四元数println!("{}", quaternion_draw);
}
/* end 测试 *//* start 绘制界面和绘图 */
pub fn main() {let window = three_d::Window::new(WindowSettings {title: "Shapes!".to_string(),max_size: Some((1280, 720)),..Default::default()}).unwrap();let context = window.gl();let mut camera = three_d::Camera::new_perspective(window.viewport(),vec3(5.0, 2.0, 2.5),vec3(0.0, 0.0, -0.5),vec3(0.0, 1.0, 0.0),degrees(45.0),0.1,1000.0,);let mut control = three_d::OrbitControl::new(camera.target(), 1.0, 100.0);/* start 绘制立体图形 */let mut sphere = three_d::Gm::new(three_d::Mesh::new(&context, &CpuMesh::sphere(16)),three_d::PhysicalMaterial::new_transparent(&context,&CpuMaterial {albedo: Srgba {r: 255,g: 0,b: 0,a: 200,},..Default::default()},),);sphere.set_transformation(Mat4::from_translation(vec3(0.0, 1.3, 0.0)) * Mat4::from_scale(0.2));let mut cylinder = three_d::Gm::new(three_d::Mesh::new(&context, &CpuMesh::cylinder(16)),three_d::PhysicalMaterial::new_transparent(&context,&CpuMaterial {albedo: Srgba {r: 0,g: 255,b: 0,a: 200,},..Default::default()},),);cylinder.set_transformation(Mat4::from_translation(vec3(1.3, 0.0, 0.0)) * Mat4::from_scale(0.2));let mut cube = three_d::Gm::new(three_d::Mesh::new(&context, &CpuMesh::cube()),three_d::PhysicalMaterial::new_transparent(&context,&CpuMaterial {albedo: Srgba {r: 0,g: 0,b: 255,a: 100,},..Default::default()},),);cube.set_transformation(Mat4::from_translation(vec3(0.0, 0.0, 1.3)) * Mat4::from_scale(0.2));let axes = three_d::Axes::new(&context, 0.1, 2.0);let bounding_box_sphere = Gm::new(BoundingBox::new(&context, sphere.aabb()),ColorMaterial {color: Srgba::BLACK,..Default::default()},);let bounding_box_cube = three_d::Gm::new(three_d::BoundingBox::new(&context, cube.aabb()),ColorMaterial {color: Srgba::BLACK,..Default::default()},);let bounding_box_cylinder = three_d::Gm::new(three_d::BoundingBox::new(&context, cylinder.aabb()),ColorMaterial {color: Srgba::BLACK,..Default::default()},);let light0 = DirectionalLight::new(&context, 1.0, Srgba::WHITE, vec3(0.0, -0.5, -0.5));let light1 = DirectionalLight::new(&context, 1.0, Srgba::WHITE, vec3(0.0, 0.5, 0.5));/* end 绘制立体图形 */let mut gui = three_d::GUI::new(&context);// main loopwindow.render_loop(move |mut frame_input| {let mut panel_width = 0.0;gui.update(&mut frame_input.events,frame_input.accumulated_time,frame_input.viewport,frame_input.device_pixel_ratio,|gui_context| {SidePanel::left("side_panel").show(gui_context, |ui| {ui.heading("Camera Pose");// 显示相机的平移向量let translation_vector = camera.position();ui.label(format!("Translation Vector: {:?}", translation_vector));// 获取相机的视图矩阵let view_matrix = camera.view();/* start 打印camera.view()返回值的数据类型 */// fn type_name_of<T>(_: &T) -> &'static str {//     std::any::type_name::<T>()// }// (|view_matrix| println!("typeof view_matrix is {:?}", type_name_of(view_matrix)))(&view_matrix);/* end 打印camera.view()返回值的数据类型 */// 从视图矩阵中提取旋转矩阵// view_matrix:cgmath::Matrix4<f32>转为nal_view_matrix:nalgebra::Matrix4<f32>数据let nal_view_matrix = nalgebra::Matrix4::new(view_matrix.x.x, view_matrix.x.y, view_matrix.x.z, view_matrix.x.w,view_matrix.y.x, view_matrix.y.y, view_matrix.y.z, view_matrix.y.w,view_matrix.z.x, view_matrix.z.y, view_matrix.z.z, view_matrix.z.w,view_matrix.w.x, view_matrix.w.y, view_matrix.w.z, view_matrix.w.w,);let rotation_matrix = nal_view_matrix.try_inverse().unwrap_or_else(|| Matrix4::identity());// let rotation_matrix = nalgebra::Matrix4::<f64>::identity();// 从 rotation_matrix 中提取 3x3 的视图let rotation_matrix_view = rotation_matrix.fixed_view::<3, 3>(0, 0);// 创建一个新的 nalgebra::Matrix3<f32> 实例let rotation_matrix3 = nalgebra::Matrix3::from(rotation_matrix_view);ui.label(format!("Rotation Matrix: {:?}", rotation_matrix3));// 从旋转矩阵中提取四元数// let quaternion = Quaternion::new(1.0, 0.0, 0.0, 0.0); // 提供了合适的数值// 注意:这里假设 rotation_matrix3 是一个纯旋转矩阵,没有平移部分let unit_quaternion = UnitQuaternion::from_matrix(&rotation_matrix3);ui.label(format!("Quaternion: {:?}", unit_quaternion));// 从四元数中提取欧拉角let euler_angles = unit_quaternion.euler_angles();ui.label(format!("Euler Angles: {:?}", euler_angles));}); // end SidePanel showpanel_width = gui_context.used_rect().width();}, // end gui_context); // end gui.update// 更新相机视图camera.set_viewport(Viewport {x: (panel_width * frame_input.device_pixel_ratio) as i32,y: 0,width: frame_input.viewport.width- (panel_width * frame_input.device_pixel_ratio) as u32,height: frame_input.viewport.height,});control.handle_events(&mut camera, &mut frame_input.events);// 绘制场景frame_input.screen().clear(ClearState::color_and_depth(0.8, 0.8, 0.8, 1.0, 1.0)).render(&camera,sphere.into_iter().chain(&cylinder).chain(&cube).chain(&axes).chain(&bounding_box_sphere).chain(&bounding_box_cube).chain(&bounding_box_cylinder),&[&light0, &light1],).write(|| gui.render()).unwrap();FrameOutput::default()});
}
/* end 绘制界面和绘图 */

效果

界面显示四元数

检查结果合理性

**总结性解决方案**
1. **翻译向量检查**- **数值**:`[4.5996685, 3.6870298, 1.3024255]`- **分析**:向量值在三维空间中大小适中,无极端值。- **结论**:翻译向量数值正确。
2. **旋转矩阵检查**- **矩阵**:\[\begin{bmatrix}0.3648478 & -0.5568863 & 0.74616593 \\0.0 & 0.8014093 & 0.59811616 \\-0.9310671 & -0.21822138 & 0.29239246 \\\end{bmatrix}\]- **正交性检查**:- 每行和每列的模长均接近1。- 不同行向量和列向量的点积接近0,满足正交条件。- **行列式检查**:- 计算得行列式约为1,符合旋转矩阵要求。- **结论**:旋转矩阵数值正确。
3. **四元数检查**- **数值**:`[0.26031038, -0.5348292, -0.17757763, 0.78400415]`- **模长检查**:- 模长计算为1,为单位四元数。- **与旋转矩阵对应性检查**:- 将四元数转换为旋转矩阵,发现与给定旋转矩阵不一致,特别是第一行第二列的元素不符。- **结论**:四元数可能有误。
4. **欧拉角检查**- **数值**:`(1.1161038, -0.8422845, -0.99080443)`(单位:弧度)- **范围分析**:- 滚转约 \(64^\circ\),俯仰约 \(-48^\circ\),偏航约 \(-56.8^\circ\),均在合理范围内。- **结论**:欧拉角数值合理。
**最终结论**
\[
\boxed{\text{翻译向量和旋转矩阵数值正确,四元数可能有误。}}
\]

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/871498.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

例题_树基础 P5318

日常题解洛谷P5318 分析 关键词n篇文章 m条参考文献引用关系 x文章有y参考文献 BFS&&DFS结果步骤定义 不仅要定义关键词,还要再定义一个容器 这里用\(set\)set<int>e[100009];注意要初始化输入 输入n m x y这几个关键字计算过程 分两步深搜 广搜输出 先调用函数…

集合框架

集合框架:Collection集合、集合存储对象的原理等学习视频 集合的概述public class Demo01 {public static void main(String[] args) {//简单确认一下Collection集合特点ArrayList<String> list = new ArrayList<>();list.add("java1");list.add("…

uniapp获取元素高度不准确问题解决

uniapp通过boundingClientRect获取的元素高度和实际高度差了不少,下面是复现和解决过程: 我的代码: 得到的结果: 高度只有105 用工具量一下: 实际有240px,遂gpt问下: 注意到了缩放比这个之前没想到的点,往下面看gpt更多的回复内容: 先获取系统缩放比,再乘以拿到的高度…

嵌入式Linux驱动开发学习--韦东山老师嵌入式Linux学习

最无益,只怕一日曝十日寒,贵在有恒。 目录2025-01-181、基础内容2、通用GPIO操作3、硬件操作4、驱动涉及的思想_面向对象_分层_分离1)面向对象2)分层3)分离5、驱动进化之路_总线设备驱动模型6、驱动进化之路_设备树的语法7、驱动进化之路_内核对设备树的处理与使用2025-01…

zabbix监控网络设备流量

项目环境一台liunx服务器,这里使用的为ubuntu22.04(192.168.86.140) 一台华为交换机,本次监控的就是该交换机的端口流量(192.168.86.20) 一台pc主机,使用该主机模拟流量变化ENSP拓扑图:安装zabbix 这里使用shell脚本进行安装,仅为ubuntu22.04的安装方式,别的版本可以参看…

关于动态规划

主要问题大概是动规基础(斐波那契),背包,打家劫舍,股票,子序列。 解决也主要是先分类,建立dp数组,明确dp数组的含义,dp数组的初始化,遍历顺序。 动规基础斐波那契数列,爬楼梯,建立dp数组的时候,递推公式的推导就要参考建立dp数组并且明确dp数组的含义 比如下面的最…

强力工具助你一臂之力:XXECheck–全面提升XML安全,防护XXE漏洞!

泷羽Sec-trackXXECheck XXECheck 是一种用于检测和防止 XML 外部实体 (XXE) 注入攻击的安全工具或库,一款XXE漏洞检测工具,支持 DoS 检测(DoS 检测默认开启)和 DNSLOG 两种检测方式,能对普通 xml 请求和 xlsx 文件上传进行 XXE 漏洞检测。 源地址:https://github.com/Wei…

知乐

儒家理想境界追求心灵的安顿过程 孔颜之乐 曾点之乐 潘立勇教授在[宋明理学休闲审美哲学的内在张力]中的一段描述:[1]宁新昌.孔颜之乐与曾点气象——兼论儒家的自由境界[C]//中华炎黄文化研究会,广东炎黄文化研究会,暨南大学.“21世纪中华文化世界论坛”第五次国际学术研讨…

[ABC283E] Don‘t Isolate Elements

题目:思路: 很明显总的情况是2^h*w种,然后不难发现改变一行只会影响到相邻两行,也就是说前面的决策不会影响到后面的决策,只有当前面的决策全部合理才能走后一步。 所以取dp:dp[i][j][k]为前i行,j(当前行是1/0否改变),k(当前行的前一行是1/0否改变)。 所以有递推式:dp[i][j]…

在 nuget 私服 BaGet 中应用https 以及 gitea action runner 自动打包

最近赋闲,想起之前工作中代码管理的一点经历,就是在打包项目的时候,类库的版本号几乎没变过,一个项目运行多少年了,版本号还是1.0.0。😂 即使用到了 nuget 私服,基本上也是手动打包的,CI 工具基本都是用到 api 项目。于是想结合 gitea 的 CI 工具 act runner 试用一下…

记录一下双多控开关接法

实际上双控就是单刀双掷开关,多控就是双刀双掷开关。多控里L1A+L1B是输入的俩个接上级出来的俩根线,LA和LB是反着的接上总有一路能通。输入俩通道输出俩通道所以可以无限串联。实际上双控就是单刀双掷开关,多控就是双刀双掷开关。 多控里L1A+L1B是输入的俩个接上级出来的俩根…

Day 7

1月17日,继续看了一些SpringBoot视频, SpringBoot的实现步骤SpringBoot项目的起步依赖 <!-- springboot工程需要继承的父工程 --> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifac…