Zum Hauptinhalt springen
tsecurity.de LIVE
Echtzeit-Radar & Feeds
Alle RSS Feeds
👥 Community & Social
Sichere ProgrammierungRefreshed repository pull requests page generally available(22.09.2026 um 03:25 Uhr)
Sichere ProgrammierungThe Joy of Learning the Basics Again(22.09.2026 um 03:28 Uhr)
Sichere ProgrammierungZero-Code OpenTelemetry Tracing for Dagster(22.09.2026 um 03:39 Uhr)
Linux Tipps & Hardening`prime-all`(22.09.2026 um 02:28 Uhr)
IT Security Toolsopensoho v0.15.2(22.09.2026 um 03:33 Uhr)
IT Security NachrichtenUS Proposes AI Incident Alert System in Talks With China, Bessent Says(22.09.2026 um 04:01 Uhr)
Sichere ProgrammierungRefreshed repository pull requests page generally available(22.09.2026 um 03:25 Uhr)
Sichere ProgrammierungThe Joy of Learning the Basics Again(22.09.2026 um 03:28 Uhr)
Sichere ProgrammierungZero-Code OpenTelemetry Tracing for Dagster(22.09.2026 um 03:39 Uhr)
Linux Tipps & Hardening`prime-all`(22.09.2026 um 02:28 Uhr)
IT Security Toolsopensoho v0.15.2(22.09.2026 um 03:33 Uhr)
IT Security NachrichtenUS Proposes AI Incident Alert System in Talks With China, Bessent Says(22.09.2026 um 04:01 Uhr)
Intelligence View
⚡ tsecurity.de Intelligence

Modeling a nut, in Rust

I tried to build one ISO 4032 M2 hex nut with the Rust cadrum crate (an OpenCASCADE-based B-Rep library), and there were more detours than I expected, so I'm leaving the notes here. I started with just a cube and grew main.rs over 6…

0
↗ Quelle (dev.to)
Reagiere als Erste:r — dein Feedback zählt!

I tried to build one ISO 4032 M2 hex nut with the Rust cadrum crate (an OpenCASCADE-based B-Rep library), and there were more detours than I expected, so I'm leaving the notes here.



I started with just a cube and grew main.rs over 6 stages, adding one feature at a time. Every step wrote out a PNG (4-view layout) and a STEP file when I ran cargo run, so dimension mistakes and shape glitches showed up right away. What I ended up with was one Solid with thread, double-face chamfer, and lead-in chamfer all per spec.









0. Project setup



Cargo.toml:




[package]
name = "nut"
version = "0.1.0"
edition = "2021"

[dependencies]
cadrum = "0.8.1"






cadrum fetches OpenCASCADE at build time, so on Windows the first build had me waiting a while. The second run was fast because the cache kicked in.









Step 1: one cube



I wanted to confirm that writing code actually got a solid onto the screen, so I just placed a single Solid::cube.




use cadrum::Solid;

fn main() -> Result<(), cadrum::Error> {
let cube: Solid = Solid::cube(10.0, 10.0, 10.0).color("orange");

cube.write_multiview_png(&mut std::fs::File::create("step1.png").unwrap())?;
Solid::write_step([&cube], &mut std::fs::File::create("step1.step").unwrap())?;
Ok(())
}






cargo run to execute.



4-view of a 10mm cube



write_multiview_png packed isometric, front, top, and right views into one PNG, with a scale bar and view-axis icons. While I was iterating on dimensions, it turned out to be faster than STEP. The write_step output I opened in FreeCAD just to sanity-check.









Step 2: extrude a hexagon



The nut's outline is a hex prism, so I built a hexagon profile and extruded it along Z.



For an M2 nut, ISO 4032 gave me width-across-flats s = 4.0 mm and thickness m = 1.6 mm. The distance from center to a vertex (circumscribed radius) came out to s / √3.




use cadrum::{DVec3, Edge, Solid};
use std::f64::consts::TAU;

