diff --git a/locales/ar-SA/opencadstudio.ftl b/locales/ar-SA/opencadstudio.ftl index d5ec2ecfd..1f6f77259 100644 --- a/locales/ar-SA/opencadstudio.ftl +++ b/locales/ar-SA/opencadstudio.ftl @@ -1350,6 +1350,31 @@ draw = .region-select-closed-polylines-or-circles = REGION: حدد خطوطًا متعددة مغلقة أو دوائر. .revcloud-specify-next-point-count-pts-enter-to = REVCLOUD حدد النقطة التالية (__ocs_arg_count__ نقاط، Enter للإغلاق): .revcloud-specify-start-point-arc-length-arc-le = REVCLOUD حدد نقطة البداية (طول القوس = __ocs_arg_arc_length__): + .revcloud-first-corner = REVCLOUD الزاوية الأولى (__ocs_arg_style__، طول القوس __ocs_arg_length__): __ocs_arg_message__ + .revcloud-opposite-corner = REVCLOUD الزاوية المقابلة: __ocs_arg_message__ + .revcloud-polygonal-start-point = REVCLOUD نقطة بداية المضلع (__ocs_arg_style__، طول القوس __ocs_arg_length__): __ocs_arg_message__ + .revcloud-next-polygonal-point = REVCLOUD نقطة المضلع التالية (__ocs_arg_count__ نقاط، Enter للإغلاق): __ocs_arg_message__ + .revcloud-freehand-first-point = REVCLOUD أول نقطة للرسم الحر (__ocs_arg_style__، طول القوس __ocs_arg_length__): __ocs_arg_message__ + .revcloud-guide-cursor = REVCLOUD حرّك المؤشر ثم انقر أو اضغط Enter للإغلاق: __ocs_arg_message__ + .revcloud-approximate-arc-chord-length = REVCLOUD طول وتر القوس التقريبي <__ocs_arg_length__>: + .revcloud-style = REVCLOUD النمط [عادي/خطّي] <__ocs_arg_style__>: + .revcloud-select-closed-curve = REVCLOUD حدد دائرة أو قطعًا ناقصًا أو خطًا متعددًا أو منحنى مغلقًا: __ocs_arg_message__ + .revcloud-reverse-arc-direction = REVCLOUD عكس اتجاه الأقواس [نعم/لا] <لا>: + .revcloud-select-revision-cloud = REVCLOUD حدد خطًا متعددًا مغلقًا لسحابة المراجعة قرب بداية الاستبدال: __ocs_arg_message__ + .revcloud-replacement-point = REVCLOUD نقطة الاستبدال __ocs_arg_count__؛ أنهِ عند رأس آخر للسحابة: + .revcloud-pick-side = REVCLOUD اختر جانب السحابة الأصلية المراد مسحه: + .revcloud-path-cannot-form = لا يمكن للمسار المحدد تكوين سحابة مراجعة. + .revcloud-select-closed-revision-cloud = حدد خطًا متعددًا مغلقًا لسحابة المراجعة. + .revcloud-polyline-not-planar = الخط المتعدد المحدد غير مستوٍ. + .revcloud-corners-non-zero-area = يجب أن تحدد الزاويتان مساحة غير صفرية. + .revcloud-arc-length-positive = يجب أن يكون طول القوس أكبر من صفر. + .revcloud-enter-valid-arc-length = أدخل طول قوس صالحًا. + .revcloud-select-supported-closed-curve = حدد منحنى مغلقًا مدعومًا. + .revcloud-select-closed-entity = حدد دائرة أو قطعًا ناقصًا أو خطًا متعددًا أو منحنى مغلقًا. + .calligraphy = خطّي + .first-point = النقطة الأولى + .freehand = رسم حر + .rectangular = مستطيل .rectangle-center = مستطيل - مركز .rectangle-rotated = مستطيل - مدوّر .rectangle-two-corners = مستطيل - زاويتان diff --git a/locales/bg-BG/opencadstudio.ftl b/locales/bg-BG/opencadstudio.ftl index fac5b0ac7..31406c0d3 100644 --- a/locales/bg-BG/opencadstudio.ftl +++ b/locales/bg-BG/opencadstudio.ftl @@ -1337,6 +1337,31 @@ draw = .region-select-closed-polylines-or-circles = REGION: избери затворени полилинии или окръжности. .revcloud-specify-next-point-count-pts-enter-to = REVCLOUD Посочи следваща точка (__ocs_arg_count__ т., Enter за затваряне): .revcloud-specify-start-point-arc-length-arc-le = REVCLOUD Посочи начална точка (дължина на дъга = __ocs_arg_arc_length__): + .revcloud-first-corner = REVCLOUD Първи ъгъл (__ocs_arg_style__, дължина на дъгата __ocs_arg_length__): __ocs_arg_message__ + .revcloud-opposite-corner = REVCLOUD Срещуположен ъгъл: __ocs_arg_message__ + .revcloud-polygonal-start-point = REVCLOUD Начална точка на многоъгълника (__ocs_arg_style__, дължина на дъгата __ocs_arg_length__): __ocs_arg_message__ + .revcloud-next-polygonal-point = REVCLOUD Следваща точка на многоъгълника (__ocs_arg_count__ точки, Enter за затваряне): __ocs_arg_message__ + .revcloud-freehand-first-point = REVCLOUD Първа точка за свободно чертане (__ocs_arg_style__, дължина на дъгата __ocs_arg_length__): __ocs_arg_message__ + .revcloud-guide-cursor = REVCLOUD Водете курсора, след това щракнете или натиснете Enter за затваряне: __ocs_arg_message__ + .revcloud-approximate-arc-chord-length = REVCLOUD Приблизителна дължина на хордата <__ocs_arg_length__>: + .revcloud-style = REVCLOUD Стил [Нормален/Калиграфски] <__ocs_arg_style__>: + .revcloud-select-closed-curve = REVCLOUD Изберете затворена окръжност, елипса, полилиния или сплайн: __ocs_arg_message__ + .revcloud-reverse-arc-direction = REVCLOUD Обръщане на посоката на дъгите [Да/Не] <Не>: + .revcloud-select-revision-cloud = REVCLOUD Изберете затворена полилиния на ревизионен облак близо до началото на замяната: __ocs_arg_message__ + .revcloud-replacement-point = REVCLOUD Точка за замяна __ocs_arg_count__; завършете в друг връх на облака: + .revcloud-pick-side = REVCLOUD Изберете страната на оригиналния облак за изтриване: + .revcloud-path-cannot-form = Избраният път не може да образува ревизионен облак. + .revcloud-select-closed-revision-cloud = Изберете затворена полилиния на ревизионен облак. + .revcloud-polyline-not-planar = Избраната полилиния не е равнинна. + .revcloud-corners-non-zero-area = Двата ъгъла трябва да определят ненулева площ. + .revcloud-arc-length-positive = Дължината на дъгата трябва да е по-голяма от нула. + .revcloud-enter-valid-arc-length = Въведете валидна дължина на дъгата. + .revcloud-select-supported-closed-curve = Изберете поддържана затворена крива. + .revcloud-select-closed-entity = Изберете затворена окръжност, елипса, полилиния или сплайн. + .calligraphy = Калиграфия + .first-point = Първа точка + .freehand = Свободно чертане + .rectangular = Правоъгълен .rectangle-center = Правоъгълник - Център .rectangle-rotated = Правоъгълник - Завъртян .rectangle-two-corners = Правоъгълник - Два ъгъла diff --git a/locales/cs-CZ/opencadstudio.ftl b/locales/cs-CZ/opencadstudio.ftl index 4bd832a7a..58ededaae 100644 --- a/locales/cs-CZ/opencadstudio.ftl +++ b/locales/cs-CZ/opencadstudio.ftl @@ -1337,6 +1337,31 @@ draw = .region-select-closed-polylines-or-circles = REGION: vyberte uzavřené křivky nebo kružnice. .revcloud-specify-next-point-count-pts-enter-to = REVCLOUD Zadejte další bod (__ocs_arg_count__ bodů, Enter pro uzavření): .revcloud-specify-start-point-arc-length-arc-le = REVCLOUD Zadejte počáteční bod (délka oblouku = __ocs_arg_arc_length__): + .revcloud-first-corner = REVCLOUD První roh (__ocs_arg_style__, délka oblouku __ocs_arg_length__): __ocs_arg_message__ + .revcloud-opposite-corner = REVCLOUD Protilehlý roh: __ocs_arg_message__ + .revcloud-polygonal-start-point = REVCLOUD Počáteční bod mnohoúhelníku (__ocs_arg_style__, délka oblouku __ocs_arg_length__): __ocs_arg_message__ + .revcloud-next-polygonal-point = REVCLOUD Další bod mnohoúhelníku (__ocs_arg_count__ bodů, Enter pro uzavření): __ocs_arg_message__ + .revcloud-freehand-first-point = REVCLOUD První bod od ruky (__ocs_arg_style__, délka oblouku __ocs_arg_length__): __ocs_arg_message__ + .revcloud-guide-cursor = REVCLOUD Veďte kurzor, poté klikněte nebo stiskněte Enter pro uzavření: __ocs_arg_message__ + .revcloud-approximate-arc-chord-length = REVCLOUD Přibližná délka tětivy oblouku <__ocs_arg_length__>: + .revcloud-style = REVCLOUD Styl [Normální/Kaligrafie] <__ocs_arg_style__>: + .revcloud-select-closed-curve = REVCLOUD Vyberte uzavřenou kružnici, elipsu, křivku nebo spline: __ocs_arg_message__ + .revcloud-reverse-arc-direction = REVCLOUD Obrátit směr oblouků [Ano/Ne] : + .revcloud-select-revision-cloud = REVCLOUD Vyberte uzavřenou křivku revizního oblaku poblíž začátku náhrady: __ocs_arg_message__ + .revcloud-replacement-point = REVCLOUD Bod náhrady __ocs_arg_count__; ukončete na jiném vrcholu oblaku: + .revcloud-pick-side = REVCLOUD Vyberte stranu původního oblaku, která se má vymazat: + .revcloud-path-cannot-form = Vybraná cesta nemůže vytvořit revizní oblak. + .revcloud-select-closed-revision-cloud = Vyberte uzavřenou křivku revizního oblaku. + .revcloud-polyline-not-planar = Vybraná křivka není rovinná. + .revcloud-corners-non-zero-area = Dva rohy musí vymezovat nenulovou plochu. + .revcloud-arc-length-positive = Délka oblouku musí být větší než nula. + .revcloud-enter-valid-arc-length = Zadejte platnou délku oblouku. + .revcloud-select-supported-closed-curve = Vyberte podporovanou uzavřenou křivku. + .revcloud-select-closed-entity = Vyberte uzavřenou kružnici, elipsu, křivku nebo spline. + .calligraphy = Kaligrafie + .first-point = První bod + .freehand = Od ruky + .rectangular = Obdélníkový .rectangle-center = Obdélník - střed .rectangle-rotated = Obdélník - otočený .rectangle-two-corners = Obdélník - dva rohy diff --git a/locales/de-DE/opencadstudio.ftl b/locales/de-DE/opencadstudio.ftl index 7a6ecdc9d..191cea3fd 100644 --- a/locales/de-DE/opencadstudio.ftl +++ b/locales/de-DE/opencadstudio.ftl @@ -1338,6 +1338,31 @@ draw = .region-select-closed-polylines-or-circles = REGION: Wählen Sie geschlossene Polylinien oder Kreise. .revcloud-specify-next-point-count-pts-enter-to = REVCLOUD Nächsten Punkt angeben (__ocs_arg_count__ Punkte, Eingabe zum Schließen): .revcloud-specify-start-point-arc-length-arc-le = REVCLOUD Startpunkt angeben (Bogenlänge = __ocs_arg_arc_length__): + .revcloud-first-corner = REVCLOUD Erste Ecke (__ocs_arg_style__, Bogenlänge __ocs_arg_length__): __ocs_arg_message__ + .revcloud-opposite-corner = REVCLOUD Gegenüberliegende Ecke: __ocs_arg_message__ + .revcloud-polygonal-start-point = REVCLOUD Polygonaler Startpunkt (__ocs_arg_style__, Bogenlänge __ocs_arg_length__): __ocs_arg_message__ + .revcloud-next-polygonal-point = REVCLOUD Nächster Polygonpunkt (__ocs_arg_count__ Punkte, Enter zum Schließen): __ocs_arg_message__ + .revcloud-freehand-first-point = REVCLOUD Erster Freihandpunkt (__ocs_arg_style__, Bogenlänge __ocs_arg_length__): __ocs_arg_message__ + .revcloud-guide-cursor = REVCLOUD Cursor führen, dann klicken oder Enter zum Schließen drücken: __ocs_arg_message__ + .revcloud-approximate-arc-chord-length = REVCLOUD Ungefähre Bogensehnenlänge <__ocs_arg_length__>: + .revcloud-style = REVCLOUD Stil [Normal/Kalligrafie] <__ocs_arg_style__>: + .revcloud-select-closed-curve = REVCLOUD Geschlossenen Kreis, Ellipse, Polylinie oder Spline auswählen: __ocs_arg_message__ + .revcloud-reverse-arc-direction = REVCLOUD Bogenrichtung umkehren [Ja/Nein] : + .revcloud-select-revision-cloud = REVCLOUD Geschlossene Revisionswolken-Polylinie nahe dem Ersetzungsanfang auswählen: __ocs_arg_message__ + .revcloud-replacement-point = REVCLOUD Ersetzungspunkt __ocs_arg_count__; an einem anderen Wolkenscheitel beenden: + .revcloud-pick-side = REVCLOUD Zu löschende Seite der ursprünglichen Wolke wählen: + .revcloud-path-cannot-form = Der ausgewählte Pfad kann keine Revisionswolke bilden. + .revcloud-select-closed-revision-cloud = Eine geschlossene Revisionswolken-Polylinie auswählen. + .revcloud-polyline-not-planar = Die ausgewählte Polylinie ist nicht planar. + .revcloud-corners-non-zero-area = Die beiden Ecken müssen eine Fläche ungleich null definieren. + .revcloud-arc-length-positive = Die Bogenlänge muss größer als null sein. + .revcloud-enter-valid-arc-length = Eine gültige Bogenlänge eingeben. + .revcloud-select-supported-closed-curve = Eine unterstützte geschlossene Kurve auswählen. + .revcloud-select-closed-entity = Einen geschlossenen Kreis, eine Ellipse, Polylinie oder einen Spline auswählen. + .calligraphy = Kalligrafie + .first-point = Erster Punkt + .freehand = Freihand + .rectangular = Rechteckig .rectangle-center = Rechteck - Mitte .rectangle-rotated = Rechteck - rotiert .rectangle-two-corners = Rechteck - Zwei Ecken diff --git a/locales/en-US/opencadstudio.ftl b/locales/en-US/opencadstudio.ftl index 313cba46a..6e6192fa9 100644 --- a/locales/en-US/opencadstudio.ftl +++ b/locales/en-US/opencadstudio.ftl @@ -1338,6 +1338,31 @@ draw = .region-select-closed-polylines-or-circles = REGION: select closed polylines or circles. .revcloud-specify-next-point-count-pts-enter-to = REVCLOUD Specify next point (__ocs_arg_count__ pts, Enter to close): .revcloud-specify-start-point-arc-length-arc-le = REVCLOUD Specify start point (arc length = __ocs_arg_arc_length__): + .revcloud-first-corner = REVCLOUD First corner (__ocs_arg_style__, arc length __ocs_arg_length__): __ocs_arg_message__ + .revcloud-opposite-corner = REVCLOUD Opposite corner: __ocs_arg_message__ + .revcloud-polygonal-start-point = REVCLOUD Polygonal start point (__ocs_arg_style__, arc length __ocs_arg_length__): __ocs_arg_message__ + .revcloud-next-polygonal-point = REVCLOUD Next polygonal point (__ocs_arg_count__ points, Enter to close): __ocs_arg_message__ + .revcloud-freehand-first-point = REVCLOUD Freehand first point (__ocs_arg_style__, arc length __ocs_arg_length__): __ocs_arg_message__ + .revcloud-guide-cursor = REVCLOUD Guide the cursor, then click or press Enter to close: __ocs_arg_message__ + .revcloud-approximate-arc-chord-length = REVCLOUD Approximate arc chord length <__ocs_arg_length__>: + .revcloud-style = REVCLOUD Style [Normal/Calligraphy] <__ocs_arg_style__>: + .revcloud-select-closed-curve = REVCLOUD Select a closed circle, ellipse, polyline, or spline: __ocs_arg_message__ + .revcloud-reverse-arc-direction = REVCLOUD Reverse arc direction [Yes/No] : + .revcloud-select-revision-cloud = REVCLOUD Select a closed revision-cloud polyline near the replacement start: __ocs_arg_message__ + .revcloud-replacement-point = REVCLOUD Replacement point __ocs_arg_count__; finish on another cloud vertex: + .revcloud-pick-side = REVCLOUD Pick the side of the original cloud to erase: + .revcloud-path-cannot-form = The selected path cannot form a revision cloud. + .revcloud-select-closed-revision-cloud = Select a closed revision-cloud polyline. + .revcloud-polyline-not-planar = The selected polyline is not planar. + .revcloud-corners-non-zero-area = The two corners must define a non-zero area. + .revcloud-arc-length-positive = Arc length must be greater than zero. + .revcloud-enter-valid-arc-length = Enter a valid arc length. + .revcloud-select-supported-closed-curve = Select a supported closed curve. + .revcloud-select-closed-entity = Select a closed circle, ellipse, polyline, or spline. + .calligraphy = Calligraphy + .first-point = First point + .freehand = Freehand + .rectangular = Rectangular .rectangle-center = Rectangle - Center .rectangle-rotated = Rectangle - Rotated .rectangle-two-corners = Rectangle - Two Corners diff --git a/locales/es-ES/opencadstudio.ftl b/locales/es-ES/opencadstudio.ftl index c1129f32d..d44ddbcca 100644 --- a/locales/es-ES/opencadstudio.ftl +++ b/locales/es-ES/opencadstudio.ftl @@ -1338,6 +1338,31 @@ draw = .region-select-closed-polylines-or-circles = REGION: seleccione polilíneas cerradas o círculos. .revcloud-specify-next-point-count-pts-enter-to = REVCLOUD Precise el siguiente punto (__ocs_arg_count__ puntos, Intro para cerrar): .revcloud-specify-start-point-arc-length-arc-le = REVCLOUD Precise el punto inicial (longitud de arco = __ocs_arg_arc_length__): + .revcloud-first-corner = REVCLOUD Primera esquina (__ocs_arg_style__, longitud de arco __ocs_arg_length__): __ocs_arg_message__ + .revcloud-opposite-corner = REVCLOUD Esquina opuesta: __ocs_arg_message__ + .revcloud-polygonal-start-point = REVCLOUD Punto inicial poligonal (__ocs_arg_style__, longitud de arco __ocs_arg_length__): __ocs_arg_message__ + .revcloud-next-polygonal-point = REVCLOUD Siguiente punto poligonal (__ocs_arg_count__ puntos, Intro para cerrar): __ocs_arg_message__ + .revcloud-freehand-first-point = REVCLOUD Primer punto a mano alzada (__ocs_arg_style__, longitud de arco __ocs_arg_length__): __ocs_arg_message__ + .revcloud-guide-cursor = REVCLOUD Guíe el cursor y luego haga clic o pulse Intro para cerrar: __ocs_arg_message__ + .revcloud-approximate-arc-chord-length = REVCLOUD Longitud aproximada de cuerda de arco <__ocs_arg_length__>: + .revcloud-style = REVCLOUD Estilo [Normal/Caligrafía] <__ocs_arg_style__>: + .revcloud-select-closed-curve = REVCLOUD Seleccione un círculo, una elipse, una polilínea o una spline cerrados: __ocs_arg_message__ + .revcloud-reverse-arc-direction = REVCLOUD Invertir dirección de arco [Sí/No] : + .revcloud-select-revision-cloud = REVCLOUD Seleccione una polilínea de nube de revisión cerrada cerca del inicio del reemplazo: __ocs_arg_message__ + .revcloud-replacement-point = REVCLOUD Punto de reemplazo __ocs_arg_count__; termine en otro vértice de la nube: + .revcloud-pick-side = REVCLOUD Elija el lado de la nube original que se borrará: + .revcloud-path-cannot-form = La trayectoria seleccionada no puede formar una nube de revisión. + .revcloud-select-closed-revision-cloud = Seleccione una polilínea de nube de revisión cerrada. + .revcloud-polyline-not-planar = La polilínea seleccionada no es plana. + .revcloud-corners-non-zero-area = Las dos esquinas deben definir un área distinta de cero. + .revcloud-arc-length-positive = La longitud de arco debe ser mayor que cero. + .revcloud-enter-valid-arc-length = Introduzca una longitud de arco válida. + .revcloud-select-supported-closed-curve = Seleccione una curva cerrada compatible. + .revcloud-select-closed-entity = Seleccione un círculo, una elipse, una polilínea o una spline cerrados. + .calligraphy = Caligrafía + .first-point = Primer punto + .freehand = Mano alzada + .rectangular = Rectangular .rectangle-center = Rectángulo - Centro .rectangle-rotated = Rectángulo - Girado .rectangle-two-corners = Rectángulo - Dos esquinas diff --git a/locales/fi-FI/opencadstudio.ftl b/locales/fi-FI/opencadstudio.ftl index 6f21d294a..677962f94 100644 --- a/locales/fi-FI/opencadstudio.ftl +++ b/locales/fi-FI/opencadstudio.ftl @@ -1337,6 +1337,31 @@ draw = .region-select-closed-polylines-or-circles = REGION: valitse suljetut polylinjat tai ympyrät. .revcloud-specify-next-point-count-pts-enter-to = REVCLOUD Määritä seuraava piste (__ocs_arg_count__ pts, Enter sulkemiseen): .revcloud-specify-start-point-arc-length-arc-le = REVCLOUD Määritä aloituspiste (kaaren pituus = __ocs_arg_arc_length__): + .revcloud-first-corner = REVCLOUD Ensimmäinen kulma (__ocs_arg_style__, kaaren pituus __ocs_arg_length__): __ocs_arg_message__ + .revcloud-opposite-corner = REVCLOUD Vastakkainen kulma: __ocs_arg_message__ + .revcloud-polygonal-start-point = REVCLOUD Monikulmion aloituspiste (__ocs_arg_style__, kaaren pituus __ocs_arg_length__): __ocs_arg_message__ + .revcloud-next-polygonal-point = REVCLOUD Seuraava monikulmiopiste (__ocs_arg_count__ pistettä, sulje Enterillä): __ocs_arg_message__ + .revcloud-freehand-first-point = REVCLOUD Vapaapiirron ensimmäinen piste (__ocs_arg_style__, kaaren pituus __ocs_arg_length__): __ocs_arg_message__ + .revcloud-guide-cursor = REVCLOUD Ohjaa kohdistinta ja sulje napsauttamalla tai painamalla Enter: __ocs_arg_message__ + .revcloud-approximate-arc-chord-length = REVCLOUD Kaaren jänteen likimääräinen pituus <__ocs_arg_length__>: + .revcloud-style = REVCLOUD Tyyli [Normaali/Kalligrafia] <__ocs_arg_style__>: + .revcloud-select-closed-curve = REVCLOUD Valitse suljettu ympyrä, ellipsi, moniviiva tai splini: __ocs_arg_message__ + .revcloud-reverse-arc-direction = REVCLOUD Käännä kaaren suunta [Kyllä/Ei] : + .revcloud-select-revision-cloud = REVCLOUD Valitse suljettu tarkistuspilven moniviiva korvauksen alun läheltä: __ocs_arg_message__ + .revcloud-replacement-point = REVCLOUD Korvauspiste __ocs_arg_count__; lopeta toiseen pilven kärkipisteeseen: + .revcloud-pick-side = REVCLOUD Valitse alkuperäisen pilven poistettava puoli: + .revcloud-path-cannot-form = Valittu polku ei voi muodostaa tarkistuspilveä. + .revcloud-select-closed-revision-cloud = Valitse suljettu tarkistuspilven moniviiva. + .revcloud-polyline-not-planar = Valittu moniviiva ei ole tasomainen. + .revcloud-corners-non-zero-area = Kahden kulman on määritettävä nollasta poikkeava pinta-ala. + .revcloud-arc-length-positive = Kaaren pituuden on oltava suurempi kuin nolla. + .revcloud-enter-valid-arc-length = Anna kelvollinen kaaren pituus. + .revcloud-select-supported-closed-curve = Valitse tuettu suljettu käyrä. + .revcloud-select-closed-entity = Valitse suljettu ympyrä, ellipsi, moniviiva tai splini. + .calligraphy = Kalligrafia + .first-point = Ensimmäinen piste + .freehand = Vapaapiirto + .rectangular = Suorakulmainen .rectangle-center = Suorakaide - keskellä .rectangle-rotated = Suorakaide - Kierretty .rectangle-two-corners = Suorakulmio - kaksi kulmaa diff --git a/locales/fr-FR/opencadstudio.ftl b/locales/fr-FR/opencadstudio.ftl index da006f6f0..494202760 100644 --- a/locales/fr-FR/opencadstudio.ftl +++ b/locales/fr-FR/opencadstudio.ftl @@ -1338,6 +1338,31 @@ draw = .region-select-closed-polylines-or-circles = REGION : sélectionnez les polylignes ou les cercles fermés. .revcloud-specify-next-point-count-pts-enter-to = REVCLOUD Spécifiez le point suivant (__ocs_arg_count__ pts, entrez pour fermer): .revcloud-specify-start-point-arc-length-arc-le = REVCLOUD Spécifier le point de départ (longueur de l'arc = __ocs_arg_arc_length__): + .revcloud-first-corner = REVCLOUD Premier coin (__ocs_arg_style__, longueur d'arc __ocs_arg_length__) : __ocs_arg_message__ + .revcloud-opposite-corner = REVCLOUD Coin opposé : __ocs_arg_message__ + .revcloud-polygonal-start-point = REVCLOUD Point de départ polygonal (__ocs_arg_style__, longueur d'arc __ocs_arg_length__) : __ocs_arg_message__ + .revcloud-next-polygonal-point = REVCLOUD Point polygonal suivant (__ocs_arg_count__ points, Entrée pour fermer) : __ocs_arg_message__ + .revcloud-freehand-first-point = REVCLOUD Premier point à main levée (__ocs_arg_style__, longueur d'arc __ocs_arg_length__) : __ocs_arg_message__ + .revcloud-guide-cursor = REVCLOUD Guidez le curseur, puis cliquez ou appuyez sur Entrée pour fermer : __ocs_arg_message__ + .revcloud-approximate-arc-chord-length = REVCLOUD Longueur approximative de la corde d'arc <__ocs_arg_length__> : + .revcloud-style = REVCLOUD Style [Normal/Calligraphie] <__ocs_arg_style__> : + .revcloud-select-closed-curve = REVCLOUD Sélectionnez un cercle, une ellipse, une polyligne ou une spline fermés : __ocs_arg_message__ + .revcloud-reverse-arc-direction = REVCLOUD Inverser la direction des arcs [Oui/Non] : + .revcloud-select-revision-cloud = REVCLOUD Sélectionnez une polyligne de nuage de révision fermée près du début du remplacement : __ocs_arg_message__ + .revcloud-replacement-point = REVCLOUD Point de remplacement __ocs_arg_count__ ; terminez sur un autre sommet du nuage : + .revcloud-pick-side = REVCLOUD Choisissez le côté du nuage d'origine à effacer : + .revcloud-path-cannot-form = Le chemin sélectionné ne peut pas former un nuage de révision. + .revcloud-select-closed-revision-cloud = Sélectionnez une polyligne de nuage de révision fermée. + .revcloud-polyline-not-planar = La polyligne sélectionnée n'est pas plane. + .revcloud-corners-non-zero-area = Les deux coins doivent définir une aire non nulle. + .revcloud-arc-length-positive = La longueur d'arc doit être supérieure à zéro. + .revcloud-enter-valid-arc-length = Saisissez une longueur d'arc valide. + .revcloud-select-supported-closed-curve = Sélectionnez une courbe fermée prise en charge. + .revcloud-select-closed-entity = Sélectionnez un cercle, une ellipse, une polyligne ou une spline fermés. + .calligraphy = Calligraphie + .first-point = Premier point + .freehand = Main levée + .rectangular = Rectangulaire .rectangle-center = Rectangle - Centre .rectangle-rotated = Rectangle - Rotation .rectangle-two-corners = Rectangle - Deux coins diff --git a/locales/hi-IN/opencadstudio.ftl b/locales/hi-IN/opencadstudio.ftl index ba92c16e8..55aaf3126 100644 --- a/locales/hi-IN/opencadstudio.ftl +++ b/locales/hi-IN/opencadstudio.ftl @@ -1326,6 +1326,31 @@ draw = .region-select-closed-polylines-or-circles = REGION: बंद पॉलीलाइन या सर्कल का चयन करें। .revcloud-specify-next-point-count-pts-enter-to = REVCLOUD अगला बिंदु निर्दिष्ट करें (__ocs_arg_count__ बिंदु, बंद करने के लिए Enter दबाएँ): .revcloud-specify-start-point-arc-length-arc-le = REVCLOUD आरंभ बिंदु निर्दिष्ट करें (चाप लंबाई = __ocs_arg_arc_length__): + .revcloud-first-corner = REVCLOUD पहला कोना (__ocs_arg_style__, चाप लंबाई __ocs_arg_length__): __ocs_arg_message__ + .revcloud-opposite-corner = REVCLOUD विपरीत कोना: __ocs_arg_message__ + .revcloud-polygonal-start-point = REVCLOUD बहुभुज का आरंभ बिंदु (__ocs_arg_style__, चाप लंबाई __ocs_arg_length__): __ocs_arg_message__ + .revcloud-next-polygonal-point = REVCLOUD अगला बहुभुज बिंदु (__ocs_arg_count__ बिंदु, बंद करने के लिए Enter): __ocs_arg_message__ + .revcloud-freehand-first-point = REVCLOUD मुक्तहस्त पहला बिंदु (__ocs_arg_style__, चाप लंबाई __ocs_arg_length__): __ocs_arg_message__ + .revcloud-guide-cursor = REVCLOUD कर्सर चलाएँ, फिर बंद करने के लिए क्लिक करें या Enter दबाएँ: __ocs_arg_message__ + .revcloud-approximate-arc-chord-length = REVCLOUD अनुमानित चाप जीवा लंबाई <__ocs_arg_length__>: + .revcloud-style = REVCLOUD शैली [सामान्य/सुलेख] <__ocs_arg_style__>: + .revcloud-select-closed-curve = REVCLOUD बंद वृत्त, दीर्घवृत्त, पॉलीलाइन या स्प्लाइन चुनें: __ocs_arg_message__ + .revcloud-reverse-arc-direction = REVCLOUD चाप दिशा उलटें [हाँ/नहीं] <नहीं>: + .revcloud-select-revision-cloud = REVCLOUD प्रतिस्थापन आरंभ के पास बंद संशोधन-बादल पॉलीलाइन चुनें: __ocs_arg_message__ + .revcloud-replacement-point = REVCLOUD प्रतिस्थापन बिंदु __ocs_arg_count__; किसी अन्य बादल शीर्ष पर समाप्त करें: + .revcloud-pick-side = REVCLOUD मूल बादल का मिटाया जाने वाला भाग चुनें: + .revcloud-path-cannot-form = चयनित पथ से संशोधन बादल नहीं बनाया जा सकता। + .revcloud-select-closed-revision-cloud = बंद संशोधन-बादल पॉलीलाइन चुनें। + .revcloud-polyline-not-planar = चयनित पॉलीलाइन समतलीय नहीं है। + .revcloud-corners-non-zero-area = दोनों कोनों से शून्य से अलग क्षेत्र बनना चाहिए। + .revcloud-arc-length-positive = चाप लंबाई शून्य से अधिक होनी चाहिए। + .revcloud-enter-valid-arc-length = मान्य चाप लंबाई दर्ज करें। + .revcloud-select-supported-closed-curve = समर्थित बंद वक्र चुनें। + .revcloud-select-closed-entity = बंद वृत्त, दीर्घवृत्त, पॉलीलाइन या स्प्लाइन चुनें। + .calligraphy = सुलेख + .first-point = पहला बिंदु + .freehand = मुक्तहस्त + .rectangular = आयताकार .rectangle-center = आयत - केंद्र .rectangle-rotated = आयत - घुमाया .rectangle-two-corners = आयत - दो कोनों diff --git a/locales/hu-HU/opencadstudio.ftl b/locales/hu-HU/opencadstudio.ftl index 7f5e9b098..dd17a2151 100644 --- a/locales/hu-HU/opencadstudio.ftl +++ b/locales/hu-HU/opencadstudio.ftl @@ -1337,6 +1337,31 @@ draw = .region-select-closed-polylines-or-circles = REGION: zárt vonalláncok vagy körök kiválasztása. .revcloud-specify-next-point-count-pts-enter-to = REVCLOUD Adja meg a következő pontot (__ocs_arg_count__ pont, Enter a bezáráshoz): .revcloud-specify-start-point-arc-length-arc-le = REVCLOUD Kezdőpont megadása (ívhossz = __ocs_arg_arc_length__): + .revcloud-first-corner = REVCLOUD Első sarok (__ocs_arg_style__, ívhossz __ocs_arg_length__): __ocs_arg_message__ + .revcloud-opposite-corner = REVCLOUD Szemközti sarok: __ocs_arg_message__ + .revcloud-polygonal-start-point = REVCLOUD Sokszög kezdőpontja (__ocs_arg_style__, ívhossz __ocs_arg_length__): __ocs_arg_message__ + .revcloud-next-polygonal-point = REVCLOUD Következő sokszögpont (__ocs_arg_count__ pont, Enter a bezáráshoz): __ocs_arg_message__ + .revcloud-freehand-first-point = REVCLOUD Szabadkézi első pont (__ocs_arg_style__, ívhossz __ocs_arg_length__): __ocs_arg_message__ + .revcloud-guide-cursor = REVCLOUD Vezesse a kurzort, majd kattintson vagy nyomja meg az Entert a bezáráshoz: __ocs_arg_message__ + .revcloud-approximate-arc-chord-length = REVCLOUD Közelítő ívhúrhossz <__ocs_arg_length__>: + .revcloud-style = REVCLOUD Stílus [Normál/Kalligráfia] <__ocs_arg_style__>: + .revcloud-select-closed-curve = REVCLOUD Válasszon zárt kört, ellipszist, vonalláncot vagy spline-t: __ocs_arg_message__ + .revcloud-reverse-arc-direction = REVCLOUD Ívirány megfordítása [Igen/Nem] : + .revcloud-select-revision-cloud = REVCLOUD Válasszon zárt revíziósfelhő-vonalláncot a csere kezdőpontja közelében: __ocs_arg_message__ + .revcloud-replacement-point = REVCLOUD Cserepont __ocs_arg_count__; fejezze be a felhő egy másik csúcsán: + .revcloud-pick-side = REVCLOUD Válassza ki az eredeti felhő törlendő oldalát: + .revcloud-path-cannot-form = A kijelölt útvonalból nem készíthető revíziós felhő. + .revcloud-select-closed-revision-cloud = Válasszon zárt revíziósfelhő-vonalláncot. + .revcloud-polyline-not-planar = A kijelölt vonallánc nem síkbeli. + .revcloud-corners-non-zero-area = A két saroknak nullától eltérő területet kell meghatároznia. + .revcloud-arc-length-positive = Az ívhossznak nullánál nagyobbnak kell lennie. + .revcloud-enter-valid-arc-length = Adjon meg érvényes ívhosszt. + .revcloud-select-supported-closed-curve = Válasszon támogatott zárt görbét. + .revcloud-select-closed-entity = Válasszon zárt kört, ellipszist, vonalláncot vagy spline-t. + .calligraphy = Kalligráfia + .first-point = Első pont + .freehand = Szabadkézi + .rectangular = Téglalap alakú .rectangle-center = Téglalap – Középen .rectangle-rotated = Téglalap – elforgatva .rectangle-two-corners = Téglalap – két sarok diff --git a/locales/it-IT/opencadstudio.ftl b/locales/it-IT/opencadstudio.ftl index 921e555f1..1ad809462 100644 --- a/locales/it-IT/opencadstudio.ftl +++ b/locales/it-IT/opencadstudio.ftl @@ -1337,6 +1337,31 @@ draw = .region-select-closed-polylines-or-circles = REGION: seleziona polilinee o cerchi chiusi. .revcloud-specify-next-point-count-pts-enter-to = REVCLOUD Specificare il punto successivo (__ocs_arg_count__ punti, Enter per chiudere): .revcloud-specify-start-point-arc-length-arc-le = REVCLOUD Specificare il punto iniziale (lunghezza arco = __ocs_arg_arc_length__): + .revcloud-first-corner = REVCLOUD Primo angolo (__ocs_arg_style__, lunghezza arco __ocs_arg_length__): __ocs_arg_message__ + .revcloud-opposite-corner = REVCLOUD Angolo opposto: __ocs_arg_message__ + .revcloud-polygonal-start-point = REVCLOUD Punto iniziale poligonale (__ocs_arg_style__, lunghezza arco __ocs_arg_length__): __ocs_arg_message__ + .revcloud-next-polygonal-point = REVCLOUD Punto poligonale successivo (__ocs_arg_count__ punti, Invio per chiudere): __ocs_arg_message__ + .revcloud-freehand-first-point = REVCLOUD Primo punto a mano libera (__ocs_arg_style__, lunghezza arco __ocs_arg_length__): __ocs_arg_message__ + .revcloud-guide-cursor = REVCLOUD Guidare il cursore, quindi fare clic o premere Invio per chiudere: __ocs_arg_message__ + .revcloud-approximate-arc-chord-length = REVCLOUD Lunghezza approssimativa della corda dell'arco <__ocs_arg_length__>: + .revcloud-style = REVCLOUD Stile [Normale/Calligrafia] <__ocs_arg_style__>: + .revcloud-select-closed-curve = REVCLOUD Selezionare un cerchio, un'ellisse, una polilinea o una spline chiusi: __ocs_arg_message__ + .revcloud-reverse-arc-direction = REVCLOUD Invertire la direzione dell'arco [Sì/No] : + .revcloud-select-revision-cloud = REVCLOUD Selezionare una polilinea chiusa di nuvola di revisione vicino all'inizio della sostituzione: __ocs_arg_message__ + .revcloud-replacement-point = REVCLOUD Punto di sostituzione __ocs_arg_count__; terminare su un altro vertice della nuvola: + .revcloud-pick-side = REVCLOUD Scegliere il lato della nuvola originale da cancellare: + .revcloud-path-cannot-form = Il percorso selezionato non può formare una nuvola di revisione. + .revcloud-select-closed-revision-cloud = Selezionare una polilinea chiusa di nuvola di revisione. + .revcloud-polyline-not-planar = La polilinea selezionata non è planare. + .revcloud-corners-non-zero-area = I due angoli devono definire un'area diversa da zero. + .revcloud-arc-length-positive = La lunghezza dell'arco deve essere maggiore di zero. + .revcloud-enter-valid-arc-length = Immettere una lunghezza dell'arco valida. + .revcloud-select-supported-closed-curve = Selezionare una curva chiusa supportata. + .revcloud-select-closed-entity = Selezionare un cerchio, un'ellisse, una polilinea o una spline chiusi. + .calligraphy = Calligrafia + .first-point = Primo punto + .freehand = Mano libera + .rectangular = Rettangolare .rectangle-center = Rettangolo - Centro .rectangle-rotated = Rettangolo - Ruotato .rectangle-two-corners = Rettangolo: due angoli diff --git a/locales/ja-JP/opencadstudio.ftl b/locales/ja-JP/opencadstudio.ftl index d9ce62cc2..0e98fa03e 100644 --- a/locales/ja-JP/opencadstudio.ftl +++ b/locales/ja-JP/opencadstudio.ftl @@ -1328,6 +1328,31 @@ draw = .region-select-closed-polylines-or-circles = REGION: 閉じたポリラインまたは円を選択してください。 .revcloud-specify-next-point-count-pts-enter-to = REVCLOUD 次の点を指定(__ocs_arg_count__ 点、Enter で閉じる): .revcloud-specify-start-point-arc-length-arc-le = REVCLOUD 始点を指定(円弧長 = __ocs_arg_arc_length__): + .revcloud-first-corner = REVCLOUD 最初のコーナー(__ocs_arg_style__、円弧長 __ocs_arg_length__): __ocs_arg_message__ + .revcloud-opposite-corner = REVCLOUD 反対側のコーナー: __ocs_arg_message__ + .revcloud-polygonal-start-point = REVCLOUD ポリゴンの始点(__ocs_arg_style__、円弧長 __ocs_arg_length__): __ocs_arg_message__ + .revcloud-next-polygonal-point = REVCLOUD 次のポリゴン点(__ocs_arg_count__ 点、Enter で閉じる): __ocs_arg_message__ + .revcloud-freehand-first-point = REVCLOUD フリーハンドの最初の点(__ocs_arg_style__、円弧長 __ocs_arg_length__): __ocs_arg_message__ + .revcloud-guide-cursor = REVCLOUD カーソルを動かし、クリックまたは Enter で閉じる: __ocs_arg_message__ + .revcloud-approximate-arc-chord-length = REVCLOUD 円弧の弦のおおよその長さ <__ocs_arg_length__>: + .revcloud-style = REVCLOUD スタイル [標準/カリグラフィー] <__ocs_arg_style__>: + .revcloud-select-closed-curve = REVCLOUD 閉じた円、楕円、ポリライン、またはスプラインを選択: __ocs_arg_message__ + .revcloud-reverse-arc-direction = REVCLOUD 円弧方向を反転 [はい/いいえ] <いいえ>: + .revcloud-select-revision-cloud = REVCLOUD 置換開始点の近くにある閉じたリビジョンクラウドのポリラインを選択: __ocs_arg_message__ + .revcloud-replacement-point = REVCLOUD 置換点 __ocs_arg_count__; 別のクラウド頂点で終了: + .revcloud-pick-side = REVCLOUD 元のクラウドから消去する側を選択: + .revcloud-path-cannot-form = 選択したパスからリビジョンクラウドを作成できません。 + .revcloud-select-closed-revision-cloud = 閉じたリビジョンクラウドのポリラインを選択してください。 + .revcloud-polyline-not-planar = 選択したポリラインは平面上にありません。 + .revcloud-corners-non-zero-area = 2 つのコーナーでゼロでない領域を指定する必要があります。 + .revcloud-arc-length-positive = 円弧長はゼロより大きくする必要があります。 + .revcloud-enter-valid-arc-length = 有効な円弧長を入力してください。 + .revcloud-select-supported-closed-curve = 対応している閉じた曲線を選択してください。 + .revcloud-select-closed-entity = 閉じた円、楕円、ポリライン、またはスプラインを選択してください。 + .calligraphy = カリグラフィー + .first-point = 最初の点 + .freehand = フリーハンド + .rectangular = 長方形 .rectangle-center = 長方形 - 中心 .rectangle-rotated = 長方形 - 回転 .rectangle-two-corners = 長方形 - 2 コーナー diff --git a/locales/ko-KR/opencadstudio.ftl b/locales/ko-KR/opencadstudio.ftl index 3d125a8e7..c90e6a014 100644 --- a/locales/ko-KR/opencadstudio.ftl +++ b/locales/ko-KR/opencadstudio.ftl @@ -1337,6 +1337,31 @@ draw = .region-select-closed-polylines-or-circles = REGION: 닫힌 폴리선이나 원을 선택합니다. .revcloud-specify-next-point-count-pts-enter-to = REVCLOUD 다음 점 지정(__ocs_arg_count__ 포인트, 닫으려면 Enter): .revcloud-specify-start-point-arc-length-arc-le = REVCLOUD 시작점 지정(호 길이 = __ocs_arg_arc_length__): + .revcloud-first-corner = REVCLOUD 첫 번째 구석점(__ocs_arg_style__, 호 길이 __ocs_arg_length__): __ocs_arg_message__ + .revcloud-opposite-corner = REVCLOUD 반대쪽 구석점: __ocs_arg_message__ + .revcloud-polygonal-start-point = REVCLOUD 다각형 시작점(__ocs_arg_style__, 호 길이 __ocs_arg_length__): __ocs_arg_message__ + .revcloud-next-polygonal-point = REVCLOUD 다음 다각형 점(__ocs_arg_count__개 점, 닫으려면 Enter): __ocs_arg_message__ + .revcloud-freehand-first-point = REVCLOUD 자유곡선 첫 번째 점(__ocs_arg_style__, 호 길이 __ocs_arg_length__): __ocs_arg_message__ + .revcloud-guide-cursor = REVCLOUD 커서를 움직인 다음 클릭하거나 Enter를 눌러 닫기: __ocs_arg_message__ + .revcloud-approximate-arc-chord-length = REVCLOUD 대략적인 호 현 길이 <__ocs_arg_length__>: + .revcloud-style = REVCLOUD 스타일 [일반/캘리그래피] <__ocs_arg_style__>: + .revcloud-select-closed-curve = REVCLOUD 닫힌 원, 타원, 폴리선 또는 스플라인 선택: __ocs_arg_message__ + .revcloud-reverse-arc-direction = REVCLOUD 호 방향 반전 [예/아니요] <아니요>: + .revcloud-select-revision-cloud = REVCLOUD 대체 시작점 근처의 닫힌 수정 구름 폴리선 선택: __ocs_arg_message__ + .revcloud-replacement-point = REVCLOUD 대체점 __ocs_arg_count__; 다른 구름 정점에서 종료: + .revcloud-pick-side = REVCLOUD 원래 구름에서 지울 쪽 선택: + .revcloud-path-cannot-form = 선택한 경로로 수정 구름을 만들 수 없습니다. + .revcloud-select-closed-revision-cloud = 닫힌 수정 구름 폴리선을 선택하십시오. + .revcloud-polyline-not-planar = 선택한 폴리선이 평면에 있지 않습니다. + .revcloud-corners-non-zero-area = 두 구석점은 0이 아닌 영역을 정의해야 합니다. + .revcloud-arc-length-positive = 호 길이는 0보다 커야 합니다. + .revcloud-enter-valid-arc-length = 유효한 호 길이를 입력하십시오. + .revcloud-select-supported-closed-curve = 지원되는 닫힌 곡선을 선택하십시오. + .revcloud-select-closed-entity = 닫힌 원, 타원, 폴리선 또는 스플라인을 선택하십시오. + .calligraphy = 캘리그래피 + .first-point = 첫 번째 점 + .freehand = 자유곡선 + .rectangular = 직사각형 .rectangle-center = 직사각형 - 중심 .rectangle-rotated = 직사각형 - 회전됨 .rectangle-two-corners = 직사각형 - 두 모서리 diff --git a/locales/nl-NL/opencadstudio.ftl b/locales/nl-NL/opencadstudio.ftl index 6b442c96c..9f74cf064 100644 --- a/locales/nl-NL/opencadstudio.ftl +++ b/locales/nl-NL/opencadstudio.ftl @@ -1338,6 +1338,31 @@ draw = .region-select-closed-polylines-or-circles = REGION: selecteer gesloten polylijnen of cirkels. .revcloud-specify-next-point-count-pts-enter-to = REVCLOUD Geef het volgende punt op (__ocs_arg_count__ punten, Enter om te sluiten): .revcloud-specify-start-point-arc-length-arc-le = REVCLOUD Geef het beginpunt op (booglengte = __ocs_arg_arc_length__): + .revcloud-first-corner = REVCLOUD Eerste hoek (__ocs_arg_style__, booglengte __ocs_arg_length__): __ocs_arg_message__ + .revcloud-opposite-corner = REVCLOUD Tegenoverliggende hoek: __ocs_arg_message__ + .revcloud-polygonal-start-point = REVCLOUD Veelhoekig beginpunt (__ocs_arg_style__, booglengte __ocs_arg_length__): __ocs_arg_message__ + .revcloud-next-polygonal-point = REVCLOUD Volgend veelhoekpunt (__ocs_arg_count__ punten, Enter om te sluiten): __ocs_arg_message__ + .revcloud-freehand-first-point = REVCLOUD Eerste vrijehandspunt (__ocs_arg_style__, booglengte __ocs_arg_length__): __ocs_arg_message__ + .revcloud-guide-cursor = REVCLOUD Leid de cursor en klik of druk op Enter om te sluiten: __ocs_arg_message__ + .revcloud-approximate-arc-chord-length = REVCLOUD Geschatte boogkoorde-lengte <__ocs_arg_length__>: + .revcloud-style = REVCLOUD Stijl [Normaal/Kalligrafie] <__ocs_arg_style__>: + .revcloud-select-closed-curve = REVCLOUD Selecteer een gesloten cirkel, ellips, polylijn of spline: __ocs_arg_message__ + .revcloud-reverse-arc-direction = REVCLOUD Boogrichting omkeren [Ja/Nee] : + .revcloud-select-revision-cloud = REVCLOUD Selecteer een gesloten revisiewolk-polylijn bij het begin van de vervanging: __ocs_arg_message__ + .revcloud-replacement-point = REVCLOUD Vervangingspunt __ocs_arg_count__; eindig op een ander wolkhoekpunt: + .revcloud-pick-side = REVCLOUD Kies de te wissen zijde van de oorspronkelijke wolk: + .revcloud-path-cannot-form = Het geselecteerde pad kan geen revisiewolk vormen. + .revcloud-select-closed-revision-cloud = Selecteer een gesloten revisiewolk-polylijn. + .revcloud-polyline-not-planar = De geselecteerde polylijn is niet vlak. + .revcloud-corners-non-zero-area = De twee hoeken moeten een oppervlak groter dan nul bepalen. + .revcloud-arc-length-positive = De booglengte moet groter zijn dan nul. + .revcloud-enter-valid-arc-length = Voer een geldige booglengte in. + .revcloud-select-supported-closed-curve = Selecteer een ondersteunde gesloten kromme. + .revcloud-select-closed-entity = Selecteer een gesloten cirkel, ellips, polylijn of spline. + .calligraphy = Kalligrafie + .first-point = Eerste punt + .freehand = Vrije hand + .rectangular = Rechthoekig .rectangle-center = Rechthoek - midden .rectangle-rotated = Rechthoek - gedraaid .rectangle-two-corners = Rechthoek - twee hoeken diff --git a/locales/pl-PL/opencadstudio.ftl b/locales/pl-PL/opencadstudio.ftl index dc3bbd6d8..3e5cc3341 100644 --- a/locales/pl-PL/opencadstudio.ftl +++ b/locales/pl-PL/opencadstudio.ftl @@ -1337,6 +1337,31 @@ draw = .region-select-closed-polylines-or-circles = REGION: wybierz zamknięte polilinie lub okręgi. .revcloud-specify-next-point-count-pts-enter-to = REVCLOUD Określ następny punkt (__ocs_arg_count__ pkt, Enter aby zamknąć): .revcloud-specify-start-point-arc-length-arc-le = REVCLOUD Podaj punkt początkowy (długość łuku = __ocs_arg_arc_length__): + .revcloud-first-corner = REVCLOUD Pierwszy narożnik (__ocs_arg_style__, długość łuku __ocs_arg_length__): __ocs_arg_message__ + .revcloud-opposite-corner = REVCLOUD Przeciwległy narożnik: __ocs_arg_message__ + .revcloud-polygonal-start-point = REVCLOUD Punkt początkowy wielokąta (__ocs_arg_style__, długość łuku __ocs_arg_length__): __ocs_arg_message__ + .revcloud-next-polygonal-point = REVCLOUD Następny punkt wielokąta (__ocs_arg_count__ punktów, Enter aby zamknąć): __ocs_arg_message__ + .revcloud-freehand-first-point = REVCLOUD Pierwszy punkt odręczny (__ocs_arg_style__, długość łuku __ocs_arg_length__): __ocs_arg_message__ + .revcloud-guide-cursor = REVCLOUD Prowadź kursor, następnie kliknij lub naciśnij Enter, aby zamknąć: __ocs_arg_message__ + .revcloud-approximate-arc-chord-length = REVCLOUD Przybliżona długość cięciwy łuku <__ocs_arg_length__>: + .revcloud-style = REVCLOUD Styl [Normalny/Kaligrafia] <__ocs_arg_style__>: + .revcloud-select-closed-curve = REVCLOUD Wybierz zamknięty okrąg, elipsę, polilinię lub splajn: __ocs_arg_message__ + .revcloud-reverse-arc-direction = REVCLOUD Odwrócić kierunek łuków [Tak/Nie] : + .revcloud-select-revision-cloud = REVCLOUD Wybierz zamkniętą polilinię chmury rewizyjnej w pobliżu początku zastąpienia: __ocs_arg_message__ + .revcloud-replacement-point = REVCLOUD Punkt zastąpienia __ocs_arg_count__; zakończ na innym wierzchołku chmury: + .revcloud-pick-side = REVCLOUD Wybierz stronę pierwotnej chmury do usunięcia: + .revcloud-path-cannot-form = Wybrana ścieżka nie może utworzyć chmury rewizyjnej. + .revcloud-select-closed-revision-cloud = Wybierz zamkniętą polilinię chmury rewizyjnej. + .revcloud-polyline-not-planar = Wybrana polilinia nie jest płaska. + .revcloud-corners-non-zero-area = Dwa narożniki muszą wyznaczać pole różne od zera. + .revcloud-arc-length-positive = Długość łuku musi być większa od zera. + .revcloud-enter-valid-arc-length = Wprowadź prawidłową długość łuku. + .revcloud-select-supported-closed-curve = Wybierz obsługiwaną zamkniętą krzywą. + .revcloud-select-closed-entity = Wybierz zamknięty okrąg, elipsę, polilinię lub splajn. + .calligraphy = Kaligrafia + .first-point = Pierwszy punkt + .freehand = Odręcznie + .rectangular = Prostokątny .rectangle-center = Prostokąt - środek .rectangle-rotated = Prostokąt — obrócony .rectangle-two-corners = Prostokąt - dwa narożniki diff --git a/locales/pt-BR/opencadstudio.ftl b/locales/pt-BR/opencadstudio.ftl index ae4690d11..122e2737c 100644 --- a/locales/pt-BR/opencadstudio.ftl +++ b/locales/pt-BR/opencadstudio.ftl @@ -1338,6 +1338,31 @@ draw = .region-select-closed-polylines-or-circles = REGION: selecione polilinhas fechadas ou círculos. .revcloud-specify-next-point-count-pts-enter-to = REVCLOUD Especifique o próximo ponto (__ocs_arg_count__ pontos, Enter para fechar): .revcloud-specify-start-point-arc-length-arc-le = REVCLOUD Especifique o ponto inicial (comprimento do arco = __ocs_arg_arc_length__): + .revcloud-first-corner = REVCLOUD Primeiro canto (__ocs_arg_style__, comprimento do arco __ocs_arg_length__): __ocs_arg_message__ + .revcloud-opposite-corner = REVCLOUD Canto oposto: __ocs_arg_message__ + .revcloud-polygonal-start-point = REVCLOUD Ponto inicial poligonal (__ocs_arg_style__, comprimento do arco __ocs_arg_length__): __ocs_arg_message__ + .revcloud-next-polygonal-point = REVCLOUD Próximo ponto poligonal (__ocs_arg_count__ pontos, Enter para fechar): __ocs_arg_message__ + .revcloud-freehand-first-point = REVCLOUD Primeiro ponto à mão livre (__ocs_arg_style__, comprimento do arco __ocs_arg_length__): __ocs_arg_message__ + .revcloud-guide-cursor = REVCLOUD Guie o cursor e clique ou pressione Enter para fechar: __ocs_arg_message__ + .revcloud-approximate-arc-chord-length = REVCLOUD Comprimento aproximado da corda do arco <__ocs_arg_length__>: + .revcloud-style = REVCLOUD Estilo [Normal/Caligrafia] <__ocs_arg_style__>: + .revcloud-select-closed-curve = REVCLOUD Selecione um círculo, elipse, polilinha ou spline fechados: __ocs_arg_message__ + .revcloud-reverse-arc-direction = REVCLOUD Inverter direção do arco [Sim/Não] : + .revcloud-select-revision-cloud = REVCLOUD Selecione uma polilinha de nuvem de revisão fechada perto do início da substituição: __ocs_arg_message__ + .revcloud-replacement-point = REVCLOUD Ponto de substituição __ocs_arg_count__; termine em outro vértice da nuvem: + .revcloud-pick-side = REVCLOUD Escolha o lado da nuvem original a apagar: + .revcloud-path-cannot-form = O caminho selecionado não pode formar uma nuvem de revisão. + .revcloud-select-closed-revision-cloud = Selecione uma polilinha de nuvem de revisão fechada. + .revcloud-polyline-not-planar = A polilinha selecionada não é plana. + .revcloud-corners-non-zero-area = Os dois cantos devem definir uma área diferente de zero. + .revcloud-arc-length-positive = O comprimento do arco deve ser maior que zero. + .revcloud-enter-valid-arc-length = Digite um comprimento de arco válido. + .revcloud-select-supported-closed-curve = Selecione uma curva fechada compatível. + .revcloud-select-closed-entity = Selecione um círculo, elipse, polilinha ou spline fechados. + .calligraphy = Caligrafia + .first-point = Primeiro ponto + .freehand = Mão livre + .rectangular = Retangular .rectangle-center = Retângulo - Centro .rectangle-rotated = Retângulo - Girado .rectangle-two-corners = Retângulo - Dois cantos diff --git a/locales/ru-RU/opencadstudio.ftl b/locales/ru-RU/opencadstudio.ftl index c2aa6f926..f78373041 100644 --- a/locales/ru-RU/opencadstudio.ftl +++ b/locales/ru-RU/opencadstudio.ftl @@ -1341,6 +1341,31 @@ draw = .region-select-closed-polylines-or-circles = REGION: выберите замкнутые полилины или окружности. .revcloud-specify-next-point-count-pts-enter-to = REVCLOUD Укажите следующую точку (__ocs_arg_count__ pts, Введите, чтобы закрыть): .revcloud-specify-start-point-arc-length-arc-le = REVCLOUD Укажите начальную точку (длина дуги = __ocs_arg_arc_length__): + .revcloud-first-corner = REVCLOUD Первый угол (__ocs_arg_style__, длина дуги __ocs_arg_length__): __ocs_arg_message__ + .revcloud-opposite-corner = REVCLOUD Противоположный угол: __ocs_arg_message__ + .revcloud-polygonal-start-point = REVCLOUD Начальная точка многоугольника (__ocs_arg_style__, длина дуги __ocs_arg_length__): __ocs_arg_message__ + .revcloud-next-polygonal-point = REVCLOUD Следующая точка многоугольника (__ocs_arg_count__ точек, Enter для замыкания): __ocs_arg_message__ + .revcloud-freehand-first-point = REVCLOUD Первая точка от руки (__ocs_arg_style__, длина дуги __ocs_arg_length__): __ocs_arg_message__ + .revcloud-guide-cursor = REVCLOUD Ведите курсор, затем щёлкните или нажмите Enter для замыкания: __ocs_arg_message__ + .revcloud-approximate-arc-chord-length = REVCLOUD Приблизительная длина хорды дуги <__ocs_arg_length__>: + .revcloud-style = REVCLOUD Стиль [Обычный/Каллиграфия] <__ocs_arg_style__>: + .revcloud-select-closed-curve = REVCLOUD Выберите замкнутую окружность, эллипс, полилинию или сплайн: __ocs_arg_message__ + .revcloud-reverse-arc-direction = REVCLOUD Обратить направление дуг [Да/Нет] <Нет>: + .revcloud-select-revision-cloud = REVCLOUD Выберите замкнутую полилинию облака изменений рядом с началом замены: __ocs_arg_message__ + .revcloud-replacement-point = REVCLOUD Точка замены __ocs_arg_count__; завершите на другой вершине облака: + .revcloud-pick-side = REVCLOUD Выберите удаляемую сторону исходного облака: + .revcloud-path-cannot-form = Выбранный путь не может образовать облако изменений. + .revcloud-select-closed-revision-cloud = Выберите замкнутую полилинию облака изменений. + .revcloud-polyline-not-planar = Выбранная полилиния не является плоской. + .revcloud-corners-non-zero-area = Два угла должны задавать ненулевую площадь. + .revcloud-arc-length-positive = Длина дуги должна быть больше нуля. + .revcloud-enter-valid-arc-length = Введите допустимую длину дуги. + .revcloud-select-supported-closed-curve = Выберите поддерживаемую замкнутую кривую. + .revcloud-select-closed-entity = Выберите замкнутую окружность, эллипс, полилинию или сплайн. + .calligraphy = Каллиграфия + .first-point = Первая точка + .freehand = От руки + .rectangular = Прямоугольный .rectangle-center = Прямоугольник - центр .rectangle-rotated = Прямоугольник - повернутый .rectangle-two-corners = Прямоугольник - два угла diff --git a/locales/tr-TR/opencadstudio.ftl b/locales/tr-TR/opencadstudio.ftl index b8d18e9c2..8ca5741cb 100644 --- a/locales/tr-TR/opencadstudio.ftl +++ b/locales/tr-TR/opencadstudio.ftl @@ -1320,6 +1320,31 @@ draw = .region-select-closed-polylines-or-circles = REGION: Kapalı çoklu çizgileri veya çemberleri seçin. .revcloud-specify-next-point-count-pts-enter-to = REVCLOUD Sonraki noktayı belirtin (__ocs_arg_count__ nokta, kapatmak için Enter): .revcloud-specify-start-point-arc-length-arc-le = REVCLOUD Başlangıç noktasını belirtin (yay uzunluğu = __ocs_arg_arc_length__): + .revcloud-first-corner = REVCLOUD İlk köşe (__ocs_arg_style__, yay uzunluğu __ocs_arg_length__): __ocs_arg_message__ + .revcloud-opposite-corner = REVCLOUD Karşı köşe: __ocs_arg_message__ + .revcloud-polygonal-start-point = REVCLOUD Çokgensel başlangıç noktası (__ocs_arg_style__, yay uzunluğu __ocs_arg_length__): __ocs_arg_message__ + .revcloud-next-polygonal-point = REVCLOUD Sonraki çokgensel nokta (__ocs_arg_count__ nokta, kapatmak için Enter): __ocs_arg_message__ + .revcloud-freehand-first-point = REVCLOUD Serbest çizim ilk noktası (__ocs_arg_style__, yay uzunluğu __ocs_arg_length__): __ocs_arg_message__ + .revcloud-guide-cursor = REVCLOUD İmleci yönlendirin, ardından kapatmak için tıklayın veya Enter'a basın: __ocs_arg_message__ + .revcloud-approximate-arc-chord-length = REVCLOUD Yaklaşık yay kirişi uzunluğu <__ocs_arg_length__>: + .revcloud-style = REVCLOUD Stil [Normal/Kaligrafi] <__ocs_arg_style__>: + .revcloud-select-closed-curve = REVCLOUD Kapalı bir çember, elips, çoklu çizgi veya eğri seçin: __ocs_arg_message__ + .revcloud-reverse-arc-direction = REVCLOUD Yay yönü ters çevrilsin mi [Evet/Hayır] : + .revcloud-select-revision-cloud = REVCLOUD Değiştirmenin başlangıcına yakın kapalı bir revizyon bulutu çoklu çizgisi seçin: __ocs_arg_message__ + .revcloud-replacement-point = REVCLOUD Değiştirme noktası __ocs_arg_count__; başka bir bulut köşesinde bitirin: + .revcloud-pick-side = REVCLOUD Özgün bulutun silinecek tarafını seçin: + .revcloud-path-cannot-form = Seçilen yoldan revizyon bulutu oluşturulamıyor. + .revcloud-select-closed-revision-cloud = Kapalı bir revizyon bulutu çoklu çizgisi seçin. + .revcloud-polyline-not-planar = Seçilen çoklu çizgi düzlemsel değil. + .revcloud-corners-non-zero-area = İki köşe sıfırdan farklı bir alan tanımlamalıdır. + .revcloud-arc-length-positive = Yay uzunluğu sıfırdan büyük olmalıdır. + .revcloud-enter-valid-arc-length = Geçerli bir yay uzunluğu girin. + .revcloud-select-supported-closed-curve = Desteklenen kapalı bir eğri seçin. + .revcloud-select-closed-entity = Kapalı bir çember, elips, çoklu çizgi veya eğri seçin. + .calligraphy = Kaligrafi + .first-point = İlk nokta + .freehand = Serbest çizim + .rectangular = Dikdörtgensel .rectangle-center = Dikdörtgen - Merkez .rectangle-rotated = Dikdörtgen - Döndürülmüş .rectangle-two-corners = Dikdörtgen - İki Köşe diff --git a/locales/zh-CN/opencadstudio.ftl b/locales/zh-CN/opencadstudio.ftl index 98376aa18..c464f3b5b 100644 --- a/locales/zh-CN/opencadstudio.ftl +++ b/locales/zh-CN/opencadstudio.ftl @@ -1325,6 +1325,31 @@ draw = .region-select-closed-polylines-or-circles = REGION:选择封闭多行或圆圈。 .revcloud-specify-next-point-count-pts-enter-to = REVCLOUD 指定下一点(__ocs_arg_count__ 个点,按 Enter 闭合): .revcloud-specify-start-point-arc-length-arc-le = REVCLOUD 指定起点(弧长 = __ocs_arg_arc_length__): + .revcloud-first-corner = REVCLOUD 第一个角点(__ocs_arg_style__,弧长 __ocs_arg_length__):__ocs_arg_message__ + .revcloud-opposite-corner = REVCLOUD 对角点:__ocs_arg_message__ + .revcloud-polygonal-start-point = REVCLOUD 多边形起点(__ocs_arg_style__,弧长 __ocs_arg_length__):__ocs_arg_message__ + .revcloud-next-polygonal-point = REVCLOUD 下一个多边形点(__ocs_arg_count__ 个点,按 Enter 闭合):__ocs_arg_message__ + .revcloud-freehand-first-point = REVCLOUD 徒手绘制第一个点(__ocs_arg_style__,弧长 __ocs_arg_length__):__ocs_arg_message__ + .revcloud-guide-cursor = REVCLOUD 移动光标,然后单击或按 Enter 闭合:__ocs_arg_message__ + .revcloud-approximate-arc-chord-length = REVCLOUD 近似弧弦长度 <__ocs_arg_length__>: + .revcloud-style = REVCLOUD 样式 [普通/书法] <__ocs_arg_style__>: + .revcloud-select-closed-curve = REVCLOUD 选择闭合的圆、椭圆、多段线或样条曲线:__ocs_arg_message__ + .revcloud-reverse-arc-direction = REVCLOUD 反转圆弧方向 [是/否] <否>: + .revcloud-select-revision-cloud = REVCLOUD 在替换起点附近选择闭合的修订云线多段线:__ocs_arg_message__ + .revcloud-replacement-point = REVCLOUD 替换点 __ocs_arg_count__;在另一个云线顶点结束: + .revcloud-pick-side = REVCLOUD 选择原云线中要删除的一侧: + .revcloud-path-cannot-form = 所选路径无法形成修订云线。 + .revcloud-select-closed-revision-cloud = 选择闭合的修订云线多段线。 + .revcloud-polyline-not-planar = 所选多段线不在同一平面上。 + .revcloud-corners-non-zero-area = 两个角点必须定义非零面积。 + .revcloud-arc-length-positive = 弧长必须大于零。 + .revcloud-enter-valid-arc-length = 输入有效的弧长。 + .revcloud-select-supported-closed-curve = 选择受支持的闭合曲线。 + .revcloud-select-closed-entity = 选择闭合的圆、椭圆、多段线或样条曲线。 + .calligraphy = 书法 + .first-point = 第一个点 + .freehand = 徒手绘制 + .rectangular = 矩形 .rectangle-center = 矩形 - 中心 .rectangle-rotated = 矩形 - 旋转 .rectangle-two-corners = 矩形 - 两点 diff --git a/locales/zh-TW/opencadstudio.ftl b/locales/zh-TW/opencadstudio.ftl index e697015c7..9a252fedb 100644 --- a/locales/zh-TW/opencadstudio.ftl +++ b/locales/zh-TW/opencadstudio.ftl @@ -1337,6 +1337,31 @@ draw = .region-select-closed-polylines-or-circles = REGION:選擇封閉多行或圓圈。 .revcloud-specify-next-point-count-pts-enter-to = REVCLOUD 指定下一點(__ocs_arg_count__ 個點,按 Enter 閉合): .revcloud-specify-start-point-arc-length-arc-le = REVCLOUD 指定起點(弧長 = __ocs_arg_arc_length__): + .revcloud-first-corner = REVCLOUD 第一個角點(__ocs_arg_style__,弧長 __ocs_arg_length__):__ocs_arg_message__ + .revcloud-opposite-corner = REVCLOUD 對角點:__ocs_arg_message__ + .revcloud-polygonal-start-point = REVCLOUD 多邊形起點(__ocs_arg_style__,弧長 __ocs_arg_length__):__ocs_arg_message__ + .revcloud-next-polygonal-point = REVCLOUD 下一個多邊形點(__ocs_arg_count__ 個點,按 Enter 閉合):__ocs_arg_message__ + .revcloud-freehand-first-point = REVCLOUD 徒手繪製第一個點(__ocs_arg_style__,弧長 __ocs_arg_length__):__ocs_arg_message__ + .revcloud-guide-cursor = REVCLOUD 移動游標,然後按一下或按 Enter 閉合:__ocs_arg_message__ + .revcloud-approximate-arc-chord-length = REVCLOUD 近似弧弦長度 <__ocs_arg_length__>: + .revcloud-style = REVCLOUD 樣式 [一般/書法] <__ocs_arg_style__>: + .revcloud-select-closed-curve = REVCLOUD 選取閉合的圓、橢圓、多段線或雲形線:__ocs_arg_message__ + .revcloud-reverse-arc-direction = REVCLOUD 反轉圓弧方向 [是/否] <否>: + .revcloud-select-revision-cloud = REVCLOUD 在取代起點附近選取閉合的修訂雲線多段線:__ocs_arg_message__ + .revcloud-replacement-point = REVCLOUD 取代點 __ocs_arg_count__;在另一個雲線頂點結束: + .revcloud-pick-side = REVCLOUD 選取原雲線中要刪除的一側: + .revcloud-path-cannot-form = 所選路徑無法形成修訂雲線。 + .revcloud-select-closed-revision-cloud = 選取閉合的修訂雲線多段線。 + .revcloud-polyline-not-planar = 所選多段線不在同一平面上。 + .revcloud-corners-non-zero-area = 兩個角點必須定義非零面積。 + .revcloud-arc-length-positive = 弧長必須大於零。 + .revcloud-enter-valid-arc-length = 輸入有效的弧長。 + .revcloud-select-supported-closed-curve = 選取支援的閉合曲線。 + .revcloud-select-closed-entity = 選取閉合的圓、橢圓、多段線或雲形線。 + .calligraphy = 書法 + .first-point = 第一個點 + .freehand = 徒手繪製 + .rectangular = 矩形 .rectangle-center = 矩形 - 中心 .rectangle-rotated = 矩形 - 旋轉 .rectangle-two-corners = 矩形 - 兩點 diff --git a/src/app/commands/draw.rs b/src/app/commands/draw.rs index 245dd26e0..2933bd22a 100644 --- a/src/app/commands/draw.rs +++ b/src/app/commands/draw.rs @@ -60,7 +60,15 @@ impl OpenCADStudio { "REVCLOUD" => { use crate::modules::draw::draw::revcloud::RevCloudCommand; - let cmd = RevCloudCommand::new(); + let view_height = self.tabs[i].scene.camera.borrow().ortho_size() as f64 * 2.0; + let default_arc_length = (view_height * 0.0125).max(1.0e-6); + let sources = self.tabs[i] + .scene + .document + .entities() + .map(|entity| (entity.common().handle, entity.clone())) + .collect(); + let cmd = RevCloudCommand::new(default_arc_length, sources); self.command_line.push_info(&cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(cmd)); } diff --git a/src/entities/lwpolyline.rs b/src/entities/lwpolyline.rs index 452ae0975..bcea38533 100644 --- a/src/entities/lwpolyline.rs +++ b/src/entities/lwpolyline.rs @@ -1,4 +1,6 @@ use acadrust::entities::{LwPolyline, LwVertex}; +use cadkernel::geom2d::{signed_area, Curve, Polyline, PolylineVertex, Vec2}; + use crate::t; use crate::command::EntityTransform; @@ -12,6 +14,8 @@ use crate::scene::model::object::{GripApply, GripDef, PropSection, PropValue, Pr use crate::scene::model::wire_model::TangentGeom; const TAU: f64 = std::f64::consts::TAU; +const REVCLOUD_BULGE: f64 = 0.5; +const MAX_REVCLOUD_VERTICES: usize = 100_000; /// Midpoint position on an arc segment defined by its bulge. fn arc_midpoint(p0: [f64; 2], p1: [f64; 2], bulge: f64) -> [f64; 2] { @@ -597,6 +601,219 @@ fn grips(pline: &LwPolyline) -> Vec { out } +pub(crate) fn is_revision_cloud(pline: &LwPolyline) -> bool { + if !pline.is_closed || pline.vertices.len() < 3 { + return false; + } + let mut sign = 0.0_f64; + let mut magnitudes = Vec::with_capacity(pline.vertices.len()); + for vertex in &pline.vertices { + let bulge = vertex.bulge; + if !bulge.is_finite() || bulge.abs() < 1.0e-9 { + return false; + } + if sign == 0.0 { + sign = bulge.signum(); + } else if bulge.signum() != sign { + return false; + } + magnitudes.push(bulge.abs()); + } + magnitudes.sort_by(f64::total_cmp); + let median = magnitudes[magnitudes.len() / 2]; + (0.35..=0.65).contains(&median) + && magnitudes + .iter() + .filter(|value| (**value - median).abs() <= 0.15) + .count() + * 5 + >= magnitudes.len() * 4 +} + +fn straight_guide(pline: &LwPolyline) -> Curve { + Curve::Polyline(Polyline { + vertices: pline + .vertices + .iter() + .map(|vertex| PolylineVertex::straight([vertex.location.x, vertex.location.y])) + .collect(), + closed: true, + }) +} + +fn revision_cloud_arc_length(pline: &LwPolyline) -> Option { + if !is_revision_cloud(pline) { + return None; + } + let total = straight_guide(pline).length(); + (total.is_finite() && total > 0.0) + .then_some(total / pline.vertices.len() as f64) +} + +fn cloud_anchors(curve: &Curve) -> Vec { + let segments = curve.segment_count(); + let mut anchors = vec![0.0]; + if segments <= 1 { + return anchors; + } + let delta = 1.0e-6 / segments as f64; + let corner_cosine = 30.0_f64.to_radians().cos(); + for index in 1..segments { + let t = index as f64 / segments as f64; + let incoming = Vec2::from(curve.tangent_at(t - delta)).normalize(); + let outgoing = Vec2::from(curve.tangent_at(t + delta)).normalize(); + if incoming + .zip(outgoing) + .is_some_and(|(left, right)| left.dot(right) < corner_cosine) + { + anchors.push(curve.length_to(t)); + } + } + anchors +} + +fn cloud_points(curve: &Curve, requested: f64) -> Option> { + if !curve.is_closed() || !requested.is_finite() || requested <= 0.0 { + return None; + } + let total = curve.length(); + if !total.is_finite() || total <= 0.0 { + return None; + } + let anchors = cloud_anchors(curve); + let spans: Vec<(f64, f64)> = anchors + .iter() + .enumerate() + .map(|(index, start)| { + let end = anchors.get(index + 1).copied().unwrap_or(total); + (*start, end) + }) + .collect(); + let mut counts = Vec::with_capacity(spans.len()); + let mut count_total = 0_usize; + for (start, end) in &spans { + let count = ((*end - *start) / requested).round().max(1.0); + if !count.is_finite() || count > MAX_REVCLOUD_VERTICES as f64 { + return None; + } + count_total = count_total.checked_add(count as usize)?; + if count_total > MAX_REVCLOUD_VERTICES { + return None; + } + counts.push(count as usize); + } + if count_total < 3 { + let longest = spans + .iter() + .enumerate() + .max_by(|(_, left), (_, right)| { + (left.1 - left.0).total_cmp(&(right.1 - right.0)) + })? + .0; + counts[longest] += 3 - count_total; + } + let mut points = Vec::new(); + for ((start, end), count) in spans.into_iter().zip(counts) { + for step in 0..count { + points.push(curve.point_at_distance( + start + (end - start) * step as f64 / count as f64, + )); + } + } + let tolerance = requested.max(total) * 1.0e-12; + points.dedup_by(|right, left| Vec2::from(*left).distance((*right).into()) <= tolerance); + if points.len() >= 2 + && Vec2::from(points[0]).distance((*points.last()?).into()) <= tolerance + { + points.pop(); + } + (points.len() >= 3).then_some(points) +} + +fn cloud_vertices( + points: &[[f64; 2]], + bulge: f64, + width_ratios: Option<(f64, f64)>, +) -> Vec { + points + .iter() + .enumerate() + .map(|(index, point)| { + let next = points[(index + 1) % points.len()]; + let chord = Vec2::from(*point).distance(next.into()); + let mut vertex = LwVertex::from_coords(point[0], point[1]); + vertex.bulge = bulge; + if let Some((start, end)) = width_ratios { + vertex.start_width = start * chord; + vertex.end_width = end * chord; + } + vertex + }) + .collect() +} + +pub(crate) fn revision_cloud_from_curve( + curve: &Curve, + requested: f64, + reverse: bool, + width_ratios: Option<(f64, f64)>, +) -> Option { + let points = cloud_points(curve, requested)?; + let area = signed_area(&points); + if !area.is_finite() || area.abs() <= f64::EPSILON { + return None; + } + let sign = if area > 0.0 { 1.0 } else { -1.0 }; + let bulge = REVCLOUD_BULGE * sign * if reverse { -1.0 } else { 1.0 }; + let mut cloud = LwPolyline::new(); + cloud.is_closed = true; + cloud.vertices = cloud_vertices(&points, bulge, width_ratios); + Some(cloud) +} + +fn median(mut values: Vec) -> Option { + values.sort_by(f64::total_cmp); + values.get(values.len() / 2).copied() +} + +fn set_revision_cloud_arc_length(pline: &mut LwPolyline, requested: f64) { + if !is_revision_cloud(pline) || !requested.is_finite() || requested <= 0.0 { + return; + } + let guide = straight_guide(pline); + let points = match cloud_points(&guide, requested) { + Some(points) => points, + None => return, + }; + let sign = pline.vertices[0].bulge.signum(); + let magnitude = match median( + pline + .vertices + .iter() + .map(|vertex| vertex.bulge.abs()) + .collect(), + ) { + Some(value) => value, + None => return, + }; + let mut starts = Vec::new(); + let mut ends = Vec::new(); + for (index, vertex) in pline.vertices.iter().enumerate() { + let next = &pline.vertices[(index + 1) % pline.vertices.len()]; + let chord = Vec2::new(vertex.location.x, vertex.location.y) + .distance(Vec2::new(next.location.x, next.location.y)); + if chord > 0.0 && (vertex.start_width > 0.0 || vertex.end_width > 0.0) { + starts.push(vertex.start_width / chord); + ends.push(vertex.end_width / chord); + } + } + let width_ratios = match (median(starts), median(ends)) { + (Some(start), Some(end)) => Some((start, end)), + _ => None, + }; + pline.vertices = cloud_vertices(&points, magnitude * sign, width_ratios); +} + fn properties(pline: &LwPolyline) -> Vec { let n = pline.vertices.len(); // The panel's Current Vertex focus, clamped to this polyline's range. @@ -611,11 +828,39 @@ fn properties(pline: &LwPolyline) -> Vec { let start_w = v.map_or(0.0, |v| v.start_width); let end_w = v.map_or(0.0, |v| v.end_width); let mp = ::mass_props(pline); + 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) }; + let mut misc_props = vec![ + Property { + label: t!("Closed").into_owned(), + field: "closed", + value: PropValue::BoolToggle { + field: "closed", + value: pline.is_closed, + }, + }, + Property { + label: t!("Linetype generation").into_owned(), + field: "plinegen", + value: PropValue::BoolToggle { + field: "plinegen", + value: pline.plinegen, + }, + }, + ]; + if let Some(arc_length) = cloud_arc_length { + misc_props.push(edit( + t!("Arc length").as_ref(), + "revcloud_arc_length", + arc_length, + )); + } vec![ PropSection { title: t!("Geometry").into_owned(), @@ -633,30 +878,19 @@ fn properties(pline: &LwPolyline) -> Vec { }, PropSection { title: t!("Misc").into_owned(), - props: vec![ - Property { - label: t!("Closed").into_owned(), - field: "closed", - value: PropValue::BoolToggle { - field: "closed", - value: pline.is_closed, - }, - }, - Property { - label: t!("Linetype generation").into_owned(), - field: "plinegen", - value: PropValue::BoolToggle { - field: "plinegen", - value: pline.plinegen, - }, - }, - ], + props: misc_props, }, ] } fn apply_geom_prop(pline: &mut LwPolyline, field: &str, value: &str) { match field { + "revcloud_arc_length" => { + if let Some(value) = parse_f64(value) { + set_revision_cloud_arc_length(pline, value); + } + return; + } "closed" => { pline.is_closed = if value == "toggle" { !pline.is_closed diff --git a/src/entities/mline.rs b/src/entities/mline.rs index a1a2ea736..47b11c97a 100644 --- a/src/entities/mline.rs +++ b/src/entities/mline.rs @@ -1,12 +1,12 @@ use acadrust::entities::MLine; -use crate::t; use crate::command::EntityTransform; use crate::entities::common::{edit_prop as edit, square_grip}; -use crate::entities::traits::{Grippable, PropertyEditable, Transformable, RenderConvertible}; +use crate::entities::traits::{Grippable, PropertyEditable, RenderConvertible, Transformable}; use crate::scene::convert::acad_to_render::{RenderEntity, RenderObject}; use crate::scene::model::object::{GripApply, GripDef, PropSection, PropValue, Property}; use crate::scene::model::wire_model::SnapHint; +use crate::t; /// One drawn line of a multiline: the polyline for a single style element (or /// an end cap), tagged with the element's colour and linetype so the @@ -350,7 +350,7 @@ impl PropertyEditable for MLine { .collect(), }, }, - edit(t!("Style scale").as_ref(), "ml_scale", self.scale_factor), + edit(t!("Scale").as_ref(), "ml_scale", self.scale_factor), ], }] } @@ -384,13 +384,42 @@ impl PropertyEditable for MLine { let Ok(v) = value.trim().parse::() else { return; }; - match field { - "ml_scale" if v != 0.0 => self.scale_factor = v, - _ => {} + if field == "ml_scale" { + set_mline_scale(self, v); } } } +fn set_mline_scale(mline: &mut MLine, scale: f64) { + let old = mline.scale_factor; + if !scale.is_finite() || scale == 0.0 || !old.is_finite() || old == 0.0 { + return; + } + let ratio = scale / old; + if !ratio.is_finite() { + return; + } + if mline + .vertices + .iter() + .flat_map(|vertex| &vertex.segments) + .filter_map(|segment| segment.parameters.first()) + .any(|offset| !offset.is_finite() || !(offset * ratio).is_finite()) + { + return; + } + for segment in mline + .vertices + .iter_mut() + .flat_map(|vertex| vertex.segments.iter_mut()) + { + if let Some(offset) = segment.parameters.first_mut() { + *offset *= ratio; + } + } + mline.scale_factor = scale; +} + impl Transformable for MLine { fn apply_transform(&mut self, t: &EntityTransform) { crate::scene::view::transform::apply_standard_entity_transform(self, t, |entity, p1, p2| { diff --git a/src/entities/names.rs b/src/entities/names.rs index 8e1460c93..55deabf1a 100644 --- a/src/entities/names.rs +++ b/src/entities/names.rs @@ -19,6 +19,11 @@ pub fn ui_name(e: &EntityType) -> &'static str { EntityType::Ellipse(_) => "Ellipse", EntityType::Spline(_) => "Spline", EntityType::Helix(_) => "Helix", + EntityType::LwPolyline(polyline) + if crate::entities::lwpolyline::is_revision_cloud(polyline) => + { + "Revcloud" + } EntityType::LwPolyline(_) => "Polyline", EntityType::Polyline(_) => "Polyline", EntityType::Polyline2D(_) => "Polyline2D", diff --git a/src/entities/traits.rs b/src/entities/traits.rs index 723bec01a..5569e69dd 100644 --- a/src/entities/traits.rs +++ b/src/entities/traits.rs @@ -118,6 +118,11 @@ pub fn entity_type_name(et: &EntityType) -> &str { EntityType::Polyline(_) => "Polyline", EntityType::Polyline2D(_) => "Polyline2D", EntityType::Polyline3D(_) => "Polyline3D", + EntityType::LwPolyline(polyline) + if crate::entities::lwpolyline::is_revision_cloud(polyline) => + { + "Revcloud" + } EntityType::LwPolyline(_) => "LwPolyline", EntityType::Text(_) => "Text", EntityType::MText(_) => "MText", diff --git a/src/locale_catalog.rs b/src/locale_catalog.rs index fd5bf0ffc..eb22b4922 100644 --- a/src/locale_catalog.rs +++ b/src/locale_catalog.rs @@ -2070,6 +2070,19 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static "RENAME: unknown type '{}'. Use LAYER BLOCK STYLE DIMSTYLE LINETYPE UCS VIEW" => Some(("styles", "rename-unknown-type-use-layer-block-style-dims")), "REVCLOUD Specify next point (%{count} pts, Enter to close):" => Some(("draw", "revcloud-specify-next-point-count-pts-enter-to")), "REVCLOUD Specify start point (arc length = %{arc_length}):" => Some(("draw", "revcloud-specify-start-point-arc-length-arc-le")), + "REVCLOUD First corner (%{style}, arc length %{length}): %{message}" => Some(("draw", "revcloud-first-corner")), + "REVCLOUD Opposite corner: %{message}" => Some(("draw", "revcloud-opposite-corner")), + "REVCLOUD Polygonal start point (%{style}, arc length %{length}): %{message}" => Some(("draw", "revcloud-polygonal-start-point")), + "REVCLOUD Next polygonal point (%{count} points, Enter to close): %{message}" => Some(("draw", "revcloud-next-polygonal-point")), + "REVCLOUD Freehand first point (%{style}, arc length %{length}): %{message}" => Some(("draw", "revcloud-freehand-first-point")), + "REVCLOUD Guide the cursor, then click or press Enter to close: %{message}" => Some(("draw", "revcloud-guide-cursor")), + "REVCLOUD Approximate arc chord length <%{length}>:" => Some(("draw", "revcloud-approximate-arc-chord-length")), + "REVCLOUD Style [Normal/Calligraphy] <%{style}>:" => Some(("draw", "revcloud-style")), + "REVCLOUD Select a closed circle, ellipse, polyline, or spline: %{message}" => Some(("draw", "revcloud-select-closed-curve")), + "REVCLOUD Reverse arc direction [Yes/No] :" => Some(("draw", "revcloud-reverse-arc-direction")), + "REVCLOUD Select a closed revision-cloud polyline near the replacement start: %{message}" => Some(("draw", "revcloud-select-revision-cloud")), + "REVCLOUD Replacement point %{count}; finish on another cloud vertex:" => Some(("draw", "revcloud-replacement-point")), + "REVCLOUD Pick the side of the original cloud to erase:" => Some(("draw", "revcloud-pick-side")), "REVERSE Select line, polyline or spline to reverse:" => Some(("modify", "reverse-select-line-polyline-or-spline-to-reve")), "REVOLVE Angle of revolution <360>:" => Some(("insert", "revolve-angle-of-revolution-360")), "REVOLVE Axis end point:" => Some(("insert", "revolve-axis-end-point")), @@ -2141,6 +2154,18 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static "Restored properties" => Some(("layers", "restored-properties")), "Retry" => Some(("common", "retry")), "Rev Cloud" => Some(("draw", "rev-cloud")), + "Calligraphy" => Some(("draw", "calligraphy")), + "First point" => Some(("draw", "first-point")), + "Freehand" => Some(("draw", "freehand")), + "Rectangular" => Some(("draw", "rectangular")), + "The selected path cannot form a revision cloud." => Some(("draw", "revcloud-path-cannot-form")), + "Select a closed revision-cloud polyline." => Some(("draw", "revcloud-select-closed-revision-cloud")), + "The selected polyline is not planar." => Some(("draw", "revcloud-polyline-not-planar")), + "The two corners must define a non-zero area." => Some(("draw", "revcloud-corners-non-zero-area")), + "Arc length must be greater than zero." => Some(("draw", "revcloud-arc-length-positive")), + "Enter a valid arc length." => Some(("draw", "revcloud-enter-valid-arc-length")), + "Select a supported closed curve." => Some(("draw", "revcloud-select-supported-closed-curve")), + "Select a closed circle, ellipse, polyline, or spline." => Some(("draw", "revcloud-select-closed-entity")), "Reverse" => Some(("common", "reverse")), "Revision" => Some(("model", "revision")), "Revolve Entity" => Some(("model", "revolve-entity")), diff --git a/src/modules/draw/draw/revcloud.rs b/src/modules/draw/draw/revcloud.rs index da872b8af..8da3960c7 100644 --- a/src/modules/draw/draw/revcloud.rs +++ b/src/modules/draw/draw/revcloud.rs @@ -1,18 +1,20 @@ -// REVCLOUD command — draw a revision cloud (arc-bumped closed polyline). -// -// Workflow: pick polygon corners (like PLINE), press Enter to close. -// Each segment of the polygon is subdivided into arc bumps (bulge = 0.5). -// Minimum arc length = `arc_length` parameter. +// REVCLOUD command — create or modify arc-bumped lightweight polylines. -use acadrust::entities::LwPolyline; -use acadrust::types::Vector2; -use acadrust::{entities::LwVertex, EntityType}; +use std::sync::atomic::{AtomicU64, AtomicU8, Ordering}; + +use acadrust::types::Vector3; +use acadrust::{EntityType, Handle}; +use cadkernel::geom2d::{distance_to, Curve, Polyline, PolylineVertex, Vec2}; +use cadkernel::space::{PlanarCurve, Plane, Vec3}; use glam::DVec3; -use crate::t; +use rustc_hash::FxHashMap; -use crate::command::{CadCommand, CmdResult, WorkingPlane}; +use crate::command::{CadCommand, CmdOption, CmdResult, WorkingPlane}; +use crate::entities::curve::{curve_points, entity_curve, ocs_plane}; +use crate::entities::lwpolyline::{is_revision_cloud, revision_cloud_from_curve}; use crate::modules::{IconKind, ModuleEvent, ToolDef}; use crate::scene::model::wire_model::WireModel; +use crate::t; pub const ICON: IconKind = IconKind::Svg(include_bytes!("../../../../assets/icons/revcloud.svg")); @@ -25,22 +27,409 @@ pub fn tool() -> ToolDef { } } -const DEFAULT_ARC_LEN: f64 = 1.0; // default arc bump length +const DEFAULT_ARC_LENGTH: f64 = 1.0; + +static LAST_ARC_LENGTH: AtomicU64 = AtomicU64::new(0); +static LAST_CREATION: AtomicU8 = AtomicU8::new(1); +static LAST_STYLE: AtomicU8 = AtomicU8::new(0); + +#[derive(Clone, Copy, PartialEq, Eq)] +enum CreationMode { + Freehand, + Rectangular, + Polygonal, +} + +impl CreationMode { + fn remembered() -> Self { + match LAST_CREATION.load(Ordering::Relaxed) { + 0 => Self::Freehand, + 2 => Self::Polygonal, + _ => Self::Rectangular, + } + } + + fn remember(self) { + let value = match self { + Self::Freehand => 0, + Self::Rectangular => 1, + Self::Polygonal => 2, + }; + LAST_CREATION.store(value, Ordering::Relaxed); + } +} + +#[derive(Clone, Copy, PartialEq, Eq)] +enum CloudStyle { + Normal, + Calligraphy, +} + +impl CloudStyle { + fn remembered() -> Self { + if LAST_STYLE.load(Ordering::Relaxed) == 1 { + Self::Calligraphy + } else { + Self::Normal + } + } + + fn remember(self) { + LAST_STYLE.store((self == Self::Calligraphy) as u8, Ordering::Relaxed); + } + + fn label(self) -> &'static str { + match self { + Self::Normal => "Normal", + Self::Calligraphy => "Calligraphy", + } + } +} + +struct PendingCloud { + entity: EntityType, + replacement: Option, +} + +struct ModifyState { + handle: Handle, + source: EntityType, + plane: Plane, + segments: Vec, + vertices: Vec, + start: usize, + end: Option, + replacement: Vec, +} + +enum Stage { + Create, + ArcLength, + Style, + Object, + Reverse(PendingCloud), + ModifySelect, + ModifyDraw(ModifyState), + ModifyErase(ModifyState), +} pub struct RevCloudCommand { points: Vec, arc_length: f64, + creation: CreationMode, + style: CloudStyle, + stage: Stage, + tracing: bool, + sources: FxHashMap, plane: WorkingPlane, + message: Option<&'static str>, } impl RevCloudCommand { - pub fn new() -> Self { + pub fn new(default_arc_length: f64, sources: FxHashMap) -> Self { + let remembered = f64::from_bits(LAST_ARC_LENGTH.load(Ordering::Relaxed)); + let arc_length = if remembered.is_finite() && remembered > 0.0 { + remembered + } else if default_arc_length.is_finite() && default_arc_length > 0.0 { + default_arc_length + } else { + DEFAULT_ARC_LENGTH + }; + LAST_ARC_LENGTH.store(arc_length.to_bits(), Ordering::Relaxed); Self { - points: vec![], - arc_length: DEFAULT_ARC_LEN, + points: Vec::new(), + arc_length, + creation: CreationMode::remembered(), + style: CloudStyle::remembered(), + stage: Stage::Create, + tracing: false, + sources, plane: WorkingPlane::default(), + message: None, } } + + fn set_creation(&mut self, mode: CreationMode) { + self.creation = mode; + self.creation.remember(); + self.stage = Stage::Create; + self.points.clear(); + self.tracing = false; + self.message = None; + } + + fn local_points(&self, points: &[DVec3]) -> Vec { + points + .iter() + .map(|point| self.plane.to_local(*point)) + .collect() + } + + fn guide_curve(&self, points: &[DVec3]) -> Curve { + let vertices = self + .local_points(points) + .into_iter() + .map(|point| PolylineVertex::straight([point.x, point.y])) + .collect(); + Curve::Polyline(Polyline { + vertices, + closed: true, + }) + } + + fn cloud_from_world_points(&self, points: &[DVec3], reverse: bool) -> Option { + let curve = self.guide_curve(points); + let cloud = revision_cloud_from_curve( + &curve, + self.arc_length, + reverse, + style_width_ratios(self.style), + )?; + Some(self.plane.place_entity(EntityType::LwPolyline(cloud))) + } + + fn prepare_cloud(&mut self, points: &[DVec3], replacement: Option) -> CmdResult { + let Some(mut entity) = self.cloud_from_world_points(points, false) else { + self.message = Some("The selected path cannot form a revision cloud."); + return CmdResult::NeedPoint; + }; + if let Some(handle) = replacement { + if let Some(source) = self.sources.get(&handle) { + *entity.common_mut() = source.common().clone(); + entity.common_mut().handle = Handle::NULL; + } + } + self.stage = Stage::Reverse(PendingCloud { + entity, + replacement, + }); + self.message = None; + CmdResult::NeedPoint + } + + fn prepare_planar_cloud( + &mut self, + curve: &PlanarCurve, + replacement: Handle, + ) -> CmdResult { + let Some(mut entity) = + cloud_entity_on_plane(curve, self.arc_length, self.style, false) + else { + self.message = Some("The selected path cannot form a revision cloud."); + return CmdResult::NeedPoint; + }; + if let Some(source) = self.sources.get(&replacement) { + *entity.common_mut() = source.common().clone(); + entity.common_mut().handle = Handle::NULL; + } + self.stage = Stage::Reverse(PendingCloud { + entity, + replacement: Some(replacement), + }); + self.message = None; + CmdResult::NeedPoint + } + + fn finish_pending(pending: PendingCloud, reverse: bool) -> CmdResult { + let entity = if reverse { + reverse_cloud_entity(pending.entity) + } else { + pending.entity + }; + if let Some(handle) = pending.replacement { + CmdResult::ReplaceEntity(handle, vec![entity]) + } else { + CmdResult::CommitAndExit(entity) + } + } + + fn object_curve(entity: &EntityType) -> Option { + let closed = match entity { + EntityType::Circle(_) => true, + EntityType::Ellipse(ellipse) => ellipse.is_full(), + EntityType::LwPolyline(polyline) => polyline.is_closed, + EntityType::Polyline2D(polyline) => polyline.is_closed(), + EntityType::Spline(spline) => spline.flags.closed || spline.flags.periodic, + _ => false, + }; + if !closed { + return None; + } + let curve = entity_curve(entity)?; + curve.is_closed().then_some(curve) + } + + fn rectangle_points(&self, first: DVec3, opposite: DVec3) -> Option> { + let first = self.plane.to_local(first); + let opposite = self.plane.to_local(opposite); + if (first.x - opposite.x).abs() <= f64::EPSILON + || (first.y - opposite.y).abs() <= f64::EPSILON + { + return None; + } + Some( + [ + DVec3::new(first.x, first.y, first.z), + DVec3::new(opposite.x, first.y, first.z), + DVec3::new(opposite.x, opposite.y, first.z), + DVec3::new(first.x, opposite.y, first.z), + ] + .into_iter() + .map(|point| self.plane.to_world(point)) + .collect(), + ) + } + + fn preview(entity: &EntityType, name: &str) -> Option { + let curve = entity_curve(entity)?; + let points = curve_points(&curve); + (points.len() >= 2).then(|| { + WireModel::solid_f64(name.to_string(), points, WireModel::CYAN, false) + }) + } + + fn preview_world_points(&self, points: &[DVec3], name: &str) -> Option { + let entity = self.cloud_from_world_points(points, false)?; + Self::preview(&entity, name) + } + + fn start_modify(&mut self, handle: Handle, picked: DVec3) -> CmdResult { + let Some(source) = self.sources.get(&handle).cloned() else { + self.message = Some("Select a closed revision-cloud polyline."); + return CmdResult::NeedPoint; + }; + let EntityType::LwPolyline(polyline) = &source else { + self.message = Some("Select a closed revision-cloud polyline."); + return CmdResult::NeedPoint; + }; + if !is_revision_cloud(polyline) { + self.message = Some("Select a closed revision-cloud polyline."); + return CmdResult::NeedPoint; + } + let Some(curve) = entity_curve(&source) else { + self.message = Some("The selected polyline is not planar."); + return CmdResult::NeedPoint; + }; + let segments = curve.curve.segments(); + if segments.len() != polyline.vertices.len() { + self.message = Some("The selected polyline is not planar."); + return CmdResult::NeedPoint; + } + let vertices: Vec = polyline + .vertices + .iter() + .map(|vertex| { + DVec3::from_array(curve.plane.point_at([ + vertex.location.x, + vertex.location.y, + ])) + }) + .collect(); + let start = vertices + .iter() + .enumerate() + .min_by(|(_, left), (_, right)| { + left.distance_squared(picked) + .total_cmp(&right.distance_squared(picked)) + }) + .map(|(index, _)| index) + .unwrap_or(0); + self.stage = Stage::ModifyDraw(ModifyState { + handle, + source, + plane: curve.plane, + segments, + vertices: vertices.clone(), + start, + end: None, + replacement: vec![vertices[start]], + }); + self.message = None; + CmdResult::NeedPoint + } + + fn modify_endpoint(state: &ModifyState, point: DVec3, tolerance: f64) -> Option { + state + .vertices + .iter() + .enumerate() + .filter(|(index, _)| *index != state.start) + .filter_map(|(index, vertex)| { + let distance = vertex.distance(point); + (distance <= tolerance).then_some((index, distance)) + }) + .min_by(|left, right| left.1.total_cmp(&right.1)) + .map(|(index, _)| index) + } + + fn path_indices(count: usize, from: usize, to: usize) -> Vec { + let mut indices = vec![from]; + let mut current = from; + while current != to && indices.len() <= count { + current = (current + 1) % count; + indices.push(current); + } + indices + } + + fn path_distance(state: &ModifyState, indices: &[usize], point: DVec3) -> f64 { + let Some(point) = state.plane.project(point.to_array()) else { + return f64::INFINITY; + }; + indices + .windows(2) + .map(|pair| distance_to(&state.segments[pair[0]], point)) + .fold(f64::INFINITY, f64::min) + } + + fn modified_cloud(&self, state: ModifyState, erase_point: DVec3) -> Option { + let end = state.end?; + let count = state.vertices.len(); + let forward = Self::path_indices(count, state.start, end); + let backward = Self::path_indices(count, end, state.start); + let erase_forward = Self::path_distance(&state, &forward, erase_point) + <= Self::path_distance(&state, &backward, erase_point); + + let mut guide = Vec::new(); + if erase_forward { + guide.extend(backward.iter().map(|index| state.vertices[*index])); + guide.extend(state.replacement.iter().skip(1).copied()); + } else { + guide.extend(forward.iter().map(|index| state.vertices[*index])); + guide.extend(state.replacement.iter().rev().skip(1).copied()); + } + let style = style_from_entity(&state.source).unwrap_or(self.style); + let curve = guide_curve_on_plane( + &state.plane, + &guide, + self.arc_length * 1.0e-6, + )?; + let mut entity = cloud_entity_on_plane( + &PlanarCurve::new(state.plane, curve), + self.arc_length, + style, + false, + )?; + let common = state.source.common().clone(); + *entity.common_mut() = common; + entity.common_mut().handle = Handle::NULL; + Some(PendingCloud { + entity, + replacement: Some(state.handle), + }) + } + + fn common_options(&self) -> Vec { + vec![ + CmdOption::new("Arc length", "A"), + CmdOption::new("Object", "O"), + CmdOption::new("Rectangular", "R"), + CmdOption::new("Polygonal", "P"), + CmdOption::new("Freehand", "F"), + CmdOption::new("Style", "S"), + CmdOption::new("Modify", "M"), + ] + } } impl CadCommand for RevCloudCommand { @@ -53,123 +442,495 @@ impl CadCommand for RevCloudCommand { } fn prompt(&self) -> String { - if self.points.is_empty() { - t!( - "REVCLOUD Specify start point (arc length = %{arc_length}):", - arc_length = format!("{:.2}", self.arc_length) + let message = self + .message + .map(|message| t!(message).into_owned()) + .unwrap_or_default(); + let style = t!(self.style.label()).into_owned(); + match &self.stage { + Stage::Create => match self.creation { + CreationMode::Rectangular if self.points.is_empty() => t!( + "REVCLOUD First corner (%{style}, arc length %{length}): %{message}", + style = &style, + length = format!("{:.4}", self.arc_length), + message = message + ) + .into_owned(), + CreationMode::Rectangular => { + t!("REVCLOUD Opposite corner: %{message}", message = message).into_owned() + } + CreationMode::Polygonal if self.points.is_empty() => t!( + "REVCLOUD Polygonal start point (%{style}, arc length %{length}): %{message}", + style = &style, + length = format!("{:.4}", self.arc_length), + message = message + ) + .into_owned(), + CreationMode::Polygonal => t!( + "REVCLOUD Next polygonal point (%{count} points, Enter to close): %{message}", + count = self.points.len(), + message = message + ) + .into_owned(), + CreationMode::Freehand if !self.tracing => t!( + "REVCLOUD Freehand first point (%{style}, arc length %{length}): %{message}", + style = &style, + length = format!("{:.4}", self.arc_length), + message = message + ) + .into_owned(), + CreationMode::Freehand => t!( + "REVCLOUD Guide the cursor, then click or press Enter to close: %{message}", + message = message + ) + .into_owned(), + }, + Stage::ArcLength => t!( + "REVCLOUD Approximate arc chord length <%{length}>:", + length = format!("{:.4}", self.arc_length) ) - .into_owned() - } else { - t!( - "REVCLOUD Specify next point (%{count} pts, Enter to close):", - count = self.points.len() + .into_owned(), + Stage::Style => t!( + "REVCLOUD Style [Normal/Calligraphy] <%{style}>:", + style = &style ) - .into_owned() + .into_owned(), + Stage::Object => t!( + "REVCLOUD Select a closed circle, ellipse, polyline, or spline: %{message}", + message = message + ) + .into_owned(), + Stage::Reverse(_) => { + t!("REVCLOUD Reverse arc direction [Yes/No] :").into_owned() + } + Stage::ModifySelect => t!( + "REVCLOUD Select a closed revision-cloud polyline near the replacement start: %{message}", + message = message + ) + .into_owned(), + Stage::ModifyDraw(state) => t!( + "REVCLOUD Replacement point %{count}; finish on another cloud vertex:", + count = state.replacement.len() + ) + .into_owned(), + Stage::ModifyErase(_) => { + t!("REVCLOUD Pick the side of the original cloud to erase:").into_owned() + } } } - fn on_point(&mut self, pt: DVec3) -> CmdResult { - self.points.push(pt); - CmdResult::NeedPoint + fn options(&self) -> Vec { + match &self.stage { + Stage::Create if self.points.is_empty() => self.common_options(), + Stage::Create if self.creation == CreationMode::Polygonal => { + let mut options = vec![CmdOption::new("Undo", "U")]; + if self.points.len() >= 3 { + options.push(CmdOption::enter("Close")); + } + options + } + Stage::Create if self.creation == CreationMode::Freehand && self.tracing => { + vec![CmdOption::enter("Close")] + } + Stage::Create => Vec::new(), + Stage::ArcLength => Vec::new(), + Stage::Style => vec![ + CmdOption::new("Normal", "N"), + CmdOption::new("Calligraphy", "C"), + ], + Stage::Object | Stage::ModifySelect => vec![CmdOption::new("Back", "B")], + Stage::Reverse(_) => vec![ + CmdOption::new("Yes", "Y"), + CmdOption::new("No", "N"), + ], + Stage::ModifyDraw(state) => { + if state.replacement.len() > 1 { + vec![CmdOption::new("Undo", "U"), CmdOption::new("First point", "F")] + } else { + Vec::new() + } + } + Stage::ModifyErase(_) => Vec::new(), + } + } + + fn on_point(&mut self, point: DVec3) -> CmdResult { + self.message = None; + match &mut self.stage { + Stage::Create => match self.creation { + CreationMode::Rectangular => { + if self.points.is_empty() { + self.points.push(point); + CmdResult::NeedPoint + } else { + let first = self.points[0]; + let Some(points) = self.rectangle_points(first, point) else { + self.message = Some("The two corners must define a non-zero area."); + return CmdResult::NeedPoint; + }; + self.points.clear(); + self.prepare_cloud(&points, None) + } + } + CreationMode::Polygonal => { + self.points.push(point); + CmdResult::NeedPoint + } + CreationMode::Freehand => { + if !self.tracing { + self.points.clear(); + self.points.push(point); + self.tracing = true; + CmdResult::NeedPoint + } else if self.points.len() >= 3 { + self.tracing = false; + let points = self.points.clone(); + self.points.clear(); + self.prepare_cloud(&points, None) + } else { + CmdResult::NeedPoint + } + } + }, + Stage::ModifyDraw(state) => { + let tolerance = self.arc_length.max(1.0e-6) * 0.75; + if state.replacement.len() >= 2 { + if let Some(end) = Self::modify_endpoint(state, point, tolerance) { + state.end = Some(end); + state.replacement.push(state.vertices[end]); + let state = match std::mem::replace(&mut self.stage, Stage::Create) { + Stage::ModifyDraw(state) => state, + _ => unreachable!(), + }; + self.stage = Stage::ModifyErase(state); + return CmdResult::NeedPoint; + } + } + state.replacement.push(point); + CmdResult::NeedPoint + } + Stage::ModifyErase(_) => { + let state = match std::mem::replace(&mut self.stage, Stage::Create) { + Stage::ModifyErase(state) => state, + _ => unreachable!(), + }; + let Some(pending) = self.modified_cloud(state, point) else { + return CmdResult::Cancel; + }; + self.stage = Stage::Reverse(pending); + CmdResult::NeedPoint + } + _ => CmdResult::NeedPoint, + } } fn on_enter(&mut self) -> CmdResult { - if self.points.len() < 3 { - return CmdResult::Cancel; + match &self.stage { + Stage::Create if self.creation == CreationMode::Polygonal && self.points.len() >= 3 => { + let points = self.points.clone(); + self.points.clear(); + self.prepare_cloud(&points, None) + } + Stage::Create if self.creation == CreationMode::Freehand && self.points.len() >= 3 => { + self.tracing = false; + let points = self.points.clone(); + self.points.clear(); + self.prepare_cloud(&points, None) + } + Stage::ArcLength | Stage::Style => { + self.stage = Stage::Create; + CmdResult::NeedPoint + } + Stage::Reverse(_) => { + let stage = std::mem::replace(&mut self.stage, Stage::Create); + match stage { + Stage::Reverse(pending) => Self::finish_pending(pending, false), + _ => unreachable!(), + } + } + _ => CmdResult::Cancel, } - let local: Vec = self - .points - .iter() - .map(|point| self.plane.to_local(*point)) - .collect(); - let entity = make_revcloud(&local, self.arc_length); - CmdResult::CommitAndExit(self.plane.place_entity(entity)) } - fn on_mouse_move(&mut self, pt: DVec3) -> Option { - let pt = pt.as_vec3(); - if self.points.is_empty() { - return None; + fn on_escape(&mut self) -> CmdResult { + CmdResult::Cancel + } + + fn wants_text_input(&self) -> bool { + true + } + + fn point_step_accepts_keywords(&self) -> bool { + matches!(self.stage, Stage::Create | Stage::ModifyDraw(_)) + } + + fn on_text_input(&mut self, text: &str) -> Option { + let input = text.trim(); + if matches!(self.stage, Stage::ArcLength) { + if let Ok(value) = input.parse::() { + if value.is_finite() && value > 0.0 { + self.arc_length = value; + LAST_ARC_LENGTH.store(value.to_bits(), Ordering::Relaxed); + self.stage = Stage::Create; + self.message = None; + } else { + self.message = Some("Arc length must be greater than zero."); + } + } else { + self.message = Some("Enter a valid arc length."); + } + return Some(CmdResult::NeedPoint); } - let mut preview_pts: Vec<[f32; 3]> = self - .points - .iter() - .map(|p| [p.x as f32, p.y as f32, p.z as f32]) - .collect(); - preview_pts.push([pt.x, pt.y, pt.z]); - preview_pts.push([ - self.points[0].x as f32, - self.points[0].y as f32, - self.points[0].z as f32, - ]); - Some(WireModel { - taper_widths: Vec::new(), - world_width: 0.0, - depth_override: None, - fill_is_3d: false, - fill_is_2d_solid: false, - render_instance: None, - pick_tris: Vec::new(), - pick_tris_low: Vec::new(), - dash_from_start: false, - dash_align_end: None, - text_verts: Vec::new(), - name: "revcloud_preview".into(), - points: preview_pts, - points_low: Vec::new(), - color: WireModel::CYAN, - selected: false, - pattern_length: 0.0, - pattern: [0.0; 8], - line_weight_px: 1.0, - snap_pts: vec![], - tangent_geoms: vec![], - aci: 0, - key_vertices: vec![], - aabb: WireModel::UNBOUNDED_AABB, - plinegen: true, - fill_tris: vec![], - fill_tris_low: Vec::new(), + let keyword = input.to_ascii_uppercase(); + if matches!(self.stage, Stage::Style) { + self.style = match keyword.as_str() { + "N" | "NORMAL" => CloudStyle::Normal, + "C" | "CALLIGRAPHY" => CloudStyle::Calligraphy, + _ => return None, + }; + self.style.remember(); + self.stage = Stage::Create; + return Some(CmdResult::NeedPoint); + } + if matches!(keyword.as_str(), "F" | "FIRST") { + if let Stage::ModifyDraw(state) = &mut self.stage { + state.replacement.truncate(1); + return Some(CmdResult::NeedPoint); + } + } + match &self.stage { + Stage::Reverse(_) => { + let reverse = match keyword.as_str() { + "Y" | "YES" => true, + "N" | "NO" => false, + _ => return None, + }; + let stage = std::mem::replace(&mut self.stage, Stage::Create); + return Some(match stage { + Stage::Reverse(pending) => Self::finish_pending(pending, reverse), + _ => unreachable!(), + }); + } + Stage::Object | Stage::ModifySelect if keyword == "B" || keyword == "BACK" => { + self.stage = Stage::Create; + return Some(CmdResult::NeedPoint); + } + Stage::ModifyDraw(_) if keyword == "U" || keyword == "UNDO" => { + return self.on_undo_step(); + } + Stage::Create => match keyword.as_str() { + "A" | "ARC" | "ARCLENGTH" => self.stage = Stage::ArcLength, + "O" | "OBJECT" => { + self.stage = Stage::Object; + self.points.clear(); + } + "R" | "RECTANGULAR" | "RECTANGLE" => { + self.set_creation(CreationMode::Rectangular) + } + "P" | "POLYGONAL" | "POLYGON" => { + self.set_creation(CreationMode::Polygonal) + } + "F" | "FREEHAND" => self.set_creation(CreationMode::Freehand), + "S" | "STYLE" => self.stage = Stage::Style, + "M" | "MODIFY" => { + self.stage = Stage::ModifySelect; + self.points.clear(); + } + "U" | "UNDO" => return self.on_undo_step(), + _ => return None, + }, + _ => return None, + } + Some(CmdResult::NeedPoint) + } + + fn needs_entity_pick(&self) -> bool { + matches!(self.stage, Stage::Object | Stage::ModifySelect) + } + + fn entity_pick_highlights_hover(&self) -> bool { + self.needs_entity_pick() + } + + fn on_entity_pick(&mut self, handle: Handle, point: DVec3) -> CmdResult { + match self.stage { + Stage::Object => { + let Some(entity) = self.sources.get(&handle) else { + self.message = Some("Select a supported closed curve."); + return CmdResult::NeedPoint; + }; + let Some(curve) = Self::object_curve(entity) else { + self.message = Some("Select a closed circle, ellipse, polyline, or spline."); + return CmdResult::NeedPoint; + }; + self.prepare_planar_cloud(&curve, handle) + } + Stage::ModifySelect => self.start_modify(handle, point), + _ => CmdResult::Cancel, + } + } + + fn on_hover_entity(&mut self, handle: Handle, _point: DVec3) -> Vec { + if !matches!(self.stage, Stage::Object) { + return Vec::new(); + } + let Some(curve) = self + .sources + .get(&handle) + .and_then(Self::object_curve) + else { + return Vec::new(); + }; + cloud_entity_on_plane(&curve, self.arc_length, self.style, false) + .as_ref() + .and_then(|entity| Self::preview(entity, "revcloud_object_preview")) + .into_iter() + .collect() + } + + fn on_undo_step(&mut self) -> Option { + match &mut self.stage { + Stage::Create if !self.points.is_empty() => { + self.points.pop(); + if self.points.is_empty() { + self.tracing = false; + } + Some(CmdResult::NeedPoint) + } + Stage::ModifyDraw(state) if state.replacement.len() > 1 => { + state.replacement.pop(); + Some(CmdResult::NeedPoint) + } + _ => None, + } + } + + fn on_mouse_move(&mut self, point: DVec3) -> Option { + match &mut self.stage { + Stage::Create if self.creation == CreationMode::Freehand && self.tracing => { + let spacing = (self.arc_length * 0.25).max(1.0e-6); + if self.points.last().is_none_or(|last| last.distance(point) >= spacing) { + self.points.push(point); + } + let points = self.points.clone(); + self.preview_world_points(&points, "revcloud_freehand_preview") + } + Stage::Create if self.creation == CreationMode::Rectangular => { + let first = *self.points.first()?; + let points = self.rectangle_points(first, point)?; + self.preview_world_points(&points, "revcloud_rectangular_preview") + } + Stage::Create if self.creation == CreationMode::Polygonal && !self.points.is_empty() => { + let mut points = self.points.clone(); + points.push(point); + self.preview_world_points(&points, "revcloud_polygonal_preview") + } + Stage::Reverse(pending) => Self::preview(&pending.entity, "revcloud_reverse_preview"), + Stage::ModifyDraw(state) => { + let mut points = state.replacement.clone(); + points.push(point); + let curve = guide_curve_on_plane( + &state.plane, + &points, + self.arc_length * 1.0e-6, + )?; + let entity = cloud_entity_on_plane( + &PlanarCurve::new(state.plane, curve), + self.arc_length, + style_from_entity(&state.source).unwrap_or(self.style), + false, + )?; + Self::preview(&entity, "revcloud_modify_preview") + } + _ => None, + } + } +} + +fn style_width_ratios(style: CloudStyle) -> Option<(f64, f64)> { + (style == CloudStyle::Calligraphy).then_some((0.04, 0.16)) +} + +fn cloud_entity_on_plane( + source: &PlanarCurve, + arc_length: f64, + style: CloudStyle, + reverse: bool, +) -> Option { + let cloud = revision_cloud_from_curve( + &source.curve, + arc_length, + reverse, + style_width_ratios(style), + )?; + let normal = Vec3::from(source.plane.normal()?); + let elevation = Vec3::from(source.plane.origin).dot(normal); + let normal = Vector3::new(normal.x, normal.y, normal.z); + let storage_plane = ocs_plane(normal, elevation); + let mut cloud = cloud; + for vertex in &mut cloud.vertices { + let world = source + .plane + .point_at([vertex.location.x, vertex.location.y]); + let local = storage_plane.project(world)?; + vertex.location.x = local[0]; + vertex.location.y = local[1]; + } + cloud.normal = normal; + cloud.elevation = elevation; + Some(EntityType::LwPolyline(cloud)) +} + +fn guide_curve_on_plane( + plane: &Plane, + points: &[DVec3], + tolerance: f64, +) -> Option { + let mut points: Vec<[f64; 2]> = points + .iter() + .map(|point| plane.project(point.to_array())) + .collect::>()?; + points.dedup_by(|right, left| { + Vec2::from(*left).distance(Vec2::from(*right)) <= tolerance + }); + if points.len() >= 2 + && Vec2::from(points[0]).distance(Vec2::from(*points.last()?)) <= tolerance + { + points.pop(); + } + (points.len() >= 3).then(|| { + Curve::Polyline(Polyline { + vertices: points + .into_iter() + .map(PolylineVertex::straight) + .collect(), + closed: true, }) - } + }) } -fn make_revcloud(pts: &[DVec3], arc_len: f64) -> EntityType { - let n = pts.len(); - let mut vertices: Vec = Vec::new(); - - // For each edge, subdivide into arc bumps (bulge ≈ 0.5) - let bump_bulge = 0.5f64; // tan(included_angle/4) ≈ 0.5 → ~53° arc - - for i in 0..n { - let p0 = pts[i]; - let p1 = pts[(i + 1) % n]; - let seg_len = ((p1.x - p0.x).powi(2) + (p1.y - p0.y).powi(2)).sqrt(); - if seg_len < 1e-6 { - continue; - } - - // How many full arcs fit? - let num_arcs = ((seg_len / arc_len).round() as usize).max(1); - let step_x = (p1.x - p0.x) / num_arcs as f64; - let step_y = (p1.y - p0.y) / num_arcs as f64; - - for j in 0..num_arcs { - let x = p0.x + step_x * j as f64; - let y = p0.y + step_y * j as f64; // DXF Y - let mut v = LwVertex::new(Vector2::new(x, y)); - v.bulge = bump_bulge; - vertices.push(v); +fn reverse_cloud_entity(mut entity: EntityType) -> EntityType { + if let EntityType::LwPolyline(polyline) = &mut entity { + for vertex in &mut polyline.vertices { + vertex.bulge = -vertex.bulge; } } - - let mut p = LwPolyline::new(); - p.is_closed = true; - p.elevation = pts.first().map_or(0.0, |point| point.z); - p.vertices = vertices; - EntityType::LwPolyline(p) + entity } +fn style_from_entity(entity: &EntityType) -> Option { + let EntityType::LwPolyline(polyline) = entity else { + return None; + }; + Some(if polyline + .vertices + .iter() + .any(|vertex| vertex.start_width > 0.0 || vertex.end_width > 0.0) + { + CloudStyle::Calligraphy + } else { + CloudStyle::Normal + }) +} -// ── Autocomplete registry ───────────────────────────────── -inventory::submit!(crate::command::CommandRegistration { names: &["REVCLOUD"] }); // RevCloudCommand +inventory::submit!(crate::command::CommandRegistration { names: &["REVCLOUD"] });