feat(model): kernel a82f17d — MASSPROP from B-rep, curved OFFSET/FILLET

- Pins: codec 8189f83, kernel a82f17d, graph d3558bb (all manifests and
  the python plugin lock).
- MASSPROP reports volume, centroid, inertia, products, radii of
  gyration and principal moments of 3D solids and bodies from their
  B-rep (exact where analytic, else from a fine mesh).
- OFFSET of ellipses and splines through the kernel's curve offset.
- FILLET between circles, ellipses and splines through the kernel's
  curve-curve fillet, trimming arcs and open splines to the tangents.

The kernel bump brings the DWG ACIS audit fixes: slices, booleans and
press/pull on cones, slanted cylinder sections, survey coordinates,
spline walls and touching parts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Hakan SevenandClaude Opus 5.5 committed 2026-10-09 08:25:09 +03:00
1 parent d0e4f4097d
commit aa0b87108c
31 files changed
+404 -109

No files matched your search

Generated
+4 -4
View File
@@ -4205,7 +4205,7 @@ dependencies = [
[[package]]
name = "opencadcodec"
version = "0.6.0"
source = "git+https://github.com/HakanSeven12/opencadcodec.git?rev=c9ffcac#c9ffcac5ecdc7a504cc4a7e3ecb52ac9d587c8bf"
source = "git+https://github.com/HakanSeven12/opencadcodec.git?rev=8189f83#8189f833574c544fc57a1d773591014c3a5907c9"
dependencies = [
"anyhow",
"bitflags 2.13.1",
@@ -4228,7 +4228,7 @@ dependencies = [
[[package]]
name = "opencadgraph"
version = "0.1.0"
source = "git+https://github.com/HakanSeven12/opencadgraph.git?rev=7f6aae7#7f6aae7aff6b7de87cc1dcdb38a06c3e64ae1922"
source = "git+https://github.com/HakanSeven12/opencadgraph.git?rev=d3558bb#d3558bb1ccd63d3819fa0358267bac1ba21ab593"
dependencies = [
"opencadkernel",
"serde_json",
@@ -4237,7 +4237,7 @@ dependencies = [
[[package]]
name = "opencadkernel"
version = "0.1.0"
source = "git+https://github.com/HakanSeven12/opencadkernel.git?rev=99b6b48#99b6b4890704d300596ba502b5a1c8ec1eefa954"
source = "git+https://github.com/HakanSeven12/opencadkernel.git?rev=a82f17d#a82f17dd34af0787023c93ed565220dd1467ff46"
dependencies = [
"cavalier_contours",
"opencadcodec",
@@ -4248,7 +4248,7 @@ dependencies = [
[[package]]
name = "opencadkernel-constraints"
version = "0.1.0"
source = "git+https://github.com/HakanSeven12/opencadkernel.git?rev=99b6b48#99b6b4890704d300596ba502b5a1c8ec1eefa954"
source = "git+https://github.com/HakanSeven12/opencadkernel.git?rev=a82f17d#a82f17dd34af0787023c93ed565220dd1467ff46"
dependencies = [
"nalgebra",
]
+4 -4
View File
@@ -34,10 +34,10 @@ rfd = "0.17"
clap = { version = "4", features = ["derive", "string"] }
env_logger = "0.11"
log = "0.4"
codec = { package = "opencadcodec", git = "https://github.com/HakanSeven12/opencadcodec.git", rev = "c9ffcac", features = ["serde"] }
kernel = { package = "opencadkernel", git = "https://github.com/HakanSeven12/opencadkernel.git", rev = "99b6b48", features = ["acis", "offset"] }
kernel-constraints = { package = "opencadkernel-constraints", git = "https://github.com/HakanSeven12/opencadkernel.git", rev = "99b6b48" }
graph = { package = "opencadgraph", git = "https://github.com/HakanSeven12/opencadgraph.git", rev = "7f6aae7" }
codec = { package = "opencadcodec", git = "https://github.com/HakanSeven12/opencadcodec.git", rev = "8189f83", features = ["serde"] }
kernel = { package = "opencadkernel", git = "https://github.com/HakanSeven12/opencadkernel.git", rev = "a82f17d", features = ["acis", "offset"] }
kernel-constraints = { package = "opencadkernel-constraints", git = "https://github.com/HakanSeven12/opencadkernel.git", rev = "a82f17d" }
graph = { package = "opencadgraph", git = "https://github.com/HakanSeven12/opencadgraph.git", rev = "d3558bb" }
dwg-thumbnailer = { path = "crates/dwg-thumbnailer" }
flate2 = "1"
image = { version = "0.25", default-features = false, features = ["png", "jpeg", "bmp", "tiff"] }
+2 -2
View File
@@ -15,7 +15,7 @@ serde_json = { version = "1", optional = true }
# Pulled in only by the `host` feature, which adds the `opencadcodec`-typed
# `HostApi` runtime surface. The default crate stays dependency-free so engine
# crates and external tooling can depend on the manifest/ribbon contract cheaply.
codec = { package = "opencadcodec", git = "https://github.com/HakanSeven12/opencadcodec.git", rev = "c9ffcac", optional = true, features = ["serde"] }
codec = { package = "opencadcodec", git = "https://github.com/HakanSeven12/opencadcodec.git", rev = "8189f83", optional = true, features = ["serde"] }
# Runtime IPC and serialization (host feature only).
interprocess = { version = "2", optional = true }
@@ -39,7 +39,7 @@ serde_json = "1"
serde = { version = "1", features = ["derive"] }
cargo-lock = "11"
# opencadcodec is scanned at build time to generate the embedded type registry.
codec = { package = "opencadcodec", git = "https://github.com/HakanSeven12/opencadcodec.git", rev = "c9ffcac", features = ["serde"] }
codec = { package = "opencadcodec", git = "https://github.com/HakanSeven12/opencadcodec.git", rev = "8189f83", features = ["serde"] }
[dev-dependencies]
serde_json = "1"
+1 -1
View File
@@ -8,7 +8,7 @@ publish = false
crate-type = ["cdylib"]
[dependencies]
codec = { package = "opencadcodec", git = "https://github.com/HakanSeven12/opencadcodec.git", rev = "c9ffcac", features = ["serde"] }
codec = { package = "opencadcodec", git = "https://github.com/HakanSeven12/opencadcodec.git", rev = "8189f83", features = ["serde"] }
bincode = "1.3"
serde = { version = "1", features = ["derive"] }
console_error_panic_hook = "0.1"
+6
View File
@@ -2521,6 +2521,12 @@ inquiry =
.id-specify-point = ID حدد نقطة:
.list-no-entities-selected-select-entities-firs = LIST: لم تُحدد كيانات. حدد الكيانات أولًا.
.massprop-no-entities-selected-select-entities = MASSPROP: لم تُحدد كيانات. حدد الكيانات أولًا.
.massprop-has-no-closed-body-to-measure = MASSPROP: __ocs_fmt_0__ لا يحتوي على جسم مغلق لقياسه.
.volume-4-centroid-4-4-4 = __ocs_fmt_0__ الحجم=__ocs_fmt_1__ مركز الثقل=(__ocs_fmt_2__,__ocs_fmt_3__,__ocs_fmt_4__)
.moments-of-inertia-x-4-y-4-z-4 = عزوم القصور الذاتي X=__ocs_fmt_0__ Y=__ocs_fmt_1__ Z=__ocs_fmt_2__
.products-of-inertia-xy-4-yz-4-zx-4 = جداءات القصور الذاتي XY=__ocs_fmt_0__ YZ=__ocs_fmt_1__ ZX=__ocs_fmt_2__
.radii-of-gyration-x-4-y-4-z-4 = أنصاف أقطار الدوران X=__ocs_fmt_0__ Y=__ocs_fmt_1__ Z=__ocs_fmt_2__
.principal-moments-about-centroid-i-4-j-4-k-4 = العزوم الرئيسية حول مركز الثقل I=__ocs_fmt_0__ J=__ocs_fmt_1__ K=__ocs_fmt_2__
.measure-select-object-to-measure = MEASURE حدد الكائن المراد قياسه:
.measure-specify-segment-length = MEASURE حدد طول المقطع:
.measuregeom-enter-an-option-distance-radius-an = MEASUREGEOM أدخل خيارًا [مسافة/نصف قطر/زاوية/مساحة]:
+6
View File
@@ -2508,6 +2508,12 @@ inquiry =
.id-specify-point = ID Посочи точка:
.list-no-entities-selected-select-entities-firs = LIST: не са избрани обекти. Първо избери обекти.
.massprop-no-entities-selected-select-entities = MASSPROP: не са избрани обекти. Първо избери обекти.
.massprop-has-no-closed-body-to-measure = MASSPROP: __ocs_fmt_0__ няма затворено тяло за измерване.
.volume-4-centroid-4-4-4 = __ocs_fmt_0__ Обем=__ocs_fmt_1__ Център на тежестта=(__ocs_fmt_2__,__ocs_fmt_3__,__ocs_fmt_4__)
.moments-of-inertia-x-4-y-4-z-4 = Инерционни моменти X=__ocs_fmt_0__ Y=__ocs_fmt_1__ Z=__ocs_fmt_2__
.products-of-inertia-xy-4-yz-4-zx-4 = Центробежни моменти XY=__ocs_fmt_0__ YZ=__ocs_fmt_1__ ZX=__ocs_fmt_2__
.radii-of-gyration-x-4-y-4-z-4 = Радиуси на инерция X=__ocs_fmt_0__ Y=__ocs_fmt_1__ Z=__ocs_fmt_2__
.principal-moments-about-centroid-i-4-j-4-k-4 = Главни моменти спрямо центъра на тежестта I=__ocs_fmt_0__ J=__ocs_fmt_1__ K=__ocs_fmt_2__
.measure-select-object-to-measure = MEASURE Избери обект за измерване:
.measure-specify-segment-length = MEASURE Посочи дължина на сегмент:
.measuregeom-enter-an-option-distance-radius-an = MEASUREGEOM Въведи опция [Разстояние/Радиус/Ъгъл/Площ]:
+6
View File
@@ -2508,6 +2508,12 @@ inquiry =
.id-specify-point = ID Určete bod:
.list-no-entities-selected-select-entities-firs = LIST: nebyly vybrány žádné entity. Nejprve vyberte entity.
.massprop-no-entities-selected-select-entities = MASSPROP: nebyly vybrány žádné entity. Nejprve vyberte entity.
.massprop-has-no-closed-body-to-measure = MASSPROP: __ocs_fmt_0__ nemá žádné uzavřené těleso k měření.
.volume-4-centroid-4-4-4 = __ocs_fmt_0__ Objem=__ocs_fmt_1__ Těžiště=(__ocs_fmt_2__,__ocs_fmt_3__,__ocs_fmt_4__)
.moments-of-inertia-x-4-y-4-z-4 = Momenty setrvačnosti X=__ocs_fmt_0__ Y=__ocs_fmt_1__ Z=__ocs_fmt_2__
.products-of-inertia-xy-4-yz-4-zx-4 = Deviační momenty XY=__ocs_fmt_0__ YZ=__ocs_fmt_1__ ZX=__ocs_fmt_2__
.radii-of-gyration-x-4-y-4-z-4 = Poloměry setrvačnosti X=__ocs_fmt_0__ Y=__ocs_fmt_1__ Z=__ocs_fmt_2__
.principal-moments-about-centroid-i-4-j-4-k-4 = Hlavní momenty k těžišti I=__ocs_fmt_0__ J=__ocs_fmt_1__ K=__ocs_fmt_2__
.measure-select-object-to-measure = MEASURE Vyberte objekt k měření:
.measure-specify-segment-length = MEASURE Zadejte délku segmentu:
.measuregeom-enter-an-option-distance-radius-an = MEASUREGEOM Enter možnost [Vzdálenost/Poloměr/Úhel/Area]:
+6
View File
@@ -2509,6 +2509,12 @@ inquiry =
.id-specify-point = ID Anzugeben ist:
.list-no-entities-selected-select-entities-firs = LIST: keine Objekte ausgewählt.
.massprop-no-entities-selected-select-entities = MASSPROP: Keine Objekte ausgewählt.
.massprop-has-no-closed-body-to-measure = MASSPROP: __ocs_fmt_0__ hat keinen geschlossenen Körper zum Messen.
.volume-4-centroid-4-4-4 = __ocs_fmt_0__ Volumen=__ocs_fmt_1__ Schwerpunkt=(__ocs_fmt_2__,__ocs_fmt_3__,__ocs_fmt_4__)
.moments-of-inertia-x-4-y-4-z-4 = Trägheitsmomente X=__ocs_fmt_0__ Y=__ocs_fmt_1__ Z=__ocs_fmt_2__
.products-of-inertia-xy-4-yz-4-zx-4 = Deviationsmomente XY=__ocs_fmt_0__ YZ=__ocs_fmt_1__ ZX=__ocs_fmt_2__
.radii-of-gyration-x-4-y-4-z-4 = Trägheitsradien X=__ocs_fmt_0__ Y=__ocs_fmt_1__ Z=__ocs_fmt_2__
.principal-moments-about-centroid-i-4-j-4-k-4 = Hauptträgheitsmomente um den Schwerpunkt I=__ocs_fmt_0__ J=__ocs_fmt_1__ K=__ocs_fmt_2__
.measure-select-object-to-measure = MEASURE Objekt auswählen, um zu messen:
.measure-specify-segment-length = MEASURE Segmentlänge angeben:
.measuregeom-enter-an-option-distance-radius-an = MEASUREGEOM Geben Sie eine Option ein [Distance/Radius/Angle/ARea]:
+6
View File
@@ -2508,6 +2508,12 @@ inquiry =
.id-specify-point = ID Ορίστε σημείο:
.list-no-entities-selected-select-entities-firs = LIST: δεν έχουν επιλεγεί οντότητες. Επιλέξτε πρώτα οντότητες.
.massprop-no-entities-selected-select-entities = MASSPROP: δεν έχουν επιλεγεί οντότητες. Επιλέξτε πρώτα οντότητες.
.massprop-has-no-closed-body-to-measure = MASSPROP: __ocs_fmt_0__ δεν έχει κλειστό σώμα για μέτρηση.
.volume-4-centroid-4-4-4 = __ocs_fmt_0__ Όγκος=__ocs_fmt_1__ Κέντρο βάρους=(__ocs_fmt_2__,__ocs_fmt_3__,__ocs_fmt_4__)
.moments-of-inertia-x-4-y-4-z-4 = Ροπές αδράνειας X=__ocs_fmt_0__ Y=__ocs_fmt_1__ Z=__ocs_fmt_2__
.products-of-inertia-xy-4-yz-4-zx-4 = Γινόμενα αδράνειας XY=__ocs_fmt_0__ YZ=__ocs_fmt_1__ ZX=__ocs_fmt_2__
.radii-of-gyration-x-4-y-4-z-4 = Ακτίνες αδράνειας X=__ocs_fmt_0__ Y=__ocs_fmt_1__ Z=__ocs_fmt_2__
.principal-moments-about-centroid-i-4-j-4-k-4 = Κύριες ροπές ως προς το κέντρο βάρους I=__ocs_fmt_0__ J=__ocs_fmt_1__ K=__ocs_fmt_2__
.measure-select-object-to-measure = MEASURE Επιλέξτε αντικείμενο προς μέτρηση:
.measure-specify-segment-length = MEASURE Ορίστε μήκος τμήματος:
.measuregeom-enter-an-option-distance-radius-an = MEASUREGEOM Δώστε επιλογή [Απόσταση/Ακτίνα/Γωνία/Εμβαδόν]:
+6
View File
@@ -2509,6 +2509,12 @@ inquiry =
.id-specify-point = ID Specify point:
.list-no-entities-selected-select-entities-firs = LIST: no entities selected. Select entities first.
.massprop-no-entities-selected-select-entities = MASSPROP: no entities selected. Select entities first.
.massprop-has-no-closed-body-to-measure = MASSPROP: __ocs_fmt_0__ has no closed body to measure.
.volume-4-centroid-4-4-4 = __ocs_fmt_0__ Volume=__ocs_fmt_1__ Centroid=(__ocs_fmt_2__,__ocs_fmt_3__,__ocs_fmt_4__)
.moments-of-inertia-x-4-y-4-z-4 = Moments of inertia X=__ocs_fmt_0__ Y=__ocs_fmt_1__ Z=__ocs_fmt_2__
.products-of-inertia-xy-4-yz-4-zx-4 = Products of inertia XY=__ocs_fmt_0__ YZ=__ocs_fmt_1__ ZX=__ocs_fmt_2__
.radii-of-gyration-x-4-y-4-z-4 = Radii of gyration X=__ocs_fmt_0__ Y=__ocs_fmt_1__ Z=__ocs_fmt_2__
.principal-moments-about-centroid-i-4-j-4-k-4 = Principal moments about centroid I=__ocs_fmt_0__ J=__ocs_fmt_1__ K=__ocs_fmt_2__
.measure-select-object-to-measure = MEASURE Select object to measure:
.measure-specify-segment-length = MEASURE Specify segment length:
.measuregeom-enter-an-option-distance-radius-an = MEASUREGEOM Enter an option [Distance/Radius/Angle/ARea]:
+6
View File
@@ -2509,6 +2509,12 @@ inquiry =
.id-specify-point = ID Especifique un punto:
.list-no-entities-selected-select-entities-firs = LIST: no hay entidades seleccionadas. Seleccione primero las entidades.
.massprop-no-entities-selected-select-entities = MASSPROP: no hay entidades seleccionadas. Seleccione primero las entidades.
.massprop-has-no-closed-body-to-measure = MASSPROP: __ocs_fmt_0__ no tiene un cuerpo cerrado que medir.
.volume-4-centroid-4-4-4 = __ocs_fmt_0__ Volumen=__ocs_fmt_1__ Centroide=(__ocs_fmt_2__,__ocs_fmt_3__,__ocs_fmt_4__)
.moments-of-inertia-x-4-y-4-z-4 = Momentos de inercia X=__ocs_fmt_0__ Y=__ocs_fmt_1__ Z=__ocs_fmt_2__
.products-of-inertia-xy-4-yz-4-zx-4 = Productos de inercia XY=__ocs_fmt_0__ YZ=__ocs_fmt_1__ ZX=__ocs_fmt_2__
.radii-of-gyration-x-4-y-4-z-4 = Radios de giro X=__ocs_fmt_0__ Y=__ocs_fmt_1__ Z=__ocs_fmt_2__
.principal-moments-about-centroid-i-4-j-4-k-4 = Momentos principales respecto al centroide I=__ocs_fmt_0__ J=__ocs_fmt_1__ K=__ocs_fmt_2__
.measure-select-object-to-measure = MEASURE Seleccione el objeto que desea medir:
.measure-specify-segment-length = MEASURE Especifique la longitud del segmento:
.measuregeom-enter-an-option-distance-radius-an = MEASUREGEOM Introduzca una opción [Distancia/Radio/Ángulo/Área]:
+6
View File
@@ -2508,6 +2508,12 @@ inquiry =
.id-specify-point = ID Määritä piste:
.list-no-entities-selected-select-entities-firs = LIST: entiteettejä ei ole valittu. Valitse ensin entiteetit.
.massprop-no-entities-selected-select-entities = MASSPROP: entiteettejä ei ole valittu. Valitse ensin entiteetit.
.massprop-has-no-closed-body-to-measure = MASSPROP: __ocs_fmt_0__ ei sisällä suljettua kappaletta mitattavaksi.
.volume-4-centroid-4-4-4 = __ocs_fmt_0__ Tilavuus=__ocs_fmt_1__ Painopiste=(__ocs_fmt_2__,__ocs_fmt_3__,__ocs_fmt_4__)
.moments-of-inertia-x-4-y-4-z-4 = Hitausmomentit X=__ocs_fmt_0__ Y=__ocs_fmt_1__ Z=__ocs_fmt_2__
.products-of-inertia-xy-4-yz-4-zx-4 = Tulohitausmomentit XY=__ocs_fmt_0__ YZ=__ocs_fmt_1__ ZX=__ocs_fmt_2__
.radii-of-gyration-x-4-y-4-z-4 = Hitaussäteet X=__ocs_fmt_0__ Y=__ocs_fmt_1__ Z=__ocs_fmt_2__
.principal-moments-about-centroid-i-4-j-4-k-4 = Päähitausmomentit painopisteen suhteen I=__ocs_fmt_0__ J=__ocs_fmt_1__ K=__ocs_fmt_2__
.measure-select-object-to-measure = MEASURE Valitse mitattava kohde:
.measure-specify-segment-length = MEASURE Määritä segmentin pituus:
.measuregeom-enter-an-option-distance-radius-an = MEASUREGEOM Enter vaihtoehto [etäisyys/säde/kulma/pinta-ala]:
+6
View File
@@ -2509,6 +2509,12 @@ inquiry =
.id-specify-point = ID Spécifier le point:
.list-no-entities-selected-select-entities-firs = LIST : aucune entité sélectionnée. Sélectionnez d'abord les entités.
.massprop-no-entities-selected-select-entities = MASSPROP : aucune entité sélectionnée. Sélectionnez d'abord les entités.
.massprop-has-no-closed-body-to-measure = MASSPROP: __ocs_fmt_0__ n'a aucun corps fermé à mesurer.
.volume-4-centroid-4-4-4 = __ocs_fmt_0__ Volume=__ocs_fmt_1__ Centre de gravité=(__ocs_fmt_2__,__ocs_fmt_3__,__ocs_fmt_4__)
.moments-of-inertia-x-4-y-4-z-4 = Moments d'inertie X=__ocs_fmt_0__ Y=__ocs_fmt_1__ Z=__ocs_fmt_2__
.products-of-inertia-xy-4-yz-4-zx-4 = Produits d'inertie XY=__ocs_fmt_0__ YZ=__ocs_fmt_1__ ZX=__ocs_fmt_2__
.radii-of-gyration-x-4-y-4-z-4 = Rayons de giration X=__ocs_fmt_0__ Y=__ocs_fmt_1__ Z=__ocs_fmt_2__
.principal-moments-about-centroid-i-4-j-4-k-4 = Moments principaux au centre de gravité I=__ocs_fmt_0__ J=__ocs_fmt_1__ K=__ocs_fmt_2__
.measure-select-object-to-measure = MEASURE Sélectionnez l'objet à mesurer :
.measure-specify-segment-length = MEASURE Spécifier la longueur du segment:
.measuregeom-enter-an-option-distance-radius-an = MEASUREGEOM Saisissez une option [Distance/Radius/Angle/ARea] :
+6
View File
@@ -2497,6 +2497,12 @@ inquiry =
.id-specify-point = ID निर्दिष्ट बिंदु:
.list-no-entities-selected-select-entities-firs = LIST: कोई ऑब्जेक्ट नहीं चुना गया। पहले ऑब्जेक्ट चुनें।
.massprop-no-entities-selected-select-entities = MASSPROP: कोई ऑब्जेक्ट नहीं चुना गया। पहले ऑब्जेक्ट चुनें।
.massprop-has-no-closed-body-to-measure = MASSPROP: __ocs_fmt_0__ में मापने के लिए कोई बंद पिंड नहीं है।
.volume-4-centroid-4-4-4 = __ocs_fmt_0__ आयतन=__ocs_fmt_1__ केन्द्रक=(__ocs_fmt_2__,__ocs_fmt_3__,__ocs_fmt_4__)
.moments-of-inertia-x-4-y-4-z-4 = जड़त्व आघूर्ण X=__ocs_fmt_0__ Y=__ocs_fmt_1__ Z=__ocs_fmt_2__
.products-of-inertia-xy-4-yz-4-zx-4 = जड़त्व गुणनफल XY=__ocs_fmt_0__ YZ=__ocs_fmt_1__ ZX=__ocs_fmt_2__
.radii-of-gyration-x-4-y-4-z-4 = घूर्णन त्रिज्याएँ X=__ocs_fmt_0__ Y=__ocs_fmt_1__ Z=__ocs_fmt_2__
.principal-moments-about-centroid-i-4-j-4-k-4 = केन्द्रक के सापेक्ष मुख्य आघूर्ण I=__ocs_fmt_0__ J=__ocs_fmt_1__ K=__ocs_fmt_2__
.measure-select-object-to-measure = MEASURE ऑब्जेक्ट को मापने के लिए चुनें:
.measure-specify-segment-length = MEASURE सेगमेंट की लंबाई निर्दिष्ट करें:
.measuregeom-enter-an-option-distance-radius-an = MEASUREGEOM विकल्प दर्ज करें [दूरी / त्रिज्या / कोण / क्षेत्रफल]:
+6
View File
@@ -2508,6 +2508,12 @@ inquiry =
.id-specify-point = ID Pont megadása:
.list-no-entities-selected-select-entities-firs = LIST: nincsenek kiválasztva entitások. Először válassza ki az entitásokat.
.massprop-no-entities-selected-select-entities = MASSPROP: nincsenek kiválasztva entitások. Először válassza ki az entitásokat.
.massprop-has-no-closed-body-to-measure = MASSPROP: __ocs_fmt_0__ nem tartalmaz mérhető zárt testet.
.volume-4-centroid-4-4-4 = __ocs_fmt_0__ Térfogat=__ocs_fmt_1__ Súlypont=(__ocs_fmt_2__,__ocs_fmt_3__,__ocs_fmt_4__)
.moments-of-inertia-x-4-y-4-z-4 = Tehetetlenségi nyomatékok X=__ocs_fmt_0__ Y=__ocs_fmt_1__ Z=__ocs_fmt_2__
.products-of-inertia-xy-4-yz-4-zx-4 = Deviációs nyomatékok XY=__ocs_fmt_0__ YZ=__ocs_fmt_1__ ZX=__ocs_fmt_2__
.radii-of-gyration-x-4-y-4-z-4 = Tehetetlenségi sugarak X=__ocs_fmt_0__ Y=__ocs_fmt_1__ Z=__ocs_fmt_2__
.principal-moments-about-centroid-i-4-j-4-k-4 = Fő tehetetlenségi nyomatékok a súlypontra I=__ocs_fmt_0__ J=__ocs_fmt_1__ K=__ocs_fmt_2__
.measure-select-object-to-measure = MEASURE Válassza ki a mérendő objektumot:
.measure-specify-segment-length = MEASURE Adja meg a szegmens hosszát:
.measuregeom-enter-an-option-distance-radius-an = MEASUREGEOM Enter egy opció [Távolság / Sugár / Szög / Terület]:
+6
View File
@@ -2508,6 +2508,12 @@ inquiry =
.id-specify-point = ID Specificare il punto:
.list-no-entities-selected-select-entities-firs = LIST: nessuna entità selezionata. Seleziona prima le entità.
.massprop-no-entities-selected-select-entities = MASSPROP: nessuna entità selezionata. Seleziona prima le entità.
.massprop-has-no-closed-body-to-measure = MASSPROP: __ocs_fmt_0__ non ha un corpo chiuso da misurare.
.volume-4-centroid-4-4-4 = __ocs_fmt_0__ Volume=__ocs_fmt_1__ Baricentro=(__ocs_fmt_2__,__ocs_fmt_3__,__ocs_fmt_4__)
.moments-of-inertia-x-4-y-4-z-4 = Momenti d'inerzia X=__ocs_fmt_0__ Y=__ocs_fmt_1__ Z=__ocs_fmt_2__
.products-of-inertia-xy-4-yz-4-zx-4 = Prodotti d'inerzia XY=__ocs_fmt_0__ YZ=__ocs_fmt_1__ ZX=__ocs_fmt_2__
.radii-of-gyration-x-4-y-4-z-4 = Raggi di girazione X=__ocs_fmt_0__ Y=__ocs_fmt_1__ Z=__ocs_fmt_2__
.principal-moments-about-centroid-i-4-j-4-k-4 = Momenti principali rispetto al baricentro I=__ocs_fmt_0__ J=__ocs_fmt_1__ K=__ocs_fmt_2__
.measure-select-object-to-measure = MEASURE Selezionare l'oggetto da misurare:
.measure-specify-segment-length = MEASURE Specificare la lunghezza del segmento:
.measuregeom-enter-an-option-distance-radius-an = MEASUREGEOM Enter un'opzione [Distanza/Raggio/Angolo/ARea]:
+6
View File
@@ -2499,6 +2499,12 @@ inquiry =
.id-specify-point = ID 点を指定:
.list-no-entities-selected-select-entities-firs = LIST: 図形が選択されていません。先に選択してください。
.massprop-no-entities-selected-select-entities = MASSPROP: 図形が選択されていません。先に選択してください。
.massprop-has-no-closed-body-to-measure = MASSPROP: __ocs_fmt_0__ には計測できる閉じたボディがありません。
.volume-4-centroid-4-4-4 = __ocs_fmt_0__ 体積=__ocs_fmt_1__ 重心=(__ocs_fmt_2__,__ocs_fmt_3__,__ocs_fmt_4__)
.moments-of-inertia-x-4-y-4-z-4 = 慣性モーメント X=__ocs_fmt_0__ Y=__ocs_fmt_1__ Z=__ocs_fmt_2__
.products-of-inertia-xy-4-yz-4-zx-4 = 慣性乗積 XY=__ocs_fmt_0__ YZ=__ocs_fmt_1__ ZX=__ocs_fmt_2__
.radii-of-gyration-x-4-y-4-z-4 = 回転半径 X=__ocs_fmt_0__ Y=__ocs_fmt_1__ Z=__ocs_fmt_2__
.principal-moments-about-centroid-i-4-j-4-k-4 = 重心まわりの主慣性モーメント I=__ocs_fmt_0__ J=__ocs_fmt_1__ K=__ocs_fmt_2__
.measure-select-object-to-measure = MEASURE 計測するオブジェクトを選択:
.measure-specify-segment-length = MEASURE 区間長を指定:
.measuregeom-enter-an-option-distance-radius-an = MEASUREGEOM オプションを入力 [距離/半径/角度/面積]:
+6
View File
@@ -2508,6 +2508,12 @@ inquiry =
.id-specify-point = ID 지점 지정:
.list-no-entities-selected-select-entities-firs = LIST: 선택된 엔터티가 없습니다. 먼저 엔터티를 선택하세요.
.massprop-no-entities-selected-select-entities = MASSPROP: 선택된 엔터티가 없습니다. 먼저 엔터티를 선택하세요.
.massprop-has-no-closed-body-to-measure = MASSPROP: __ocs_fmt_0__ 에 측정할 닫힌 바디가 없습니다.
.volume-4-centroid-4-4-4 = __ocs_fmt_0__ 부피=__ocs_fmt_1__ 도심=(__ocs_fmt_2__,__ocs_fmt_3__,__ocs_fmt_4__)
.moments-of-inertia-x-4-y-4-z-4 = 관성 모멘트 X=__ocs_fmt_0__ Y=__ocs_fmt_1__ Z=__ocs_fmt_2__
.products-of-inertia-xy-4-yz-4-zx-4 = 관성 곱 XY=__ocs_fmt_0__ YZ=__ocs_fmt_1__ ZX=__ocs_fmt_2__
.radii-of-gyration-x-4-y-4-z-4 = 회전 반경 X=__ocs_fmt_0__ Y=__ocs_fmt_1__ Z=__ocs_fmt_2__
.principal-moments-about-centroid-i-4-j-4-k-4 = 도심에 대한 주 모멘트 I=__ocs_fmt_0__ J=__ocs_fmt_1__ K=__ocs_fmt_2__
.measure-select-object-to-measure = MEASURE 측정할 물체 선택:
.measure-specify-segment-length = MEASURE 세그먼트 길이 지정:
.measuregeom-enter-an-option-distance-radius-an = MEASUREGEOM Enter 옵션 [거리/반경/각도/지역]:
+6
View File
@@ -2509,6 +2509,12 @@ inquiry =
.id-specify-point = ID ກຳນົດຈຸດ:
.list-no-entities-selected-select-entities-firs = LIST: ບໍ່ມີອົງປະກອບຖືກເລືອກ. ກະລຸນາເລືອກອົງປະກອບກ່ອນ.
.massprop-no-entities-selected-select-entities = MASSPROP: ບໍ່ມີອົງປະກອບຖືກເລືອກ. ກະລຸນາເລືອກອົງປະກອບກ່ອນ.
.massprop-has-no-closed-body-to-measure = MASSPROP: __ocs_fmt_0__ ບໍ່ມີຕົວປິດໃຫ້ວັດແທກ.
.volume-4-centroid-4-4-4 = __ocs_fmt_0__ ປະລິມາດ=__ocs_fmt_1__ ຈຸດສູນກາງມວນ=(__ocs_fmt_2__,__ocs_fmt_3__,__ocs_fmt_4__)
.moments-of-inertia-x-4-y-4-z-4 = ໂມເມັນຄວາມເສື່ອຍ X=__ocs_fmt_0__ Y=__ocs_fmt_1__ Z=__ocs_fmt_2__
.products-of-inertia-xy-4-yz-4-zx-4 = ຜົນຄູນຄວາມເສື່ອຍ XY=__ocs_fmt_0__ YZ=__ocs_fmt_1__ ZX=__ocs_fmt_2__
.radii-of-gyration-x-4-y-4-z-4 = ລັດສະໝີການໝູນ X=__ocs_fmt_0__ Y=__ocs_fmt_1__ Z=__ocs_fmt_2__
.principal-moments-about-centroid-i-4-j-4-k-4 = ໂມເມັນຫຼັກອ້ອມຈຸດສູນກາງມວນ I=__ocs_fmt_0__ J=__ocs_fmt_1__ K=__ocs_fmt_2__
.measure-select-object-to-measure = MEASURE ເລືອກວັດຖຸທີ່ຈະວັດແທກ:
.measure-specify-segment-length = MEASURE ກຳນົດຄວາມຍາວຂອງສ່ວນແບ່ງ:
.measuregeom-enter-an-option-distance-radius-an = MEASUREGEOM ປ້ອນຕົວເລືອກ [Distance/Radius/Angle/ARea]:
+6
View File
@@ -2509,6 +2509,12 @@ inquiry =
.id-specify-point = ID Specificeer punt:
.list-no-entities-selected-select-entities-firs = LIST: geen objecten geselecteerd. Selecteer eerst objecten.
.massprop-no-entities-selected-select-entities = MASSPROP: geen objecten geselecteerd. Selecteer eerst objecten.
.massprop-has-no-closed-body-to-measure = MASSPROP: __ocs_fmt_0__ heeft geen gesloten lichaam om te meten.
.volume-4-centroid-4-4-4 = __ocs_fmt_0__ Volume=__ocs_fmt_1__ Zwaartepunt=(__ocs_fmt_2__,__ocs_fmt_3__,__ocs_fmt_4__)
.moments-of-inertia-x-4-y-4-z-4 = Traagheidsmomenten X=__ocs_fmt_0__ Y=__ocs_fmt_1__ Z=__ocs_fmt_2__
.products-of-inertia-xy-4-yz-4-zx-4 = Deviatiemomenten XY=__ocs_fmt_0__ YZ=__ocs_fmt_1__ ZX=__ocs_fmt_2__
.radii-of-gyration-x-4-y-4-z-4 = Gyratiestralen X=__ocs_fmt_0__ Y=__ocs_fmt_1__ Z=__ocs_fmt_2__
.principal-moments-about-centroid-i-4-j-4-k-4 = Hoofdtraagheidsmomenten om het zwaartepunt I=__ocs_fmt_0__ J=__ocs_fmt_1__ K=__ocs_fmt_2__
.measure-select-object-to-measure = MEASURE Selecteer object om mee te meten:
.measure-specify-segment-length = MEASURE segmentlengte specificeren:
.measuregeom-enter-an-option-distance-radius-an = MEASUREGEOM Voer een optie in [Afstand / Straal / Hoek / Oppervlakte]:
+6
View File
@@ -2508,6 +2508,12 @@ inquiry =
.id-specify-point = ID Określ punkt:
.list-no-entities-selected-select-entities-firs = LIST: nie wybrano żadnych elementów. Najpierw wybierz elementy.
.massprop-no-entities-selected-select-entities = MASSPROP: nie wybrano żadnych elementów. Najpierw wybierz elementy.
.massprop-has-no-closed-body-to-measure = MASSPROP: __ocs_fmt_0__ nie ma zamkniętej bryły do zmierzenia.
.volume-4-centroid-4-4-4 = __ocs_fmt_0__ Objętość=__ocs_fmt_1__ Środek ciężkości=(__ocs_fmt_2__,__ocs_fmt_3__,__ocs_fmt_4__)
.moments-of-inertia-x-4-y-4-z-4 = Momenty bezwładności X=__ocs_fmt_0__ Y=__ocs_fmt_1__ Z=__ocs_fmt_2__
.products-of-inertia-xy-4-yz-4-zx-4 = Momenty dewiacyjne XY=__ocs_fmt_0__ YZ=__ocs_fmt_1__ ZX=__ocs_fmt_2__
.radii-of-gyration-x-4-y-4-z-4 = Promienie bezwładności X=__ocs_fmt_0__ Y=__ocs_fmt_1__ Z=__ocs_fmt_2__
.principal-moments-about-centroid-i-4-j-4-k-4 = Główne momenty względem środka ciężkości I=__ocs_fmt_0__ J=__ocs_fmt_1__ K=__ocs_fmt_2__
.measure-select-object-to-measure = MEASURE Wybierz obiekt do pomiaru:
.measure-specify-segment-length = MEASURE Określ długość segmentu:
.measuregeom-enter-an-option-distance-radius-an = MEASUREGEOM Enter opcja [Odległość/Promień/Kąt/Powierzchnia]:
+6
View File
@@ -2509,6 +2509,12 @@ inquiry =
.id-specify-point = ID Especifique o ponto:
.list-no-entities-selected-select-entities-firs = LIST: nenhuma entidade selecionada. Selecione primeiro as entidades.
.massprop-no-entities-selected-select-entities = MASSPROP: nenhuma entidade selecionada. Selecione primeiro as entidades.
.massprop-has-no-closed-body-to-measure = MASSPROP: __ocs_fmt_0__ não tem um corpo fechado para medir.
.volume-4-centroid-4-4-4 = __ocs_fmt_0__ Volume=__ocs_fmt_1__ Centroide=(__ocs_fmt_2__,__ocs_fmt_3__,__ocs_fmt_4__)
.moments-of-inertia-x-4-y-4-z-4 = Momentos de inércia X=__ocs_fmt_0__ Y=__ocs_fmt_1__ Z=__ocs_fmt_2__
.products-of-inertia-xy-4-yz-4-zx-4 = Produtos de inércia XY=__ocs_fmt_0__ YZ=__ocs_fmt_1__ ZX=__ocs_fmt_2__
.radii-of-gyration-x-4-y-4-z-4 = Raios de giração X=__ocs_fmt_0__ Y=__ocs_fmt_1__ Z=__ocs_fmt_2__
.principal-moments-about-centroid-i-4-j-4-k-4 = Momentos principais em relação ao centroide I=__ocs_fmt_0__ J=__ocs_fmt_1__ K=__ocs_fmt_2__
.measure-select-object-to-measure = MEASURE Selecione o objeto a medir:
.measure-specify-segment-length = MEASURE Especifique o comprimento do segmento:
.measuregeom-enter-an-option-distance-radius-an = MEASUREGEOM Insira uma opção [Distância/Raio/Ângulo/ÁRea]:
+6
View File
@@ -2512,6 +2512,12 @@ inquiry =
.id-specify-point = ID Укажите точку:
.list-no-entities-selected-select-entities-firs = LIST: не выбрано ни одного объекта.
.massprop-no-entities-selected-select-entities = MASSPROP: не выбрано ни одного объекта.
.massprop-has-no-closed-body-to-measure = MASSPROP: __ocs_fmt_0__ не имеет замкнутого тела для измерения.
.volume-4-centroid-4-4-4 = __ocs_fmt_0__ Объём=__ocs_fmt_1__ Центр масс=(__ocs_fmt_2__,__ocs_fmt_3__,__ocs_fmt_4__)
.moments-of-inertia-x-4-y-4-z-4 = Моменты инерции X=__ocs_fmt_0__ Y=__ocs_fmt_1__ Z=__ocs_fmt_2__
.products-of-inertia-xy-4-yz-4-zx-4 = Центробежные моменты XY=__ocs_fmt_0__ YZ=__ocs_fmt_1__ ZX=__ocs_fmt_2__
.radii-of-gyration-x-4-y-4-z-4 = Радиусы инерции X=__ocs_fmt_0__ Y=__ocs_fmt_1__ Z=__ocs_fmt_2__
.principal-moments-about-centroid-i-4-j-4-k-4 = Главные моменты относительно центра масс I=__ocs_fmt_0__ J=__ocs_fmt_1__ K=__ocs_fmt_2__
.measure-select-object-to-measure = MEASURE Выберите объект для измерения:
.measure-specify-segment-length = MEASURE Укажите длину сегмента:
.measuregeom-enter-an-option-distance-radius-an = MEASUREGEOM Введите опцию [Расстояние/Радиус/Англия/Арея]:
+6
View File
@@ -2490,6 +2490,12 @@ inquiry =
.id-specify-point = ID Noktayı belirtin:
.list-no-entities-selected-select-entities-firs = LIST: Nesne seçilmedi. Önce nesneleri seçin.
.massprop-no-entities-selected-select-entities = MASSPROP: Nesne seçilmedi. Önce nesneleri seçin.
.massprop-has-no-closed-body-to-measure = MASSPROP: __ocs_fmt_0__ için ölçülecek kapalı gövde yok.
.volume-4-centroid-4-4-4 = __ocs_fmt_0__ Hacim=__ocs_fmt_1__ Ağırlık merkezi=(__ocs_fmt_2__,__ocs_fmt_3__,__ocs_fmt_4__)
.moments-of-inertia-x-4-y-4-z-4 = Eylemsizlik momentleri X=__ocs_fmt_0__ Y=__ocs_fmt_1__ Z=__ocs_fmt_2__
.products-of-inertia-xy-4-yz-4-zx-4 = Eylemsizlik çarpımları XY=__ocs_fmt_0__ YZ=__ocs_fmt_1__ ZX=__ocs_fmt_2__
.radii-of-gyration-x-4-y-4-z-4 = Jirasyon yarıçapları X=__ocs_fmt_0__ Y=__ocs_fmt_1__ Z=__ocs_fmt_2__
.principal-moments-about-centroid-i-4-j-4-k-4 = Ağırlık merkezine göre asal momentler I=__ocs_fmt_0__ J=__ocs_fmt_1__ K=__ocs_fmt_2__
.measure-select-object-to-measure = MEASURE Ölçülecek nesneyi seçin:
.measure-specify-segment-length = MEASURE Parça uzunluğunu belirtin:
.measuregeom-enter-an-option-distance-radius-an = MEASUREGEOM Bir seçenek girin [Mesafe/Yarıçap/Açı/Alan]:
+6
View File
@@ -2496,6 +2496,12 @@ inquiry =
.id-specify-point = ID 指定点:
.list-no-entities-selected-select-entities-firs = LIST:没有选择实体。首先选择实体。
.massprop-no-entities-selected-select-entities = MASSPROP:没有选择实体。首先选择实体。
.massprop-has-no-closed-body-to-measure = MASSPROP: __ocs_fmt_0__ 没有可测量的封闭实体。
.volume-4-centroid-4-4-4 = __ocs_fmt_0__ 体积=__ocs_fmt_1__ 质心=(__ocs_fmt_2__,__ocs_fmt_3__,__ocs_fmt_4__)
.moments-of-inertia-x-4-y-4-z-4 = 惯性矩 X=__ocs_fmt_0__ Y=__ocs_fmt_1__ Z=__ocs_fmt_2__
.products-of-inertia-xy-4-yz-4-zx-4 = 惯性积 XY=__ocs_fmt_0__ YZ=__ocs_fmt_1__ ZX=__ocs_fmt_2__
.radii-of-gyration-x-4-y-4-z-4 = 回转半径 X=__ocs_fmt_0__ Y=__ocs_fmt_1__ Z=__ocs_fmt_2__
.principal-moments-about-centroid-i-4-j-4-k-4 = 关于质心的主力矩 I=__ocs_fmt_0__ J=__ocs_fmt_1__ K=__ocs_fmt_2__
.measure-select-object-to-measure = MEASURE 选择要定距等分的对象:
.measure-specify-segment-length = MEASURE 指定线段长度:
.measuregeom-enter-an-option-distance-radius-an = MEASUREGEOM 输入选项 [距离/半径/角度/面积]:
+6
View File
@@ -2508,6 +2508,12 @@ inquiry =
.id-specify-point = ID 指定點:
.list-no-entities-selected-select-entities-firs = LIST:沒有選擇實體。首先選擇實體。
.massprop-no-entities-selected-select-entities = MASSPROP:沒有選擇實體。首先選擇實體。
.massprop-has-no-closed-body-to-measure = MASSPROP: __ocs_fmt_0__ 沒有可量測的封閉實體。
.volume-4-centroid-4-4-4 = __ocs_fmt_0__ 體積=__ocs_fmt_1__ 質心=(__ocs_fmt_2__,__ocs_fmt_3__,__ocs_fmt_4__)
.moments-of-inertia-x-4-y-4-z-4 = 慣性矩 X=__ocs_fmt_0__ Y=__ocs_fmt_1__ Z=__ocs_fmt_2__
.products-of-inertia-xy-4-yz-4-zx-4 = 慣性積 XY=__ocs_fmt_0__ YZ=__ocs_fmt_1__ ZX=__ocs_fmt_2__
.radii-of-gyration-x-4-y-4-z-4 = 迴轉半徑 X=__ocs_fmt_0__ Y=__ocs_fmt_1__ Z=__ocs_fmt_2__
.principal-moments-about-centroid-i-4-j-4-k-4 = 關於質心的主力矩 I=__ocs_fmt_0__ J=__ocs_fmt_1__ K=__ocs_fmt_2__
.measure-select-object-to-measure = MEASURE 選擇要定距等分的物件:
.measure-specify-segment-length = MEASURE 指定線段長度:
.measuregeom-enter-an-option-distance-radius-an = MEASUREGEOM 輸入選項 [距離/半徑/角度/面積]:
+1 -1
View File
@@ -1264,7 +1264,7 @@ dependencies = [
[[package]]
name = "opencadcodec"
version = "0.6.0"
source = "git+https://github.com/HakanSeven12/opencadcodec.git?rev=c9ffcac#c9ffcac5ecdc7a504cc4a7e3ecb52ac9d587c8bf"
source = "git+https://github.com/HakanSeven12/opencadcodec.git?rev=8189f83#8189f833574c544fc57a1d773591014c3a5907c9"
dependencies = [
"anyhow",
"bitflags",
+57
View File
@@ -1114,6 +1114,63 @@ impl OpenCADStudio {
for (handle, _) in &selected {
if let Some(entity) = self.tabs[i].scene.document.get_entity(*handle) {
use crate::entities::traits::EntityTypeOps;
// A solid's properties come from its B-rep: exact
// where the kernel knows the shape, a fine mesh
// otherwise — not the display mesh.
let acis = match entity {
codec::EntityType::Solid3D(solid) => Some(&solid.acis_data),
codec::EntityType::Body(body) => Some(&body.acis_data),
_ => None,
};
if let Some(acis) = acis {
let properties = crate::scene::convert::solid3d_tess::kernel_acis_body(acis)
.and_then(|body| kernel::brep::mass_properties(&body));
let Some(p) = properties else {
self.command_line.push_error(crate::tf!(
"MASSPROP: {} has no closed body to measure.",
crate::entities::names::dxf_name(entity),
).as_ref());
continue;
};
let [x, y, z] = p.centroid;
let [ix, iy, iz] = p.moment_of_inertia;
let [pxy, pyz, pzx] = p.product_of_inertia;
let [rx, ry, rz] = p.radii_of_gyration;
let [i1, i2, i3] = p.principal_moments;
self.command_line.push_output(crate::tf!(
"{} Volume={:.4} Centroid=({:.4},{:.4},{:.4})",
crate::entities::names::dxf_name(entity),
p.volume,
x,
y,
z,
).as_ref());
self.command_line.push_output(crate::tf!(
"Moments of inertia X={:.4} Y={:.4} Z={:.4}",
ix,
iy,
iz,
).as_ref());
self.command_line.push_output(crate::tf!(
"Products of inertia XY={:.4} YZ={:.4} ZX={:.4}",
pxy,
pyz,
pzx,
).as_ref());
self.command_line.push_output(crate::tf!(
"Radii of gyration X={:.4} Y={:.4} Z={:.4}",
rx,
ry,
rz,
).as_ref());
self.command_line.push_output(crate::tf!(
"Principal moments about centroid I={:.4} J={:.4} K={:.4}",
i1,
i2,
i3,
).as_ref());
continue;
}
if let Some(props) = entity.mass_props() {
self.command_line.push_output(crate::tf!(
"{} Area={:.4} Perimeter={:.4} Centroid=({:.4},{:.4})",
+6
View File
@@ -1933,6 +1933,12 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static
"M density" => Some(("properties", "m-density")),
"M vertex count" => Some(("properties", "m-vertex-count")),
"MASSPROP: no entities selected. Select entities first." => Some(("inquiry", "massprop-no-entities-selected-select-entities")),
"MASSPROP: {} has no closed body to measure." => Some(("inquiry", "massprop-has-no-closed-body-to-measure")),
"{} Volume={:.4} Centroid=({:.4},{:.4},{:.4})" => Some(("inquiry", "volume-4-centroid-4-4-4")),
"Moments of inertia X={:.4} Y={:.4} Z={:.4}" => Some(("inquiry", "moments-of-inertia-x-4-y-4-z-4")),
"Products of inertia XY={:.4} YZ={:.4} ZX={:.4}" => Some(("inquiry", "products-of-inertia-xy-4-yz-4-zx-4")),
"Radii of gyration X={:.4} Y={:.4} Z={:.4}" => Some(("inquiry", "radii-of-gyration-x-4-y-4-z-4")),
"Principal moments about centroid I={:.4} J={:.4} K={:.4}" => Some(("inquiry", "principal-moments-about-centroid-i-4-j-4-k-4")),
"MATCHPROP Select destination objects (Enter to finish):" => Some(("properties", "matchprop-select-destination-objects-enter-to")),
"MATCHPROP Select source object:" => Some(("properties", "matchprop-select-source-object")),
"MEASURE Select object to measure:" => Some(("inquiry", "measure-select-object-to-measure")),
+136
View File
@@ -769,6 +769,9 @@ enum FilletEntity {
handle: Handle,
seg_idx: usize,
},
/// A circle, ellipse or spline: filleted through the kernel's general
/// construction, and trimmed only when it has ends to trim.
Curve(EntityType),
}
impl FilletEntity {
@@ -776,6 +779,9 @@ impl FilletEntity {
match e {
EntityType::Line(l) => Some(Self::Line(l.clone())),
EntityType::Arc(a) => Some(Self::Arc(a.clone())),
EntityType::Circle(_) | EntityType::Ellipse(_) | EntityType::Spline(_) => {
crate::entities::curve::entity_curve_xy(e).map(|_| Self::Curve(e.clone()))
}
_ => None,
}
}
@@ -794,6 +800,7 @@ impl FilletEntity {
Self::Line(l) => EntityType::Line(l.clone()),
Self::Arc(a) => EntityType::Arc(a.clone()),
Self::LwPoly { poly, .. } => EntityType::LwPolyline(poly.clone()),
Self::Curve(entity) => entity.clone(),
}
}
@@ -802,6 +809,20 @@ impl FilletEntity {
Self::Line(l) => l.start.z,
Self::Arc(a) => a.center.z,
Self::LwPoly { poly, .. } => poly.elevation,
Self::Curve(EntityType::Circle(c)) => c.center.z,
Self::Curve(EntityType::Ellipse(e)) => e.center.z,
Self::Curve(EntityType::Spline(s)) => s.control_points.first().map_or(0.0, |p| p.z),
Self::Curve(_) => 0.0,
}
}
/// The shape as a kernel curve in plan.
fn kernel_curve(&self) -> Option<KernelCurve> {
match self {
Self::Line(l) => Some(line_curve(l)),
Self::Arc(a) => Some(arc_curve(a)),
Self::LwPoly { poly, seg_idx, .. } => Some(line_curve(&lwpoly_seg_as_line(poly, *seg_idx))),
Self::Curve(entity) => crate::entities::curve::entity_curve_xy(entity),
}
}
}
@@ -825,6 +846,9 @@ fn compute_fillet_entities(
if picks_arc_segment(e1) || picks_arc_segment(e2) {
return None;
}
if matches!(e1, FilletEntity::Curve(_)) || matches!(e2, FilletEntity::Curve(_)) {
return fillet_curves(e1, click1, e2, click2, radius, z);
}
match (e1, e2) {
// ── Line × Line ───────────────────────────────────────────────────
@@ -1011,6 +1035,8 @@ fn compute_fillet_entities(
None
}
}
// Answered by `fillet_curves` above.
(FilletEntity::Curve(_), _) | (_, FilletEntity::Curve(_)) => None,
}
}
@@ -1268,6 +1294,116 @@ fn fillet_arc_arc(
None
}
/// FILLET where one side is a circle, ellipse or spline. The kernel finds the
/// tangent arcs; each side is then cut back to its tangent point, keeping the
/// part that was picked. A closed curve has no end to cut back and is left
/// whole.
fn fillet_curves(
e1: &FilletEntity,
click1: [f64; 2],
e2: &FilletEntity,
click2: [f64; 2],
radius: f64,
z: f64,
) -> Option<(EntityType, EntityType, Option<EntityType>)> {
if radius < 1e-9 {
return None;
}
let (a, b) = (e1.kernel_curve()?, e2.kernel_curve()?);
let common = match e1 {
FilletEntity::Line(l) => l.common.clone(),
FilletEntity::Arc(a) => a.common.clone(),
FilletEntity::LwPoly { poly, .. } => poly.common.clone(),
FilletEntity::Curve(entity) => entity.common().clone(),
};
for fillet in ranked_fillets(&a, click1, &b, click2, radius) {
let (Some(first), Some(second)) = (
trim_to_tangent(e1, &a, fillet.tangent1, click1),
trim_to_tangent(e2, &b, fillet.tangent2, click2),
) else {
continue;
};
return Some((
first,
second,
Some(EntityType::Arc(fillet_entity(&fillet, radius, z, &common))),
));
}
None
}
/// One side of a fillet cut back to where the arc touches it, the picked part
/// kept. `None` when the touch is off a bounded curve's drawn part — only a
/// line may be extended to meet the arc.
fn trim_to_tangent(
entity: &FilletEntity,
curve: &KernelCurve,
touch: [f64; 2],
click: [f64; 2],
) -> Option<EntityType> {
match entity {
FilletEntity::Line(l) => trim_line_to_point(l, touch, click).map(EntityType::Line),
FilletEntity::LwPoly { poly, seg_idx, .. } => {
let line = lwpoly_seg_as_line(poly, *seg_idx);
let trimmed = trim_line_to_point(&line, touch, click)?;
Some(EntityType::LwPolyline(rebuild_poly_from_trimmed_line(poly, *seg_idx, &line, &trimmed)))
}
FilletEntity::Arc(arc) => {
let (centre, _, start, end, _) = arc_geom(arc);
let at = arc_angle_at(centre, touch);
let clamped = clamp_angle_to_arc(at, start, end);
if (norm_angle(at) - norm_angle(clamped)).abs() > 0.01 {
return None;
}
let picked = clamp_angle_to_arc(arc_angle_at(centre, click), start, end);
Some(EntityType::Arc(
if (clamped - start).rem_euclid(TAU) <= (picked - start).rem_euclid(TAU) {
trim_arc(arc, clamped, end)
} else {
trim_arc(arc, start, clamped)
},
))
}
FilletEntity::Curve(source) => {
let at = curve.parameter_at(touch);
let on = curve.point_at(at);
let scale = 1e-6 * (1.0 + touch[0].abs().max(touch[1].abs()));
if (on[0] - touch[0]).hypot(on[1] - touch[1]) > scale.max(1e-6 * curve.length()) {
return None;
}
if curve.is_closed() {
return Some(source.clone());
}
let keep_end = curve.parameter_at(click) >= at;
match source {
// Its plan image runs the entity's own parameter only when
// it faces up; a mirrored one is left alone.
EntityType::Ellipse(ellipse) if ellipse.normal.z > 0.0 => {
let mut trimmed = ellipse.clone();
trimmed.common.handle = Handle::NULL;
let sweep = {
let raw = ellipse.end_parameter - ellipse.start_parameter;
if raw <= 0.0 { raw + TAU } else { raw }
};
let cut = ellipse.start_parameter + at * sweep;
if keep_end {
trimmed.start_parameter = cut;
} else {
trimmed.end_parameter = cut;
}
Some(EntityType::Ellipse(trimmed))
}
EntityType::Spline(spline) => {
let (low, high) = super::spline_ops::spline_range(spline)?;
let (head, tail) = super::spline_ops::spline_cut(spline, low + at * (high - low))?;
Some(EntityType::Spline(if keep_end { tail } else { head }))
}
_ => None,
}
}
}
}
/// Trim a line endpoint nearest to the intersection point, keeping the click side.
fn trim_line_to_point(line: &LineEnt, isect: [f64; 2], click: [f64; 2]) -> Option<LineEnt> {
let mut l = line.clone();
+61 -97
View File
@@ -15,7 +15,6 @@
// offset at once on the cursor's side; a negative one, on the other side.
use crate::entities::curve::{entity_curve, lwpolyline_world_xy};
use crate::modules::draw::modify::spline_ops::spline_sample_xy;
use codec::entities::LwVertex;
use codec::entities::{
Arc as ArcEnt, Circle as CircleEnt, Ellipse as EllipseEnt, Line as LineEnt, LwPolyline,
@@ -24,7 +23,6 @@ use codec::entities::{
use codec::{EntityType, Handle};
// Polyline offsetting, and the angle normalisation that goes with it, come
// from the kernel; only the entity conversion stays here.
use kernel::geom2d::nurbs::clamped_uniform_knots;
use kernel::geom2d::{
offset_polyline, Polyline as KernelPolyline, PolylineVertex as KernelVertex,
};
@@ -205,111 +203,77 @@ fn offset_lwpolyline(p: &LwPolyline, dist: f64, side_pt: Vec3) -> Option<EntityT
offset_lwpolylines(p, dist, side_pt).into_iter().next()
}
// ── Ellipse offset ─────────────────────────────────────────────────────────
// ── Ellipse and spline offset ──────────────────────────────────────────────
//
// A true offset of an ellipse is a Lamé curve, not an ellipse. As an
// acceptable CAD approximation we scale both semi-axes uniformly and keep
// the same orientation, center and parameter range. The sign of the offset
// is determined by whether side_pt is inside or outside the ellipse.
// Neither has a parallel of its own kind — an ellipse's is not an ellipse —
// so both come back as a spline fitted through true offset points, within a
// millionth of the curve's size, by the kernel. The work happens in the
// curve's own plane, so a tilted or extruded one offsets in place.
fn offset_ellipse(e: &EllipseEnt, dist: f64, side_pt: Vec3) -> Option<EntityType> {
let a = (e.major_axis.x.powi(2) + e.major_axis.y.powi(2)).sqrt();
if a < 1e-9 {
return None;
}
let b = a * e.minor_axis_ratio;
let nx = e.major_axis.x / a;
let ny = e.major_axis.y / a;
// Project side_pt onto ellipse local frame and test inside/outside.
let rx = side_pt.x as f64 - e.center.x;
let ry = side_pt.y as f64 - e.center.y;
let xl = rx * nx + ry * ny;
let yl = -rx * ny + ry * nx;
let inside = (xl / a).powi(2) + (yl / b).powi(2) < 1.0;
let sign = if inside { -1.0 } else { 1.0 };
let new_a = a + sign * dist;
let new_b = b + sign * dist;
if new_a <= 1e-9 || new_b <= 1e-9 {
return None;
}
let mut new_e = e.clone();
new_e.common.handle = Handle::NULL;
// Scale the major_axis vector proportionally.
let scale = new_a / a;
new_e.major_axis.x *= scale;
new_e.major_axis.y *= scale;
new_e.major_axis.z *= scale;
new_e.minor_axis_ratio = new_b / new_a;
Some(EntityType::Ellipse(new_e))
let source = EntityType::Ellipse(e.clone());
let mut template = SplineEnt::new();
template.common = e.common.clone();
offset_curved(&source, template, dist, side_pt)
}
// ── Spline offset ──────────────────────────────────────────────────────────
//
// Strategy: sample the spline into N points, offset each sample point by
// `dist` along the local perpendicular (based on the finite-difference
// tangent), then fit a new spline through the offset points.
fn offset_spline(spl: &SplineEnt, dist: f64, side_pt: Vec3) -> Option<EntityType> {
let (ts_knot, pts) = spline_sample_xy(spl, 64);
let n = pts.len();
if n < 2 {
offset_curved(&EntityType::Spline(spl.clone()), spl.clone(), dist, side_pt)
}
fn offset_curved(
source: &EntityType,
mut template: SplineEnt,
dist: f64,
side_pt: Vec3,
) -> Option<EntityType> {
let planar = entity_curve(source)?;
let side = planar
.plane
.project([side_pt.x as f64, side_pt.y as f64, side_pt.z as f64])?;
// Left of the curve's direction of travel is the kernel's positive side.
let curve = &planar.curve;
let at = curve.parameter_at(side);
let step = 1e-6;
let (before, after) = if curve.is_closed() {
(curve.point_at((at - step).rem_euclid(1.0)), curve.point_at((at + step).rem_euclid(1.0)))
} else {
(curve.point_at((at - step).max(0.0)), curve.point_at((at + step).min(1.0)))
};
let foot = curve.point_at(at);
let along = [after[0] - before[0], after[1] - before[1]];
let toward = [side[0] - foot[0], side[1] - foot[1]];
let left = along[0] * toward[1] - along[1] * toward[0] > 0.0;
let signed = if left { dist.abs() } else { -dist.abs() };
let tolerance = (curve.length() * 1e-6).max(1e-9);
let kernel::geom2d::Curve::Nurbs(parallel) =
kernel::geom2d::offset_curve(curve, signed, tolerance)?
else {
return None;
}
// Determine offset sign from the first non-degenerate tangent.
let sign: f64 = (0..n - 1).find_map(|i| {
let dx = pts[i + 1][0] - pts[i][0];
let dy = pts[i + 1][1] - pts[i][1];
let len = (dx * dx + dy * dy).sqrt();
if len < 1e-12 {
return None;
}
let vx = side_pt.x as f64 - pts[i][0];
let vy = side_pt.y as f64 - pts[i][1];
let cross = dx * vy - dy * vx;
Some(if cross >= 0.0 { 1.0 } else { -1.0 })
})?;
// Offset each sample point along the local normal.
let offset_pts: Vec<codec::types::Vector3> = pts
};
template.common.handle = Handle::NULL;
template.degree = parallel.degree() as i32;
template.knots = parallel.knots().to_vec();
template.control_points = parallel
.control_points()
.iter()
.enumerate()
.map(|(i, p)| {
// Tangent via central / forward / backward difference.
let (dx, dy) = if i == 0 {
let d = [pts[1][0] - pts[0][0], pts[1][1] - pts[0][1]];
(d[0], d[1])
} else if i == n - 1 {
let d = [pts[n - 1][0] - pts[n - 2][0], pts[n - 1][1] - pts[n - 2][1]];
(d[0], d[1])
} else {
(
(pts[i + 1][0] - pts[i - 1][0]) * 0.5,
(pts[i + 1][1] - pts[i - 1][1]) * 0.5,
)
};
let len = (dx * dx + dy * dy).sqrt().max(1e-12);
let nx = -dy / len; // left perpendicular
let ny = dx / len;
let z = spl.control_points.first().map(|v| v.z).unwrap_or(0.0);
codec::types::Vector3::new(p[0] + sign * nx * dist, p[1] + sign * ny * dist, z)
.map(|uv| {
let [x, y, z] = planar.plane.point_at(*uv);
codec::types::Vector3::new(x, y, z)
})
.collect();
let _ = ts_knot;
// Build a new spline from the offset control points (treat sample pts as fit pts → ctrl pts).
let degree = spl.degree.max(1) as usize;
let new_ctrl: Vec<codec::types::Vector3> = offset_pts;
let n_ctrl = new_ctrl.len();
let mut new_spl = spl.clone();
new_spl.common.handle = Handle::NULL;
new_spl.control_points = new_ctrl;
new_spl.knots = clamped_uniform_knots(degree, n_ctrl);
new_spl.fit_points.clear();
new_spl.weights.clear();
Some(EntityType::Spline(new_spl))
template.weights = if parallel.is_rational() { parallel.weights().to_vec() } else { Vec::new() };
template.flags.rational = parallel.is_rational();
template.flags.periodic = false;
template.flags.closed = curve.is_closed();
if let Some([x, y, z]) = planar.plane.normal() {
template.normal = codec::types::Vector3::new(x, y, z);
}
// A parallel is no longer the curve through the source's fit points.
template.fit_points.clear();
template.begin_tangent = codec::types::Vector3::new(0.0, 0.0, 0.0);
template.end_tangent = codec::types::Vector3::new(0.0, 0.0, 0.0);
Some(EntityType::Spline(template))
}
// ── Dispatch ───────────────────────────────────────────────────────────────