Rib/Web: grow along the sketch normal (#2056) (#2057)

Thanks @pablopeza.
This commit is contained in:
Pablo López authored and GitHub committed 2026-10-17 14:15:03 -04:00
1 parent e347f820e6
commit cb857f1fa7
19 files changed
+173 -10

No files matched your search

+3
View File
@@ -82,6 +82,7 @@
"Density": "Hustota", "Density": "Hustota",
"Designs you open or save appear here.": "Zde se zobrazí návrhy, které otevřete nebo uložíte.", "Designs you open or save appear here.": "Zde se zobrazí návrhy, které otevřete nebo uložíte.",
"Determined by To Object. Choose Angle to edit the angular extent.": "Určeno volbou K objektu. Pro úpravu úhlového rozsahu zvolte Úhel.", "Determined by To Object. Choose Angle to edit the angular extent.": "Určeno volbou K objektu. Pro úpravu úhlového rozsahu zvolte Úhel.",
"Direction": "Směr",
"Discard": "Zahodit", "Discard": "Zahodit",
"Do not export hidden edges": "Neexportovat skryté hrany", "Do not export hidden edges": "Neexportovat skryté hrany",
"Double-click to rename": "Dvojklikem přejmenujete", "Double-click to rename": "Dvojklikem přejmenujete",
@@ -121,6 +122,7 @@
"Home view": "Výchozí pohled", "Home view": "Výchozí pohled",
"In This Design": "V tomto návrhu", "In This Design": "V tomto návrhu",
"In place (surfaces)": "Na místě (plochy)", "In place (surfaces)": "Na místě (plochy)",
"In plane": "V rovině",
"Inside": "Uvnitř", "Inside": "Uvnitř",
"Interferes": "Koliduje", "Interferes": "Koliduje",
"Issue title": "Název hlášení", "Issue title": "Název hlášení",
@@ -154,6 +156,7 @@
"No matching appearances": "Žádné odpovídající vzhledy", "No matching appearances": "Žádné odpovídající vzhledy",
"No visible bodies to export.": "Žádná viditelná tělesa k exportu.", "No visible bodies to export.": "Žádná viditelná tělesa k exportu.",
"None": "Žádné", "None": "Žádné",
"Normal to sketch": "Kolmo ke skice",
"Not available yet": "Zatím není k dispozici", "Not available yet": "Zatím není k dispozici",
"Notch": "Zářez", "Notch": "Zářez",
"Opacity": "Neprůhlednost", "Opacity": "Neprůhlednost",
+3
View File
@@ -26,6 +26,7 @@
"Current orthographic view (1:1 mm)": "Aktuelle orthogonale Ansicht (1:1 mm)", "Current orthographic view (1:1 mm)": "Aktuelle orthogonale Ansicht (1:1 mm)",
"Current perspective view": "Aktuelle perspektivische Ansicht", "Current perspective view": "Aktuelle perspektivische Ansicht",
"Curvature (G2)": "Krümmung (G2)", "Curvature (G2)": "Krümmung (G2)",
"Direction": "Richtung",
"Do not export hidden edges": "Verdeckte Kanten nicht exportieren", "Do not export hidden edges": "Verdeckte Kanten nicht exportieren",
"Don't ask again": "Nicht mehr fragen", "Don't ask again": "Nicht mehr fragen",
"Drive Joint": "Gelenk antreiben", "Drive Joint": "Gelenk antreiben",
@@ -47,6 +48,7 @@
"Holes": "Löcher", "Holes": "Löcher",
"Holes (edges on them)": "Löcher (ihre Kanten)", "Holes (edges on them)": "Löcher (ihre Kanten)",
"In place (surfaces)": "An Ort und Stelle (Flächen)", "In place (surfaces)": "An Ort und Stelle (Flächen)",
"In plane": "In der Ebene",
"Inside": "Innen", "Inside": "Innen",
"Length": "Länge", "Length": "Länge",
"Max": "Max", "Max": "Max",
@@ -55,6 +57,7 @@
"New study": "Neue Untersuchung", "New study": "Neue Untersuchung",
"No visible bodies to export.": "Keine sichtbaren Körper zum Exportieren.", "No visible bodies to export.": "Keine sichtbaren Körper zum Exportieren.",
"None": "Keine", "None": "Keine",
"Normal to sketch": "Normal zur Skizze",
"Notch": "Ausklinkung", "Notch": "Ausklinkung",
"Outside": "Außen", "Outside": "Außen",
"Overall length": "Gesamtlänge", "Overall length": "Gesamtlänge",
+3
View File
@@ -86,6 +86,7 @@
"Density": "Density", "Density": "Density",
"Designs you open or save appear here.": "Designs you open or save appear here.", "Designs you open or save appear here.": "Designs you open or save appear here.",
"Determined by To Object. Choose Angle to edit the angular extent.": "Determined by To Object. Choose Angle to edit the angular extent.", "Determined by To Object. Choose Angle to edit the angular extent.": "Determined by To Object. Choose Angle to edit the angular extent.",
"Direction": "Direction",
"Discard": "Discard", "Discard": "Discard",
"Do not export hidden edges": "Do not export hidden edges", "Do not export hidden edges": "Do not export hidden edges",
"Don't ask again": "Don't ask again", "Don't ask again": "Don't ask again",
@@ -128,6 +129,7 @@
"Home view": "Home view", "Home view": "Home view",
"In This Design": "In This Design", "In This Design": "In This Design",
"In place (surfaces)": "In place (surfaces)", "In place (surfaces)": "In place (surfaces)",
"In plane": "In plane",
"Inside": "Inside", "Inside": "Inside",
"Interferes": "Interferes", "Interferes": "Interferes",
"Issue title": "Issue title", "Issue title": "Issue title",
@@ -163,6 +165,7 @@
"No matching appearances": "No matching appearances", "No matching appearances": "No matching appearances",
"No visible bodies to export.": "No visible bodies to export.", "No visible bodies to export.": "No visible bodies to export.",
"None": "None", "None": "None",
"Normal to sketch": "Normal to sketch",
"Not available yet": "Not available yet", "Not available yet": "Not available yet",
"Notch": "Notch", "Notch": "Notch",
"Opacity": "Opacity", "Opacity": "Opacity",
+3
View File
@@ -82,6 +82,7 @@
"Density": "Densidad", "Density": "Densidad",
"Designs you open or save appear here.": "Aquí aparecen los diseños que abras o guardes.", "Designs you open or save appear here.": "Aquí aparecen los diseños que abras o guardes.",
"Determined by To Object. Choose Angle to edit the angular extent.": "Determinado por «Hasta objeto». Elige «Ángulo» para modificar la extensión angular.", "Determined by To Object. Choose Angle to edit the angular extent.": "Determinado por «Hasta objeto». Elige «Ángulo» para modificar la extensión angular.",
"Direction": "Dirección",
"Discard": "Descartar", "Discard": "Descartar",
"Do not export hidden edges": "No exportar aristas ocultas", "Do not export hidden edges": "No exportar aristas ocultas",
"Double-click to rename": "Doble clic para cambiar el nombre", "Double-click to rename": "Doble clic para cambiar el nombre",
@@ -120,6 +121,7 @@
"Home view": "Vista de inicio", "Home view": "Vista de inicio",
"In This Design": "En este diseño", "In This Design": "En este diseño",
"In place (surfaces)": "En su posición (superficies)", "In place (surfaces)": "En su posición (superficies)",
"In plane": "En el plano",
"Inside": "Interior", "Inside": "Interior",
"Interferes": "Interfiere", "Interferes": "Interfiere",
"Issue title": "Título de la incidencia", "Issue title": "Título de la incidencia",
@@ -153,6 +155,7 @@
"No matching appearances": "No hay apariencias coincidentes", "No matching appearances": "No hay apariencias coincidentes",
"No visible bodies to export.": "No hay cuerpos visibles para exportar.", "No visible bodies to export.": "No hay cuerpos visibles para exportar.",
"None": "Ninguno", "None": "Ninguno",
"Normal to sketch": "Normal al boceto",
"Not available yet": "Todavía no disponible", "Not available yet": "Todavía no disponible",
"Notch": "Muesca", "Notch": "Muesca",
"Opacity": "Opacidad", "Opacity": "Opacidad",
+3
View File
@@ -86,6 +86,7 @@
"Density": "Masse volumique", "Density": "Masse volumique",
"Designs you open or save appear here.": "Les conceptions que vous ouvrez ou enregistrez apparaissent ici.", "Designs you open or save appear here.": "Les conceptions que vous ouvrez ou enregistrez apparaissent ici.",
"Determined by To Object. Choose Angle to edit the angular extent.": "Déterminé par Jusqu'à l'objet. Choisir Angle pour modifier l'étendue angulaire.", "Determined by To Object. Choose Angle to edit the angular extent.": "Déterminé par Jusqu'à l'objet. Choisir Angle pour modifier l'étendue angulaire.",
"Direction": "Direction",
"Discard": "Abandonner", "Discard": "Abandonner",
"Do not export hidden edges": "Ne pas exporter les arêtes cachées", "Do not export hidden edges": "Ne pas exporter les arêtes cachées",
"Don't ask again": "Ne plus demander", "Don't ask again": "Ne plus demander",
@@ -127,6 +128,7 @@
"Home view": "Vue d'accueil", "Home view": "Vue d'accueil",
"In This Design": "Dans cette conception", "In This Design": "Dans cette conception",
"In place (surfaces)": "En place (surfaces)", "In place (surfaces)": "En place (surfaces)",
"In plane": "Dans le plan",
"Inside": "Intérieur", "Inside": "Intérieur",
"Interferes": "Interfère", "Interferes": "Interfère",
"Issue title": "Titre du ticket", "Issue title": "Titre du ticket",
@@ -161,6 +163,7 @@
"No matching appearances": "Aucune apparence correspondante", "No matching appearances": "Aucune apparence correspondante",
"No visible bodies to export.": "Aucun corps visible à exporter.", "No visible bodies to export.": "Aucun corps visible à exporter.",
"None": "Aucun", "None": "Aucun",
"Normal to sketch": "Normale à l'esquisse",
"Not available yet": "Pas encore disponible", "Not available yet": "Pas encore disponible",
"Notch": "Encoche", "Notch": "Encoche",
"Opacity": "Opacité", "Opacity": "Opacité",
+3
View File
@@ -82,6 +82,7 @@
"Density": "Densidade", "Density": "Densidade",
"Designs you open or save appear here.": "Os deseños que abras ou gardes aparecen aquí.", "Designs you open or save appear here.": "Os deseños que abras ou gardes aparecen aquí.",
"Determined by To Object. Choose Angle to edit the angular extent.": "Determinado por Ata o obxecto. Escolle Ángulo para editar a extensión angular.", "Determined by To Object. Choose Angle to edit the angular extent.": "Determinado por Ata o obxecto. Escolle Ángulo para editar a extensión angular.",
"Direction": "Dirección",
"Discard": "Descartar", "Discard": "Descartar",
"Do not export hidden edges": "Non exportar as arestas ocultas", "Do not export hidden edges": "Non exportar as arestas ocultas",
"Double-click to rename": "Dobre clic para renomear", "Double-click to rename": "Dobre clic para renomear",
@@ -121,6 +122,7 @@
"Home view": "Vista de inicio", "Home view": "Vista de inicio",
"In This Design": "Neste deseño", "In This Design": "Neste deseño",
"In place (surfaces)": "No sitio (superficies)", "In place (surfaces)": "No sitio (superficies)",
"In plane": "No plano",
"Inside": "Dentro", "Inside": "Dentro",
"Interferes": "Interfire", "Interferes": "Interfire",
"Issue title": "Título da incidencia", "Issue title": "Título da incidencia",
@@ -154,6 +156,7 @@
"No matching appearances": "Non hai aparencias coincidentes", "No matching appearances": "Non hai aparencias coincidentes",
"No visible bodies to export.": "Non hai corpos visibles para exportar.", "No visible bodies to export.": "Non hai corpos visibles para exportar.",
"None": "Ningún", "None": "Ningún",
"Normal to sketch": "Normal ao bosquexo",
"Not available yet": "Aínda non dispoñible", "Not available yet": "Aínda non dispoñible",
"Notch": "Entalle", "Notch": "Entalle",
"Opacity": "Opacidade", "Opacity": "Opacidade",
+3
View File
@@ -86,6 +86,7 @@
"Density": "密度", "Density": "密度",
"Designs you open or save appear here.": "開いたデザインや保存したデザインがここに表示されます。", "Designs you open or save appear here.": "開いたデザインや保存したデザインがここに表示されます。",
"Determined by To Object. Choose Angle to edit the angular extent.": "「対象オブジェクトまで」により決定されます。角度範囲を編集するには「角度」を選択してください。", "Determined by To Object. Choose Angle to edit the angular extent.": "「対象オブジェクトまで」により決定されます。角度範囲を編集するには「角度」を選択してください。",
"Direction": "方向",
"Discard": "破棄", "Discard": "破棄",
"Do not export hidden edges": "出力しない", "Do not export hidden edges": "出力しない",
"Don't ask again": "今後確認しない", "Don't ask again": "今後確認しない",
@@ -128,6 +129,7 @@
"Home view": "ホームビュー", "Home view": "ホームビュー",
"In This Design": "このデザイン内", "In This Design": "このデザイン内",
"In place (surfaces)": "その場で(サーフェス)", "In place (surfaces)": "その場で(サーフェス)",
"In plane": "平面内",
"Inside": "内側", "Inside": "内側",
"Interferes": "干渉あり", "Interferes": "干渉あり",
"Issue title": "Issueタイトル", "Issue title": "Issueタイトル",
@@ -163,6 +165,7 @@
"No matching appearances": "一致する外観がありません", "No matching appearances": "一致する外観がありません",
"No visible bodies to export.": "出力する表示中のボディがありません。", "No visible bodies to export.": "出力する表示中のボディがありません。",
"None": "なし", "None": "なし",
"Normal to sketch": "スケッチに垂直",
"Not available yet": "まだ利用できません", "Not available yet": "まだ利用できません",
"Notch": "ノッチ", "Notch": "ノッチ",
"Opacity": "不透明度", "Opacity": "不透明度",
+3
View File
@@ -26,6 +26,7 @@
"Current orthographic view (1:1 mm)": "현재 직교 투영 뷰 (1:1 mm)", "Current orthographic view (1:1 mm)": "현재 직교 투영 뷰 (1:1 mm)",
"Current perspective view": "현재 원근 투영 뷰", "Current perspective view": "현재 원근 투영 뷰",
"Curvature (G2)": "곡률 (G2)", "Curvature (G2)": "곡률 (G2)",
"Direction": "방향",
"Do not export hidden edges": "숨겨진 모서리 내보내지 않기", "Do not export hidden edges": "숨겨진 모서리 내보내지 않기",
"Don't ask again": "다시 묻지 않기", "Don't ask again": "다시 묻지 않기",
"Drive Joint": "조인트 구동", "Drive Joint": "조인트 구동",
@@ -47,6 +48,7 @@
"Holes": "구멍", "Holes": "구멍",
"Holes (edges on them)": "구멍 (구멍의 모서리)", "Holes (edges on them)": "구멍 (구멍의 모서리)",
"In place (surfaces)": "제자리에서 (서피스)", "In place (surfaces)": "제자리에서 (서피스)",
"In plane": "평면 내",
"Inside": "안쪽", "Inside": "안쪽",
"Length": "길이", "Length": "길이",
"Max": "최대", "Max": "최대",
@@ -55,6 +57,7 @@
"New study": "새 연구", "New study": "새 연구",
"No visible bodies to export.": "표시된 바디가 없어 내보낼 수 없습니다.", "No visible bodies to export.": "표시된 바디가 없어 내보낼 수 없습니다.",
"None": "없음", "None": "없음",
"Normal to sketch": "스케치에 수직",
"Notch": "노치", "Notch": "노치",
"Outside": "바깥쪽", "Outside": "바깥쪽",
"Overall length": "전체 길이", "Overall length": "전체 길이",
+3
View File
@@ -82,6 +82,7 @@
"Density": "Densidade", "Density": "Densidade",
"Designs you open or save appear here.": "Os projetos que você abrir ou salvar aparecem aqui.", "Designs you open or save appear here.": "Os projetos que você abrir ou salvar aparecem aqui.",
"Determined by To Object. Choose Angle to edit the angular extent.": "Determinado por Até o objeto. Escolha Ângulo para editar a extensão angular.", "Determined by To Object. Choose Angle to edit the angular extent.": "Determinado por Até o objeto. Escolha Ângulo para editar a extensão angular.",
"Direction": "Direção",
"Discard": "Descartar", "Discard": "Descartar",
"Do not export hidden edges": "Não exportar arestas ocultas", "Do not export hidden edges": "Não exportar arestas ocultas",
"Double-click to rename": "Clique duas vezes para renomear", "Double-click to rename": "Clique duas vezes para renomear",
@@ -121,6 +122,7 @@
"Home view": "Vista inicial", "Home view": "Vista inicial",
"In This Design": "Neste projeto", "In This Design": "Neste projeto",
"In place (surfaces)": "No lugar (superfícies)", "In place (surfaces)": "No lugar (superfícies)",
"In plane": "No plano",
"Inside": "Dentro", "Inside": "Dentro",
"Interferes": "Interfere", "Interferes": "Interfere",
"Issue title": "Título da issue", "Issue title": "Título da issue",
@@ -154,6 +156,7 @@
"No matching appearances": "Nenhuma aparência correspondente", "No matching appearances": "Nenhuma aparência correspondente",
"No visible bodies to export.": "Nenhum corpo visível para exportar.", "No visible bodies to export.": "Nenhum corpo visível para exportar.",
"None": "Nenhum", "None": "Nenhum",
"Normal to sketch": "Normal ao esboço",
"Not available yet": "Ainda não disponível", "Not available yet": "Ainda não disponível",
"Notch": "Entalhe", "Notch": "Entalhe",
"Opacity": "Opacidade", "Opacity": "Opacidade",
+3
View File
@@ -76,6 +76,7 @@
"Density": "Плотность", "Density": "Плотность",
"Designs you open or save appear here.": "Здесь появляются открытые и сохранённые дизайны.", "Designs you open or save appear here.": "Здесь появляются открытые и сохранённые дизайны.",
"Determined by To Object. Choose Angle to edit the angular extent.": "Определяется параметром «До объекта». Выберите «Угол», чтобы задать угловой предел.", "Determined by To Object. Choose Angle to edit the angular extent.": "Определяется параметром «До объекта». Выберите «Угол», чтобы задать угловой предел.",
"Direction": "Направление",
"Discard": "Отменить", "Discard": "Отменить",
"Double-click to rename": "Двойной щелчок — переименовать", "Double-click to rename": "Двойной щелчок — переименовать",
"Double-clicking the middle button in the view: Home (the home view, framing what is shown), Fit (frame what is shown, keeping the orientation) or nothing": "Двойной щелчок средней кнопкой в виде: «Главная» (главный вид, кадрирует показанное), «Вписать» (кадрирует, сохраняя ориентацию) или ничего", "Double-clicking the middle button in the view: Home (the home view, framing what is shown), Fit (frame what is shown, keeping the orientation) or nothing": "Двойной щелчок средней кнопкой в виде: «Главная» (главный вид, кадрирует показанное), «Вписать» (кадрирует, сохраняя ориентацию) или ничего",
@@ -106,6 +107,7 @@
"Height": "Высота", "Height": "Высота",
"Home view": "Главный вид", "Home view": "Главный вид",
"In This Design": "В этом дизайне", "In This Design": "В этом дизайне",
"In plane": "В плоскости",
"Interferes": "Пересекается", "Interferes": "Пересекается",
"Issue title": "Заголовок Issue", "Issue title": "Заголовок Issue",
"Joint Origin1": "Точка сопряжения1", "Joint Origin1": "Точка сопряжения1",
@@ -134,6 +136,7 @@
"New study": "Новое исследование", "New study": "Новое исследование",
"No crash reports.": "Нет отчётов о сбоях.", "No crash reports.": "Нет отчётов о сбоях.",
"No matching appearances": "Нет подходящих внешних видов", "No matching appearances": "Нет подходящих внешних видов",
"Normal to sketch": "По нормали к эскизу",
"Not available yet": "Пока недоступно", "Not available yet": "Пока недоступно",
"Notch": "Вырез", "Notch": "Вырез",
"Opacity": "Непрозрачность", "Opacity": "Непрозрачность",
+3
View File
@@ -80,6 +80,7 @@
"Density": "Yoğunluk", "Density": "Yoğunluk",
"Designs you open or save appear here.": "Açtığınız veya kaydettiğiniz tasarımlar burada görünür.", "Designs you open or save appear here.": "Açtığınız veya kaydettiğiniz tasarımlar burada görünür.",
"Determined by To Object. Choose Angle to edit the angular extent.": "Nesneye kadar seçeneğiyle belirlenir. Açısal kapsamı düzenlemek için Açı seçeneğini seçin.", "Determined by To Object. Choose Angle to edit the angular extent.": "Nesneye kadar seçeneğiyle belirlenir. Açısal kapsamı düzenlemek için Açı seçeneğini seçin.",
"Direction": "Yön",
"Discard": "At", "Discard": "At",
"Double-click to rename": "Yeniden adlandırmak için çift tıklayın", "Double-click to rename": "Yeniden adlandırmak için çift tıklayın",
"Double-clicking the middle button in the view: Home (the home view, framing what is shown), Fit (frame what is shown, keeping the orientation) or nothing": "Görünümde orta tuşla çift tıklama: Başlangıç (gösterileni çerçeveleyen başlangıç görünümü), Sığdır (yönü koruyarak gösterileni çerçeveler) veya hiçbir şey", "Double-clicking the middle button in the view: Home (the home view, framing what is shown), Fit (frame what is shown, keeping the orientation) or nothing": "Görünümde orta tuşla çift tıklama: Başlangıç (gösterileni çerçeveleyen başlangıç görünümü), Sığdır (yönü koruyarak gösterileni çerçeveler) veya hiçbir şey",
@@ -114,6 +115,7 @@
"Home view": "Başlangıç görünümü", "Home view": "Başlangıç görünümü",
"In This Design": "Bu tasarımda", "In This Design": "Bu tasarımda",
"In place (surfaces)": "Yerinde (yüzeyler)", "In place (surfaces)": "Yerinde (yüzeyler)",
"In plane": "Düzlemde",
"Inside": "İç", "Inside": "İç",
"Interferes": "Girişim var", "Interferes": "Girişim var",
"Issue title": "Sorun başlığı", "Issue title": "Sorun başlığı",
@@ -147,6 +149,7 @@
"No crash reports.": "Çökme raporu yok.", "No crash reports.": "Çökme raporu yok.",
"No matching appearances": "Eşleşen görünüm yok", "No matching appearances": "Eşleşen görünüm yok",
"None": "Hiçbiri", "None": "Hiçbiri",
"Normal to sketch": "Eskize dik",
"Not available yet": "Henüz kullanılamıyor", "Not available yet": "Henüz kullanılamıyor",
"Opacity": "Opaklık", "Opacity": "Opaklık",
"Open GitHub draft": "GitHub taslağını aç", "Open GitHub draft": "GitHub taslağını aç",
+3
View File
@@ -86,6 +86,7 @@
"Density": "Густина", "Density": "Густина",
"Designs you open or save appear here.": "Проєкти, які ви відкриваєте чи зберігаєте, з'являються тут.", "Designs you open or save appear here.": "Проєкти, які ви відкриваєте чи зберігаєте, з'являються тут.",
"Determined by To Object. Choose Angle to edit the angular extent.": "Визначається об'єктом призначення. Виберіть «Кут», щоб редагувати кутову протяжність.", "Determined by To Object. Choose Angle to edit the angular extent.": "Визначається об'єктом призначення. Виберіть «Кут», щоб редагувати кутову протяжність.",
"Direction": "Напрямок",
"Discard": "Відхилити", "Discard": "Відхилити",
"Do not export hidden edges": "Не експортувати приховані ребра", "Do not export hidden edges": "Не експортувати приховані ребра",
"Double-click to rename": "Двічі клацніть, щоб перейменувати", "Double-click to rename": "Двічі клацніть, щоб перейменувати",
@@ -126,6 +127,7 @@
"Home view": "Головний вид", "Home view": "Головний вид",
"In This Design": "У цьому проєкті", "In This Design": "У цьому проєкті",
"In place (surfaces)": "На місці (поверхні)", "In place (surfaces)": "На місці (поверхні)",
"In plane": "У площині",
"Inside": "Усередину", "Inside": "Усередину",
"Interferes": "Перетинається", "Interferes": "Перетинається",
"Issue title": "Заголовок повідомлення", "Issue title": "Заголовок повідомлення",
@@ -160,6 +162,7 @@
"No matching appearances": "Немає відповідних виглядів", "No matching appearances": "Немає відповідних виглядів",
"No visible bodies to export.": "Немає видимих тіл для експорту.", "No visible bodies to export.": "Немає видимих тіл для експорту.",
"None": "Немає", "None": "Немає",
"Normal to sketch": "За нормаллю до ескізу",
"Not available yet": "Ще недоступно", "Not available yet": "Ще недоступно",
"Notch": "Виріз", "Notch": "Виріз",
"Opacity": "Непрозорість", "Opacity": "Непрозорість",
+3
View File
@@ -86,6 +86,7 @@
"Density": "密度", "Density": "密度",
"Designs you open or save appear here.": "你打开或保存的设计会显示在此处。", "Designs you open or save appear here.": "你打开或保存的设计会显示在此处。",
"Determined by To Object. Choose Angle to edit the angular extent.": "由“到对象”确定。选择“角度”可编辑角度范围。", "Determined by To Object. Choose Angle to edit the angular extent.": "由“到对象”确定。选择“角度”可编辑角度范围。",
"Direction": "方向",
"Discard": "放弃", "Discard": "放弃",
"Do not export hidden edges": "不导出隐藏线", "Do not export hidden edges": "不导出隐藏线",
"Don't ask again": "不再询问", "Don't ask again": "不再询问",
@@ -128,6 +129,7 @@
"Home view": "主视图", "Home view": "主视图",
"In This Design": "在此设计中", "In This Design": "在此设计中",
"In place (surfaces)": "原位(曲面)", "In place (surfaces)": "原位(曲面)",
"In plane": "在平面内",
"Inside": "内侧", "Inside": "内侧",
"Interferes": "发生干涉", "Interferes": "发生干涉",
"Issue title": "Issue 标题", "Issue title": "Issue 标题",
@@ -163,6 +165,7 @@
"No matching appearances": "没有匹配的外观", "No matching appearances": "没有匹配的外观",
"No visible bodies to export.": "没有可导出的可见实体。", "No visible bodies to export.": "没有可导出的可见实体。",
"None": "无", "None": "无",
"Normal to sketch": "垂直于草图",
"Not available yet": "暂不可用", "Not available yet": "暂不可用",
"Notch": "缺口", "Notch": "缺口",
"Opacity": "不透明度", "Opacity": "不透明度",
+6 -1
View File
@@ -901,7 +901,8 @@ pub enum FeatureKind {
targets: Vec<BodyName>, targets: Vec<BodyName>,
}, },
/// A thin wall from open sketch curves: `thickness` across the sketch plane, filling from /// A thin wall from open sketch curves: `thickness` across the sketch plane, filling from
/// the curves toward the body (to the next face, or `depth`). A web makes one per curve. /// the curves toward the body (to the next face, or `depth`), or with `normal` along the
/// sketch normal. A web makes one per curve.
Rib { Rib {
sketch: SketchId, sketch: SketchId,
curves: Vec<String>, curves: Vec<String>,
@@ -917,6 +918,10 @@ pub enum FeatureKind {
/// Web: each curve is its own wall. /// Web: each curve is its own wall.
#[serde(default, skip_serializing_if = "std::ops::Not::not")] #[serde(default, skip_serializing_if = "std::ops::Not::not")]
web: bool, web: bool,
/// Grow the wall along the sketch normal (thickness across the curves, in the sketch
/// plane) instead of across the plane from the curves.
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
normal: bool,
}, },
/// Move planar faces (at the given points) onto a target plane parallel to them. /// Move planar faces (at the given points) onto a target plane parallel to them.
ReplaceFace { ReplaceFace {
+79 -2
View File
@@ -69,7 +69,7 @@ pub(super) fn eval(doc: &Document, vals: &BTreeMap<String, Value>, f: &Feature,
} }
apply_op(st, f, tools, if *deboss { Operation::Cut } else { Operation::Join }, &crate::plain(targets)) apply_op(st, f, tools, if *deboss { Operation::Cut } else { Operation::Join }, &crate::plain(targets))
} }
FeatureKind::Rib { sketch, curves, thickness, depth, flip, web } => { FeatureKind::Rib { sketch, curves, thickness, depth, flip, web, normal } => {
let ss = st.sketch(sketch.0).ok_or_else(|| DocError::Unknown(format!("sketch {sketch}")))?.clone(); let ss = st.sketch(sketch.0).ok_or_else(|| DocError::Unknown(format!("sketch {sketch}")))?.clone();
let t = val(vals, thickness, Kind::Length)?; let t = val(vals, thickness, Kind::Length)?;
if !(t > 0.0) { if !(t > 0.0) {
@@ -88,7 +88,7 @@ pub(super) fn eval(doc: &Document, vals: &BTreeMap<String, Value>, f: &Feature,
} }
let mut tools = Vec::new(); let mut tools = Vec::new();
for g in &groups { for g in &groups {
tools.push(rib_tool(st, &ss, g, t, depth, *flip)?); tools.push(if *normal { rib_tool_normal(st, &ss, g, t, depth, *flip)? } else { rib_tool(st, &ss, g, t, depth, *flip)? });
} }
apply_op(st, f, tools, Operation::Join, &[]) apply_op(st, f, tools, Operation::Join, &[])
} }
@@ -286,6 +286,83 @@ fn rib_tool(st: &ModelState, ss: &SolvedSketch, ids: &[String], t: f64, depth: O
tools.into_iter().next().ok_or_else(|| DocError::Invalid("the rib made no solid".into())) tools.into_iter().next().ok_or_else(|| DocError::Invalid("the rib made no solid".into()))
} }
/// A rib wall along the sketch normal: the curves widened to `t` in the sketch plane, run from
/// the sketch toward the body by `depth` (or until the whole wall reaches the body).
fn rib_tool_normal(st: &ModelState, ss: &SolvedSketch, ids: &[String], t: f64, depth: Option<f64>, flip: bool) -> Result<Body> {
let pts = chain_points(ss, ids)?;
let len: f64 = pts.windows(2).map(|w| w[0].dist(w[1])).sum();
if !(len > 1e-9) {
return Err(DocError::Invalid("a zero-length rib curve".into()));
}
let samples = ((len / (t * 0.5).max(len / 400.0)).ceil() as usize).clamp(2, 400);
let along: Vec<Vec3> = (0..=samples).map(|k| ss.plane.to_world(point_on_polyline(&pts, len * k as f64 / samples as f64))).collect();
let normal = ss.plane.normal();
let reach = st.bodies.iter().map(|b| b.mesh().bounds()).fold(0.0f64, |acc, bb| acc.max((bb.max - bb.min).len()));
let inside = |q: Vec3| st.bodies.iter().any(|b| b.mesh().contains(q));
// Where the body begins from each point of the curves, along `sign` × the sketch normal.
let limit = reach * 2.0 + 1.0;
let step = (limit / 400.0).max(1e-3);
let hits = |sign: f64| -> Vec<f64> {
let mut out = Vec::new();
for q in &along {
let Some(k) = (1..=400).find(|k| inside(*q + normal * (sign * *k as f64 * step))) else { continue };
let (mut lo, mut hi) = ((k - 1) as f64 * step, k as f64 * step);
for _ in 0..30 {
let m = 0.5 * (lo + hi);
if inside(*q + normal * (sign * m)) { hi = m } else { lo = m }
}
out.push(hi);
}
out
};
// Toward the material: the side of the sketch where the curves meet the bodies (unless flipped).
let (front, back) = (hits(1.0), hits(-1.0));
let mut sign = if back.len() > front.len() { -1.0 } else { 1.0 };
let mut reached = if sign > 0.0 { front } else { back };
if flip {
sign = -sign;
reached = hits(sign);
}
let h = match depth {
Some(d) => d,
None => {
let far = reached.iter().copied().fold(f64::NAN, f64::max);
if !far.is_finite() {
return Err(DocError::Invalid("the rib curves do not face a body along the sketch normal (give a depth)".into()));
}
far + (t * 0.5).max(step)
}
};
// The curves widened to `t`: each point offset by half the thickness along the miter of its
// neighbours, both sides joined into one band.
let half = t / 2.0;
let mut left = Vec::with_capacity(pts.len());
let mut right = Vec::with_capacity(pts.len());
for (i, p) in pts.iter().enumerate() {
let prev = i.checked_sub(1).and_then(|j| pts.get(j)).and_then(|q| (*p - *q).normalized());
let next = pts.get(i + 1).and_then(|q| (*q - *p).normalized());
let (n, scale) = match (prev, next) {
(Some(a), Some(b)) => {
let m = (a.perp() + b.perp()).normalized().unwrap_or(a.perp());
(m, 1.0 / m.dot(a.perp()).max(0.25))
}
(Some(a), None) | (None, Some(a)) => (a.perp(), 1.0),
(None, None) => continue,
};
left.push(*p + n * (half * scale));
right.push(*p - n * (half * scale));
}
left.extend(right.into_iter().rev());
let mut lp = Loop2::polygon(&left);
if lp.signed_area() < 0.0 {
lp = lp.reversed();
}
let region = Region2 { outer: lp, holes: Vec::new() };
let (a, b) = if sign > 0.0 { (0.0, h) } else { (-h, 0.0) };
let tools = kernel::extrude(&ss.plane, &[region], a, b)?;
tools.into_iter().next().ok_or_else(|| DocError::Invalid("the rib made no solid".into()))
}
/// The point `d` along a polyline. /// The point `d` along a polyline.
fn point_on_polyline(pts: &[Vec2], d: f64) -> Vec2 { fn point_on_polyline(pts: &[Vec2], d: f64) -> Vec2 {
let mut left = d.max(0.0); let mut left = d.max(0.0);
@@ -0,0 +1,4 @@
//! A Rib or Web can grow along the sketch normal (`normal: true`): the wall is `thickness` wide
//! across its curves in the sketch plane and runs from the sketch toward the body. Older designs
//! need no transformation: a rib without `normal` grows across the sketch plane as before; 0.14.x
//! asks for an update instead of building a normal rib in the plane.
+8 -3
View File
@@ -25,11 +25,11 @@ pub static COMMANDS: &[CommandSpec] = &[
CommandSpec::new("solid.rib", "Rib", rib) CommandSpec::new("solid.rib", "Rib", rib)
.at("SOLID", "CREATE") .at("SOLID", "CREATE")
.icon("rib") .icon("rib")
.params("sketch, curves: [open curve ids, in order]; thickness (across the sketch plane); depth? (default: to the next face); flip?: bool (fill to the other side)"), .params("sketch, curves: [open curve ids, in order]; thickness (across the sketch plane, or across the curves when normal); depth? (default: to the next face); flip?: bool (fill to the other side); direction?: in_plane|normal (grow across the sketch plane, the default, or along its normal)"),
CommandSpec::new("solid.web", "Web", web) CommandSpec::new("solid.web", "Web", web)
.at("SOLID", "CREATE") .at("SOLID", "CREATE")
.icon("web") .icon("web")
.params("sketch, curves: [open curve ids] (one wall each); thickness; depth? (default: to the next face); flip?: bool"), .params("sketch, curves: [open curve ids] (one wall each); thickness; depth? (default: to the next face); flip?: bool; direction?: in_plane|normal"),
CommandSpec::new("solid.emboss", "Emboss", emboss) CommandSpec::new("solid.emboss", "Emboss", emboss)
.at("SOLID", "CREATE") .at("SOLID", "CREATE")
.icon("emboss") .icon("emboss")
@@ -133,7 +133,12 @@ fn rib_like(s: &mut Session, p: &Value, cmd: &str, web: bool) -> Result<Value> {
check_expr(s, d, Kind::Length, cmd, "depth")?; check_expr(s, d, Kind::Length, cmd, "depth")?;
} }
let flip = bool_(p, "flip").unwrap_or(false); let flip = bool_(p, "flip").unwrap_or(false);
add_feature(s, p, FeatureKind::Rib { sketch: SketchId(sketch), curves, thickness, depth, flip, web }) let normal = match str_(p, "direction").map(str::to_ascii_lowercase).as_deref() {
None | Some("in_plane" | "plane") => false,
Some("normal") => true,
Some(o) => return Err(bad(cmd, format!("unknown direction `{o}` (in_plane or normal)"))),
};
add_feature(s, p, FeatureKind::Rib { sketch: SketchId(sketch), curves, thickness, depth, flip, web, normal })
} }
fn rib(s: &mut Session, p: &Value) -> Result<Value> { fn rib(s: &mut Session, p: &Value) -> Result<Value> {
@@ -77,6 +77,28 @@ fn rib_with_a_depth_and_web() {
assert!(e.is_err_and(|e| e.to_string().contains("do not face a body"))); assert!(e.is_err_and(|e| e.to_string().contains("do not face a body")));
} }
#[test]
fn rib_along_the_sketch_normal_reaches_the_body() {
// A 20 x 20 x 10 box; a line along X on a sketch 10 in front of it (y = -10), whose plane
// the box does not cross, so only a rib along the sketch normal can reach it.
let mut s = Session::default();
run(&mut s, "solid.box", json!({"length": 20, "width": 20, "height": 10, "corner": [0, 0, 0]}));
run(&mut s, "sketch.create", json!({"plane": {"origin": [0, -10, 0], "x_dir": [1, 0, 0], "y_dir": [0, 0, 1]}, "name": "Front"}));
run(&mut s, "sketch.line", json!({"points": [[2, 5], [18, 5]], "ids": ["l1"]}));
run(&mut s, "sketch.finish", json!({}));
let e = s.execute("solid.rib", &json!({"sketch": "Front", "curves": ["l1"], "thickness": 2}));
assert!(e.is_err_and(|e| e.to_string().contains("do not face a body")));
run(&mut s, "solid.rib", json!({"sketch": "Front", "curves": ["l1"], "thickness": 2, "direction": "normal"}));
// 16 long, 2 thick, 10 from the sketch to the box.
assert!(rel(volume(&mut s), 4000.0 + 320.0) < 1e-6, "{}", volume(&mut s));
assert_eq!(measure(&mut s)["body_count"], 1);
run(&mut s, "edit.undo", json!({}));
run(&mut s, "solid.rib", json!({"sketch": "Front", "curves": ["l1"], "thickness": 2, "direction": "normal", "depth": 4}));
assert!(rel(volume(&mut s), 4000.0 + 128.0) < 1e-6, "{}", volume(&mut s));
let e = s.execute("solid.rib", &json!({"sketch": "Front", "curves": ["l1"], "thickness": 2, "direction": "sideways"}));
assert!(e.is_err_and(|e| e.to_string().contains("unknown direction")));
}
#[test] #[test]
fn emboss_and_deboss_on_a_face() { fn emboss_and_deboss_on_a_face() {
let mut s = Session::default(); let mut s = Session::default();
+15 -4
View File
@@ -151,6 +151,8 @@ pub enum Kind {
thickness: String, thickness: String,
depth: String, depth: String,
flip: bool, flip: bool,
/// Grow along the sketch normal rather than across the plane.
normal: bool,
}, },
/// Raise or sink profiles of a sketch on a face. /// Raise or sink profiles of a sketch on a face.
Emboss { Emboss {
@@ -491,7 +493,7 @@ impl Dialog {
Dialog::new(Kind::Mirror, vec![SelInput::new("Objects", BODIES, true), SelInput::new("Mirror Plane", PLANES | PLANAR_FACES, false)]) Dialog::new(Kind::Mirror, vec![SelInput::new("Objects", BODIES, true), SelInput::new("Mirror Plane", PLANES | PLANAR_FACES, false)])
} }
"solid.rib" | "solid.web" => Dialog::new( "solid.rib" | "solid.web" => Dialog::new(
Kind::Rib { web: id == "solid.web", thickness: "2 mm".into(), depth: String::new(), flip: false }, Kind::Rib { web: id == "solid.web", thickness: "2 mm".into(), depth: String::new(), flip: false, normal: false },
vec![SelInput::new("Curves", CURVES, true)], vec![SelInput::new("Curves", CURVES, true)],
), ),
"solid.emboss" => Dialog::new( "solid.emboss" => Dialog::new(
@@ -2701,7 +2703,12 @@ pub fn show(app: &mut SolveApp, ctx: &egui::Context) {
ui.end_row(); ui.end_row();
} }
Kind::Split | Kind::ReplaceFace | Kind::Remove | Kind::Midplane => {} Kind::Split | Kind::ReplaceFace | Kind::Remove | Kind::Midplane => {}
Kind::Rib { thickness, depth, flip, .. } => { Kind::Rib { thickness, depth, flip, normal, .. } => {
row_label(ui, "Direction");
let mut i = usize::from(*normal);
combo(ui, "rib_direction", &["In plane", "Normal to sketch"], &mut i);
*normal = i == 1;
ui.end_row();
row_label(ui, "Thickness"); row_label(ui, "Thickness");
enter |= field(ui, thickness); enter |= field(ui, thickness);
ui.end_row(); ui.end_row();
@@ -3977,11 +3984,14 @@ fn dialog_commands(app: &SolveApp, d: &Dialog) -> Result<Vec<(String, Value)>, S
}; };
("construct.plane.at_angle", json!({"base": base, "axis": axis, "angle": angle})) ("construct.plane.at_angle", json!({"base": base, "axis": axis, "angle": angle}))
} }
Kind::Rib { web, thickness, depth, flip } => { Kind::Rib { web, thickness, depth, flip, normal } => {
need(0, "open sketch curves")?; need(0, "open sketch curves")?;
let curves = curve_ids(d, 0); let curves = curve_ids(d, 0);
let sketch = curves_sketch(app, &curves, None).ok_or("the curves must be in one sketch")?; let sketch = curves_sketch(app, &curves, None).ok_or("the curves must be in one sketch")?;
let mut p = json!({"sketch": sketch, "curves": curves, "thickness": thickness, "flip": flip}); let mut p = json!({"sketch": sketch, "curves": curves, "thickness": thickness, "flip": flip});
if *normal {
p["direction"] = json!("normal");
}
if !depth.trim().is_empty() { if !depth.trim().is_empty() {
p["depth"] = json!(depth); p["depth"] = json!(depth);
} }
@@ -5204,13 +5214,14 @@ pub fn for_feature(app: &SolveApp, id: u64, marker: Option<usize>) -> Option<Dia
} }
d d
} }
FeatureKind::Rib { curves, thickness, depth, flip, web, .. } => { FeatureKind::Rib { curves, thickness, depth, flip, web, normal, .. } => {
let mut d = start(if *web { "solid.web" } else { "solid.rib" })?; let mut d = start(if *web { "solid.web" } else { "solid.rib" })?;
d.kind = Kind::Rib { d.kind = Kind::Rib {
web: *web, web: *web,
thickness: thickness.to_string(), thickness: thickness.to_string(),
depth: depth.as_deref().map(str::to_string).unwrap_or_default(), depth: depth.as_deref().map(str::to_string).unwrap_or_default(),
flip: *flip, flip: *flip,
normal: *normal,
}; };
if let Some(inp) = d.inputs.first_mut() { if let Some(inp) = d.inputs.first_mut() {
inp.items = curves.iter().map(|id| Sel::SketchCurve { id: id.clone() }).collect(); inp.items = curves.iter().map(|id| Sel::SketchCurve { id: id.clone() }).collect();