fn main() -> Result<(), cadrum::Error> {
let s = 4.0;
let m = 1.6;
let r_circum = s / 3f64.sqrt();

let hex_pts: Vec<DVec3> = (0..6)
.map(|i| {
let a = i as f64 * TAU / 6.0;
DVec3::new(r_circum * a.cos(), r_circum * a.sin(), 0.0)
})
.collect();
let hex_profile = Edge::polygon(&hex_pts)?;

let body: Solid = Solid::extrude(&hex_profile, DVec3::Z * m)?.color("orange");

body.write_multiview_png(&mut std::fs::File::create("step2.png").unwrap())?;
Solid::write_step([&body], &mut std::fs::File::create("step2.step").unwrap())?;
Ok(())
}








Passing an array of vertices to Edge::polygon returned a closed polygonal wire. Handing that to Solid::extrude(profile, DVec3::Z * m) let me give direction and length as a single vector.









Step 3: drill the hole



I needed a threaded hole through the middle, so I made a cylinder and subtracted it.



cadrum overloads + / - / * between &Solid for union, difference, and intersection. The return type is Result<Solid, _>, so I chained with ?.




use cadrum::{DVec3, Edge, Solid};
use std::f64::consts::TAU;

fn main() -> Result<(), cadrum::Error> {
let r = 1.0; // M2 nominal radius (major Ø2.0)
let pitch = 0.4; // M2 pitch
let h = 3f64.sqrt() / 2.0 * pitch; // ISO 68-1 fundamental triangle height
let s = 4.0;
let m = 1.6;

let r_circum = s / 3f64.sqrt();
let r_minor = r - h * 6.0 / 8.0; // minor radius (= thread-cutter shaft radius)

let hex_pts: Vec<DVec3> = (0..6)
.map(|i| {
let a = i as f64 * TAU / 6.0;
DVec3::new(r_circum * a.cos(), r_circum * a.sin(), 0.0)
})
.collect();
let body = Solid::extrude(&Edge::polygon(&hex_pts)?, DVec3::Z * m)?;
let bore = Solid::cylinder(r_minor, DVec3::Z, m + 0.4).translate(DVec3::Z * -0.2);

let part: Solid = (&body - &bore)?.color("orange");

part.write_multiview_png(&mut std::fs::File::create("step3.png").unwrap())?;
Solid::write_step([&part], &mut std::fs::File::create("step3.step").unwrap())?;
Ok(())
}






step3: hex prism with hole



At first I set the hole length exactly to m, and the coplanar boolean at the top and bottom faces left a paper-thin shell. Bumping it to m + 0.4 so it pokes out 0.2 mm on each side cleaned that up.



I also went back and forth on the formula for r_minor. The ISO minor radius itself is r - h * 5/8, but using that left a thin wall after the thread cutter was subtracted later. I wanted it to match the thread-cutter shaft radius, so I went with r - h * 6/8.









Step 4: double-face chamfer



Looking at a real ISO 4032 nut from above, the outline came out to a regular 12-gon. It's the corners of the hexagon getting sliced off by a flat plane.




      ___      ←  this "corner sliced at an angle" is the chamfer
/ \
/ \
\ /
\___/






I first reached for the chamfer_edges API, but it only rounds edges and doesn't give the flat facets the spec calls for. The most direct option turned out to be subtracting half_space 12 times — 6 vertices × top/bottom.




use cadrum::{DVec3, Edge, Solid};
use std::f64::consts::TAU;

fn main() -> Result<(), cadrum::Error> {
let r = 1.0;
let pitch = 0.4;
let h = 3f64.sqrt() / 2.0 * pitch;
let s = 4.0;
let m = 1.6;

let r_apothem = s / 2.0; // inscribed radius
let r_circum = s / 3f64.sqrt();
let r_minor = r - h * 6.0 / 8.0;

let cham_angle = 30f64.to_radians(); // ISO 4032 maximum
let r_cham_reach = r_apothem - 0.1;
let cham_outer_h = (r_circum - r_cham_reach) * cham_angle.tan();

let hex_pts: Vec<DVec3> = (0..6)
.map(|i| {
let a = i as f64 * TAU / 6.0;
DVec3::new(r_circum * a.cos(), r_circum * a.sin(), 0.0)
})
.collect();
let mut body = Solid::extrude(&Edge::polygon(&hex_pts)?, DVec3::Z * m)?;

let nr = cham_angle.sin();
let nz = cham_angle.cos();
for i in 0..6 {
let theta = i as f64 * TAU / 6.0;
let p_corner = DVec3::new(r_circum * theta.cos(), r_circum * theta.sin(), 0.0);
let n_radial = DVec3::new(nr * theta.cos(), nr * theta.sin(), 0.0);
body = (&body - &Solid::half_space(
p_corner + DVec3::Z * (m - cham_outer_h),
n_radial + DVec3::Z * nz,
))?;
body = (&body - &Solid::half_space(
p_corner + DVec3::Z * cham_outer_h,
n_radial - DVec3::Z * nz,
))?;
}

let bore = Solid::cylinder(r_minor, DVec3::Z, m + 0.4).translate(DVec3::Z * -0.2);
let part: Solid = (&body - &bore)?.color("orange");

part.write_multiview_png(&mut std::fs::File::create("step4.png").unwrap())?;
Solid::write_step([&part], &mut std::fs::File::create("step4.step").unwrap())?;
Ok(())
}






