Merge fixes from the ui test pass and the gates

Main's thread.direction keys go with develop's thread hand, which replaced them. Vietnamese for the
messages develop added while main made vi a complete catalog. CGA and 1-bit themes set develop's
comb and section hatch colours. A ribbon button with a persistent effect is pressed only while its
tool runs, so persistent_ribbon_pressed alone decides how its effect is shown; other analyses stay
pressed while on. Two of main's ui tests listed as failing on SMLib, with why.
Gates: fixed thread tables become slices, one collapsed if, rustfmt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MNXLXkZLQ6T7TSi4wswcmk
This commit is contained in:
brett19
2026-10-31 19:38:55 -07:00
parent 11a89713c7
commit 48fcb7aac1
35 changed files with 206 additions and 89 deletions
-3
View File
@@ -3842,10 +3842,7 @@
"the two origins are on the same component": "oba ishodišta su na istoj komponenti",
"thread.automatic_size": "Automatska veličina",
"thread.designation": "Oznaka",
"thread.direction": "Smjer navoja",
"thread.iso_metric": "ISO metrički",
"thread.left_hand": "Lijevi",
"thread.right_hand": "Desni",
"thread.select_size": "Izaberite veličinu",
"timeline.delete_after_marker_one": "Izbriši 1 operaciju nakon oznake vraćanja",
"timeline.delete_after_marker_other": "Izbriši operacije nakon oznake vraćanja: {count}",
-3
View File
@@ -3842,10 +3842,7 @@
"the two origins are on the same component": "oba počátky jsou na téže komponentě",
"thread.automatic_size": "Automatická velikost",
"thread.designation": "Označení",
"thread.direction": "Směr závitu",
"thread.iso_metric": "Metrický ISO",
"thread.left_hand": "Levotočivý",
"thread.right_hand": "Pravotočivý",
"thread.select_size": "Vybrat velikost",
"timeline.delete_after_marker_one": "Smazat 1 prvek za značkou historie",
"timeline.delete_after_marker_other": "Smazat prvky za značkou historie: {count}",
-3
View File
@@ -3842,10 +3842,7 @@
"the two origins are on the same component": "de to nulpunkter er på samme komponent",
"thread.automatic_size": "Automatisk størrelse",
"thread.designation": "Betegnelse",
"thread.direction": "Gevindretning",
"thread.iso_metric": "ISO-metrisk",
"thread.left_hand": "Venstregænget",
"thread.right_hand": "Højregænget",
"thread.select_size": "Vælg størrelse",
"timeline.delete_after_marker_one": "Slet 1 feature efter historikmarkøren",
"timeline.delete_after_marker_other": "Slet {count} features efter historikmarkøren",
-3
View File
@@ -3883,10 +3883,7 @@
"the two origins are on the same component": "die beiden Ursprünge liegen auf derselben Komponente",
"thread.automatic_size": "Automatische Größe",
"thread.designation": "Bezeichnung",
"thread.direction": "Gewinderichtung",
"thread.iso_metric": "ISO metrisch",
"thread.left_hand": "Linksgewinde",
"thread.right_hand": "Rechtsgewinde",
"thread.select_size": "Größe wählen",
"timeline.delete_after_marker_one": "1 Merkmal nach der Verlaufsmarkierung löschen",
"timeline.delete_after_marker_other": "{count} Merkmale nach der Verlaufsmarkierung löschen",
-3
View File
@@ -3589,10 +3589,7 @@
"the two origins are on the same component": "los dos orígenes están en el mismo componente",
"thread.automatic_size": "Tamaño automático",
"thread.designation": "Designación",
"thread.direction": "Dirección de la rosca",
"thread.iso_metric": "Métrica ISO",
"thread.left_hand": "Zurda",
"thread.right_hand": "Diestra",
"thread.select_size": "Seleccionar tamaño",
"timeline.delete_after_marker_one": "Eliminar 1 operación después del marcador de la línea de tiempo",
"timeline.delete_after_marker_other": "Eliminar {count} operaciones después del marcador de la línea de tiempo",
-3
View File
@@ -3563,10 +3563,7 @@
"the two origins are on the same component": "molemmat origot ovat samassa komponentissa",
"thread.automatic_size": "Automaattinen koko",
"thread.designation": "Merkintä",
"thread.direction": "Kierteen suunta",
"thread.iso_metric": "ISO-metrinen",
"thread.left_hand": "Vasenkätinen",
"thread.right_hand": "Oikeakätinen",
"thread.select_size": "Valitse koko",
"timeline.delete_after_marker_one": "Poista 1 piirre historiamerkin jälkeen",
"timeline.delete_after_marker_other": "Poista {count} piirrettä historiamerkin jälkeen",
-3
View File
@@ -3852,10 +3852,7 @@
"the two origins are on the same component": "les deux origines se trouvent sur le même composant",
"thread.automatic_size": "Taille automatique",
"thread.designation": "Désignation",
"thread.direction": "Sens du filetage",
"thread.iso_metric": "Métrique ISO",
"thread.left_hand": "À gauche",
"thread.right_hand": "À droite",
"thread.select_size": "Sélectionner la taille",
"timeline.delete_after_marker_one": "Supprimer 1 fonction après le marqueur d'historique",
"timeline.delete_after_marker_other": "Supprimer {count} fonctions après le marqueur d'historique",
-3
View File
@@ -3842,10 +3842,7 @@
"the two origins are on the same component": "as dúas orixes están no mesmo compoñente",
"thread.automatic_size": "Tamaño automático",
"thread.designation": "Designación",
"thread.direction": "Dirección da rosca",
"thread.iso_metric": "Métrica ISO",
"thread.left_hand": "Á esquerda",
"thread.right_hand": "Á dereita",
"thread.select_size": "Seleccionar o tamaño",
"timeline.delete_after_marker_one": "Eliminar 1 operación despois do marcador de historial",
"timeline.delete_after_marker_other": "Eliminar {count} operacións despois do marcador de historial",
-3
View File
@@ -3842,10 +3842,7 @@
"the two origins are on the same component": "oba ishodišta nalaze se na istoj komponenti",
"thread.automatic_size": "Automatska veličina",
"thread.designation": "Oznaka",
"thread.direction": "Smjer navoja",
"thread.iso_metric": "ISO metrički",
"thread.left_hand": "Lijevi",
"thread.right_hand": "Desni",
"thread.select_size": "Odaberite veličinu",
"timeline.delete_after_marker_one": "Izbriši 1 značajku nakon oznake vraćanja",
"timeline.delete_after_marker_other": "Izbriši značajke nakon oznake vraćanja: {count}",
-3
View File
@@ -3842,10 +3842,7 @@
"the two origins are on the same component": "a két origó ugyanazon a komponensen van",
"thread.automatic_size": "Automatikus méret",
"thread.designation": "Jelölés",
"thread.direction": "Menet iránya",
"thread.iso_metric": "ISO metrikus",
"thread.left_hand": "Balmenetes",
"thread.right_hand": "Jobbmenetes",
"thread.select_size": "Méret kiválasztása",
"timeline.delete_after_marker_one": "1 elem törlése az előzményjelölő után",
"timeline.delete_after_marker_other": "{count} elem törlése az előzményjelölő után",
-3
View File
@@ -3842,10 +3842,7 @@
"the two origins are on the same component": "kedua origin berada pada komponen yang sama",
"thread.automatic_size": "Ukuran otomatis",
"thread.designation": "Penunjukan",
"thread.direction": "Arah ulir",
"thread.iso_metric": "ISO metrik",
"thread.left_hand": "Ulir kiri",
"thread.right_hand": "Ulir kanan",
"thread.select_size": "Pilih ukuran",
"timeline.delete_after_marker_one": "Hapus 1 Fitur Setelah Penanda Riwayat",
"timeline.delete_after_marker_other": "Hapus {count} Fitur Setelah Penanda Riwayat",
-3
View File
@@ -3842,10 +3842,7 @@
"the two origins are on the same component": "kedua-dua titik asal berada pada komponen yang sama",
"thread.automatic_size": "Saiz automatik",
"thread.designation": "Penamaan",
"thread.direction": "Arah bebenang",
"thread.iso_metric": "Metrik ISO",
"thread.left_hand": "Tangan kiri",
"thread.right_hand": "Tangan kanan",
"thread.select_size": "Pilih saiz",
"timeline.delete_after_marker_one": "Padam 1 Ciri Selepas Penanda Sejarah",
"timeline.delete_after_marker_other": "Padam {count} Ciri Selepas Penanda Sejarah",
-3
View File
@@ -3842,10 +3842,7 @@
"the two origins are on the same component": "de to origoene ligger på samme komponent",
"thread.automatic_size": "Automatisk størrelse",
"thread.designation": "Betegnelse",
"thread.direction": "Gjengeretning",
"thread.iso_metric": "ISO-metrisk",
"thread.left_hand": "Venstregjenget",
"thread.right_hand": "Høyregjenget",
"thread.select_size": "Velg størrelse",
"timeline.delete_after_marker_one": "Slett 1 funksjon etter historikkmarkøren",
"timeline.delete_after_marker_other": "Slett {count} funksjoner etter historikkmarkøren",
-3
View File
@@ -3842,10 +3842,7 @@
"the two origins are on the same component": "de twee oorsprongen liggen op hetzelfde component",
"thread.automatic_size": "Automatische grootte",
"thread.designation": "Aanduiding",
"thread.direction": "Richting van schroefdraad",
"thread.iso_metric": "ISO metrisch",
"thread.left_hand": "Linksdraaiend",
"thread.right_hand": "Rechtsdraaiend",
"thread.select_size": "Grootte selecteren",
"timeline.delete_after_marker_one": "1 kenmerk na geschiedenismarkering verwijderen",
"timeline.delete_after_marker_other": "{count} kenmerken na geschiedenismarkering verwijderen",
+121
View File
@@ -0,0 +1,121 @@
{
"vi": {
"ui.continuity_smooth_curvature_change": "Thay đổi độ cong trơn",
"ui.continuity_smooth_curvature_change_tip": "Thay đổi độ cong trơn (G4): ngay cả cách độ cong thay đổi cũng liền mạch qua đường nối.",
"ui.fillet_corner_tangent_tip": "Tiếp tuyến (G1): góc bi lăn, một phần mặt cầu của bi lăn tiếp tuyến với từng đường bo.",
"ui.fillet_corner_curvature_tip": "Độ cong (G2): một mảng trơn. Góc được lùi dọc theo mỗi cạnh một khoảng Lùi ({factor} × bán kính nếu bạn không nhập) rồi được lấp bằng một mảng, nên phản chiếu đi qua mà không bị gãy.",
"ui.continuity_curvature_change_tip": "Thay đổi độ cong (G3): độ cong cũng thay đổi trơn qua đường nối.",
"ui.continuity_curvature_tip": "Liên tục độ cong (G2): phản chiếu đi qua mà không bị gãy.",
"ui.continuity_position_tip": "Vị trí (G0): các bề mặt gặp nhau, tạo nếp gấp tại nơi đó.",
"ui.continuity_tangent_tip": "Tiếp tuyến (G1): không có nếp gấp, nhưng độ cong nhảy qua đường nối.",
"ui.comb_length": "Độ dài lược",
"ui.comb_length_tip": "Độ dài của các răng: bội số của độ dài tự động, độ dài này làm răng dài nhất bằng một phần năm kích thước các đường cong được hiển thị.",
"ui.comb_teeth": "Răng",
"ui.comb_teeth_tip": "Số răng của mỗi lược, cách đều dọc theo đường cong. Càng nhiều răng càng thấy rõ độ cong thay đổi thế nào.",
"ui.fillet_corner_ball": "Bi lăn",
"ui.fillet_corner_ball_tip": "Một phần mặt cầu của bi lăn, tiếp tuyến (G1) với từng đường bo.",
"ui.fillet_corner_needs_constant": "Góc mảng trơn (G2) cần Bán kính không đổi và Khớp chính xác.",
"ui.fillet_corner_patch": "Mảng trơn",
"ui.fillet_corner_patch_tip": "Liên tục độ cong (G2): góc được lùi dọc theo mỗi cạnh và được lấp bằng một mảng, nên phản chiếu đi qua mà không bị gãy.",
"ui.fillet_fit_needs_constant": "Khớp bán kính cần Bán kính không đổi.",
"ui.fillet_setback": "Lùi",
"ui.fillet_setback_hint": "{factor} × bán kính",
"ui.fillet_setback_tip": "Nơi mảng góc trơn bắt đầu: khoảng cách dọc theo mỗi cạnh tính từ góc. Mặc định là {factor} × bán kính. Giá trị phải lớn hơn khoảng lùi nhỏ nhất, tại đó các đường bo gặp nhau mà không cần lùi: bằng bán kính khi các mặt vuông góc với nhau.",
"ui.fillet_smooth_radius": "Thay đổi bán kính trơn",
"ui.fillet_type_curvature": "Liên tục độ cong",
"ui.fillet_type_tip": "Mặt cắt ngang qua cạnh. Liên tục độ cong: mặt cắt liên tục độ cong (G2) giữa các mặt phẳng, nên phản chiếu đi từ các mặt vào đường bo mà không bị gãy.",
"ui.copy_details": "Sao chép chi tiết",
"ui.copy_details_tip": "Sao chép toàn bộ thông báo để tra cứu hoặc báo cáo.",
"ui.fillet_error_corner": "Không thể tạo góc trơn tại đây. Hãy thử khoảng Lùi khác hoặc góc Tiếp tuyến.",
"ui.fillet_error_failed": "Không thể tạo đường bo này trên các cạnh này. Hãy thử bán kính khác hoặc bo ít cạnh hơn một lần.",
"ui.fillet_error_too_large": "Bán kính quá lớn so với các mặt bên cạnh những cạnh này. Hãy thử bán kính nhỏ hơn hoặc đặt Giới hạn mặt thành Bán kính lớn nhất.",
"ui.fillet_fit": "Giới hạn mặt",
"ui.fillet_fit_exact": "Bán kính chính xác",
"ui.fillet_fit_reduce": "Bán kính lớn nhất",
"ui.fillet_fit_taper": "Bán kính thu hẹp",
"ui.fillet_fit_tip": "Khi bán kính lớn hơn mức các mặt bên cạnh một cạnh cho phép.\nBán kính chính xác: đúng bán kính đã nhập, hoặc không bo gì và có thông báo lý do.\nBán kính lớn nhất: mọi cạnh được bo với bán kính lớn nhất vừa khớp.\nBán kính thu hẹp: giữ bán kính, đường bo chỉ thu hẹp ở nơi các mặt hết chỗ.",
"ui.fillet_options_tip": "Chuỗi tiếp tuyến: bo cả các cạnh nối tiếp trơn từ những cạnh đã chọn.\nXem trước: hiển thị kết quả trong khung nhìn khi bạn chỉnh sửa.",
"ui.fillet_type_choices_tip": "Bán kính không đổi: một bán kính dọc suốt cạnh.\nĐộ dài dây cung: độ rộng ngang đường bo giữ nguyên, bất kể góc gặp nhau của các mặt.\nBán kính thay đổi: một bán kính ở mỗi đầu, và thêm dọc cạnh nếu bạn thêm vào.\nLiên tục độ cong: mặt cắt không làm gãy phản chiếu tại nơi gặp các mặt phẳng (G2).\nSpline: bán kính dọc cạnh theo các bán kính điều khiển bạn đặt.",
"ui.fillet_setback_least": "Lớn hơn {least}",
"help.version": "Phiên bản {version}",
"import_report.refused_one": "1 chi tiết không thể tạo thành khối đặc và không được nhập.",
"import_report.refused_other": "{count} chi tiết không thể tạo thành khối đặc và không được nhập.",
"import_report.details": "Chi tiết:",
"import_report.file_one": "Có 1 vấn đề khác với tệp.",
"import_report.file_other": "Có {count} vấn đề khác với tệp.",
"import_report.hint": "Xuất lại các chi tiết này từ chương trình đã tạo ra chúng có thể khắc phục được. Sao chép thông báo cảnh báo có đầy đủ chi tiết.",
"import_report.left_out_one": "1 mặt không thể sửa và bị thiếu.",
"import_report.left_out_other": "{count} mặt không thể sửa và bị thiếu.",
"import_report.not_imported_one": "1 chi tiết không thể nhập.",
"import_report.not_imported_other": "{count} chi tiết không thể nhập.",
"import_report.not_shown_one": "1 mặt không thể vẽ và bị ẩn.",
"import_report.not_shown_other": "{count} mặt không thể vẽ và bị ẩn.",
"import_report.open_one": "1 chi tiết có khe hở giữa các mặt và được nhập thành thân hở.",
"import_report.open_other": "{count} chi tiết có khe hở giữa các mặt và được nhập thành thân hở.",
"import_report.repaired_one": "Đã sửa các lỗi nhỏ trong 1 chi tiết.",
"import_report.repaired_other": "Đã sửa các lỗi nhỏ trong {count} chi tiết.",
"import_report.solids": "Đã nhập {solids} trong {parts} chi tiết thành khối đặc.",
"import_report.surfaces_one": "1 chi tiết không thể tạo thành khối đặc và được nhập thành bề mặt.",
"import_report.surfaces_other": "{count} chi tiết không thể tạo thành khối đặc và được nhập thành bề mặt.",
"file_load.step_build": "Đang dựng mô hình",
"file_load.step_feature": "Đặc trưng {done} trong {total}",
"file_load.step_part": "Chi tiết {done} trong {total}",
"file_load.step_percent": "{percent}%",
"file_load.step_read": "Đang đọc tệp",
"file_load.step_rebuild": "Đang dựng lại các đặc trưng",
"file_load.step_repair": "Đang kiểm tra và sửa các chi tiết",
"file_load.step_show": "Đang chuẩn bị khung nhìn",
"ui.patch_continuity_corner_tip": "Mảng gặp các mặt xung quanh với độ liên tục đã chọn dọc theo mỗi cạnh. Tại góc nơi các mặt đó gặp nhau với độ cong khác nhau, mảng giữ tiếp tuyến (G1) ở đó.",
"thread.form_bspp": "Ống G (ISO 228-1)",
"thread.form_bspt": "Ống côn R, Rc (ISO 7-1)",
"thread.form_npt": "Ống côn NPT (ASME B1.20.1)",
"thread.form_rp": "Ống Rp (ISO 7-1)",
"cmd.diagnostics_profile": "Hồ sơ đo",
"prefs.profile_operations": "Đo thời gian thao tác",
"prefs.profile_operations_note": "Ghi lại thời gian các lệnh gọi SMLib, thao tác kernel, đặc trưng, lệnh và lần nhập. Tác tử đọc bằng diagnostics.profile.",
"diagnostic.reselect_reference": "{feature}: tham chiếu `{reference}` không còn tồn tại như khi được chọn; hãy chọn lại",
"Repair Part": "Sửa chi tiết",
"browser.needs_repair": "{name}: cần sửa",
"cmd.body_repair": "Sửa chi tiết",
"import_report.needs_repair_one": "1 chi tiết cần sửa trước khi có thể mô hình hóa tiếp.",
"import_report.needs_repair_other": "{count} chi tiết cần sửa trước khi có thể mô hình hóa tiếp.",
"refused.change": "Không thể thay đổi {body}.",
"refused.cut": "Không thể cắt {body}.",
"refused.face_curved": "Một trong các mặt cong của nó không dùng được để mô hình hóa.",
"refused.face_flat": "Một trong các mặt phẳng của nó không dùng được để mô hình hóa.",
"refused.face_freeform": "Một trong các mặt tự do của nó không dùng được để mô hình hóa.",
"refused.face_rounded": "Một trong các mặt bo tròn của nó không dùng được để mô hình hóa.",
"refused.faces_apart": "Các mặt của nó không gặp nhau đủ khít để dùng cho mô hình hóa.",
"refused.intersect": "Không thể giao với {body}.",
"refused.join": "Không thể nối vào {body}.",
"refused.repair": "Sửa chi tiết",
"refused.show_face": "Hiện mặt",
"prefs.section_hatch": "Gạch mặt cắt",
"prefs.section_hatch_by_part": "Theo chi tiết",
"prefs.section_hatch_one": "Một kiểu",
"thread.drill_range": "Mũi khoan của nhà sản xuất taro là {drill} mm. Đường kính trong tiêu chuẩn của ren trong là {minor} mm. Chấp nhận mũi khoan từ {low} đến {high} mm.",
"thread.drill_range_custom": "Mũi khoan bằng đường kính ngoài trừ bước ren, {drill} mm. Đường kính trong tiêu chuẩn của ren trong là {minor} mm. Chấp nhận mũi khoan từ {low} đến {high} mm.",
"thread.drill_range_taper": "Mũi khoan của nhà sản xuất taro là {drill} mm. Đường kính trong theo tiêu chuẩn tại nơi ren kết thúc là {minor} mm. Chấp nhận mũi khoan từ {low} đến {high} mm.",
"thread.fits_the_face": "vừa với mặt, ví dụ {example}",
"ui.chamfer_angle": "Góc vát",
"ui.chamfer_depth": "Chiều sâu vát",
"ui.ends": "Các đầu",
"ui.extent": "Phạm vi",
"ui.iso_4753": "ISO 4753",
"thread.chamfer_depth_needed": "bắt buộc với dạng này",
"thread.end_square": "Vuông",
"thread.fade_short": "1,25 P (ISO 3508)",
"thread.fade_standard": "2,5 P (ISO 3508)",
"ui.end": "Đầu",
"ui.fade": "Thoát ren",
"ui.fade_length": "Chiều dài thoát ren",
"cmd.diagnostics_validation": "Kiểm tra",
"prefs.validation": "Kiểm tra thân",
"prefs.validation.each_feature": "Một lần mỗi đặc trưng",
"prefs.validation.each_step": "Sau mỗi bước",
"prefs.validation.label": "Kiểm tra kernel đầy đủ",
"prefs.validation.off": "Tắt (thử nghiệm)",
"prefs.validation_note": "Sau mỗi bước: kiểm tra từng lần quét, phép boolean và đường bo.\nMột lần mỗi đặc trưng: kiểm tra kết quả của từng đặc trưng một lần.\nTắt (thử nghiệm): không kiểm tra.",
"cmd.inspect_analysis": "Phân tích khung nhìn"
}
}
-3
View File
@@ -2248,10 +2248,7 @@
"the two origins are on the same component": "oba początki leżą na tym samym komponencie",
"thread.automatic_size": "Rozmiar automatyczny",
"thread.designation": "Oznaczenie",
"thread.direction": "Kierunek gwintu",
"thread.iso_metric": "Metryczny ISO",
"thread.left_hand": "Lewy",
"thread.right_hand": "Prawy",
"thread.select_size": "Wybierz rozmiar",
"timeline.delete_after_marker_one": "Usuń 1 funkcję za znacznikiem historii",
"timeline.delete_after_marker_other": "Usuń funkcje za znacznikiem historii: {count}",
-3
View File
@@ -3842,10 +3842,7 @@
"the two origins are on the same component": "cele două origini sunt pe aceeași componentă",
"thread.automatic_size": "Dimensiune automată",
"thread.designation": "Denumire",
"thread.direction": "Sensul filetului",
"thread.iso_metric": "ISO metric",
"thread.left_hand": "Pe stânga",
"thread.right_hand": "Pe dreapta",
"thread.select_size": "Selectați dimensiunea",
"timeline.delete_after_marker_one": "Șterge 1 operație după marcajul de istoric",
"timeline.delete_after_marker_other": "Șterge {count} operații după marcajul de istoric",
-3
View File
@@ -3748,10 +3748,7 @@
"the two origins are on the same component": "обе точки сопряжения находятся на одном компоненте",
"thread.automatic_size": "Автоматический размер",
"thread.designation": "Обозначение",
"thread.direction": "Направление резьбы",
"thread.iso_metric": "Метрическая ISO",
"thread.left_hand": "Левая",
"thread.right_hand": "Правая",
"thread.select_size": "Выберите размер",
"timeline.delete_after_marker_one": "Удалить 1 операцию после маркера истории",
"timeline.delete_after_marker_other": "Удалить операции после маркера истории: {count}",
-3
View File
@@ -3842,10 +3842,7 @@
"the two origins are on the same component": "оба исходишта су на истој компоненти",
"thread.automatic_size": "Аутоматска величина",
"thread.designation": "Ознака",
"thread.direction": "Смер навоја",
"thread.iso_metric": "ISO метрички",
"thread.left_hand": "Леви",
"thread.right_hand": "Десни",
"thread.select_size": "Изаберите величину",
"timeline.delete_after_marker_one": "Избриши 1 операцију након ознаке враћања",
"timeline.delete_after_marker_other": "Избриши операције након ознаке враћања: {count}",
-3
View File
@@ -3842,10 +3842,7 @@
"the two origins are on the same component": "de två origona ligger på samma komponent",
"thread.automatic_size": "Automatisk storlek",
"thread.designation": "Beteckning",
"thread.direction": "Gängriktning",
"thread.iso_metric": "ISO-metrisk",
"thread.left_hand": "Vänstergängad",
"thread.right_hand": "Högergängad",
"thread.select_size": "Välj storlek",
"timeline.delete_after_marker_one": "Ta bort 1 funktion efter historikmarkören",
"timeline.delete_after_marker_other": "Ta bort {count} funktioner efter historikmarkören",
-3
View File
@@ -3843,10 +3843,7 @@
"the two origins are on the same component": "iki orijin aynı bileşende",
"thread.automatic_size": "Otomatik boyut",
"thread.designation": "Tanım",
"thread.direction": "Diş yönü",
"thread.iso_metric": "ISO metrik",
"thread.left_hand": "Sol el",
"thread.right_hand": "Sağ el",
"thread.select_size": "Boyut seç",
"timeline.delete_after_marker_one": "Geçmiş İşaretçisinden Sonra 1 Özelliği Sil",
"timeline.delete_after_marker_other": "Geçmiş İşaretçisinden Sonra {count} Özelliği Sil",
-3
View File
@@ -3842,10 +3842,7 @@
"the two origins are on the same component": "обидва початки координат належать одному компоненту",
"thread.automatic_size": "Автоматичний розмір",
"thread.designation": "Позначення",
"thread.direction": "Напрямок різьби",
"thread.iso_metric": "Метрична ISO",
"thread.left_hand": "Ліва",
"thread.right_hand": "Права",
"thread.select_size": "Вибрати розмір",
"timeline.delete_after_marker_one": "Видалити 1 операцію після маркера історії",
"timeline.delete_after_marker_other": "Видалити операції після маркера історії: {count}",
-3
View File
@@ -3897,10 +3897,7 @@
"the two origins are on the same component": "hai gốc tọa độ nằm trên cùng một thành phần",
"thread.automatic_size": "Kích thước tự động",
"thread.designation": "Ký hiệu ren",
"thread.direction": "Chiều ren",
"thread.iso_metric": "Hệ mét ISO",
"thread.left_hand": "Ren trái",
"thread.right_hand": "Ren phải",
"thread.select_size": "Chọn kích thước",
"timeline.delete_after_marker_one": "Xóa 1 tính năng sau điểm đánh dấu lịch sử",
"timeline.delete_after_marker_other": "Xóa {count} tính năng sau điểm đánh dấu lịch sử",
-3
View File
@@ -3843,10 +3843,7 @@
"the two origins are on the same component": "這兩個原點位於同一個元件上",
"thread.automatic_size": "自動尺寸",
"thread.designation": "螺紋標示",
"thread.direction": "螺紋方向",
"thread.iso_metric": "ISO 公制",
"thread.left_hand": "左旋",
"thread.right_hand": "右旋",
"thread.select_size": "選擇尺寸",
"timeline.delete_after_marker_one": "刪除歷程標記之後的 1 個特徵",
"timeline.delete_after_marker_other": "刪除歷程標記之後的 {count} 個特徵",
+6
View File
@@ -29,6 +29,8 @@
"chip": "#ffffff",
"construction_geom": "#ffffff",
"construction_plane": "#000000",
"comb_envelope": "#ffffff",
"comb_teeth": "#ffffff",
"continuity": [
"#ffffff",
"#ffffff",
@@ -132,6 +134,10 @@
"render_soft": "#000000",
"rubber_band": "#ffffff",
"section_cap": "#ffffff",
"section_hatch": [
"#ffffff",
"#ffffff"
],
"sel_edge": "#000000",
"sel_edge_rim": "#000000",
"sel_face": "#ffffff",
+6
View File
@@ -29,6 +29,8 @@
"chip": "#000000",
"construction_geom": "#000000",
"construction_plane": "#ffffff",
"comb_envelope": "#000000",
"comb_teeth": "#000000",
"continuity": [
"#000000",
"#000000",
@@ -132,6 +134,10 @@
"render_soft": "#ffffff",
"rubber_band": "#000000",
"section_cap": "#000000",
"section_hatch": [
"#000000",
"#000000"
],
"sel_edge": "#ffffff",
"sel_edge_rim": "#ffffff",
"sel_face": "#000000",
+6
View File
@@ -29,6 +29,8 @@
"chip": "#aa00aa",
"construction_geom": "#aaaaaa",
"construction_plane": "#ffff5522",
"comb_envelope": "#ff55ff",
"comb_teeth": "#55ffff",
"continuity": [
"#55ff55",
"#5555ff",
@@ -132,6 +134,10 @@
"render_soft": "#000000",
"rubber_band": "#55ffff",
"section_cap": "#ffff55",
"section_hatch": [
"#555555",
"#aaaaaa"
],
"sel_edge": "#ffff55",
"sel_edge_rim": "#000000",
"sel_face": "#ffff55",
+1 -1
View File
@@ -33,7 +33,7 @@ pub struct ThreadInfo {
/// Nominal diameters and coarse pitches (mm) used for automatic ISO metric thread sizing.
/// Other diameter/pitch pairs can be supplied as explicit designations.
pub fn metric_thread_sizes() -> &'static [(f64, f64)] {
&crate::thread_forms::ISO_COARSE
crate::thread_forms::ISO_COARSE
}
/// "M8" or "M8x1" → (nominal diameter, pitch).
+3 -3
View File
@@ -97,7 +97,7 @@ pub struct ThreadSize {
}
/// ISO 261/262 coarse pitches: (nominal diameter, pitch).
pub(crate) const ISO_COARSE: [(f64, f64); 22] = [
pub(crate) const ISO_COARSE: &[(f64, f64)] = &[
(1.6, 0.35),
(2.0, 0.4),
(2.5, 0.45),
@@ -124,7 +124,7 @@ pub(crate) const ISO_COARSE: [(f64, f64); 22] = [
/// The ISO 2902 diameter-pitch series' middle pitch per diameter, which ISO 2904 (trapezoidal)
/// and DIN 513-2 (buttress, from 10 mm) share: (nominal diameter, pitch).
const ISO_2902_MEDIUM: [(f64, f64); 41] = [
const ISO_2902_MEDIUM: &[(f64, f64)] = &[
(8.0, 1.5),
(9.0, 2.0),
(10.0, 2.0),
@@ -169,7 +169,7 @@ const ISO_2902_MEDIUM: [(f64, f64); 41] = [
];
/// ASME B1.5 general purpose Acme: (nominal diameter, threads per inch), in inches.
const ACME_SIZES: [(&str, f64, f64); 23] = [
const ACME_SIZES: &[(&str, f64, f64)] = &[
("1/4", 0.25, 16.0),
("5/16", 0.3125, 14.0),
("3/8", 0.375, 12.0),
+7 -7
View File
@@ -971,13 +971,13 @@ impl Session {
self.selection = selection.into_iter().filter(|s| !matches!(s, Sel::Occurrence {path} if self.occurrence_placement(path).is_err())).collect();
// A selected body that is gone cannot be deleted, measured or shown: drop it, so a
// later body taking its name is never deleted in its place (#9737).
if self.selection.iter().any(|s| s.body_name().is_some()) {
if let Ok(world) = self.world_state() {
self.selection.retain(|s| match s.body_name() {
Some(name) => world.body(name).is_some(),
None => true,
});
}
if self.selection.iter().any(|s| s.body_name().is_some())
&& let Ok(world) = self.world_state()
{
self.selection.retain(|s| match s.body_name() {
Some(name) => world.body(name).is_some(),
None => true,
});
}
let hidden = std::mem::take(&mut self.visibility.hidden_occurrences);
self.visibility.hidden_occurrences = hidden.into_iter().filter(|p| self.occurrence_placement(p).is_ok()).collect();
+2 -1
View File
@@ -51,7 +51,8 @@ fn on_both_surfaces(c: &C3, surfaces: &[Box<crate::brep::Surface>; 2], tolerance
const MAX_SAMPLES: usize = 512;
let [Ok(a), Ok(b)] = [SurfaceEval::new(&surfaces[0]), SurfaceEval::new(&surfaces[1])] else { return false };
let breaks = c.breaks();
let samples: Vec<f64> = breaks.windows(2).flat_map(|w| (0..4).map(move |i| w[0] + (w[1] - w[0]) * f64::from(i) / 4.0)).chain(breaks.last().copied()).collect();
let samples: Vec<f64> =
breaks.windows(2).flat_map(|w| (0..4).map(move |i| w[0] + (w[1] - w[0]) * f64::from(i) / 4.0)).chain(breaks.last().copied()).collect();
let stride = (samples.len() / MAX_SAMPLES).max(1);
samples.iter().step_by(stride).all(|t| {
let p = c.at(*t);
+42 -5
View File
@@ -2690,7 +2690,10 @@ fn an_edge_is_cut_to_its_interval_on_its_unbounded_curve() {
leader: Box::new(leader),
tolerance: 1e-4,
};
let flat = brep::Curve::Spline { knots: vec![0., 0., 0., 0., 0.5, 1., 1., 1., 1.], points: vec![[0., 0., 0.], [1., 3., 0.], [2., -3., 0.], [3., 3., 0.], [4., 0., 0.]] };
let flat = brep::Curve::Spline {
knots: vec![0., 0., 0., 0., 0.5, 1., 1., 1., 1.],
points: vec![[0., 0., 0.], [1., 3., 0.], [2., -3., 0.], [3., 3., 0.], [4., 0., 0.]],
};
let (whole, _) = C3::of_edge(&bounded_edge(flat.clone(), None, [0., 1.])).unwrap();
let (c, acc) = C3::of_edge(&bounded_edge(traced(flat), None, [0.25, 0.75])).unwrap();
assert_eq!(acc, CurveAccuracy::Bounded { max_deviation_mm: 1e-4 });
@@ -2792,7 +2795,21 @@ fn a_traced_rim_is_drawn_on_the_true_intersection_or_refused() {
let tolerance = 5e-3;
let k = 10. * 4. / 3. * (std::f64::consts::FRAC_PI_8).tan();
let knots = vec![0., 0., 0., 0., 1., 1., 1., 2., 2., 2., 3., 3., 3., 4., 4., 4., 4.];
let quarter = [[10., 0.], [10., k], [k, 10.], [0., 10.], [-k, 10.], [-10., k], [-10., 0.], [-10., -k], [-k, -10.], [0., -10.], [k, -10.], [10., -k], [10., 0.]];
let quarter = [
[10., 0.],
[10., k],
[k, 10.],
[0., 10.],
[-k, 10.],
[-10., k],
[-10., 0.],
[-10., -k],
[-k, -10.],
[0., -10.],
[k, -10.],
[10., -k],
[10., 0.],
];
let traced = |z: f64| {
let mut body = cylinder(10., 10.);
let side = Box::new(body.shells[0].faces[0].surface.clone());
@@ -2806,7 +2823,13 @@ fn a_traced_rim_is_drawn_on_the_true_intersection_or_refused() {
let def = prepare(&traced(10.), &o).unwrap();
let rim_points = |view: &View| -> Vec<[f64; 2]> {
let d = hidden_lines(std::slice::from_ref(&def), &[identity(0, [0.; 3])], view, &o, &never).unwrap();
let rim: Vec<_> = d.lines.iter().filter(|l| matches!(l.source, Source::Edge { edge: 1, .. })).flat_map(|l| &l.pieces).flat_map(|p| p.points.iter().copied()).collect();
let rim: Vec<_> = d
.lines
.iter()
.filter(|l| matches!(l.source, Source::Edge { edge: 1, .. }))
.flat_map(|l| &l.pieces)
.flat_map(|p| p.points.iter().copied())
.collect();
assert!(rim.len() > 16, "{rim:?}");
rim
};
@@ -2816,8 +2839,22 @@ fn a_traced_rim_is_drawn_on_the_true_intersection_or_refused() {
assert!(spread(0) > 19.9 && spread(1) > 19.9, "the whole circle");
let side = rim_points(&View::new(Vec3::new(0., 1., 0.), Vec3::new(0., 0., 1.)).unwrap());
let height = side.first().map_or(f64::NAN, |p| p[1]);
let lowest = hidden_lines(std::slice::from_ref(&def), &[identity(0, [0.; 3])], &View::new(Vec3::new(0., 1., 0.), Vec3::new(0., 0., 1.)).unwrap(), &o, &never).unwrap();
let bottom = lowest.lines.iter().filter(|l| matches!(l.source, Source::Edge { edge: 0, .. })).flat_map(|l| &l.pieces).flat_map(|p| p.points.iter()).next().map_or(f64::NAN, |p| p[1]);
let lowest = hidden_lines(
std::slice::from_ref(&def),
&[identity(0, [0.; 3])],
&View::new(Vec3::new(0., 1., 0.), Vec3::new(0., 0., 1.)).unwrap(),
&o,
&never,
)
.unwrap();
let bottom = lowest
.lines
.iter()
.filter(|l| matches!(l.source, Source::Edge { edge: 0, .. }))
.flat_map(|l| &l.pieces)
.flat_map(|p| p.points.iter())
.next()
.map_or(f64::NAN, |p| p[1]);
assert!(side.iter().all(|p| (p[1] - height).abs() <= tolerance) && ((height - bottom) - 10.).abs() <= tolerance, "{side:?} over {bottom}");
let off = prepare(&traced(11.), &o).err();
assert!(matches!(off, Some(HlrError::Unsupported("surface intersection curve"))), "{off:?}");
+2 -1
View File
@@ -76,7 +76,8 @@ fn a_cut_cylinders_rims_are_drawn_on_their_own_arc() {
near(under(flats()), chord, "far chord");
// From the flat side, along x, the rims are level lines as wide as the chord.
let (lines, record) = draw(&body, Vec3::new(1., 0., 0.), Vec3::new(0., 0., 1.));
let wide: Vec<Vec2> = lines.iter().filter(|l| is_circle(l, &record, true)).flat_map(|l| &l.pieces).flat_map(|p| &p.geometry).flat_map(points).collect();
let wide: Vec<Vec2> =
lines.iter().filter(|l| is_circle(l, &record, true)).flat_map(|l| &l.pieces).flat_map(|p| &p.geometry).flat_map(points).collect();
let reach = wide.iter().map(|p| p.x.abs()).fold(0., f64::max);
near(reach, chord / 2., "rims from the flat side");
}
+8 -2
View File
@@ -249,6 +249,12 @@ pub(crate) fn shows_pressed(app: &SolveApp, id: &str) -> bool {
running_command(app).as_deref() == Some(id) || app.session.active_analysis() == Some(id)
}
/// [`shows_pressed`] for a ribbon button: a command with a persistent effect is pressed only while
/// its tool runs, since `persistent_ribbon_pressed` alone decides how its effect is shown.
fn ribbon_shows_pressed(app: &SolveApp, id: &str) -> bool {
if persistent_available(app, id) { running_command(app).as_deref() == Some(id) } else { shows_pressed(app, id) }
}
/// The Report a bug button: labelled when the layout asks for it and there is room for its text
/// (`label_width`, measured in the current language), otherwise just the icon. An icon-only
/// layout gives it a square the size of Settings and Help. `version_width` is the measured version.
@@ -1789,7 +1795,7 @@ pub fn toolbar(app: &mut SolveApp, ui: &mut egui::Ui) {
let family_open = family.is_some_and(|f| egui::Popup::is_id_open(ui.ctx(), ui.id().with(("family", f.label)).with("popup")));
let neutral = if !finish
&& (resp.is_pointer_button_down_on()
|| (shows_pressed(app, selected.id) && !(persistent && app.preferences.persistent_ribbon_pressed))
|| (ribbon_shows_pressed(app, selected.id) && !(persistent && app.preferences.persistent_ribbon_pressed))
|| family_open
|| (selected.id == "view.shots_strip" && app.preferences.render_shots_strip))
{
@@ -1907,7 +1913,7 @@ pub fn toolbar(app: &mut SolveApp, ui: &mut egui::Ui) {
);
let info = crate::render_workspace::interactive_command_info(app, c);
let resp = ui.interact(cell, ui.id().with(("cmd", c.id)), Sense::click_and_drag());
let active = shows_pressed(app, c.id);
let active = ribbon_shows_pressed(app, c.id);
let accent = crate::theme::TabAccent::for_tab(&t, &tab);
painter.rect(
cell,
+2
View File
@@ -19,3 +19,5 @@ solidcraft-kernel::blend_corner::tests::issue2422_vertical_after_separate_bottom
solidcraft-kernel::corner_g2::tests::a_long_setback_keeps_the_g2_patch_inside_the_box at set backs 11 to 15 the G2 corner patch bulges 2.6e-6 to 1.33e-3 past the box (the explicit corner is in progress)
solidcraft-kernel::corner_g2::tests::the_box_corner_g2_patch_is_a_closed_solid_curvature_continuous_to_its_neighbours the fitted G2 corner patch bulges 1.7e-5 past the box, which SMLib's exact tight box now sees (the explicit G2 corner builds it inside)
solidcraft-kernel::tests::fillet_tabbed_plate_outer_loop_and_partial_selection_after_hole the arc and tab rounds together remove 9.20067 against 9.20420 (3.8e-4 off, 1e-5 allowed)
solidcraft-ui-egui::timeline::tests::each_chip_finds_its_own_result main's test: its radius 20 fillet with fit reduce on a 10 mm box builds on SMLib without a reduced-radius warning, so the fixture has no warned feature (same reply on develop's kernel)
solidcraft-ui-egui::viewport::appearance_tests::bodies_out_of_the_view_are_not_drawn main's test counts 12 triangles a box; SMLib's mesher gives a 10 mm box 108, so it stops at 216 against 24 before it reaches the culling