diff --git a/locales/ar-SA/opencadstudio.ftl b/locales/ar-SA/opencadstudio.ftl index 10a6c1685..54252271b 100644 --- a/locales/ar-SA/opencadstudio.ftl +++ b/locales/ar-SA/opencadstudio.ftl @@ -326,6 +326,7 @@ common = .block-rotation = دوران الكتلة .blocks = الكتل .blue = أزرق + .bulge = التحدب .boolean-failed-the-solids-may-not-overlap = فشلت العملية المنطقية — قد لا تتداخل المجسمات. .boolean-select-exactly-two-solids-created-this = العملية المنطقية: حدد مجسمين بالضبط أُنشئا في هذه الجلسة. .bottom = أسفل @@ -1341,6 +1342,18 @@ draw = .ray-specify-through-point = RAY حدد نقطة العبور: .rect-specify-first-corner = RECT حدد الزاوية الأولى: .rect-specify-opposite-corner = RECT حدد الزاوية المقابلة: + .rect-calculate-dimensions-based-on-length-width = RECT احسب الأبعاد استنادًا إلى [الطول / العرض] <الطول>: + .rect-specify-elevation = RECT حدد الارتفاع <__ocs_fmt_0__>: + .rect-specify-fillet-radius = RECT حدد نصف قطر التدوير <__ocs_fmt_0__>: + .rect-specify-first-chamfer-distance = RECT حدد مسافة الشطف الأولى <__ocs_fmt_0__>: + .rect-specify-orientation-from-the-first-corner = RECT حدد الاتجاه من الزاوية الأولى: + .rect-specify-rectangle-area = RECT حدد مساحة المستطيل: + .rect-specify-rectangle-length = RECT حدد طول المستطيل: + .rect-specify-rectangle-width = RECT حدد عرض المستطيل: + .rect-specify-rotation-angle = RECT حدد زاوية الدوران <__ocs_fmt_0__>: + .rect-specify-second-chamfer-distance = RECT حدد مسافة الشطف الثانية <__ocs_fmt_0__>: + .rect-specify-thickness = RECT حدد السمك <__ocs_fmt_0__>: + .rect-specify-width = RECT حدد العرض <__ocs_fmt_0__>: .rect-cen-specify-center-point = RECT CEN حدد نقطة المركز: .rect-cen-specify-corner-point = RECT CEN حدد نقطة الزاوية: .rect-rot-specify-adjacent-corner-defines-edge = RECT ROT حدد الزاوية المجاورة (تحدد اتجاه الحافة): diff --git a/locales/bg-BG/opencadstudio.ftl b/locales/bg-BG/opencadstudio.ftl index 2c828215f..5da170641 100644 --- a/locales/bg-BG/opencadstudio.ftl +++ b/locales/bg-BG/opencadstudio.ftl @@ -313,6 +313,7 @@ common = .block-rotation = Завъртане на блок .blocks = Блокове .blue = Синьо + .bulge = Изпъкналост .boolean-failed-the-solids-may-not-overlap = Булева операция неуспешна — телата може да не се припокриват. .boolean-select-exactly-two-solids-created-this = Булева операция: избери точно две тела, създадени в тази сесия. .bottom = Долу @@ -1328,6 +1329,18 @@ draw = .ray-specify-through-point = RAY Посочи точка на преминаване: .rect-specify-first-corner = RECT Посочи първи ъгъл: .rect-specify-opposite-corner = RECT Посочи противоположен ъгъл: + .rect-calculate-dimensions-based-on-length-width = RECT Изчисли размерите по [Дължина / Ширина] <Дължина>: + .rect-specify-elevation = RECT Посочи кота <__ocs_fmt_0__>: + .rect-specify-fillet-radius = RECT Посочи радиус на закръгляне <__ocs_fmt_0__>: + .rect-specify-first-chamfer-distance = RECT Посочи първо разстояние на фаската <__ocs_fmt_0__>: + .rect-specify-orientation-from-the-first-corner = RECT Посочи ориентация от първия ъгъл: + .rect-specify-rectangle-area = RECT Посочи площ на правоъгълника: + .rect-specify-rectangle-length = RECT Посочи дължина на правоъгълника: + .rect-specify-rectangle-width = RECT Посочи ширина на правоъгълника: + .rect-specify-rotation-angle = RECT Посочи ъгъл на завъртане <__ocs_fmt_0__>: + .rect-specify-second-chamfer-distance = RECT Посочи второ разстояние на фаската <__ocs_fmt_0__>: + .rect-specify-thickness = RECT Посочи дебелина <__ocs_fmt_0__>: + .rect-specify-width = RECT Посочи ширина <__ocs_fmt_0__>: .rect-cen-specify-center-point = RECT CEN Посочи централна точка: .rect-cen-specify-corner-point = RECT CEN Посочи ъглова точка: .rect-rot-specify-adjacent-corner-defines-edge = RECT ROT Посочи съседен ъгъл (определя посоката на страната): diff --git a/locales/cs-CZ/opencadstudio.ftl b/locales/cs-CZ/opencadstudio.ftl index 60f9e032f..7c2337f8c 100644 --- a/locales/cs-CZ/opencadstudio.ftl +++ b/locales/cs-CZ/opencadstudio.ftl @@ -313,6 +313,7 @@ common = .block-rotation = Rotace bloku .blocks = Bloky .blue = Modrá + .bulge = Vyklenutí .boolean-failed-the-solids-may-not-overlap = Logická hodnota selhala – tělesa se nemusí překrývat. .boolean-select-exactly-two-solids-created-this = Boolean: vyberte přesně dvě tělesa vytvořená v této relaci. .bottom = Spodní @@ -1328,6 +1329,18 @@ draw = .ray-specify-through-point = RAY Určete průchozí bod: .rect-specify-first-corner = RECT Určete první roh: .rect-specify-opposite-corner = RECT Určete opačný roh: + .rect-calculate-dimensions-based-on-length-width = RECT Vypočítat rozměry podle [Délky / Šířky] : + .rect-specify-elevation = RECT Určete výšku <__ocs_fmt_0__>: + .rect-specify-fillet-radius = RECT Určete poloměr zaoblení <__ocs_fmt_0__>: + .rect-specify-first-chamfer-distance = RECT Určete první vzdálenost zkosení <__ocs_fmt_0__>: + .rect-specify-orientation-from-the-first-corner = RECT Určete orientaci od prvního rohu: + .rect-specify-rectangle-area = RECT Určete plochu obdélníku: + .rect-specify-rectangle-length = RECT Určete délku obdélníku: + .rect-specify-rectangle-width = RECT Určete šířku obdélníku: + .rect-specify-rotation-angle = RECT Určete úhel otočení <__ocs_fmt_0__>: + .rect-specify-second-chamfer-distance = RECT Určete druhou vzdálenost zkosení <__ocs_fmt_0__>: + .rect-specify-thickness = RECT Určete tloušťku <__ocs_fmt_0__>: + .rect-specify-width = RECT Určete šířku <__ocs_fmt_0__>: .rect-cen-specify-center-point = RECT CEN Zadejte střed: .rect-cen-specify-corner-point =RECT CEN Zadejte rohový bod: .rect-rot-specify-adjacent-corner-defines-edge = RECT ROT Určete sousední roh (definuje směr hrany): diff --git a/locales/de-DE/opencadstudio.ftl b/locales/de-DE/opencadstudio.ftl index e5631d614..c17b0730a 100644 --- a/locales/de-DE/opencadstudio.ftl +++ b/locales/de-DE/opencadstudio.ftl @@ -314,6 +314,7 @@ common = .block-rotation = Blockdrehung .blocks = Blöcke .blue = Blau + .bulge = Wölbung .boolean-failed-the-solids-may-not-overlap = Boolean versagte - die Feststoffe überlappen sich möglicherweise nicht. .boolean-select-exactly-two-solids-created-this = Boolean: Wählen Sie genau zwei Feststoffe erstellt diese Sitzung. .bottom = Unten @@ -1329,6 +1330,18 @@ draw = .ray-specify-through-point = RAY Geben Sie durch Punkt an: .rect-specify-first-corner = RECT Geben Sie die erste Ecke an: .rect-specify-opposite-corner = RECT Geben Sie die gegenüberliegende Ecke an: + .rect-calculate-dimensions-based-on-length-width = RECT Abmessungen anhand von [Länge / Breite] berechnen : + .rect-specify-elevation = RECT Erhebung angeben <__ocs_fmt_0__>: + .rect-specify-fillet-radius = RECT Abrundungsradius angeben <__ocs_fmt_0__>: + .rect-specify-first-chamfer-distance = RECT Ersten Fasenabstand angeben <__ocs_fmt_0__>: + .rect-specify-orientation-from-the-first-corner = RECT Ausrichtung von der ersten Ecke angeben: + .rect-specify-rectangle-area = RECT Rechteckfläche angeben: + .rect-specify-rectangle-length = RECT Rechtecklänge angeben: + .rect-specify-rectangle-width = RECT Rechteckbreite angeben: + .rect-specify-rotation-angle = RECT Drehwinkel angeben <__ocs_fmt_0__>: + .rect-specify-second-chamfer-distance = RECT Zweiten Fasenabstand angeben <__ocs_fmt_0__>: + .rect-specify-thickness = RECT Stärke angeben <__ocs_fmt_0__>: + .rect-specify-width = RECT Breite angeben <__ocs_fmt_0__>: .rect-cen-specify-center-point = RECT CEN Mittelpunkt angeben: .rect-cen-specify-corner-point = RECT CEN Eckpunkt angeben: .rect-rot-specify-adjacent-corner-defines-edge = RECT ROT Angrenzende Ecke angeben (Kantenrichtung definieren): diff --git a/locales/en-US/opencadstudio.ftl b/locales/en-US/opencadstudio.ftl index 62ad9cb4d..12e6b94b6 100644 --- a/locales/en-US/opencadstudio.ftl +++ b/locales/en-US/opencadstudio.ftl @@ -314,6 +314,7 @@ common = .block-rotation = Block rotation .blocks = Blocks .blue = Blue + .bulge = Bulge .boolean-failed-the-solids-may-not-overlap = Boolean failed — the solids may not overlap. .boolean-select-exactly-two-solids-created-this = Boolean: select exactly two solids created this session. .bottom = Bottom @@ -1329,6 +1330,18 @@ draw = .ray-specify-through-point = RAY Specify through point: .rect-specify-first-corner = RECT Specify first corner: .rect-specify-opposite-corner = RECT Specify opposite corner: + .rect-calculate-dimensions-based-on-length-width = RECT Calculate dimensions based on [Length / Width] : + .rect-specify-elevation = RECT Specify elevation <__ocs_fmt_0__>: + .rect-specify-fillet-radius = RECT Specify fillet radius <__ocs_fmt_0__>: + .rect-specify-first-chamfer-distance = RECT Specify first chamfer distance <__ocs_fmt_0__>: + .rect-specify-orientation-from-the-first-corner = RECT Specify orientation from the first corner: + .rect-specify-rectangle-area = RECT Specify rectangle area: + .rect-specify-rectangle-length = RECT Specify rectangle length: + .rect-specify-rectangle-width = RECT Specify rectangle width: + .rect-specify-rotation-angle = RECT Specify rotation angle <__ocs_fmt_0__>: + .rect-specify-second-chamfer-distance = RECT Specify second chamfer distance <__ocs_fmt_0__>: + .rect-specify-thickness = RECT Specify thickness <__ocs_fmt_0__>: + .rect-specify-width = RECT Specify width <__ocs_fmt_0__>: .rect-cen-specify-center-point = RECT CEN Specify center point: .rect-cen-specify-corner-point = RECT CEN Specify corner point: .rect-rot-specify-adjacent-corner-defines-edge = RECT ROT Specify adjacent corner (defines edge direction): diff --git a/locales/es-ES/opencadstudio.ftl b/locales/es-ES/opencadstudio.ftl index 162e2b2df..cf6101bdc 100644 --- a/locales/es-ES/opencadstudio.ftl +++ b/locales/es-ES/opencadstudio.ftl @@ -314,6 +314,7 @@ common = .block-rotation = Rotación del bloque .blocks = Bloques .blue = Azul + .bulge = Curvatura .boolean-failed-the-solids-may-not-overlap = Falló la operación booleana; es posible que los sólidos no se solapen. .boolean-select-exactly-two-solids-created-this = Operación booleana: seleccione exactamente dos sólidos creados en esta sesión. .bottom = Inferior @@ -1329,6 +1330,18 @@ draw = .ray-specify-through-point = RAY Precise el punto de paso: .rect-specify-first-corner = RECT Precise la primera esquina: .rect-specify-opposite-corner = RECT Precise la esquina opuesta: + .rect-calculate-dimensions-based-on-length-width = RECT Calcule las dimensiones según [Longitud / Anchura] : + .rect-specify-elevation = RECT Precise la elevación <__ocs_fmt_0__>: + .rect-specify-fillet-radius = RECT Precise el radio de empalme <__ocs_fmt_0__>: + .rect-specify-first-chamfer-distance = RECT Precise la primera distancia de chaflán <__ocs_fmt_0__>: + .rect-specify-orientation-from-the-first-corner = RECT Precise la orientación desde la primera esquina: + .rect-specify-rectangle-area = RECT Precise el área del rectángulo: + .rect-specify-rectangle-length = RECT Precise la longitud del rectángulo: + .rect-specify-rectangle-width = RECT Precise la anchura del rectángulo: + .rect-specify-rotation-angle = RECT Precise el ángulo de rotación <__ocs_fmt_0__>: + .rect-specify-second-chamfer-distance = RECT Precise la segunda distancia de chaflán <__ocs_fmt_0__>: + .rect-specify-thickness = RECT Precise el espesor <__ocs_fmt_0__>: + .rect-specify-width = RECT Precise la anchura <__ocs_fmt_0__>: .rect-cen-specify-center-point = RECT CEN Precise el punto central: .rect-cen-specify-corner-point = RECT CEN Precise el punto de esquina: .rect-rot-specify-adjacent-corner-defines-edge = RECT ROT Precise la esquina adyacente (define la dirección de la arista): diff --git a/locales/fi-FI/opencadstudio.ftl b/locales/fi-FI/opencadstudio.ftl index c037195f7..01752032c 100644 --- a/locales/fi-FI/opencadstudio.ftl +++ b/locales/fi-FI/opencadstudio.ftl @@ -313,6 +313,7 @@ common = .block-rotation = Lohkon kierto .blocks = Lohkot .blue = Sininen + .bulge = Kaarevuus .boolean-failed-the-solids-may-not-overlap = Boolen arvo epäonnistui – kiinteät aineet eivät ehkä mene päällekkäin. .boolean-select-exactly-two-solids-created-this = Boolen arvo: valitse täsmälleen kaksi tässä istunnossa luotua solidia. .bottom = Pohja @@ -1328,6 +1329,18 @@ draw = .ray-specify-through-point = RAY Määritä pisteen kautta: .rect-specify-first-corner = RECT Määritä ensimmäinen kulma: .rect-specify-opposite-corner = RECT Määritä vastakkainen kulma: + .rect-calculate-dimensions-based-on-length-width = RECT Laske mitat [Pituuden / Leveyden] perusteella : + .rect-specify-elevation = RECT Määritä korkeusasema <__ocs_fmt_0__>: + .rect-specify-fillet-radius = RECT Määritä pyöristyssäde <__ocs_fmt_0__>: + .rect-specify-first-chamfer-distance = RECT Määritä ensimmäinen viiste-etäisyys <__ocs_fmt_0__>: + .rect-specify-orientation-from-the-first-corner = RECT Määritä suunta ensimmäisestä kulmasta: + .rect-specify-rectangle-area = RECT Määritä suorakulmion pinta-ala: + .rect-specify-rectangle-length = RECT Määritä suorakulmion pituus: + .rect-specify-rectangle-width = RECT Määritä suorakulmion leveys: + .rect-specify-rotation-angle = RECT Määritä kiertokulma <__ocs_fmt_0__>: + .rect-specify-second-chamfer-distance = RECT Määritä toinen viiste-etäisyys <__ocs_fmt_0__>: + .rect-specify-thickness = RECT Määritä paksuus <__ocs_fmt_0__>: + .rect-specify-width = RECT Määritä leveys <__ocs_fmt_0__>: .rect-cen-specify-center-point = RECT CEN Määritä keskipiste: .rect-cen-specify-corner-point =RECT CEN Määritä kulmapiste: .rect-rot-specify-adjacent-corner-defines-edge = RECT ROT Määritä viereinen kulma (määrittää reunasuunnan): diff --git a/locales/fr-FR/opencadstudio.ftl b/locales/fr-FR/opencadstudio.ftl index 53fd01193..8270c93de 100644 --- a/locales/fr-FR/opencadstudio.ftl +++ b/locales/fr-FR/opencadstudio.ftl @@ -314,6 +314,7 @@ common = .block-rotation = Rotation par blocs .blocks = Blocs .blue = Bleu + .bulge = Courbure .boolean-failed-the-solids-may-not-overlap = Booléenne échoué — les solides ne peuvent pas se chevaucher. .boolean-select-exactly-two-solids-created-this = Booléen : sélectionnez exactement deux solides créés cette session. .bottom = En bas @@ -1329,6 +1330,18 @@ draw = .ray-specify-through-point = RAY Spécifiez par le point: .rect-specify-first-corner = RECT Spécifiez le premier coin : .rect-specify-opposite-corner = RECT Spécifiez le coin opposé : + .rect-calculate-dimensions-based-on-length-width = RECT Calculer les dimensions selon [Longueur / Largeur] : + .rect-specify-elevation = RECT Spécifiez l’élévation <__ocs_fmt_0__> : + .rect-specify-fillet-radius = RECT Spécifiez le rayon de raccord <__ocs_fmt_0__> : + .rect-specify-first-chamfer-distance = RECT Spécifiez la première distance de chanfrein <__ocs_fmt_0__> : + .rect-specify-orientation-from-the-first-corner = RECT Spécifiez l’orientation depuis le premier coin : + .rect-specify-rectangle-area = RECT Spécifiez l’aire du rectangle : + .rect-specify-rectangle-length = RECT Spécifiez la longueur du rectangle : + .rect-specify-rectangle-width = RECT Spécifiez la largeur du rectangle : + .rect-specify-rotation-angle = RECT Spécifiez l’angle de rotation <__ocs_fmt_0__> : + .rect-specify-second-chamfer-distance = RECT Spécifiez la deuxième distance de chanfrein <__ocs_fmt_0__> : + .rect-specify-thickness = RECT Spécifiez l’épaisseur <__ocs_fmt_0__> : + .rect-specify-width = RECT Spécifiez la largeur <__ocs_fmt_0__> : .rect-cen-specify-center-point = RECT CEN Spécifier le point central: .rect-cen-specify-corner-point = RECT CEN Spécifiez le point d'angle: .rect-rot-specify-adjacent-corner-defines-edge = RECT ROT Spécifiez le coin adjacent (définit la direction du bord): diff --git a/locales/hi-IN/opencadstudio.ftl b/locales/hi-IN/opencadstudio.ftl index 11d804572..abf200abc 100644 --- a/locales/hi-IN/opencadstudio.ftl +++ b/locales/hi-IN/opencadstudio.ftl @@ -302,6 +302,7 @@ common = .block-rotation = ब्लॉक रोटेशन .blocks = ब्लॉक .blue = नीला + .bulge = उभार .boolean-failed-the-solids-may-not-overlap = बोओलेन विफल रहा - ठोस ओवरलैप नहीं हो सकता। .boolean-select-exactly-two-solids-created-this = बोओलान: इस सत्र को बनाने वाले दो ठोस चुनें। .bottom = नीचे @@ -1317,6 +1318,18 @@ draw = .ray-specify-through-point = RAY पॉइंट के माध्यम से निर्दिष्ट करें: .rect-specify-first-corner = RECT पहले कोने को निर्दिष्ट करें: .rect-specify-opposite-corner = RECT विपरीत कोने निर्दिष्ट करें: + .rect-calculate-dimensions-based-on-length-width = RECT [लंबाई / चौड़ाई] के आधार पर आयाम निकालें <लंबाई>: + .rect-specify-elevation = RECT ऊंचाई निर्दिष्ट करें <__ocs_fmt_0__>: + .rect-specify-fillet-radius = RECT फिलेट त्रिज्या निर्दिष्ट करें <__ocs_fmt_0__>: + .rect-specify-first-chamfer-distance = RECT पहली चैम्फर दूरी निर्दिष्ट करें <__ocs_fmt_0__>: + .rect-specify-orientation-from-the-first-corner = RECT पहले कोने से दिशा निर्दिष्ट करें: + .rect-specify-rectangle-area = RECT आयत का क्षेत्रफल निर्दिष्ट करें: + .rect-specify-rectangle-length = RECT आयत की लंबाई निर्दिष्ट करें: + .rect-specify-rectangle-width = RECT आयत की चौड़ाई निर्दिष्ट करें: + .rect-specify-rotation-angle = RECT घूर्णन कोण निर्दिष्ट करें <__ocs_fmt_0__>: + .rect-specify-second-chamfer-distance = RECT दूसरी चैम्फर दूरी निर्दिष्ट करें <__ocs_fmt_0__>: + .rect-specify-thickness = RECT मोटाई निर्दिष्ट करें <__ocs_fmt_0__>: + .rect-specify-width = RECT चौड़ाई निर्दिष्ट करें <__ocs_fmt_0__>: .rect-cen-specify-center-point = RECT CEN केंद्र बिंदु निर्दिष्ट करें: .rect-cen-specify-corner-point = RECT CEN कोने बिंदु निर्दिष्ट करें: .rect-rot-specify-adjacent-corner-defines-edge = RECT ROT निकटवर्ती कोने (डिफ़ाइन किनारे दिशा) निर्दिष्ट करें: diff --git a/locales/hu-HU/opencadstudio.ftl b/locales/hu-HU/opencadstudio.ftl index 5b51c6c85..13e4c28c5 100644 --- a/locales/hu-HU/opencadstudio.ftl +++ b/locales/hu-HU/opencadstudio.ftl @@ -313,6 +313,7 @@ common = .block-rotation = Blokkforgatás .blocks = Blokkok .blue = kék + .bulge = Kidudorodás .boolean-failed-the-solids-may-not-overlap = A logikai elemzés sikertelen – előfordulhat, hogy a szilárdtestek nem fedik át egymást. .boolean-select-exactly-two-solids-created-this = Logikai: jelöljön ki pontosan két szilárdtestet, amelyek ebben a munkamenetben jöttek létre. .bottom = Alul @@ -1328,6 +1329,18 @@ draw = .ray-specify-through-point = RAY Pont megadása: .rect-specify-first-corner = RECT Adja meg az első sarkot: .rect-specify-opposite-corner = RECT Adja meg a szemközti sarkot: + .rect-calculate-dimensions-based-on-length-width = RECT Méretek számítása [Hossz / Szélesség] alapján : + .rect-specify-elevation = RECT Adja meg a magasságot <__ocs_fmt_0__>: + .rect-specify-fillet-radius = RECT Adja meg a lekerekítés sugarát <__ocs_fmt_0__>: + .rect-specify-first-chamfer-distance = RECT Adja meg az első letörési távolságot <__ocs_fmt_0__>: + .rect-specify-orientation-from-the-first-corner = RECT Adja meg az irányt az első saroktól: + .rect-specify-rectangle-area = RECT Adja meg a téglalap területét: + .rect-specify-rectangle-length = RECT Adja meg a téglalap hosszát: + .rect-specify-rectangle-width = RECT Adja meg a téglalap szélességét: + .rect-specify-rotation-angle = RECT Adja meg az elforgatási szöget <__ocs_fmt_0__>: + .rect-specify-second-chamfer-distance = RECT Adja meg a második letörési távolságot <__ocs_fmt_0__>: + .rect-specify-thickness = RECT Adja meg a vastagságot <__ocs_fmt_0__>: + .rect-specify-width = RECT Adja meg a szélességet <__ocs_fmt_0__>: .rect-cen-specify-center-point = RECT CEN Középpont megadása: .rect-cen-specify-corner-point =RECT CEN Sarokpont megadása: .rect-rot-specify-adjacent-corner-defines-edge = RECT ROT Adja meg a szomszédos sarkot (meghatározza az él irányát): diff --git a/locales/it-IT/opencadstudio.ftl b/locales/it-IT/opencadstudio.ftl index 16bf92147..5acada86d 100644 --- a/locales/it-IT/opencadstudio.ftl +++ b/locales/it-IT/opencadstudio.ftl @@ -313,6 +313,7 @@ common = .block-rotation = Bloccare la rotazione .blocks = Blocchi .blue = Blu + .bulge = Curvatura .boolean-failed-the-solids-may-not-overlap = Booleano non riuscito: i solidi potrebbero non sovrapporsi. .boolean-select-exactly-two-solids-created-this = Booleano: seleziona esattamente due solidi creati in questa sessione. .bottom = In basso @@ -1328,6 +1329,18 @@ draw = .ray-specify-through-point = RAY Specificare il punto di passaggio: .rect-specify-first-corner = RECT Specificare il primo angolo: .rect-specify-opposite-corner = RECT Specificare l'angolo opposto: + .rect-calculate-dimensions-based-on-length-width = RECT Calcolare le dimensioni in base a [Lunghezza / Larghezza] : + .rect-specify-elevation = RECT Specificare l’elevazione <__ocs_fmt_0__>: + .rect-specify-fillet-radius = RECT Specificare il raggio di raccordo <__ocs_fmt_0__>: + .rect-specify-first-chamfer-distance = RECT Specificare la prima distanza di smusso <__ocs_fmt_0__>: + .rect-specify-orientation-from-the-first-corner = RECT Specificare l’orientamento dal primo angolo: + .rect-specify-rectangle-area = RECT Specificare l’area del rettangolo: + .rect-specify-rectangle-length = RECT Specificare la lunghezza del rettangolo: + .rect-specify-rectangle-width = RECT Specificare la larghezza del rettangolo: + .rect-specify-rotation-angle = RECT Specificare l’angolo di rotazione <__ocs_fmt_0__>: + .rect-specify-second-chamfer-distance = RECT Specificare la seconda distanza di smusso <__ocs_fmt_0__>: + .rect-specify-thickness = RECT Specificare lo spessore <__ocs_fmt_0__>: + .rect-specify-width = RECT Specificare la larghezza <__ocs_fmt_0__>: .rect-cen-specify-center-point = RECT CEN Specificare il punto centrale: .rect-cen-specify-corner-point =RECT CEN Specificare il punto d'angolo: .rect-rot-specify-adjacent-corner-defines-edge = RECT ROT Specificare l'angolo adiacente (definisce la direzione del bordo): diff --git a/locales/ja-JP/opencadstudio.ftl b/locales/ja-JP/opencadstudio.ftl index 9882b99a6..c35743c22 100644 --- a/locales/ja-JP/opencadstudio.ftl +++ b/locales/ja-JP/opencadstudio.ftl @@ -304,6 +304,7 @@ common = .block-rotation = ブロック回転 .blocks = ブロック .blue = 青 + .bulge = ふくらみ .boolean-failed-the-solids-may-not-overlap = ブール演算に失敗しました — ソリッドが重なっていない可能性があります。 .boolean-select-exactly-two-solids-created-this = ブール演算: このセッションで作成したソリッドを 2 つ選択してください。 .bottom = 下 @@ -1319,6 +1320,18 @@ draw = .ray-specify-through-point = RAY 通過点を指定: .rect-specify-first-corner = RECT 最初のコーナーを指定: .rect-specify-opposite-corner = RECT 反対側のコーナーを指定: + .rect-calculate-dimensions-based-on-length-width = RECT [長さ / 幅] に基づいて寸法を計算 <長さ>: + .rect-specify-elevation = RECT 高度を指定 <__ocs_fmt_0__>: + .rect-specify-fillet-radius = RECT フィレット半径を指定 <__ocs_fmt_0__>: + .rect-specify-first-chamfer-distance = RECT 1 番目の面取り距離を指定 <__ocs_fmt_0__>: + .rect-specify-orientation-from-the-first-corner = RECT 最初のコーナーから方向を指定: + .rect-specify-rectangle-area = RECT 長方形の面積を指定: + .rect-specify-rectangle-length = RECT 長方形の長さを指定: + .rect-specify-rectangle-width = RECT 長方形の幅を指定: + .rect-specify-rotation-angle = RECT 回転角度を指定 <__ocs_fmt_0__>: + .rect-specify-second-chamfer-distance = RECT 2 番目の面取り距離を指定 <__ocs_fmt_0__>: + .rect-specify-thickness = RECT 厚さを指定 <__ocs_fmt_0__>: + .rect-specify-width = RECT 幅を指定 <__ocs_fmt_0__>: .rect-cen-specify-center-point = RECT CEN 中心点を指定: .rect-cen-specify-corner-point = RECT CEN コーナー点を指定: .rect-rot-specify-adjacent-corner-defines-edge = RECT ROT 隣接コーナーを指定(エッジ方向を定義): diff --git a/locales/ko-KR/opencadstudio.ftl b/locales/ko-KR/opencadstudio.ftl index 3a70c1e8c..d2c649d83 100644 --- a/locales/ko-KR/opencadstudio.ftl +++ b/locales/ko-KR/opencadstudio.ftl @@ -313,6 +313,7 @@ common = .block-rotation = 블록 회전 .blocks = 블록 .blue = 블루 + .bulge = 벌지 .boolean-failed-the-solids-may-not-overlap = 부울 실패 - 솔리드가 겹치지 않을 수 있습니다. .boolean-select-exactly-two-solids-created-this = 부울: 이번 세션에서 생성된 정확히 두 개의 솔리드를 선택합니다. .bottom = 바닥 @@ -1328,6 +1329,18 @@ draw = .ray-specify-through-point = RAY 통과점 지정: .rect-specify-first-corner = RECT 첫 번째 구석 지정: .rect-specify-opposite-corner = RECT 반대편 모서리 지정: + .rect-calculate-dimensions-based-on-length-width = RECT [길이 / 폭]을 기준으로 치수 계산 <길이>: + .rect-specify-elevation = RECT 고도 지정 <__ocs_fmt_0__>: + .rect-specify-fillet-radius = RECT 모깎기 반지름 지정 <__ocs_fmt_0__>: + .rect-specify-first-chamfer-distance = RECT 첫 번째 모따기 거리 지정 <__ocs_fmt_0__>: + .rect-specify-orientation-from-the-first-corner = RECT 첫 번째 구석에서 방향 지정: + .rect-specify-rectangle-area = RECT 직사각형 면적 지정: + .rect-specify-rectangle-length = RECT 직사각형 길이 지정: + .rect-specify-rectangle-width = RECT 직사각형 폭 지정: + .rect-specify-rotation-angle = RECT 회전 각도 지정 <__ocs_fmt_0__>: + .rect-specify-second-chamfer-distance = RECT 두 번째 모따기 거리 지정 <__ocs_fmt_0__>: + .rect-specify-thickness = RECT 두께 지정 <__ocs_fmt_0__>: + .rect-specify-width = RECT 폭 지정 <__ocs_fmt_0__>: .rect-cen-specify-center-point = RECT CEN 중심점 지정: .rect-cen-specify-corner-point =RECT CEN 코너점 지정: .rect-rot-specify-adjacent-corner-defines-edge = RECT ROT 인접 모서리 지정(모서리 방향 정의): diff --git a/locales/nl-NL/opencadstudio.ftl b/locales/nl-NL/opencadstudio.ftl index 86df2f9f6..246e073f2 100644 --- a/locales/nl-NL/opencadstudio.ftl +++ b/locales/nl-NL/opencadstudio.ftl @@ -314,6 +314,7 @@ common = .block-rotation = Blokrotatie .blocks = Blokken .blue = Blauw + .bulge = Bolling .boolean-failed-the-solids-may-not-overlap = Boolean mislukt . . de vaste stoffen mogen niet overlappen. .boolean-select-exactly-two-solids-created-this = Boolean: selecteer precies twee vaste stoffen die deze sessie hebben gemaakt. .bottom = Onderkant @@ -1329,6 +1330,18 @@ draw = .ray-specify-through-point = RAY Door het punt opgeven: .rect-specify-first-corner = RECT Eerste hoek opgeven: .rect-specify-opposite-corner = RECT Geef de andere hoek op: + .rect-calculate-dimensions-based-on-length-width = RECT Bereken afmetingen op basis van [Lengte / Breedte] : + .rect-specify-elevation = RECT Geef hoogte op <__ocs_fmt_0__>: + .rect-specify-fillet-radius = RECT Geef afrondingsstraal op <__ocs_fmt_0__>: + .rect-specify-first-chamfer-distance = RECT Geef eerste afschuiningsafstand op <__ocs_fmt_0__>: + .rect-specify-orientation-from-the-first-corner = RECT Geef richting vanaf de eerste hoek op: + .rect-specify-rectangle-area = RECT Geef oppervlakte van rechthoek op: + .rect-specify-rectangle-length = RECT Geef lengte van rechthoek op: + .rect-specify-rectangle-width = RECT Geef breedte van rechthoek op: + .rect-specify-rotation-angle = RECT Geef rotatiehoek op <__ocs_fmt_0__>: + .rect-specify-second-chamfer-distance = RECT Geef tweede afschuiningsafstand op <__ocs_fmt_0__>: + .rect-specify-thickness = RECT Geef dikte op <__ocs_fmt_0__>: + .rect-specify-width = RECT Geef breedte op <__ocs_fmt_0__>: .rect-cen-specify-center-point = RECT CEN Centrumpunt opgeven: .rect-cen-specify-corner-point = RECT CEN Geef hoekpunt op: .rect-rot-specify-adjacent-corner-defines-edge = RECT ROT Aangrenzende hoek opgeven (randrichting definiëren): diff --git a/locales/pl-PL/opencadstudio.ftl b/locales/pl-PL/opencadstudio.ftl index dbdbd8818..cf5a981ee 100644 --- a/locales/pl-PL/opencadstudio.ftl +++ b/locales/pl-PL/opencadstudio.ftl @@ -313,6 +313,7 @@ common = .block-rotation = Obrót bloku .blocks = Bloki .blue = Niebieski + .bulge = Wybrzuszenie .boolean-failed-the-solids-may-not-overlap = Wartość logiczna nie powiodła się — bryły nie mogą się nakładać. .boolean-select-exactly-two-solids-created-this = Boolean: wybierz dokładnie dwie bryły utworzone w tej sesji. .bottom = Dół @@ -1328,6 +1329,18 @@ draw = .ray-specify-through-point = RAY Określ punkt przelotowy: .rect-specify-first-corner = RECT Określ pierwszy narożnik: .rect-specify-opposite-corner = RECT Określ przeciwny narożnik: + .rect-calculate-dimensions-based-on-length-width = RECT Oblicz wymiary na podstawie [Długości / Szerokości] : + .rect-specify-elevation = RECT Określ rzędną <__ocs_fmt_0__>: + .rect-specify-fillet-radius = RECT Określ promień zaokrąglenia <__ocs_fmt_0__>: + .rect-specify-first-chamfer-distance = RECT Określ pierwszą odległość fazowania <__ocs_fmt_0__>: + .rect-specify-orientation-from-the-first-corner = RECT Określ orientację od pierwszego narożnika: + .rect-specify-rectangle-area = RECT Określ pole prostokąta: + .rect-specify-rectangle-length = RECT Określ długość prostokąta: + .rect-specify-rectangle-width = RECT Określ szerokość prostokąta: + .rect-specify-rotation-angle = RECT Określ kąt obrotu <__ocs_fmt_0__>: + .rect-specify-second-chamfer-distance = RECT Określ drugą odległość fazowania <__ocs_fmt_0__>: + .rect-specify-thickness = RECT Określ grubość <__ocs_fmt_0__>: + .rect-specify-width = RECT Określ szerokość <__ocs_fmt_0__>: .rect-cen-specify-center-point = RECT CEN Określ punkt środkowy: .rect-cen-specify-corner-point =RECT CEN Określ punkt narożny: .rect-rot-specify-adjacent-corner-defines-edge = RECT ROT Określ sąsiadujący narożnik (określa kierunek krawędzi): diff --git a/locales/pt-BR/opencadstudio.ftl b/locales/pt-BR/opencadstudio.ftl index 57e8f09c2..128a43dc4 100644 --- a/locales/pt-BR/opencadstudio.ftl +++ b/locales/pt-BR/opencadstudio.ftl @@ -314,6 +314,7 @@ common = .block-rotation = Rotação do bloco .blocks = Blocos .blue = Azul + .bulge = Curvatura .boolean-failed-the-solids-may-not-overlap = A operação booleana falhou — os sólidos podem não se sobrepor. .boolean-select-exactly-two-solids-created-this = Operação booleana: selecione exatamente dois sólidos criados nesta sessão. .bottom = Inferior @@ -1329,6 +1330,18 @@ draw = .ray-specify-through-point = RAY Especifique o ponto de passagem: .rect-specify-first-corner = RECT Especifique o primeiro canto: .rect-specify-opposite-corner = RECT Especifique o canto oposto: + .rect-calculate-dimensions-based-on-length-width = RECT Calcule as dimensões com base em [Comprimento / Largura] : + .rect-specify-elevation = RECT Especifique a elevação <__ocs_fmt_0__>: + .rect-specify-fillet-radius = RECT Especifique o raio de concordância <__ocs_fmt_0__>: + .rect-specify-first-chamfer-distance = RECT Especifique a primeira distância do chanfro <__ocs_fmt_0__>: + .rect-specify-orientation-from-the-first-corner = RECT Especifique a orientação a partir do primeiro canto: + .rect-specify-rectangle-area = RECT Especifique a área do retângulo: + .rect-specify-rectangle-length = RECT Especifique o comprimento do retângulo: + .rect-specify-rectangle-width = RECT Especifique a largura do retângulo: + .rect-specify-rotation-angle = RECT Especifique o ângulo de rotação <__ocs_fmt_0__>: + .rect-specify-second-chamfer-distance = RECT Especifique a segunda distância do chanfro <__ocs_fmt_0__>: + .rect-specify-thickness = RECT Especifique a espessura <__ocs_fmt_0__>: + .rect-specify-width = RECT Especifique a largura <__ocs_fmt_0__>: .rect-cen-specify-center-point = RECT CEN Especifique o ponto central: .rect-cen-specify-corner-point = RECT CEN Especifique o ponto de canto: .rect-rot-specify-adjacent-corner-defines-edge = RECT ROT Especifique o canto adjacente (define a direção da aresta): diff --git a/locales/ru-RU/opencadstudio.ftl b/locales/ru-RU/opencadstudio.ftl index beb8b62c4..0f154f491 100644 --- a/locales/ru-RU/opencadstudio.ftl +++ b/locales/ru-RU/opencadstudio.ftl @@ -317,6 +317,7 @@ common = .block-rotation = Вращение блока .blocks = Блоки .blue = Синий + .bulge = Выпуклость .boolean-failed-the-solids-may-not-overlap = Булевый не сработал — твердые вещества могут не перекрываться. .boolean-select-exactly-two-solids-created-this = Булев: выберите ровно два твердых тела, созданных на этом сеансе. .bottom = Снизу @@ -1332,6 +1333,18 @@ draw = .ray-specify-through-point = RAY Укажите через точку: .rect-specify-first-corner = RECT Укажите первый угол: .rect-specify-opposite-corner = RECT Укажите противоположный угол: + .rect-calculate-dimensions-based-on-length-width = RECT Рассчитать размеры по [Длине / Ширине] <Длина>: + .rect-specify-elevation = RECT Укажите отметку <__ocs_fmt_0__>: + .rect-specify-fillet-radius = RECT Укажите радиус скругления <__ocs_fmt_0__>: + .rect-specify-first-chamfer-distance = RECT Укажите первое расстояние фаски <__ocs_fmt_0__>: + .rect-specify-orientation-from-the-first-corner = RECT Укажите направление от первого угла: + .rect-specify-rectangle-area = RECT Укажите площадь прямоугольника: + .rect-specify-rectangle-length = RECT Укажите длину прямоугольника: + .rect-specify-rectangle-width = RECT Укажите ширину прямоугольника: + .rect-specify-rotation-angle = RECT Укажите угол поворота <__ocs_fmt_0__>: + .rect-specify-second-chamfer-distance = RECT Укажите второе расстояние фаски <__ocs_fmt_0__>: + .rect-specify-thickness = RECT Укажите толщину <__ocs_fmt_0__>: + .rect-specify-width = RECT Укажите ширину <__ocs_fmt_0__>: .rect-cen-specify-center-point = RECT CEN Укажите центральную точку: .rect-cen-specify-corner-point = RECT CEN Укажите угловую точку: .rect-rot-specify-adjacent-corner-defines-edge = RECT ROT Укажите смежный угол (определите направление кромки): diff --git a/locales/tr-TR/opencadstudio.ftl b/locales/tr-TR/opencadstudio.ftl index e286badcf..520a80b93 100644 --- a/locales/tr-TR/opencadstudio.ftl +++ b/locales/tr-TR/opencadstudio.ftl @@ -300,6 +300,7 @@ common = .block-rotation = Blok döndürmesi .blocks = Bloklar .blue = Mavi + .bulge = Kavis .boolean-failed-the-solids-may-not-overlap = Boolean işlemi başarısız oldu; katılar çakışmıyor olabilir. .boolean-select-exactly-two-solids-created-this = Boolean: bu oturumda oluşturulmuş tam iki katı seçin. .bottom = Alt @@ -1311,6 +1312,18 @@ draw = .ray-specify-through-point = RAY Geçiş noktasını belirtin: .rect-specify-first-corner = RECT İlk köşeyi belirtin: .rect-specify-opposite-corner = RECT Karşı köşeyi belirtin: + .rect-calculate-dimensions-based-on-length-width = RECT Boyutları [Uzunluk / Genişlik] temelinde hesaplayın : + .rect-specify-elevation = RECT Kotu belirtin <__ocs_fmt_0__>: + .rect-specify-fillet-radius = RECT Yuvarlatma yarıçapını belirtin <__ocs_fmt_0__>: + .rect-specify-first-chamfer-distance = RECT İlk pah mesafesini belirtin <__ocs_fmt_0__>: + .rect-specify-orientation-from-the-first-corner = RECT İlk köşeden yönü belirtin: + .rect-specify-rectangle-area = RECT Dikdörtgen alanını belirtin: + .rect-specify-rectangle-length = RECT Dikdörtgen uzunluğunu belirtin: + .rect-specify-rectangle-width = RECT Dikdörtgen genişliğini belirtin: + .rect-specify-rotation-angle = RECT Döndürme açısını belirtin <__ocs_fmt_0__>: + .rect-specify-second-chamfer-distance = RECT İkinci pah mesafesini belirtin <__ocs_fmt_0__>: + .rect-specify-thickness = RECT Kalınlığı belirtin <__ocs_fmt_0__>: + .rect-specify-width = RECT Genişliği belirtin <__ocs_fmt_0__>: .rect-cen-specify-center-point = RECT CEN Merkez noktasını belirtin: .rect-cen-specify-corner-point = RECT CEN Köşe noktasını belirtin: .rect-rot-specify-adjacent-corner-defines-edge = RECT ROT Bitişik köşeyi belirtin (kenar yönünü tanımlar): diff --git a/locales/zh-CN/opencadstudio.ftl b/locales/zh-CN/opencadstudio.ftl index aa6c9247a..3440c4738 100644 --- a/locales/zh-CN/opencadstudio.ftl +++ b/locales/zh-CN/opencadstudio.ftl @@ -301,6 +301,7 @@ common = .block-rotation = 块旋转 .blocks = 块状物 .blue = 蓝色 + .bulge = 凸度 .boolean-failed-the-solids-may-not-overlap = 布尔失败——固件可能不会重叠. .boolean-select-exactly-two-solids-created-this = 布尔: 选择创建此会话的正中两个固件 。 .bottom = 底部 @@ -1316,6 +1317,18 @@ draw = .ray-specify-through-point = RAY通过点指定 : .rect-specify-first-corner = RECT 指定第一角 : .rect-specify-opposite-corner = RECT 指定相向角 : + .rect-calculate-dimensions-based-on-length-width = RECT 根据 [长度 / 宽度] 计算尺寸 <长度>: + .rect-specify-elevation = RECT 指定标高 <__ocs_fmt_0__>: + .rect-specify-fillet-radius = RECT 指定圆角半径 <__ocs_fmt_0__>: + .rect-specify-first-chamfer-distance = RECT 指定第一个倒角距离 <__ocs_fmt_0__>: + .rect-specify-orientation-from-the-first-corner = RECT 从第一个角点指定方向: + .rect-specify-rectangle-area = RECT 指定矩形面积: + .rect-specify-rectangle-length = RECT 指定矩形长度: + .rect-specify-rectangle-width = RECT 指定矩形宽度: + .rect-specify-rotation-angle = RECT 指定旋转角度 <__ocs_fmt_0__>: + .rect-specify-second-chamfer-distance = RECT 指定第二个倒角距离 <__ocs_fmt_0__>: + .rect-specify-thickness = RECT 指定厚度 <__ocs_fmt_0__>: + .rect-specify-width = RECT 指定宽度 <__ocs_fmt_0__>: .rect-cen-specify-center-point = RECT CEN 指定中心点: .rect-cen-specify-corner-point = RECT CEN 指定角点: .rect-rot-specify-adjacent-corner-defines-edge = RECTROT指定相邻的角( 定边方向) : diff --git a/locales/zh-TW/opencadstudio.ftl b/locales/zh-TW/opencadstudio.ftl index e6eac9b29..fb781a283 100644 --- a/locales/zh-TW/opencadstudio.ftl +++ b/locales/zh-TW/opencadstudio.ftl @@ -313,6 +313,7 @@ common = .block-rotation = 圖塊旋轉 .blocks = 圖塊 .blue = 藍色 + .bulge = 凸度 .boolean-failed-the-solids-may-not-overlap = 布爾失敗——固件可能不會重疊. .boolean-select-exactly-two-solids-created-this = 布爾: 選擇建立此會話的正中兩個固件 。 .bottom = 底部 @@ -1328,6 +1329,18 @@ draw = .ray-specify-through-point = RAY通過點指定 : .rect-specify-first-corner = RECT 指定第一角 : .rect-specify-opposite-corner = RECT 指定相向角 : + .rect-calculate-dimensions-based-on-length-width = RECT 根據 [長度 / 寬度] 計算尺寸 <長度>: + .rect-specify-elevation = RECT 指定高程 <__ocs_fmt_0__>: + .rect-specify-fillet-radius = RECT 指定圓角半徑 <__ocs_fmt_0__>: + .rect-specify-first-chamfer-distance = RECT 指定第一個倒角距離 <__ocs_fmt_0__>: + .rect-specify-orientation-from-the-first-corner = RECT 從第一個角點指定方向: + .rect-specify-rectangle-area = RECT 指定矩形面積: + .rect-specify-rectangle-length = RECT 指定矩形長度: + .rect-specify-rectangle-width = RECT 指定矩形寬度: + .rect-specify-rotation-angle = RECT 指定旋轉角度 <__ocs_fmt_0__>: + .rect-specify-second-chamfer-distance = RECT 指定第二個倒角距離 <__ocs_fmt_0__>: + .rect-specify-thickness = RECT 指定厚度 <__ocs_fmt_0__>: + .rect-specify-width = RECT 指定寬度 <__ocs_fmt_0__>: .rect-cen-specify-center-point = RECT CEN 指定中心點: .rect-cen-specify-corner-point = RECT CEN 指定角點: .rect-rot-specify-adjacent-corner-defines-edge = RECTROT指定相鄰的角( 定邊方向) : diff --git a/src/app/history.rs b/src/app/history.rs index 45c933a0f..d424167aa 100644 --- a/src/app/history.rs +++ b/src/app/history.rs @@ -668,9 +668,8 @@ impl OpenCADStudio { true } - /// Add is delta-safe on an already-existing layer unless it creates block - /// records. RasterImage definitions are captured as exact object-map deltas; - /// Viewport commits explicitly record their added entity handle. + /// Add is delta-safe when its layer and XData application IDs already exist + /// and it creates no block records. pub(super) fn delta_add_safe(&self, i: usize, entity: &EntityType) -> bool { if matches!( entity, @@ -680,7 +679,18 @@ impl OpenCADStudio { return false; } let layer = entity.common().layer.clone(); - layer.trim().is_empty() || self.tabs[i].scene.document.layers.contains(&layer) + let doc = &self.tabs[i].scene.document; + let layer_exists = layer.trim().is_empty() || doc.layers.contains(&layer); + let app_ids_exist = entity + .common() + .extended_data + .records() + .iter() + .all(|record| { + record.application_name.trim().is_empty() + || doc.app_ids.contains(&record.application_name) + }); + layer_exists && app_ids_exist } /// Close the delta transaction opened by [`OpenCADStudio::begin_undo`]: diff --git a/src/entities/common.rs b/src/entities/common.rs index 617bf97fe..f8417b75f 100644 --- a/src/entities/common.rs +++ b/src/entities/common.rs @@ -148,6 +148,18 @@ pub fn format_length(value: f64) -> String { } } +/// Format an area with the drawing's linear precision. Area stays a decimal +/// scalar even when linear distances use architectural or fractional notation. +pub fn format_area(value: f64) -> String { + let ctx = unit_context(); + let precision = ctx.luprec.max(0).min(15) as usize; + if ctx.lunits == 1 { + format!("{:.*e}", precision, value) + } else { + format!("{:.*}", precision, value) + } +} + /// Format an angle (input in radians) using AUNITS / AUPREC. pub fn format_angle(value_rad: f64) -> String { let ctx = unit_context(); @@ -420,6 +432,11 @@ pub fn parse_direction(text: &str) -> Option { Some(relative + ctx.angbase) } +/// Read a direction typed at a command prompt, accepting a decimal comma. +pub fn parse_typed_direction(text: &str) -> Option { + parse_direction(&text.replace(',', ".")) +} + /// Two interior triangles covering a quad (flat list, 6 vertices) — the /// click-anywhere pick surface for frame-like entities (image, OLE frame, /// underlay, wipeout). Corners in ring order. diff --git a/src/entities/lwpolyline.rs b/src/entities/lwpolyline.rs index bcea38533..e51bc8bb8 100644 --- a/src/entities/lwpolyline.rs +++ b/src/entities/lwpolyline.rs @@ -5,8 +5,8 @@ use crate::t; use crate::command::EntityTransform; use crate::entities::common::{ - edit_prop as edit, parse_f64, rectangle_grip, ro_prop as ro, square_grip, - stepper_prop as stepper, + edit_prop as edit, edit_scalar_prop as edit_scalar, format_area, format_length, parse_f64, + rectangle_grip, ro_prop as ro, square_grip, stepper_prop as stepper, }; use crate::entities::traits::RenderConvertible; use crate::scene::convert::acad_to_render::{extrusion_wall_tris, RenderEntity, RenderObject}; @@ -814,6 +814,33 @@ fn set_revision_cloud_arc_length(pline: &mut LwPolyline, requested: f64) { pline.vertices = cloud_vertices(&points, magnitude * sign, width_ratios); } +pub(crate) fn is_rectangle(pline: &LwPolyline) -> bool { + if pline.common.extended_data.get_record("OCS_RECTANGLE").is_some() { + return true; + } + if !pline.is_closed + || pline.vertices.len() != 4 + || pline.vertices.iter().any(|vertex| vertex.bulge.abs() > 1.0e-9) + { + return false; + } + let edges: Vec = (0..4) + .map(|index| { + let from = pline.vertices[index].location; + let to = pline.vertices[(index + 1) % 4].location; + Vec2::new(to.x - from.x, to.y - from.y) + }) + .collect(); + let lengths: Vec = edges.iter().map(|edge| edge.length()).collect(); + if lengths.iter().any(|length| *length <= 1.0e-12) { + return false; + } + let tolerance = 1.0e-9; + edges[0].dot(edges[1]).abs() <= tolerance * lengths[0] * lengths[1] + && edges[0].cross(edges[2]).abs() <= tolerance * lengths[0] * lengths[2] + && edges[1].cross(edges[3]).abs() <= tolerance * lengths[1] * lengths[3] +} + fn properties(pline: &LwPolyline) -> Vec { let n = pline.vertices.len(); // The panel's Current Vertex focus, clamped to this polyline's range. @@ -831,10 +858,8 @@ fn properties(pline: &LwPolyline) -> Vec { let cloud_arc_length = revision_cloud_arc_length(pline); let vertex_label = if n == 0 { "—".to_string() - } else if cloud_arc_length.is_some() { - format!("{}", vi + 1) } else { - format!("{} / {}", vi + 1, n) + format!("{}", vi + 1) }; let mut misc_props = vec![ Property { @@ -861,20 +886,26 @@ fn properties(pline: &LwPolyline) -> Vec { arc_length, )); } + let mut geometry_props = vec![ + stepper(t!("Current Vertex").as_ref(), "current_vertex", vertex_label), + edit(t!("Vertex X").as_ref(), "vertex_x", vx), + edit(t!("Vertex Y").as_ref(), "vertex_y", vy), + edit_scalar(t!("Bulge").as_ref(), "bulge", v.map_or(0.0, |vertex| vertex.bulge)), + ]; + if !is_rectangle(pline) { + geometry_props.push(edit(t!("Start segment width").as_ref(), "start_width", start_w)); + geometry_props.push(edit(t!("End segment width").as_ref(), "end_width", end_w)); + } + geometry_props.extend([ + edit(t!("Global width").as_ref(), "global_width", pline.constant_width), + edit(t!("Elevation").as_ref(), "elevation", pline.elevation), + ro(t!("Area").as_ref(), "area", format_area(mp.area)), + ro(t!("Length").as_ref(), "length", format_length(mp.perimeter)), + ]); vec![ PropSection { title: t!("Geometry").into_owned(), - props: vec![ - stepper(t!("Current Vertex").as_ref(), "current_vertex", vertex_label), - edit(t!("Vertex X").as_ref(), "vertex_x", vx), - edit(t!("Vertex Y").as_ref(), "vertex_y", vy), - edit(t!("Start segment width").as_ref(), "start_width", start_w), - edit(t!("End segment width").as_ref(), "end_width", end_w), - edit(t!("Global width").as_ref(), "global_width", pline.constant_width), - edit(t!("Elevation").as_ref(), "elevation", pline.elevation), - ro(t!("Area").as_ref(), "area", format!("{:.4}", mp.area)), - ro(t!("Length").as_ref(), "length", format!("{:.4}", mp.perimeter)), - ], + props: geometry_props, }, PropSection { title: t!("Misc").into_owned(), @@ -954,6 +985,13 @@ fn apply_geom_prop(pline: &mut LwPolyline, field: &str, value: &str) { vtx.end_width = v; } } + "bulge" => { + if v.is_finite() { + if let Some(vtx) = pline.vertices.get_mut(vi) { + vtx.bulge = v.clamp(-1.0e6, 1.0e6); + } + } + } _ => {} } } diff --git a/src/locale_catalog.rs b/src/locale_catalog.rs index b8ee4016c..0f86e6468 100644 --- a/src/locale_catalog.rs +++ b/src/locale_catalog.rs @@ -428,6 +428,7 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static "Blocks ({}) — insert with INSERT : {}" => Some(("insert", "blocks-insert-with-insert-name")), "Blocks ({}): {}" => Some(("insert", "blocks")), "Blue" => Some(("common", "blue")), + "Bulge" => Some(("common", "bulge")), "Body" => Some(("properties", "body")), "Bold" => Some(("properties", "bold")), "Book" => Some(("properties", "book")), @@ -2051,8 +2052,20 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static "RAY Specify start point:" => Some(("draw", "ray-specify-start-point")), "RAY Specify through point:" => Some(("draw", "ray-specify-through-point")), "README could not be loaded" => Some(("plugins", "readme-could-not-be-loaded")), + "RECT Calculate dimensions based on [Length / Width] :" => Some(("draw", "rect-calculate-dimensions-based-on-length-width")), + "RECT Specify elevation <{}>:" => Some(("draw", "rect-specify-elevation")), + "RECT Specify fillet radius <{}>:" => Some(("draw", "rect-specify-fillet-radius")), + "RECT Specify first chamfer distance <{}>:" => Some(("draw", "rect-specify-first-chamfer-distance")), "RECT Specify first corner:" => Some(("draw", "rect-specify-first-corner")), + "RECT Specify orientation from the first corner:" => Some(("draw", "rect-specify-orientation-from-the-first-corner")), "RECT Specify opposite corner:" => Some(("draw", "rect-specify-opposite-corner")), + "RECT Specify rectangle area:" => Some(("draw", "rect-specify-rectangle-area")), + "RECT Specify rectangle length:" => Some(("draw", "rect-specify-rectangle-length")), + "RECT Specify rectangle width:" => Some(("draw", "rect-specify-rectangle-width")), + "RECT Specify rotation angle <{}>:" => Some(("draw", "rect-specify-rotation-angle")), + "RECT Specify second chamfer distance <{}>:" => Some(("draw", "rect-specify-second-chamfer-distance")), + "RECT Specify thickness <{}>:" => Some(("draw", "rect-specify-thickness")), + "RECT Specify width <{}>:" => Some(("draw", "rect-specify-width")), "RECT CEN Specify center point:" => Some(("draw", "rect-cen-specify-center-point")), "RECT CEN Specify corner point:" => Some(("draw", "rect-cen-specify-corner-point")), "RECT ROT Specify adjacent corner (defines edge direction):" => Some(("draw", "rect-rot-specify-adjacent-corner-defines-edge")), diff --git a/src/modules/draw/defaults.rs b/src/modules/draw/defaults.rs index d5f165473..a3598fa8b 100644 --- a/src/modules/draw/defaults.rs +++ b/src/modules/draw/defaults.rs @@ -19,6 +19,13 @@ thread_local! { static ARRAY_P_ANGLE: Cell = Cell::new(360.0); // degrees static ARRAY_PATH_COUNT: Cell = Cell::new(6.0); static POLYGON_SIDES: Cell = Cell::new(6.0); + static RECT_ELEVATION: Cell = Cell::new(0.0); + static RECT_THICKNESS: Cell = Cell::new(0.0); + static RECT_WIDTH: Cell = Cell::new(0.0); + static RECT_ROTATION: Cell = Cell::new(0.0); // degrees + static RECT_CHAMFER1: Cell = Cell::new(0.0); + static RECT_CHAMFER2: Cell = Cell::new(0.0); + static RECT_FILLET: Cell = Cell::new(0.0); } macro_rules! accessors { @@ -54,3 +61,10 @@ accessors!(get_array_p_count, set_array_p_count, ARRAY_P_COUNT); accessors!(get_array_p_angle, set_array_p_angle, ARRAY_P_ANGLE); accessors!(get_array_path_count, set_array_path_count, ARRAY_PATH_COUNT); accessors!(get_polygon_sides, set_polygon_sides, POLYGON_SIDES); +accessors!(get_rect_elevation, set_rect_elevation, RECT_ELEVATION); +accessors!(get_rect_thickness, set_rect_thickness, RECT_THICKNESS); +accessors!(get_rect_width, set_rect_width, RECT_WIDTH); +accessors!(get_rect_rotation, set_rect_rotation, RECT_ROTATION); +accessors!(get_rect_chamfer1, set_rect_chamfer1, RECT_CHAMFER1); +accessors!(get_rect_chamfer2, set_rect_chamfer2, RECT_CHAMFER2); +accessors!(get_rect_fillet, set_rect_fillet, RECT_FILLET); diff --git a/src/modules/draw/draw/shapes.rs b/src/modules/draw/draw/shapes.rs index 3a388f869..c73f201a5 100644 --- a/src/modules/draw/draw/shapes.rs +++ b/src/modules/draw/draw/shapes.rs @@ -13,6 +13,11 @@ use acadrust::entities::LwVertex; use acadrust::types::Vector2; use acadrust::{EntityType, LwPolyline}; +use cadkernel::geom2d::{ + arc_span, fillet_between_rays, Curve as KernelCurve, Frame as KernelFrame, + Polyline as KernelPolyline, PolylineVertex as KernelVertex, Ray as KernelRay, + Transform as KernelTransform, Vec2 as KernelVec2, +}; use crate::t; use crate::command::{CadCommand, CmdResult, WorkingPlane}; @@ -21,21 +26,6 @@ use crate::modules::IconKind; use crate::scene::model::wire_model::WireModel; use glam::DVec3; -/// Build the four corners of an axis-aligned box between opposite corners `a` -/// and `b`, axis-aligned in the active UCS (`ucs` = UCS→wire affine, identity = -/// world). The two given corners stay put; the other two are placed square to -/// the UCS axes instead of the world axes. -fn ucs_box_corners(a: DVec3, b: DVec3, plane: WorkingPlane) -> [DVec3; 4] { - let au = plane.to_local(a); - let bu = plane.to_local(b); - [ - plane.to_world(DVec3::new(au.x, au.y, au.z)), - plane.to_world(DVec3::new(bu.x, au.y, au.z)), - plane.to_world(DVec3::new(bu.x, bu.y, au.z)), - plane.to_world(DVec3::new(au.x, bu.y, au.z)), - ] -} - /// Four corners of a box centred at `c` with half-extents taken from `corner`, /// axis-aligned in the active UCS (`ucs` = UCS→wire affine, identity = world). fn ucs_box_around_center(c: DVec3, corner: DVec3, plane: WorkingPlane) -> [DVec3; 4] { @@ -96,6 +86,235 @@ fn make_pline(points: &[DVec3], plane: WorkingPlane) -> EntityType { })) } +#[derive(Clone, Copy)] +struct RectStyle { + chamfer_first: f64, + chamfer_second: f64, + fillet_radius: f64, + width: f64, + thickness: f64, +} + +fn rectangle_corners( + first: DVec3, + cursor: DVec3, + plane: WorkingPlane, + rotation_deg: f64, + fixed_dimensions: Option<(f64, f64)>, +) -> Option<[DVec3; 4]> { + let first_local = plane.to_local(first); + let cursor_local = plane.to_local(cursor); + if !rotation_deg.is_finite() + || !first_local.is_finite() + || !cursor_local.is_finite() + { + return None; + } + let rotation = KernelTransform::rotation(rotation_deg.to_radians()); + let axis_x = KernelVec2::from(rotation.apply_vector([1.0, 0.0])); + let axis_y = KernelVec2::from(rotation.apply_vector([0.0, 1.0])); + let first_2d = KernelVec2::new(first_local.x, first_local.y); + let delta = KernelVec2::new( + cursor_local.x - first_local.x, + cursor_local.y - first_local.y, + ); + let raw_width = delta.dot(axis_x); + let raw_height = delta.dot(axis_y); + let (width, height) = fixed_dimensions.map_or((raw_width, raw_height), |(w, h)| { + (w.copysign(raw_width), h.copysign(raw_height)) + }); + if !width.is_finite() + || !height.is_finite() + || width.abs() <= 1.0e-9 + || height.abs() <= 1.0e-9 + { + return None; + } + let local = [ + first_2d, + first_2d + axis_x * width, + first_2d + axis_x * width + axis_y * height, + first_2d + axis_y * height, + ]; + Some(local.map(|point| { + plane.to_world(DVec3::new(point.x, point.y, first_local.z)) + })) +} + +fn rectangle_polyline( + corners: [DVec3; 4], + plane: WorkingPlane, + style: RectStyle, +) -> Option<(KernelPolyline, f64)> { + if !style.chamfer_first.is_finite() + || !style.chamfer_second.is_finite() + || !style.fillet_radius.is_finite() + || !style.width.is_finite() + || !style.thickness.is_finite() + || style.chamfer_first < 0.0 + || style.chamfer_second < 0.0 + || style.fillet_radius < 0.0 + || style.width < 0.0 + { + return None; + } + let local = corners.map(|point| plane.to_local(point)); + let elevation = local[0].z; + let frame_points = local.map(|point| [point.x, point.y, 0.0]); + let frame = KernelFrame::around(frame_points.iter()); + let points = frame_points.map(|point| { + let lifted = frame.lift(point); + KernelVec2::new(lifted[0], lifted[1]) + }); + let use_fillet = style.fillet_radius > 1.0e-9; + let use_chamfer = !use_fillet + && (style.chamfer_first > 1.0e-9 || style.chamfer_second > 1.0e-9); + if !use_fillet && !use_chamfer { + return Some(( + KernelPolyline { + vertices: local + .iter() + .map(|point| KernelVertex::straight([point.x, point.y])) + .collect(), + closed: true, + }, + elevation, + )); + } + + let mut trims = Vec::with_capacity(4); + for index in 0..4 { + let corner = points[index]; + let previous = points[(index + 3) % 4]; + let next = points[(index + 1) % 4]; + let incoming = (previous - corner).normalize()?; + let outgoing = (next - corner).normalize()?; + let (incoming_point, outgoing_point, bulge) = if use_fillet { + let fillet = fillet_between_rays( + corner.to_array(), + incoming.to_array(), + outgoing.to_array(), + style.fillet_radius, + )?; + let sweep = arc_span(fillet.start_angle, fillet.end_angle); + ( + KernelVec2::from(fillet.tangent1), + KernelVec2::from(fillet.tangent2), + (sweep * 0.25) + .tan() + .copysign(-incoming.cross(outgoing)), + ) + } else { + let incoming_ray = KernelCurve::Ray(KernelRay { + origin: corner.to_array(), + direction: incoming.to_array(), + }); + let outgoing_ray = KernelCurve::Ray(KernelRay { + origin: corner.to_array(), + direction: outgoing.to_array(), + }); + let incoming_point = incoming_ray.point_at(style.chamfer_first); + let outgoing_point = outgoing_ray.point_at(style.chamfer_second); + ( + KernelVec2::from(incoming_point), + KernelVec2::from(outgoing_point), + 0.0, + ) + }; + let incoming_distance = corner.distance(incoming_point); + let outgoing_distance = corner.distance(outgoing_point); + if !incoming_point.x.is_finite() + || !incoming_point.y.is_finite() + || !outgoing_point.x.is_finite() + || !outgoing_point.y.is_finite() + || !incoming_distance.is_finite() + || !outgoing_distance.is_finite() + || !bulge.is_finite() + { + return None; + } + trims.push(( + incoming_point, + outgoing_point, + incoming_distance, + outgoing_distance, + bulge, + )); + } + for index in 0..4 { + let next = (index + 1) % 4; + let side_length = points[index].distance(points[next]); + let used = trims[index].3 + trims[next].2; + if used + side_length * 1.0e-9 >= side_length { + return None; + } + } + + let mut vertices = Vec::with_capacity(8); + for (incoming_point, outgoing_point, _, _, bulge) in trims { + let incoming = frame.lower([incoming_point.x, incoming_point.y, 0.0]); + let outgoing = frame.lower([outgoing_point.x, outgoing_point.y, 0.0]); + vertices.push(KernelVertex::curved([incoming[0], incoming[1]], bulge)); + vertices.push(KernelVertex::straight([outgoing[0], outgoing[1]])); + } + Some(( + KernelPolyline { + vertices, + closed: true, + }, + elevation, + )) +} + +fn make_rect_pline( + corners: [DVec3; 4], + plane: WorkingPlane, + style: RectStyle, +) -> Option { + let (geometry, elevation) = rectangle_polyline(corners, plane, style)?; + let mut polyline = LwPolyline { + vertices: geometry + .vertices + .iter() + .map(|point| { + let mut vertex = + LwVertex::new(Vector2::new(point.position[0], point.position[1])); + vertex.bulge = point.bulge; + vertex + }) + .collect(), + elevation, + is_closed: true, + constant_width: style.width, + thickness: style.thickness, + ..Default::default() + }; + let mut marker = acadrust::xdata::ExtendedDataRecord::new("OCS_RECTANGLE"); + marker.add_value(acadrust::xdata::XDataValue::Integer16(1)); + polyline.common.extended_data.add_record(marker); + Some(plane.place_entity(EntityType::LwPolyline(polyline))) +} + +fn rectangle_wire( + corners: [DVec3; 4], + plane: WorkingPlane, + style: RectStyle, +) -> Option { + let (polyline, elevation) = rectangle_polyline(corners, plane, style)?; + let mut points = KernelCurve::Polyline(polyline).tessellate(8.0); + if points.len() > 1 && points.first() == points.last() { + points.pop(); + } + let points = points + .into_iter() + .map(|point| { + let world = plane.to_world(DVec3::new(point[0], point[1], elevation)); + [world.x, world.y, world.z] + }) + .collect(); + Some(wire_loop(points)) +} + fn wire_loop(pts: Vec<[f64; 3]>) -> WireModel { let mut p = pts; if let Some(&first) = p.first() { @@ -155,18 +374,81 @@ fn plane_distance(from: DVec3, to: DVec3, plane: WorkingPlane) -> f64 { // ── Command: Rectangle — Two Corners (RECT) ────────────────────────────── +#[derive(Clone, Copy)] +enum RectStep { + FirstCorner, + Opposite, + ChamferFirst, + ChamferSecond, + Elevation, + Fillet, + Thickness, + Width, + Rotation, + AreaValue, + AreaBasis(f64), + AreaDimension { area: f64, by_length: bool }, + DimensionsLength, + DimensionsWidth(f64), + PlaceSized { width: f64, height: f64 }, +} + pub struct RectCommand { - a: Option, + step: RectStep, + first: Option, plane: WorkingPlane, + chamfer_first: f64, + chamfer_second: f64, + fillet_radius: f64, + elevation: f64, + thickness: f64, + width: f64, + rotation_deg: f64, } impl RectCommand { pub fn new() -> Self { Self { - a: None, + step: RectStep::FirstCorner, + first: None, plane: WorkingPlane::default(), + chamfer_first: defaults::get_rect_chamfer1().max(0.0), + chamfer_second: defaults::get_rect_chamfer2().max(0.0), + fillet_radius: defaults::get_rect_fillet().max(0.0), + elevation: defaults::get_rect_elevation(), + thickness: defaults::get_rect_thickness(), + width: defaults::get_rect_width().max(0.0), + rotation_deg: defaults::get_rect_rotation(), } } + + fn style(&self) -> RectStyle { + RectStyle { + chamfer_first: self.chamfer_first, + chamfer_second: self.chamfer_second, + fillet_radius: self.fillet_radius, + width: self.width, + thickness: self.thickness, + } + } + + fn finish(&self, cursor: DVec3, fixed_dimensions: Option<(f64, f64)>) -> CmdResult { + let Some(first) = self.first else { + return CmdResult::NeedPoint; + }; + let Some(corners) = rectangle_corners( + first, + cursor, + self.plane, + self.rotation_deg, + fixed_dimensions, + ) else { + return CmdResult::NeedPoint; + }; + make_rect_pline(corners, self.plane, self.style()) + .map(CmdResult::CommitAndExit) + .unwrap_or(CmdResult::NeedPoint) + } } impl CadCommand for RectCommand { @@ -177,78 +459,363 @@ impl CadCommand for RectCommand { self.plane = plane; } fn prompt(&self) -> String { - if self.a.is_none() { - crate::t!("RECT Specify first corner:").into_owned() - } else { - crate::t!("RECT Specify opposite corner:").into_owned() + match self.step { + RectStep::FirstCorner => crate::t!("RECT Specify first corner:").into_owned(), + RectStep::Opposite => crate::t!("RECT Specify opposite corner:").into_owned(), + RectStep::ChamferFirst => crate::tf!( + "RECT Specify first chamfer distance <{}>:", + crate::entities::common::format_length(self.chamfer_first) + ) + .into_owned(), + RectStep::ChamferSecond => crate::tf!( + "RECT Specify second chamfer distance <{}>:", + crate::entities::common::format_length(self.chamfer_second) + ) + .into_owned(), + RectStep::Elevation => { + crate::tf!( + "RECT Specify elevation <{}>:", + crate::entities::common::format_length(self.elevation) + ) + .into_owned() + } + RectStep::Fillet => { + crate::tf!( + "RECT Specify fillet radius <{}>:", + crate::entities::common::format_length(self.fillet_radius) + ) + .into_owned() + } + RectStep::Thickness => { + crate::tf!( + "RECT Specify thickness <{}>:", + crate::entities::common::format_length(self.thickness) + ) + .into_owned() + } + RectStep::Width => crate::tf!( + "RECT Specify width <{}>:", + crate::entities::common::format_length(self.width) + ) + .into_owned(), + RectStep::Rotation => { + crate::tf!( + "RECT Specify rotation angle <{}>:", + crate::entities::common::format_direction(self.rotation_deg.to_radians()) + ) + .into_owned() + } + RectStep::AreaValue => crate::t!("RECT Specify rectangle area:").into_owned(), + RectStep::AreaBasis(_) => crate::t!( + "RECT Calculate dimensions based on [Length / Width] :" + ) + .into_owned(), + RectStep::AreaDimension { by_length: true, .. } => { + crate::t!("RECT Specify rectangle length:").into_owned() + } + RectStep::AreaDimension { by_length: false, .. } => { + crate::t!("RECT Specify rectangle width:").into_owned() + } + RectStep::DimensionsLength => { + crate::t!("RECT Specify rectangle length:").into_owned() + } + RectStep::DimensionsWidth(_) => { + crate::t!("RECT Specify rectangle width:").into_owned() + } + RectStep::PlaceSized { .. } => crate::t!( + "RECT Specify orientation from the first corner:" + ) + .into_owned(), } } fn options(&self) -> Vec { use crate::command::CmdOption; - // First corner also offers the alternate rectangle methods; later steps - // are plain point picks. (#304) - if self.a.is_none() { - vec![ + match self.step { + RectStep::FirstCorner => vec![ + CmdOption::new("Chamfer", "CHAMFER"), + CmdOption::new("Elevation", "ELEVATION"), + CmdOption::new("Fillet", "FILLET"), + CmdOption::new("Thickness", "THICKNESS"), + CmdOption::new("Width", "WIDTH"), + ], + RectStep::Opposite => vec![ + CmdOption::new("Area", "AREA"), + CmdOption::new("Dimensions", "DIMENSIONS"), CmdOption::new("Rotation", "ROTATION"), - CmdOption::new("Center", "CENTER"), - ] - } else { - vec![] + ], + RectStep::AreaBasis(_) => vec![ + CmdOption::new("Length", "LENGTH"), + CmdOption::new("Width", "WIDTH"), + ], + _ => vec![], } } fn point_step_accepts_keywords(&self) -> bool { - self.a.is_none() + matches!(self.step, RectStep::FirstCorner | RectStep::Opposite) + } + + fn wants_text_input(&self) -> bool { + !matches!(self.step, RectStep::PlaceSized { .. }) + } + + fn dyn_field(&self) -> crate::command::DynField { + match self.step { + RectStep::Rotation => crate::command::DynField::Angle, + RectStep::ChamferFirst + | RectStep::ChamferSecond + | RectStep::Elevation + | RectStep::Fillet + | RectStep::Thickness + | RectStep::Width + | RectStep::AreaValue + | RectStep::AreaBasis(_) + | RectStep::AreaDimension { .. } + | RectStep::DimensionsLength + | RectStep::DimensionsWidth(_) => crate::command::DynField::Scalar, + _ => crate::command::DynField::Point, + } } fn on_text_input(&mut self, text: &str) -> Option { - // At the first corner, keyword options hand off to the dedicated - // variant command. (#304) - if self.a.is_none() { - return match text.trim().to_uppercase().as_str() { - "R" | "ROTATION" => Some(CmdResult::Dispatch("RECT_ROT".into())), - "C" | "CENTER" => Some(CmdResult::Dispatch("RECT_CEN".into())), - _ => None, - }; + let upper = text.trim().to_uppercase(); + match self.step { + RectStep::FirstCorner => { + return match upper.as_str() { + "C" | "CHAMFER" => { + self.step = RectStep::ChamferFirst; + Some(CmdResult::NeedPoint) + } + "E" | "ELEVATION" => { + self.step = RectStep::Elevation; + Some(CmdResult::NeedPoint) + } + "F" | "FILLET" => { + self.step = RectStep::Fillet; + Some(CmdResult::NeedPoint) + } + "T" | "THICKNESS" => { + self.step = RectStep::Thickness; + Some(CmdResult::NeedPoint) + } + "W" | "WIDTH" => { + self.step = RectStep::Width; + Some(CmdResult::NeedPoint) + } + _ => None, + }; + } + RectStep::Opposite => { + return match upper.as_str() { + "A" | "AREA" => { + self.step = RectStep::AreaValue; + Some(CmdResult::NeedPoint) + } + "D" | "DIMENSIONS" => { + self.step = RectStep::DimensionsLength; + Some(CmdResult::NeedPoint) + } + "R" | "ROTATION" => { + self.step = RectStep::Rotation; + Some(CmdResult::NeedPoint) + } + _ => None, + }; + } + RectStep::AreaBasis(area) => { + return match upper.as_str() { + "L" | "LENGTH" => { + self.step = RectStep::AreaDimension { + area, + by_length: true, + }; + Some(CmdResult::NeedPoint) + } + "W" | "WIDTH" => { + self.step = RectStep::AreaDimension { + area, + by_length: false, + }; + Some(CmdResult::NeedPoint) + } + _ => None, + }; + } + _ => {} } - None + + if matches!(self.step, RectStep::Rotation) { + let angle = crate::entities::common::parse_typed_direction(text)?; + if !angle.is_finite() { + return None; + } + self.rotation_deg = angle.to_degrees(); + defaults::set_rect_rotation(self.rotation_deg); + self.step = RectStep::Opposite; + return Some(CmdResult::NeedPoint); + } + let value = if matches!(self.step, RectStep::AreaValue) { + text.trim().replace(',', ".").parse().ok()? + } else { + crate::entities::common::parse_typed_length(text)? + }; + if !value.is_finite() { + return None; + } + match self.step { + RectStep::ChamferFirst if value >= 0.0 => { + self.chamfer_first = value; + defaults::set_rect_chamfer1(value); + self.step = RectStep::ChamferSecond; + } + RectStep::ChamferSecond if value >= 0.0 => { + self.chamfer_second = value; + self.fillet_radius = 0.0; + defaults::set_rect_chamfer2(value); + defaults::set_rect_fillet(0.0); + self.step = RectStep::FirstCorner; + } + RectStep::Elevation => { + self.elevation = value; + defaults::set_rect_elevation(value); + self.step = RectStep::FirstCorner; + } + RectStep::Fillet if value >= 0.0 => { + self.fillet_radius = value; + self.chamfer_first = 0.0; + self.chamfer_second = 0.0; + defaults::set_rect_fillet(value); + defaults::set_rect_chamfer1(0.0); + defaults::set_rect_chamfer2(0.0); + self.step = RectStep::FirstCorner; + } + RectStep::Thickness => { + self.thickness = value; + defaults::set_rect_thickness(value); + self.step = RectStep::FirstCorner; + } + RectStep::Width if value >= 0.0 => { + self.width = value; + defaults::set_rect_width(value); + self.step = RectStep::FirstCorner; + } + RectStep::AreaValue if value > 0.0 => { + self.step = RectStep::AreaBasis(value); + } + RectStep::AreaDimension { area, by_length } if value > 0.0 => { + let (width, height) = if by_length { + (value, area / value) + } else { + (area / value, value) + }; + if !width.is_finite() || !height.is_finite() { + return None; + } + self.step = RectStep::PlaceSized { width, height }; + } + RectStep::DimensionsLength if value > 0.0 => { + self.step = RectStep::DimensionsWidth(value); + } + RectStep::DimensionsWidth(length) if value > 0.0 => { + self.step = RectStep::PlaceSized { + width: length, + height: value, + }; + } + _ => return None, + } + Some(CmdResult::NeedPoint) } fn on_point(&mut self, pt: DVec3) -> CmdResult { - match self.a { - None => { - self.a = Some(pt); + match self.step { + RectStep::FirstCorner => { + let mut local = self.plane.to_local(pt); + local.z = self.elevation; + self.first = Some(self.plane.to_world(local)); + self.step = RectStep::Opposite; CmdResult::NeedPoint } - Some(a) => { - let c = ucs_box_corners(a, pt, self.plane); - CmdResult::CommitAndExit(make_pline(&c, self.plane)) + RectStep::Opposite => self.finish(pt, None), + RectStep::PlaceSized { width, height } => { + self.finish(pt, Some((width, height))) } + RectStep::Rotation => { + let Some(first) = self.first else { + return CmdResult::NeedPoint; + }; + let delta = self.plane.vector_to_local(pt - first); + if delta.x.hypot(delta.y) <= 1.0e-9 { + return CmdResult::NeedPoint; + } + self.rotation_deg = delta.y.atan2(delta.x).to_degrees(); + defaults::set_rect_rotation(self.rotation_deg); + self.step = RectStep::Opposite; + CmdResult::NeedPoint + } + _ => CmdResult::NeedPoint, } } fn on_enter(&mut self) -> CmdResult { - CmdResult::Cancel + match self.step { + RectStep::FirstCorner => CmdResult::Cancel, + RectStep::ChamferFirst => { + self.step = RectStep::ChamferSecond; + CmdResult::NeedPoint + } + RectStep::ChamferSecond => { + self.fillet_radius = 0.0; + defaults::set_rect_fillet(0.0); + self.step = RectStep::FirstCorner; + CmdResult::NeedPoint + } + RectStep::Elevation + | RectStep::Fillet + | RectStep::Thickness + | RectStep::Width => { + self.step = RectStep::FirstCorner; + CmdResult::NeedPoint + } + RectStep::Rotation => { + self.step = RectStep::Opposite; + CmdResult::NeedPoint + } + RectStep::AreaBasis(area) => { + self.step = RectStep::AreaDimension { + area, + by_length: true, + }; + CmdResult::NeedPoint + } + _ => CmdResult::NeedPoint, + } } fn on_escape(&mut self) -> CmdResult { CmdResult::Cancel } fn on_mouse_move(&mut self, pt: DVec3) -> Option { - let a = self.a?; - let c = ucs_box_corners(a, pt, self.plane); - Some(wire_loop(vec![ - [c[0].x, c[0].y, c[0].z], - [c[1].x, c[1].y, c[1].z], - [c[2].x, c[2].y, c[2].z], - [c[3].x, c[3].y, c[3].z], - ])) + let first = self.first?; + let fixed_dimensions = match self.step { + RectStep::PlaceSized { width, height } => Some((width, height)), + RectStep::Opposite => None, + _ => return None, + }; + let corners = rectangle_corners( + first, + pt, + self.plane, + self.rotation_deg, + fixed_dimensions, + )?; + rectangle_wire(corners, self.plane, self.style()) } fn dyn_spec(&self) -> Option { use crate::command::{DynAnchor, DynFieldSpec, DynGuide, DynRole, DynSpec}; - // Opposite corner: enter width and height (signed deltas from the first - // corner), with the rectangle drawn as the guide. First corner is a - // normal point pick. - self.a.map(|a| DynSpec { - anchor: DynAnchor::Point(a), + if !matches!(self.step, RectStep::Opposite) || self.rotation_deg.abs() > 1.0e-9 { + return None; + } + self.first.map(|first| DynSpec { + anchor: DynAnchor::Point(first), fields: vec![ DynFieldSpec::new(DynRole::Width), DynFieldSpec::new(DynRole::Height), @@ -305,6 +872,9 @@ impl CadCommand for RectRotCommand { CmdResult::NeedPoint } 1 => { + if plane_distance(self.a, pt, self.plane) <= 1.0e-9 { + return CmdResult::NeedPoint; + } self.b = pt; self.step = 2; CmdResult::NeedPoint @@ -318,6 +888,9 @@ impl CadCommand for RectRotCommand { let dir = (b - a).normalize_or_zero(); let perp = DVec3::new(-dir.y, dir.x, 0.0); let h = (pt - b).dot(perp); // signed height + if h.abs() <= 1.0e-9 { + return CmdResult::NeedPoint; + } let c = b + perp * h; let d = a + perp * h; let corners = [a, b, c, d].map(|point| self.plane.to_world(point)); @@ -420,6 +993,10 @@ impl CadCommand for RectCenCommand { CmdResult::NeedPoint } Some(c) => { + let delta = self.plane.vector_to_local(pt - c); + if delta.x.abs() <= 1.0e-9 || delta.y.abs() <= 1.0e-9 { + return CmdResult::NeedPoint; + } let q = ucs_box_around_center(c, pt, self.plane); CmdResult::CommitAndExit(make_pline(&q, self.plane)) } diff --git a/src/scene/cache/properties.rs b/src/scene/cache/properties.rs index 6076b7579..b65880a6b 100644 --- a/src/scene/cache/properties.rs +++ b/src/scene/cache/properties.rs @@ -91,6 +91,10 @@ pub fn general_section(entity: &EntityType) -> PropSection { ], }; + if matches!(entity, EntityType::LwPolyline(polyline) if crate::entities::lwpolyline::is_rectangle(polyline)) { + section.props.retain(|prop| prop.field != "handle"); + } + if matches!(entity, EntityType::Point(_)) { section.props.retain(|prop| prop.field != "handle"); let hyperlink = section.props.iter().position(|prop| prop.field == "hyperlink"); diff --git a/src/scene/entity.rs b/src/scene/entity.rs index f12f59a16..6d92855f5 100644 --- a/src/scene/entity.rs +++ b/src/scene/entity.rs @@ -134,6 +134,15 @@ impl Scene { let _ = self.document.layers.add(layer); } + fn ensure_app_id(&mut self, name: &str) { + if name.trim().is_empty() || self.document.app_ids.contains(name) { + return; + } + let mut app_id = acadrust::tables::AppId::new(name); + app_id.handle = self.document.allocate_handle(); + let _ = self.document.app_ids.add(app_id); + } + pub fn add_entity(&mut self, entity: EntityType) -> Handle { self.add_entity_internal(entity, true) } @@ -256,6 +265,20 @@ impl Scene { let creates_layer = self.is_recording_undo() && !layer.trim().is_empty() && !self.document.layers.contains(&layer); self.ensure_layer(&layer); + let app_ids: Vec = entity + .common() + .extended_data + .records() + .iter() + .map(|record| record.application_name.clone()) + .collect(); + let creates_app_id = self.is_recording_undo() + && app_ids.iter().any(|name| { + !name.trim().is_empty() && !self.document.app_ids.contains(name) + }); + for name in &app_ids { + self.ensure_app_id(name); + } // Route to the correct block based on current editing mode: // - BEDIT block editor: geometry belongs to the edited block record, @@ -308,12 +331,10 @@ impl Scene { } // Delta-undo: the new handle's before-image is "nothing" (it did not // exist). Poison the recording if this add also mutated non-entity - // state (a new layer / block) so the app knows a pure-entity delta - // would be incomplete. Raster image definitions are captured as - // targeted object before-images above. + // state (a new layer, application ID, or block). if self.is_recording_undo() { self.record_undo_before(handle, None); - if creates_layer || mutates_block_structure { + if creates_layer || creates_app_id || mutates_block_structure { self.poison_undo_recording(); } }