step4: with double-face chamfer



Solid::half_space(origin, normal) is an infinite solid occupying the side the normal points to; subtracting it removes everything on that side. I used 12 planes, each passing through one of the vertical corner edges, tilted up or down 30° from horizontal.



Initially I had r_cham_reach = r_apothem, sitting exactly on the inscribed circle. In the 4-view, the chamfer width looked noticeably narrower than a conical cut would. A flat plane doesn't reach the middle of each face — it only shaves the corner — so it removes less material than a cone at the same reach. Pulling the reach in by 0.1 mm balanced the look (rule of thumb for M2).



The chamfer-height formula is Δr · tan(angle). I had cot instead of tan once and the result looked like 60°, which someone pointed out.









Step 5: lead-in chamfer



The top and bottom openings of the bore needed a taper — the part that helps a bolt enter.



I wanted to add a conical frustum to each end of the straight cylinder, so I used Solid::cone(r1, r2, axis, h). Even with r1 < r2 it produces a flared frustum without complaint.




use cadrum::{DVec3, Edge, Solid};
use std::f64::consts::TAU;

fn main() -> Result<(), cadrum::Error> {
let r = 1.0;
let pitch = 0.4;
let h = 3f64.sqrt() / 2.0 * pitch;
let s = 4.0;
let m = 1.6;

let r_apothem = s / 2.0;
let r_circum = s / 3f64.sqrt();
let r_minor = r - h * 6.0 / 8.0;

let cham_angle = 30f64.to_radians();
let r_cham_reach = r_apothem - 0.1;
let cham_outer_h = (r_circum - r_cham_reach) * cham_angle.tan();

let r_lead = r;
let cham_lead_h = 0.20;

let hex_pts: Vec<DVec3> = (0..6)
.map(|i| {
let a = i as f64 * TAU / 6.0;
DVec3::new(r_circum * a.cos(), r_circum * a.sin(), 0.0)
})
.collect();
let mut body = Solid::extrude(&Edge::polygon(&hex_pts)?, DVec3::Z * m)?;

let nr = cham_angle.sin();
let nz = cham_angle.cos();
for i in 0..6 {
let theta = i as f64 * TAU / 6.0;
let p_corner = DVec3::new(r_circum * theta.cos(), r_circum * theta.sin(), 0.0);
let n_radial = DVec3::new(nr * theta.cos(), nr * theta.sin(), 0.0);
body = (&body - &Solid::half_space(
p_corner + DVec3::Z * (m - cham_outer_h),
n_radial + DVec3::Z * nz,
))?;
body = (&body - &Solid::half_space(
p_corner + DVec3::Z * cham_outer_h,
n_radial - DVec3::Z * nz,
))?;
}

let main_bore = Solid::cylinder(r_minor, DVec3::Z, m + 0.4)
.translate(DVec3::Z * -0.2);
let top_flare = Solid::cone(r_minor, r_lead, DVec3::Z, cham_lead_h)
.translate(DVec3::Z * (m - cham_lead_h));
let bot_flare = Solid::cone(r_minor, r_lead, -DVec3::Z, cham_lead_h)
.translate(DVec3::Z * cham_lead_h);

// union the three pieces into one cutter and subtract once
let bore_cutter = (&(&main_bore + &top_flare)? + &bot_flare)?;
let part: Solid = (&body - &bore_cutter)?.color("orange");

part.write_multiview_png(&mut std::fs::File::create("step5.png").unwrap())?;
Solid::write_step([&part], &mut std::fs::File::create("step5.step").unwrap())?;
Ok(())
}






step5: with lead-in chamfer



At first I subtracted the bore, top flare, and bottom flare in three separate - calls. The coplanar edges tripped up the boolean engine occasionally. Unioning the three with + into a single cutter and subtracting once made it steady.



For Solid::cone, r1 is the radius at the origin and r2 is at axis * h; passing -DVec3::Z as the axis gives a cone pointing downward.









Step 6: internal thread



Building an ISO 68-1 metric thread from scratch looked like a chore, but framing it as "build the male thread and subtract it from the bore" made it tractable. Same construction as cadrum's own 07_sweep.rs.



The thread cutter combines three pieces:




  1. A triangle swept along a helix — the raw thread fin.

  2. A shaft cylinder (minor diameter) — fills between fins into a continuous shaft.

  3. A crest cylinder (slightly below the major radius) — clips the triangle tips into a flat crest.




use cadrum::{DVec3, Edge, ProfileOrient, Solid, Wire};
use std::f64::consts::TAU;

fn build_m2_hex_nut() -> Result<Solid, cadrum::Error> {
let r = 1.0;
let pitch = 0.4;
let h = 3f64.sqrt() / 2.0 * pitch;
let s = 4.0;
let m = 1.6;

let r_apothem = s / 2.0;
let r_circum = s / 3f64.sqrt();
let r_minor = r - h * 6.0 / 8.0;

let cham_angle = 30f64.to_radians();
let r_cham_reach = r_apothem - 0.1;
let cham_outer_h = (r_circum - r_cham_reach) * cham_angle.tan();

let r_lead = r;
let cham_lead_h = 0.20;

// 1. Hex prism
let hex_pts: Vec<DVec3> = (0..6)
.map(|i| {
let a = i as f64 * TAU / 6.0;
DVec3::new(r_circum * a.cos(), r_circum * a.sin(), 0.0)
})
.collect();
let mut body = Solid::extrude(&Edge::polygon(&hex_pts)?, DVec3::Z * m)?;

// 2. Double-face chamfer (12 half-space cuts)
let nr = cham_angle.sin();
let nz = cham_angle.cos();
for i in 0..6 {
let theta = i as f64 * TAU / 6.0;
let p_corner = DVec3::new(r_circum * theta.cos(), r_circum * theta.sin(), 0.0);
let n_radial = DVec3::new(nr * theta.cos(), nr * theta.sin(), 0.0);
body = (&body - &Solid::half_space(
p_corner + DVec3::Z * (m - cham_outer_h),
n_radial + DVec3::Z * nz,
))?;
body = (&body - &Solid::half_space(
p_corner + DVec3::Z * cham_outer_h,
n_radial - DVec3::Z * nz,
))?;
}
let body = body;

// 3. Bore + lead-in chamfer (union once, subtract once)
let main_bore = Solid::cylinder(r_minor, DVec3::Z, m + 0.4)
.translate(DVec3::Z * -0.2);
let top_flare = Solid::cone(r_minor, r_lead, DVec3::Z, cham_lead_h)
.translate(DVec3::Z * (m - cham_lead_h));
let bot_flare = Solid::cone(r_minor, r_lead, -DVec3::Z, cham_lead_h)
.translate(DVec3::Z * cham_lead_h);
let bore_cutter = (&(&main_bore + &top_flare)? + &bot_flare)?;
let body = (&body - &bore_cutter)?;

// 4. Internal thread
let thread_h = m + 0.4;
let helix = Edge::helix(r - h, pitch, thread_h, DVec3::Z, DVec3::X)?;

// ISO 68-1 fundamental triangle (base on the helix, apex pointing outward)
let tri = Edge::polygon(&[
DVec3::new(0.0, -pitch / 2.0, 0.0),
DVec3::new(h, 0.0, 0.0),
DVec3::new(0.0, pitch / 2.0, 0.0),
])?;
let tri = tri
.align_z(helix.start_tangent(), helix.start_point())
.translate(helix.start_point());

let thread = Solid::sweep(&tri, &[helix], ProfileOrient::Up(DVec3::Z))?;
let shaft = Solid::cylinder(r - h * 6.0 / 8.0, DVec3::Z, thread_h);
let crest = Solid::cylinder(r - h / 8.0, DVec3::Z, thread_h);

// (thread ∪ shaft) ∩ crest = a shaft with fins whose tips have been clipped
let cutter = (&(&thread + &shaft)? * &crest)?.translate(DVec3::Z * -0.2);

let nut = (&body - &cutter)?;
Ok(nut.color("orange"))
}

fn main() -> Result<(), cadrum::Error> {
let nut: Solid = build_m2_hex_nut()?;
nut.write_multiview_png(&mut std::fs::File::create("step6.png").unwrap())?;
Solid::write_step([&nut], &mut std::fs::File::create("step6.step").unwrap())?;
Ok(())
}






step6: final nut



The first time I forgot align_z, the triangle stayed tilted as it traveled along the helix and the thread came out wavy. Calling Wire::align_z(tangent, point) to align the profile's Z axis with the helix tangent kept the same orientation all the way around.



ProfileOrient::Up(DVec3::Z) on Solid::sweep is the "keep the profile pointed Z-up while traversing the spine" mode. That one tripped me up on the first try too.



The final * &crest is the intersection that clips the triangle tips into a flat crest. &a + &b for union, &a - &b for difference, &a * &b for intersection — the consistency made it easy to revisit the code later.



Final screen shots with Fusion 360.



Fusion360



cross section



cross section









APIs that came up












































API Use

Solid::cube / cylinder / cone / sphere
Primitives

Edge::polygon / Edge::helix
Profiles and spines
Solid::extrude(profile, dir) Profile + straight line → solid
Solid::sweep(profile, spine, orient) Profile + curve → solid
Solid::half_space(origin, normal) Infinite half-space (chamfers, splits)

&a + &b / &a - &b / &a * &b
Union / difference / intersection
Wire::align_z(tangent, point) Orient the profile

Solid::write_step / write_multiview_png
Output


This was enough to make it all the way through to the thread. When I changed let r = 1.0; to 2.0 and set s = 7.0; m = 3.2;, an ISO 4032 M4 nut came out as-is, so parameterizing the dimensions paid off.



Next I'll pair it with an M2 bolt and try assembly output via Solid::write_step([&nut, &bolt], ...).

Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten Modeling a nut, in Rust

Thematisch verwandte Begriffe: Modeling, Rust · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Zum Aktualisieren ziehen
ZERO-DAY CVE-2026-61647 | NotebookLM MCP is an MCP server and HTTP service for interacting with Go…
Advisory →
TTS Reader • tsecurity.de Voice
tsecurity.de Icon
tsecurity.de App
Offline-Lesen, Eilmeldungen & 0ms Ladezeit

Installiere tsecurity.de direkt auf deinen Home-Bildschirm für das ultimative Vollbild-Magazinerlebnis ohne Browser-Leisten.

Nächster Beitrag
Themen-Radar & Intelligence Matrix
Echtzeit-Taxonomie nach Angriffsvektoren & Plattformen

tsecurity.de Live Threat Radar

🔴 LIVE RADAR
MONITORING
AKTIV
CVE-DATENBANK
LIVE
🔍
Community Radar & Live Chat
Sentinel Bot online • Live-Stream
Dein Cluster: Security Explorer
Match:
lädt…
Verbindung zum Community-Stream wird aufgebaut...
Bearbeitungsmodus — Senden überschreibt deine Nachricht
Community-Puls — was gerade passiert
lädt…
Aktivitäten deiner Analysten
lädt…
Neues Thema oder Eilmeldung einreichen

Reiche interessante Links, Zero-Days oder Debatten ein. Die Community entscheidet per Upvote über die Veröffentlichung.

Heiß diskutierte Einreichungen
🔖 Gespeicherte Artikel
📂 Keine gespeicherten Artikel vorhanden.
Zurück Ziehen Vor
Links: vorheriger Artikel Rechts: nächster Artikel unten: schließen
News NIS-2 Frühwarnung Tier-1 Intel ⏱️ 3 Min vor 10 Min
Artikeldaten werden geladen...

Zurück: vorheriger Vor: nächster
↗ Original-Quelle
Social Reaktionen Deine Reaktion zählt
Einstufung & Relevanz-Poll 0 Stimmen
In sozialen Netzwerken teilen 1-Klick