diff --git a/Cargo.lock b/Cargo.lock index e899b9ed..874944ff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -72,7 +72,7 @@ checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618" [[package]] name = "acadrust" version = "0.4.1" -source = "git+https://github.com/HakanSeven12/cadcodec.git?rev=cf46d04#cf46d047211d1adcb37a079812bc38baa45c98f6" +source = "git+https://github.com/HakanSeven12/cadcodec.git?rev=0908da7#0908da7b6e4f702a6c78359a57f53e2b79cf39eb" dependencies = [ "ahash 0.8.12", "anyhow", @@ -878,7 +878,7 @@ checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" [[package]] name = "cadkernel" version = "0.1.0" -source = "git+https://github.com/HakanSeven12/cadkernel.git?rev=b3f3122#b3f3122176e3f3860dd3bbc23f12c92b56f06095" +source = "git+https://github.com/HakanSeven12/cadkernel.git?rev=ebeb2ec#ebeb2ecc2d17d92c588b0fa8bbe3864b156bc71f" dependencies = [ "acadrust", "cavalier_contours", diff --git a/Cargo.toml b/Cargo.toml index 25afbec4..99c2c2e5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,8 +27,8 @@ glam = { version = "0.33", features = ["bytemuck"] } rfd = "0.17" clap = { version = "4", features = ["derive"] } env_logger = "0.11" -acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "cf46d04", features = ["serde"] } -cadkernel = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "b3f3122", features = ["acis", "offset"] } +acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "0908da7", features = ["serde"] } +cadkernel = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "ebeb2ec", features = ["acis", "offset"] } dwg-thumbnailer = { path = "crates/dwg-thumbnailer" } flate2 = "1" image = { version = "0.25", default-features = false, features = ["png", "jpeg", "bmp", "tiff"] } diff --git a/crates/ocs_plugin_api/Cargo.toml b/crates/ocs_plugin_api/Cargo.toml index 44dafeb8..3d393aa2 100644 --- a/crates/ocs_plugin_api/Cargo.toml +++ b/crates/ocs_plugin_api/Cargo.toml @@ -14,7 +14,7 @@ serde = { version = "1", features = ["derive"] } # Pulled in only by the `host` feature, which adds the `acadrust`-typed # `HostApi` runtime surface. The default crate stays dependency-free so engine # crates and external tooling can depend on the manifest/ribbon contract cheaply. -acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "cf46d04", optional = true, features = ["serde"] } +acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "0908da7", optional = true, features = ["serde"] } # Runtime IPC and serialization (host feature only). interprocess = { version = "2", optional = true } @@ -37,7 +37,7 @@ serde_json = "1" serde = { version = "1", features = ["derive"] } cargo-lock = "11" # acadrust is scanned at build time to generate the embedded type registry. -acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "cf46d04", features = ["serde"] } +acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "0908da7", features = ["serde"] } [dev-dependencies] serde_json = "1" diff --git a/crates/ocs_web_worker/Cargo.toml b/crates/ocs_web_worker/Cargo.toml index 945b460f..671ef9ea 100644 --- a/crates/ocs_web_worker/Cargo.toml +++ b/crates/ocs_web_worker/Cargo.toml @@ -8,7 +8,7 @@ publish = false crate-type = ["cdylib"] [dependencies] -acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "cf46d04", features = ["serde"] } +acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "0908da7", features = ["serde"] } bincode = "1.3" serde = { version = "1", features = ["derive"] } console_error_panic_hook = "0.1" diff --git a/locales/ar-SA/opencadstudio.ftl b/locales/ar-SA/opencadstudio.ftl index 4298b6aa..d92776f0 100644 --- a/locales/ar-SA/opencadstudio.ftl +++ b/locales/ar-SA/opencadstudio.ftl @@ -1385,6 +1385,19 @@ draw = .section-offset-along-z = SECTION الإزاحة على Z: .sketch-pen-down-move-to-sketch-click-to-lift-e = SKETCH القلم لأسفل — حرّك للرسم، انقر لرفعه، Enter للتسجيل: .sketch-pen-up-click-to-lower-the-pen-enter-to = SKETCH القلم لأعلى — انقر لإنزاله، Enter للتسجيل: + .sketch-pen = القلم + .sketch-increment = الزيادة + .sketch-record = تسجيل + .sketch-stop-erasing = إيقاف المسح + .sketch-connect = توصيل + .sketch-exit = خروج + .sketch-quit = إلغاء + .sketch-object-type = SKETCH نوع الكائن [خط/متعدد الخطوط/Spline]: + .sketch-record-increment = SKETCH زيادة التسجيل <__ocs_arg_value__>: + .sketch-spline-fit-tolerance = SKETCH تفاوت ملاءمة منحنى Spline <__ocs_arg_value__>: + .sketch-erase-move-backward = SKETCH المسح — تحرّك للخلف فوق الرسم المؤقت: + .sketch-pen-down-move = SKETCH (__ocs_arg_type__، الزيادة __ocs_arg_increment__) القلم لأسفل — حرّك للرسم: + .sketch-pen-up-click = SKETCH (__ocs_arg_type__، الزيادة __ocs_arg_increment__) القلم لأعلى — انقر لإنزاله: .slice-cutting-plane-axis-x-y-z-add-top-bottom = SLICE محور مستوى القطع [X / Y / Z] (أضف TOP/BOTTOM بالكتابة): .slice-offset-along-x = SLICE الإزاحة على X: .slice-offset-along-y = SLICE الإزاحة على Y: diff --git a/locales/bg-BG/opencadstudio.ftl b/locales/bg-BG/opencadstudio.ftl index c929fb81..59905288 100644 --- a/locales/bg-BG/opencadstudio.ftl +++ b/locales/bg-BG/opencadstudio.ftl @@ -1372,6 +1372,19 @@ draw = .section-offset-along-z = SECTION отместване по Z: .sketch-pen-down-move-to-sketch-click-to-lift-e = SKETCH Перо долу — премести за скициране, кликни за вдигане, Enter за записване: .sketch-pen-up-click-to-lower-the-pen-enter-to = SKETCH Перо горе — кликни за сваляне на перото, Enter за записване: + .sketch-pen = Перо + .sketch-increment = Стъпка + .sketch-record = Запис + .sketch-stop-erasing = Спри изтриването + .sketch-connect = Свържи + .sketch-exit = Изход + .sketch-quit = Отказ + .sketch-object-type = SKETCH Тип обект [Линия/Полилиния/Spline]: + .sketch-record-increment = SKETCH Стъпка на запис <__ocs_arg_value__>: + .sketch-spline-fit-tolerance = SKETCH Толеранс на напасване на Spline <__ocs_arg_value__>: + .sketch-erase-move-backward = SKETCH Изтриване — движете се назад върху временната скица: + .sketch-pen-down-move = SKETCH (__ocs_arg_type__, стъпка __ocs_arg_increment__) Перо долу — движете за скициране: + .sketch-pen-up-click = SKETCH (__ocs_arg_type__, стъпка __ocs_arg_increment__) Перо горе — кликнете за сваляне: .slice-cutting-plane-axis-x-y-z-add-top-bottom = SLICE ос на режещата равнина [X / Y / Z] (добави TOP/BOTTOM чрез въвеждане): .slice-offset-along-x = SLICE отместване по X: .slice-offset-along-y = SLICE отместване по Y: diff --git a/locales/cs-CZ/opencadstudio.ftl b/locales/cs-CZ/opencadstudio.ftl index c03c335c..c7480eb1 100644 --- a/locales/cs-CZ/opencadstudio.ftl +++ b/locales/cs-CZ/opencadstudio.ftl @@ -1372,6 +1372,19 @@ draw = .section-offset-along-z = Odsazení SECTION podél Z: .sketch-pen-down-move-to-sketch-click-to-lift-e = SKETCH Perem dolů – pohybem na skicu, kliknutím zvedněte, Enter zaznamenáte: .sketch-pen-up-click-to-lower-the-pen-enter-to = SKETCH Pero nahoru – kliknutím spustíte pero, Enter zaznamenáte: + .sketch-pen = Pero + .sketch-increment = Přírůstek + .sketch-record = Zaznamenat + .sketch-stop-erasing = Zastavit mazání + .sketch-connect = Připojit + .sketch-exit = Ukončit + .sketch-quit = Zahodit + .sketch-object-type = SKETCH Typ objektu [Úsečka/Polylinie/Spline]: + .sketch-record-increment = SKETCH Krok záznamu <__ocs_arg_value__>: + .sketch-spline-fit-tolerance = SKETCH Tolerance proložení spline <__ocs_arg_value__>: + .sketch-erase-move-backward = SKETCH Mazání — pohybujte se zpět po dočasné skice: + .sketch-pen-down-move = SKETCH (__ocs_arg_type__, krok __ocs_arg_increment__) Pero dole — kreslete pohybem: + .sketch-pen-up-click = SKETCH (__ocs_arg_type__, krok __ocs_arg_increment__) Pero nahoře — kliknutím jej spusťte: .slice-cutting-plane-axis-x-y-z-add-top-bottom = SLICE osa řezné roviny [X / Y / Z] (přidejte TOP/BOTTOM zadáním): .slice-offset-along-x = Odsazení SLICE podél X: .slice-offset-along-y = Odsazení SLICE podél Y: diff --git a/locales/de-DE/opencadstudio.ftl b/locales/de-DE/opencadstudio.ftl index 22b4becf..cc7d2bd6 100644 --- a/locales/de-DE/opencadstudio.ftl +++ b/locales/de-DE/opencadstudio.ftl @@ -1373,6 +1373,19 @@ draw = .section-offset-along-z = SECTION Versetzt entlang Z: .sketch-pen-down-move-to-sketch-click-to-lift-e = SKETCH Pen down — zum Skizzieren bewegen, zum Liften klicken, zum Aufzeichnen eingeben: .sketch-pen-up-click-to-lower-the-pen-enter-to = SKETCH Pen up — klicken Sie auf den Stift zu senken, geben Sie ein, um aufzuzeichnen: + .sketch-pen = Stift + .sketch-increment = Schrittweite + .sketch-record = Aufzeichnen + .sketch-stop-erasing = Radieren beenden + .sketch-connect = Verbinden + .sketch-exit = Beenden + .sketch-quit = Verwerfen + .sketch-object-type = SKETCH Objekttyp [Linie/Polylinie/Spline]: + .sketch-record-increment = SKETCH Aufzeichnungsschritt <__ocs_arg_value__>: + .sketch-spline-fit-tolerance = SKETCH Spline-Anpassungstoleranz <__ocs_arg_value__>: + .sketch-erase-move-backward = SKETCH Radieren — rückwärts über die temporäre Skizze bewegen: + .sketch-pen-down-move = SKETCH (__ocs_arg_type__, Schrittweite __ocs_arg_increment__) Stift unten — zum Skizzieren bewegen: + .sketch-pen-up-click = SKETCH (__ocs_arg_type__, Schrittweite __ocs_arg_increment__) Stift oben — zum Absenken klicken: .slice-cutting-plane-axis-x-y-z-add-top-bottom = SLICE Schnittebenenachse [X / Y / Z] (fügen Sie TOP/BOTTOM durch Eingabe hinzu): .slice-offset-along-x = SLICE versetzt entlang X: .slice-offset-along-y = SLICE längs Y versetzt: diff --git a/locales/en-US/opencadstudio.ftl b/locales/en-US/opencadstudio.ftl index 00c715c4..14e069c8 100644 --- a/locales/en-US/opencadstudio.ftl +++ b/locales/en-US/opencadstudio.ftl @@ -1373,6 +1373,19 @@ draw = .section-offset-along-z = SECTION offset along Z: .sketch-pen-down-move-to-sketch-click-to-lift-e = SKETCH Pen down — move to sketch, click to lift, Enter to record: .sketch-pen-up-click-to-lower-the-pen-enter-to = SKETCH Pen up — click to lower the pen, Enter to record: + .sketch-pen = Pen + .sketch-increment = Increment + .sketch-record = Record + .sketch-stop-erasing = Stop erasing + .sketch-connect = Connect + .sketch-exit = Exit + .sketch-quit = Quit + .sketch-object-type = SKETCH Object type [Line/Polyline/Spline]: + .sketch-record-increment = SKETCH Record increment <__ocs_arg_value__>: + .sketch-spline-fit-tolerance = SKETCH Spline fit tolerance <__ocs_arg_value__>: + .sketch-erase-move-backward = SKETCH Erase — move backward over the temporary sketch: + .sketch-pen-down-move = SKETCH (__ocs_arg_type__, increment __ocs_arg_increment__) Pen down — move to sketch: + .sketch-pen-up-click = SKETCH (__ocs_arg_type__, increment __ocs_arg_increment__) Pen up — click to lower the pen: .slice-cutting-plane-axis-x-y-z-add-top-bottom = SLICE cutting-plane axis [X / Y / Z] (add TOP/BOTTOM by typing): .slice-offset-along-x = SLICE offset along X: .slice-offset-along-y = SLICE offset along Y: diff --git a/locales/es-ES/opencadstudio.ftl b/locales/es-ES/opencadstudio.ftl index 10e4c952..b1cd44aa 100644 --- a/locales/es-ES/opencadstudio.ftl +++ b/locales/es-ES/opencadstudio.ftl @@ -1373,6 +1373,19 @@ draw = .section-offset-along-z = SECTION desfase a lo largo de Z: .sketch-pen-down-move-to-sketch-click-to-lift-e = SKETCH Lápiz abajo: mueva para dibujar, pulse para levantarlo, Intro para registrar: .sketch-pen-up-click-to-lower-the-pen-enter-to = SKETCH Lápiz arriba: pulse para bajarlo, Intro para registrar: + .sketch-pen = Lápiz + .sketch-increment = Incremento + .sketch-record = Registrar + .sketch-stop-erasing = Detener borrado + .sketch-connect = Conectar + .sketch-exit = Salir + .sketch-quit = Descartar + .sketch-object-type = SKETCH Tipo de objeto [Línea/Polilínea/Spline]: + .sketch-record-increment = SKETCH Incremento de registro <__ocs_arg_value__>: + .sketch-spline-fit-tolerance = SKETCH Tolerancia de ajuste de spline <__ocs_arg_value__>: + .sketch-erase-move-backward = SKETCH Borrar: retroceda sobre el boceto temporal: + .sketch-pen-down-move = SKETCH (__ocs_arg_type__, incremento __ocs_arg_increment__) Lápiz abajo: mueva para dibujar: + .sketch-pen-up-click = SKETCH (__ocs_arg_type__, incremento __ocs_arg_increment__) Lápiz arriba: pulse para bajarlo: .slice-cutting-plane-axis-x-y-z-add-top-bottom = SLICE eje del plano de corte [X / Y / Z] (escriba TOP/BOTTOM para añadirlo): .slice-offset-along-x = SLICE desfase a lo largo de X: .slice-offset-along-y = SLICE desfase a lo largo de Y: diff --git a/locales/fi-FI/opencadstudio.ftl b/locales/fi-FI/opencadstudio.ftl index c6e99861..e5f60b3f 100644 --- a/locales/fi-FI/opencadstudio.ftl +++ b/locales/fi-FI/opencadstudio.ftl @@ -1372,6 +1372,19 @@ draw = .section-offset-along-z = SECTION offset pitkin Z: .sketch-pen-down-move-to-sketch-click-to-lift-e = SKETCH Kynä alas — siirrä luonnos, napsauta nostaaksesi, Enter tallentaa: .sketch-pen-up-click-to-lower-the-pen-enter-to = SKETCH Kynä ylös — napsauta laskeaksesi kynää, Enter tallentaaksesi: + .sketch-pen = Kynä + .sketch-increment = Askel + .sketch-record = Tallenna + .sketch-stop-erasing = Lopeta pyyhkiminen + .sketch-connect = Yhdistä + .sketch-exit = Lopeta + .sketch-quit = Hylkää + .sketch-object-type = SKETCH Kohdetyyppi [Viiva/Moniviiva/Spline]: + .sketch-record-increment = SKETCH Tallennusaskel <__ocs_arg_value__>: + .sketch-spline-fit-tolerance = SKETCH Splinen sovitustoleranssi <__ocs_arg_value__>: + .sketch-erase-move-backward = SKETCH Pyyhi — liiku taaksepäin väliaikaisen luonnoksen päällä: + .sketch-pen-down-move = SKETCH (__ocs_arg_type__, askel __ocs_arg_increment__) Kynä alhaalla — piirrä liikuttamalla: + .sketch-pen-up-click = SKETCH (__ocs_arg_type__, askel __ocs_arg_increment__) Kynä ylhäällä — laske napsauttamalla: .slice-cutting-plane-axis-x-y-z-add-top-bottom = SLICE leikkaustason akseli [X / Y / Z] (lisää TOP/BOTTOM kirjoittamalla): .slice-offset-along-x = SLICE offset pitkin X: .slice-offset-along-y = SLICE offset pitkin Y: diff --git a/locales/fr-FR/opencadstudio.ftl b/locales/fr-FR/opencadstudio.ftl index 5021ef57..895e2025 100644 --- a/locales/fr-FR/opencadstudio.ftl +++ b/locales/fr-FR/opencadstudio.ftl @@ -1373,6 +1373,19 @@ draw = .section-offset-along-z = SECTION décalage le long de Z: .sketch-pen-down-move-to-sketch-click-to-lift-e = SKETCH Pen vers le bas — déplacer vers le croquis, cliquer pour soulever, Entrer pour enregistrer: .sketch-pen-up-click-to-lower-the-pen-enter-to = SKETCH Pen up — cliquez pour baisser le stylo, entrez pour enregistrer: + .sketch-pen = Stylo + .sketch-increment = Incrément + .sketch-record = Enregistrer + .sketch-stop-erasing = Arrêter d’effacer + .sketch-connect = Relier + .sketch-exit = Terminer + .sketch-quit = Abandonner + .sketch-object-type = SKETCH Type d’objet [Ligne/Polyligne/Spline] : + .sketch-record-increment = SKETCH Pas d’enregistrement <__ocs_arg_value__> : + .sketch-spline-fit-tolerance = SKETCH Tolérance d’ajustement de la spline <__ocs_arg_value__> : + .sketch-erase-move-backward = SKETCH Effacer — revenez en arrière sur l’esquisse temporaire : + .sketch-pen-down-move = SKETCH (__ocs_arg_type__, incrément __ocs_arg_increment__) Stylo baissé — déplacez pour dessiner : + .sketch-pen-up-click = SKETCH (__ocs_arg_type__, incrément __ocs_arg_increment__) Stylo levé — cliquez pour l’abaisser : .slice-cutting-plane-axis-x-y-z-add-top-bottom = SLICE axe du plan de coupe [X / Y / Z] (ajouter TOP/BOTTOM par typage): .slice-offset-along-x = SLICE décalage le long de X: .slice-offset-along-y = SLICE décalage le long de Y: diff --git a/locales/hi-IN/opencadstudio.ftl b/locales/hi-IN/opencadstudio.ftl index b0ede3fe..083cdad0 100644 --- a/locales/hi-IN/opencadstudio.ftl +++ b/locales/hi-IN/opencadstudio.ftl @@ -1361,6 +1361,19 @@ draw = .section-offset-along-z = SECTION Z के साथ ऑफसेट: .sketch-pen-down-move-to-sketch-click-to-lift-e = SKETCH पेन नीचे — स्केच करने के लिए चलाएँ, पेन उठाने के लिए क्लिक करें, रिकॉर्ड करने के लिए Enter दबाएँ: .sketch-pen-up-click-to-lower-the-pen-enter-to = SKETCH पेन ऊपर — पेन नीचे करने के लिए क्लिक करें, रिकॉर्ड करने के लिए Enter दबाएँ: + .sketch-pen = पेन + .sketch-increment = वृद्धि + .sketch-record = रिकॉर्ड + .sketch-stop-erasing = मिटाना रोकें + .sketch-connect = जोड़ें + .sketch-exit = बाहर निकलें + .sketch-quit = छोड़ें + .sketch-object-type = SKETCH ऑब्जेक्ट प्रकार [रेखा/पॉलीलाइन/Spline]: + .sketch-record-increment = SKETCH रिकॉर्ड वृद्धि <__ocs_arg_value__>: + .sketch-spline-fit-tolerance = SKETCH Spline फ़िट सहनशीलता <__ocs_arg_value__>: + .sketch-erase-move-backward = SKETCH मिटाएँ — अस्थायी स्केच पर पीछे की ओर चलें: + .sketch-pen-down-move = SKETCH (__ocs_arg_type__, वृद्धि __ocs_arg_increment__) पेन नीचे — स्केच करने के लिए चलाएँ: + .sketch-pen-up-click = SKETCH (__ocs_arg_type__, वृद्धि __ocs_arg_increment__) पेन ऊपर — नीचे करने के लिए क्लिक करें: .slice-cutting-plane-axis-x-y-z-add-top-bottom = SLICE कटिंग-प्लेन अक्ष [X / Y / Z] (Typing द्वारा शीर्ष/BOTTOM को शामिल किया गया): .slice-offset-along-x = SLICE X के साथ ऑफसेट: .slice-offset-along-y = SLICE वाई के साथ ऑफसेट: diff --git a/locales/hu-HU/opencadstudio.ftl b/locales/hu-HU/opencadstudio.ftl index d2f8938c..f6c62247 100644 --- a/locales/hu-HU/opencadstudio.ftl +++ b/locales/hu-HU/opencadstudio.ftl @@ -1372,6 +1372,19 @@ draw = .section-offset-along-z = SECTION eltolás a Z mentén: .sketch-pen-down-move-to-sketch-click-to-lift-e = SKETCH Toll lefelé – mozgassa a vázlatot, kattintson az emeléshez, Enter a rögzítéshez: .sketch-pen-up-click-to-lower-the-pen-enter-to = SKETCH Toll felfelé – kattintson a toll leengedéséhez, Enter a rögzítéshez: + .sketch-pen = Toll + .sketch-increment = Lépésköz + .sketch-record = Rögzítés + .sketch-stop-erasing = Törlés leállítása + .sketch-connect = Csatlakoztatás + .sketch-exit = Kilépés + .sketch-quit = Elvetés + .sketch-object-type = SKETCH Objektumtípus [Vonal/Vonallánc/Spline]: + .sketch-record-increment = SKETCH Rögzítési lépés <__ocs_arg_value__>: + .sketch-spline-fit-tolerance = SKETCH Spline illesztési tűrése <__ocs_arg_value__>: + .sketch-erase-move-backward = SKETCH Törlés — haladjon visszafelé az ideiglenes vázlaton: + .sketch-pen-down-move = SKETCH (__ocs_arg_type__, lépés __ocs_arg_increment__) Toll lent — mozgassa a rajzoláshoz: + .sketch-pen-up-click = SKETCH (__ocs_arg_type__, lépés __ocs_arg_increment__) Toll fent — kattintson a leengedéshez: .slice-cutting-plane-axis-x-y-z-add-top-bottom = SLICE vágósík tengelye [X / Y / Z] (adja hozzá a TOP/BOTTOM-t beírással): .slice-offset-along-x = SLICE eltolás a X mentén: .slice-offset-along-y = SLICE eltolás a Y mentén: diff --git a/locales/it-IT/opencadstudio.ftl b/locales/it-IT/opencadstudio.ftl index 0b22f4f3..7285efa0 100644 --- a/locales/it-IT/opencadstudio.ftl +++ b/locales/it-IT/opencadstudio.ftl @@ -1372,6 +1372,19 @@ draw = .section-offset-along-z = Scostamento SECTION lungo Z: .sketch-pen-down-move-to-sketch-click-to-lift-e = SKETCH Penna giù: sposta sullo schizzo, fai clic per sollevare, Enter per registrare: .sketch-pen-up-click-to-lower-the-pen-enter-to = SKETCH Penna su: fare clic per abbassare la penna, Enter per registrare: + .sketch-pen = Penna + .sketch-increment = Incremento + .sketch-record = Registra + .sketch-stop-erasing = Interrompi cancellazione + .sketch-connect = Connetti + .sketch-exit = Esci + .sketch-quit = Abbandona + .sketch-object-type = SKETCH Tipo di oggetto [Linea/Polilinea/Spline]: + .sketch-record-increment = SKETCH Incremento di registrazione <__ocs_arg_value__>: + .sketch-spline-fit-tolerance = SKETCH Tolleranza di adattamento spline <__ocs_arg_value__>: + .sketch-erase-move-backward = SKETCH Cancella — spostati indietro sullo schizzo temporaneo: + .sketch-pen-down-move = SKETCH (__ocs_arg_type__, incremento __ocs_arg_increment__) Penna abbassata — spostati per disegnare: + .sketch-pen-up-click = SKETCH (__ocs_arg_type__, incremento __ocs_arg_increment__) Penna sollevata — fai clic per abbassarla: .slice-cutting-plane-axis-x-y-z-add-top-bottom = Asse del piano di taglio SLICE [X / Y / Z] (aggiungere TOP/BOTTOM digitando): .slice-offset-along-x = Scostamento SLICE lungo X: .slice-offset-along-y = Scostamento SLICE lungo Y: diff --git a/locales/ja-JP/opencadstudio.ftl b/locales/ja-JP/opencadstudio.ftl index 3c4b0050..1228ae23 100644 --- a/locales/ja-JP/opencadstudio.ftl +++ b/locales/ja-JP/opencadstudio.ftl @@ -1363,6 +1363,19 @@ draw = .section-offset-along-z = SECTION Z 方向オフセット: .sketch-pen-down-move-to-sketch-click-to-lift-e = SKETCH ペンを下げました — 移動してスケッチ、クリックで上げ、Enter で記録: .sketch-pen-up-click-to-lower-the-pen-enter-to = SKETCH ペンを上げました — クリックで下げ、Enter で記録: + .sketch-pen = ペン + .sketch-increment = 増分 + .sketch-record = 記録 + .sketch-stop-erasing = 消去を停止 + .sketch-connect = 接続 + .sketch-exit = 終了 + .sketch-quit = 破棄 + .sketch-object-type = SKETCH オブジェクト タイプ [線分/ポリライン/スプライン]: + .sketch-record-increment = SKETCH 記録増分 <__ocs_arg_value__>: + .sketch-spline-fit-tolerance = SKETCH スプライン フィット許容差 <__ocs_arg_value__>: + .sketch-erase-move-backward = SKETCH 消去 — 一時スケッチ上を後方へ移動してください: + .sketch-pen-down-move = SKETCH (__ocs_arg_type__、増分 __ocs_arg_increment__) ペンを下げました — 移動してスケッチ: + .sketch-pen-up-click = SKETCH (__ocs_arg_type__、増分 __ocs_arg_increment__) ペンを上げました — クリックで下げます: .slice-cutting-plane-axis-x-y-z-add-top-bottom = SLICE 切断面の軸 [X / Y / Z](TOP/BOTTOM を追加可能): .slice-offset-along-x = SLICE X 方向オフセット: .slice-offset-along-y = SLICE Y 方向オフセット: diff --git a/locales/ko-KR/opencadstudio.ftl b/locales/ko-KR/opencadstudio.ftl index ef320dd7..133efd9d 100644 --- a/locales/ko-KR/opencadstudio.ftl +++ b/locales/ko-KR/opencadstudio.ftl @@ -1372,6 +1372,19 @@ draw = .section-offset-along-z = Z를 따라 SECTION 오프셋: .sketch-pen-down-move-to-sketch-click-to-lift-e = SKETCH 펜 아래로 — 스케치로 이동하고 클릭하여 올리며 Enter로 기록: .sketch-pen-up-click-to-lower-the-pen-enter-to = SKETCH 펜 업 — 펜을 내리려면 클릭하고 기록하려면 Enter를 클릭하세요. + .sketch-pen = 펜 + .sketch-increment = 증분 + .sketch-record = 기록 + .sketch-stop-erasing = 지우기 중지 + .sketch-connect = 연결 + .sketch-exit = 종료 + .sketch-quit = 취소 + .sketch-object-type = SKETCH 객체 유형 [선/폴리선/스플라인]: + .sketch-record-increment = SKETCH 기록 증분 <__ocs_arg_value__>: + .sketch-spline-fit-tolerance = SKETCH 스플라인 맞춤 공차 <__ocs_arg_value__>: + .sketch-erase-move-backward = SKETCH 지우기 — 임시 스케치 위를 뒤로 이동하십시오: + .sketch-pen-down-move = SKETCH (__ocs_arg_type__, 증분 __ocs_arg_increment__) 펜 내림 — 이동하여 스케치: + .sketch-pen-up-click = SKETCH (__ocs_arg_type__, 증분 __ocs_arg_increment__) 펜 올림 — 클릭하여 내리기: .slice-cutting-plane-axis-x-y-z-add-top-bottom = SLICE 절단 평면 축 [X / Y / Z] (입력하여 TOP/BOTTOM 추가): .slice-offset-along-x = X를 따라 SLICE 오프셋: .slice-offset-along-y = Y를 따라 SLICE 오프셋: diff --git a/locales/nl-NL/opencadstudio.ftl b/locales/nl-NL/opencadstudio.ftl index 0d667a5f..61f1391b 100644 --- a/locales/nl-NL/opencadstudio.ftl +++ b/locales/nl-NL/opencadstudio.ftl @@ -1373,6 +1373,19 @@ draw = .section-offset-along-z = SECTION offset langs Z: .sketch-pen-down-move-to-sketch-click-to-lift-e = SKETCH Pen naar beneden .sketch-pen-up-click-to-lower-the-pen-enter-to = SKETCH Pen omhoog + .sketch-pen = Pen + .sketch-increment = Stapgrootte + .sketch-record = Vastleggen + .sketch-stop-erasing = Wissen stoppen + .sketch-connect = Verbinden + .sketch-exit = Afsluiten + .sketch-quit = Annuleren + .sketch-object-type = SKETCH Objecttype [Lijn/Polylijn/Spline]: + .sketch-record-increment = SKETCH Opnamestap <__ocs_arg_value__>: + .sketch-spline-fit-tolerance = SKETCH Spline-aanpassingstolerantie <__ocs_arg_value__>: + .sketch-erase-move-backward = SKETCH Wissen — beweeg terug over de tijdelijke schets: + .sketch-pen-down-move = SKETCH (__ocs_arg_type__, stap __ocs_arg_increment__) Pen omlaag — beweeg om te schetsen: + .sketch-pen-up-click = SKETCH (__ocs_arg_type__, stap __ocs_arg_increment__) Pen omhoog — klik om de pen neer te zetten: .slice-cutting-plane-axis-x-y-z-add-top-bottom = SLICE snijvlakas [X / Y / Z] (voeg TOP/BOTTOM toe door te typen): .slice-offset-along-x = SLICE offset langs X: .slice-offset-along-y = SLICE offset langs Y: diff --git a/locales/pl-PL/opencadstudio.ftl b/locales/pl-PL/opencadstudio.ftl index e158bcc9..8966f79c 100644 --- a/locales/pl-PL/opencadstudio.ftl +++ b/locales/pl-PL/opencadstudio.ftl @@ -1372,6 +1372,19 @@ draw = .section-offset-along-z = Przesunięcie SECTION wzdłuż Z: .sketch-pen-down-move-to-sketch-click-to-lift-e = SKETCH Opuść pióro — przejdź do szkicu, kliknij, aby podnieść, Enter, aby nagrać: .sketch-pen-up-click-to-lower-the-pen-enter-to = SKETCH Pióro w górę — kliknij, aby opuścić pióro, Enter, aby nagrać: + .sketch-pen = Pióro + .sketch-increment = Przyrost + .sketch-record = Zapisz + .sketch-stop-erasing = Zatrzymaj wymazywanie + .sketch-connect = Połącz + .sketch-exit = Zakończ + .sketch-quit = Porzuć + .sketch-object-type = SKETCH Typ obiektu [Linia/Polilinia/Spline]: + .sketch-record-increment = SKETCH Przyrost zapisu <__ocs_arg_value__>: + .sketch-spline-fit-tolerance = SKETCH Tolerancja dopasowania spline <__ocs_arg_value__>: + .sketch-erase-move-backward = SKETCH Wymazywanie — przesuń się wstecz po szkicu tymczasowym: + .sketch-pen-down-move = SKETCH (__ocs_arg_type__, przyrost __ocs_arg_increment__) Pióro opuszczone — przesuń, aby szkicować: + .sketch-pen-up-click = SKETCH (__ocs_arg_type__, przyrost __ocs_arg_increment__) Pióro podniesione — kliknij, aby opuścić: .slice-cutting-plane-axis-x-y-z-add-top-bottom = Oś płaszczyzny cięcia SLICE [X / Y / Z] (dodaj TOP/BOTTOM wpisując): .slice-offset-along-x = Przesunięcie SLICE wzdłuż X: .slice-offset-along-y = Przesunięcie SLICE wzdłuż Y: diff --git a/locales/pt-BR/opencadstudio.ftl b/locales/pt-BR/opencadstudio.ftl index 76593d51..6f6352a7 100644 --- a/locales/pt-BR/opencadstudio.ftl +++ b/locales/pt-BR/opencadstudio.ftl @@ -1373,6 +1373,19 @@ draw = .section-offset-along-z = SECTION deslocamento ao longo de Z: .sketch-pen-down-move-to-sketch-click-to-lift-e = SKETCH Caneta abaixada — mova para esboçar, clique para levantar, Enter para registrar: .sketch-pen-up-click-to-lower-the-pen-enter-to = SKETCH Caneta levantada — clique para abaixar, Enter para registrar: + .sketch-pen = Caneta + .sketch-increment = Incremento + .sketch-record = Registrar + .sketch-stop-erasing = Parar de apagar + .sketch-connect = Conectar + .sketch-exit = Sair + .sketch-quit = Descartar + .sketch-object-type = SKETCH Tipo de objeto [Linha/Polilinha/Spline]: + .sketch-record-increment = SKETCH Incremento de registro <__ocs_arg_value__>: + .sketch-spline-fit-tolerance = SKETCH Tolerância de ajuste da spline <__ocs_arg_value__>: + .sketch-erase-move-backward = SKETCH Apagar — mova para trás sobre o esboço temporário: + .sketch-pen-down-move = SKETCH (__ocs_arg_type__, incremento __ocs_arg_increment__) Caneta abaixada — mova para esboçar: + .sketch-pen-up-click = SKETCH (__ocs_arg_type__, incremento __ocs_arg_increment__) Caneta levantada — clique para abaixar: .slice-cutting-plane-axis-x-y-z-add-top-bottom = SLICE eixo do plano de corte [X / Y / Z] (digite TOP/BOTTOM para adicionar): .slice-offset-along-x = SLICE deslocamento ao longo de X: .slice-offset-along-y = SLICE deslocamento ao longo de Y: diff --git a/locales/ru-RU/opencadstudio.ftl b/locales/ru-RU/opencadstudio.ftl index d6a8c048..b66f4e06 100644 --- a/locales/ru-RU/opencadstudio.ftl +++ b/locales/ru-RU/opencadstudio.ftl @@ -1376,6 +1376,19 @@ draw = .section-offset-along-z = SECTION Смещение вдоль Z: .sketch-pen-down-move-to-sketch-click-to-lift-e = SKETCH Нажмите вниз — перейдите к эскизу, нажмите, чтобы поднять, Введите, чтобы записать: .sketch-pen-up-click-to-lower-the-pen-enter-to = SKETCH Pen up — нажмите, чтобы опустить ручку, Введите для записи: + .sketch-pen = Перо + .sketch-increment = Шаг + .sketch-record = Записать + .sketch-stop-erasing = Остановить стирание + .sketch-connect = Соединить + .sketch-exit = Завершить + .sketch-quit = Отменить + .sketch-object-type = SKETCH Тип объекта [Отрезок/Полилиния/Сплайн]: + .sketch-record-increment = SKETCH Шаг записи <__ocs_arg_value__>: + .sketch-spline-fit-tolerance = SKETCH Допуск аппроксимации сплайна <__ocs_arg_value__>: + .sketch-erase-move-backward = SKETCH Стирание — двигайтесь назад по временному эскизу: + .sketch-pen-down-move = SKETCH (__ocs_arg_type__, шаг __ocs_arg_increment__) Перо опущено — двигайте для рисования: + .sketch-pen-up-click = SKETCH (__ocs_arg_type__, шаг __ocs_arg_increment__) Перо поднято — щёлкните, чтобы опустить: .slice-cutting-plane-axis-x-y-z-add-top-bottom = SLICE ось режущей плоскости [X/Y/Z] (добавить TOP/BOTTOM путем набора текста): .slice-offset-along-x = SLICE Смещение вдоль X: .slice-offset-along-y = SLICE смещение вдоль Y: diff --git a/locales/tr-TR/opencadstudio.ftl b/locales/tr-TR/opencadstudio.ftl index 5188fe73..8235a7cc 100644 --- a/locales/tr-TR/opencadstudio.ftl +++ b/locales/tr-TR/opencadstudio.ftl @@ -1355,6 +1355,19 @@ draw = .section-offset-along-z = SECTION Z boyunca ofset: .sketch-pen-down-move-to-sketch-click-to-lift-e = SKETCH Kalem aşağıda — çizmek için hareket ettirin, kaldırmak için tıklayın, kaydetmek için Enter: .sketch-pen-up-click-to-lower-the-pen-enter-to = SKETCH Kalem yukarıda — indirmek için tıklayın, kaydetmek için Enter: + .sketch-pen = Kalem + .sketch-increment = Artış + .sketch-record = Kaydet + .sketch-stop-erasing = Silmeyi durdur + .sketch-connect = Bağla + .sketch-exit = Çıkış + .sketch-quit = Vazgeç + .sketch-object-type = SKETCH Nesne türü [Çizgi/Çoklu çizgi/Spline]: + .sketch-record-increment = SKETCH Kayıt artışı <__ocs_arg_value__>: + .sketch-spline-fit-tolerance = SKETCH Spline uyum toleransı <__ocs_arg_value__>: + .sketch-erase-move-backward = SKETCH Sil — geçici çizim üzerinde geriye doğru hareket edin: + .sketch-pen-down-move = SKETCH (__ocs_arg_type__, artış __ocs_arg_increment__) Kalem aşağıda — çizmek için hareket ettirin: + .sketch-pen-up-click = SKETCH (__ocs_arg_type__, artış __ocs_arg_increment__) Kalem yukarıda — indirmek için tıklayın: .slice-cutting-plane-axis-x-y-z-add-top-bottom = SLICE Kesme düzlemi ekseni [X / Y / Z] (TOP/BOTTOM yazarak ekleyin): .slice-offset-along-x = SLICE X boyunca ofset: .slice-offset-along-y = SLICE Y boyunca ofset: diff --git a/locales/zh-CN/opencadstudio.ftl b/locales/zh-CN/opencadstudio.ftl index 6f195b6f..7ae66390 100644 --- a/locales/zh-CN/opencadstudio.ftl +++ b/locales/zh-CN/opencadstudio.ftl @@ -1360,6 +1360,19 @@ draw = .section-offset-along-z = SECTION 与 Z 相冲: .sketch-pen-down-move-to-sketch-click-to-lift-e = SKETCH 落笔 — 移动以绘图,单击抬笔,按 Enter 记录: .sketch-pen-up-click-to-lower-the-pen-enter-to = SKETCH 抬笔 — 单击落笔,按 Enter 记录: + .sketch-pen = 画笔 + .sketch-increment = 增量 + .sketch-record = 记录 + .sketch-stop-erasing = 停止擦除 + .sketch-connect = 连接 + .sketch-exit = 退出 + .sketch-quit = 放弃 + .sketch-object-type = SKETCH 对象类型 [直线/多段线/样条曲线]: + .sketch-record-increment = SKETCH 记录增量 <__ocs_arg_value__>: + .sketch-spline-fit-tolerance = SKETCH 样条曲线拟合公差 <__ocs_arg_value__>: + .sketch-erase-move-backward = SKETCH 擦除 — 沿临时草图向后移动: + .sketch-pen-down-move = SKETCH(__ocs_arg_type__,增量 __ocs_arg_increment__) 落笔 — 移动以绘图: + .sketch-pen-up-click = SKETCH(__ocs_arg_type__,增量 __ocs_arg_increment__) 抬笔 — 单击落笔: .slice-cutting-plane-axis-x-y-z-add-top-bottom = SLICE 剖切平面轴 [X / Y / Z](输入 TOP/BOTTOM 可追加): .slice-offset-along-x = SLICE 页:1 .slice-offset-along-y = SLICE 与 Y 相冲: diff --git a/locales/zh-TW/opencadstudio.ftl b/locales/zh-TW/opencadstudio.ftl index 9317f23d..d2e6823d 100644 --- a/locales/zh-TW/opencadstudio.ftl +++ b/locales/zh-TW/opencadstudio.ftl @@ -1372,6 +1372,19 @@ draw = .section-offset-along-z = SECTION 與 Z 相衝: .sketch-pen-down-move-to-sketch-click-to-lift-e = SKETCH 落筆 — 移動以繪圖,按一下抬筆,按 Enter 記錄: .sketch-pen-up-click-to-lower-the-pen-enter-to = SKETCH 抬筆 — 按一下落筆,按 Enter 記錄: + .sketch-pen = 畫筆 + .sketch-increment = 增量 + .sketch-record = 記錄 + .sketch-stop-erasing = 停止擦除 + .sketch-connect = 連接 + .sketch-exit = 結束 + .sketch-quit = 放棄 + .sketch-object-type = SKETCH 物件類型 [直線/聚合線/雲形線]: + .sketch-record-increment = SKETCH 記錄增量 <__ocs_arg_value__>: + .sketch-spline-fit-tolerance = SKETCH 雲形線擬合公差 <__ocs_arg_value__>: + .sketch-erase-move-backward = SKETCH 擦除 — 沿暫存草圖向後移動: + .sketch-pen-down-move = SKETCH(__ocs_arg_type__,增量 __ocs_arg_increment__) 落筆 — 移動以繪圖: + .sketch-pen-up-click = SKETCH(__ocs_arg_type__,增量 __ocs_arg_increment__) 抬筆 — 按一下落筆: .slice-cutting-plane-axis-x-y-z-add-top-bottom = SLICE 剖切平面軸 [X / Y / Z](輸入 TOP/BOTTOM 可追加): .slice-offset-along-x = SLICE 頁:1 .slice-offset-along-y = SLICE 與 Y 相衝: diff --git a/src/app/command_driver.rs b/src/app/command_driver.rs index 669d8c19..e0bf9acc 100644 --- a/src/app/command_driver.rs +++ b/src/app/command_driver.rs @@ -642,6 +642,26 @@ impl OpenCADStudio { /// Pure-selection commands (SELECTALL, QSELECT, …) run without an active /// command, so `was_active` is false and their selection is preserved. pub(super) fn apply_cmd_result(&mut self, result: CmdResult) -> Task { + let settings = self.tabs[self.active_tab] + .active_cmd + .as_ref() + .and_then(|command| command.sketch_settings()); + if let Some((sketch_type, increment, tolerance)) = settings { + let tab = &mut self.tabs[self.active_tab]; + let changed = tab.scene.document.header.sketch_type != sketch_type + || (tab.scene.document.header.sketch_increment - increment).abs() > f64::EPSILON + || (tab.scene.document.header.sketch_tolerance - tolerance).abs() + > f64::EPSILON; + if changed { + crate::io::set_sketch_settings( + &mut tab.scene.document, + sketch_type, + increment, + tolerance, + ); + tab.dirty = true; + } + } let was_active = self.tabs[self.active_tab].active_cmd.is_some(); let preserve_selection = matches!(result, CmdResult::Relaunch(..) | CmdResult::Dispatch(..)); @@ -785,6 +805,7 @@ impl OpenCADStudio { self.commit_entity(entity); } self.tabs[i].dirty = true; + self.tabs[i].scene.clear_preview_wire(); let prompt = self.tabs[i].active_cmd.as_ref().map(|c| c.prompt()); if let Some(p) = prompt { self.command_line.push_info(&p); diff --git a/src/app/commands/draw.rs b/src/app/commands/draw.rs index b2e9ca5f..4f32b015 100644 --- a/src/app/commands/draw.rs +++ b/src/app/commands/draw.rs @@ -487,7 +487,12 @@ impl OpenCADStudio { "SKETCH" => { use crate::modules::draw::draw::sketch::SketchCommand; - let new_cmd = SketchCommand::new(); + let header = &self.tabs[i].scene.document.header; + let new_cmd = SketchCommand::new( + header.sketch_type, + header.sketch_increment, + header.sketch_tolerance, + ); self.command_line.push_info(&new_cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(new_cmd)); } diff --git a/src/app/commands/mod.rs b/src/app/commands/mod.rs index 7745eef1..ffa66bcb 100644 --- a/src/app/commands/mod.rs +++ b/src/app/commands/mod.rs @@ -526,6 +526,8 @@ inventory::submit!(crate::command::CommandRegistration { "HALOGAP", "TRACEWID", "SKETCHINC", + "SKPOLY", + "SKTOLERANCE", // Reset selected entities' overrides to follow their layer. "SETBYLAYER", // Remove duplicate objects; set drawing base point; audit integrity; diff --git a/src/app/commands/styleprops.rs b/src/app/commands/styleprops.rs index b04aa5fa..26635003 100644 --- a/src/app/commands/styleprops.rs +++ b/src/app/commands/styleprops.rs @@ -931,6 +931,8 @@ impl OpenCADStudio { | "HALOGAP" | "TRACEWID" | "SKETCHINC" + | "SKPOLY" + | "SKTOLERANCE" ) => { return self.dispatch_styleprops(&format!("SETVAR {cmd}"), i); @@ -953,7 +955,7 @@ impl OpenCADStudio { let value = it.next().map(|s| s.trim().to_string()); if name.is_empty() || name == "?" { self.command_line.push_info( - "SETVAR: LTSCALE CELTSCALE PDMODE PDSIZE TEXTSIZE ORTHOMODE FILLMODE MIRRTEXT FRAME IMAGEFRAME PDFFRAME WIPEOUTFRAME XCLIPFRAME POINTCLOUDCLIPFRAME ZOOMWHEEL ZOOMFACTOR CURSORSIZE PICKBOX CURSORTYPE SNAPANG ATTREQ ATTDIA DIMASSOC ANGBASE ANGDIR | CLAYER CELTYPE TEXTSTYLE (read-only)", + "SETVAR: LTSCALE CELTSCALE PDMODE PDSIZE TEXTSIZE ORTHOMODE FILLMODE MIRRTEXT FRAME IMAGEFRAME PDFFRAME WIPEOUTFRAME XCLIPFRAME POINTCLOUDCLIPFRAME ZOOMWHEEL ZOOMFACTOR CURSORSIZE PICKBOX CURSORTYPE SNAPANG ATTREQ ATTDIA DIMASSOC ANGBASE ANGDIR SKETCHINC SKPOLY SKTOLERANCE | CLAYER CELTYPE TEXTSTYLE (read-only)", ); } else { let frame_kind = crate::scene::frame::kind_for_name(&name); @@ -1700,15 +1702,41 @@ impl OpenCADStudio { None => Ok((format!("TRACEWID = {}", h.trace_width), false)), }, "SKETCHINC" => match &value { - Some(v) => v - .parse::() - .map(|x| { + Some(v) => match v.parse::() { + Ok(x) if x.is_finite() && x > 0.0 => { + let changed = h.sketch_increment != x; h.sketch_increment = x; - (format!("SKETCHINC = {x}"), true) - }) - .map_err(|_| "SETVAR: numeric value required.".into()), + Ok((format!("SKETCHINC = {x}"), changed)) + } + _ => Err("SETVAR: positive numeric value required.".into()), + }, None => Ok((format!("SKETCHINC = {}", h.sketch_increment), false)), }, + "SKPOLY" => match &value { + Some(v) => match v.parse::() { + Ok(x @ 0..=2) => { + let changed = h.sketch_type != x; + h.sketch_type = x; + Ok((format!("SKPOLY = {x}"), changed)) + } + _ => Err("SETVAR: integer value from 0 to 2 required.".into()), + }, + None => Ok((format!("SKPOLY = {}", h.sketch_type), false)), + }, + "SKTOLERANCE" => match &value { + Some(v) => match v.parse::() { + Ok(x) if x.is_finite() && (0.0..=1.0).contains(&x) => { + let changed = h.sketch_tolerance != x; + h.sketch_tolerance = x; + Ok((format!("SKTOLERANCE = {x}"), changed)) + } + _ => Err("SETVAR: numeric value from 0 to 1 required.".into()), + }, + None => Ok(( + format!("SKTOLERANCE = {}", h.sketch_tolerance), + false, + )), + }, "CLAYER" => match &value { Some(_) => Err( "SETVAR: CLAYER is read-only here — use the CLAYER command." diff --git a/src/command.rs b/src/command.rs index 35f1b904..96f5e4d0 100644 --- a/src/command.rs +++ b/src/command.rs @@ -1822,6 +1822,11 @@ pub trait CadCommand: Send { false } + /// Current drawing-persisted SKETCH settings. + fn sketch_settings(&self) -> Option<(i16, f64, f64)> { + None + } + /// Returns `true` when the active text prompt expects free-form prose /// that can legitimately contain whitespace (the body of a TEXT / /// MTEXT / DDEDIT entity, an attribute default value, etc.). For diff --git a/src/io/mod.rs b/src/io/mod.rs index 1dda8e2b..20458481 100644 --- a/src/io/mod.rs +++ b/src/io/mod.rs @@ -1784,6 +1784,7 @@ fn fix_current_style_names(doc: &mut CadDocument) { doc.header.current_mleader_style_name = v; } } + reflect_sketch_settings(doc); } /// Find the handle of the `DictionaryVariable` registered under `name` in any @@ -1818,6 +1819,32 @@ pub(crate) fn drawing_variable(doc: &CadDocument, name: &str) -> Option vardict_value(doc, name) } +fn reflect_sketch_settings(doc: &mut CadDocument) { + let sketch_type = vardict_value(doc, "SKPOLY") + .and_then(|value| value.parse::().ok()) + .unwrap_or(doc.header.sketch_type) + .clamp(0, 2); + let increment = vardict_value(doc, "SKETCHINC") + .and_then(|value| value.parse::().ok()) + .filter(|value| value.is_finite() && *value > 0.0) + .unwrap_or(doc.header.sketch_increment); + let tolerance = vardict_value(doc, "SKTOLERANCE") + .and_then(|value| value.parse::().ok()) + .filter(|value| value.is_finite() && (0.0..=1.0).contains(value)) + .unwrap_or(doc.header.sketch_tolerance); + doc.header.sketch_type = sketch_type; + doc.header.sketch_increment = if increment.is_finite() && increment > 0.0 { + increment + } else { + 0.1 + }; + doc.header.sketch_tolerance = if tolerance.is_finite() { + tolerance.clamp(0.0, 1.0) + } else { + 0.5 + }; +} + /// Write a drawing variable, creating the variable dictionary and record when /// needed so new drawings preserve the value too. pub(crate) fn set_drawing_variable(doc: &mut CadDocument, name: &str, value: &str) { @@ -1859,6 +1886,31 @@ pub(crate) fn set_drawing_variable(doc: &mut CadDocument, name: &str, value: &st } } +pub(crate) fn set_sketch_settings( + doc: &mut CadDocument, + sketch_type: i16, + increment: f64, + tolerance: f64, +) { + let sketch_type = sketch_type.clamp(0, 2); + let increment = if increment.is_finite() && increment > 0.0 { + increment + } else { + 0.1 + }; + let tolerance = if tolerance.is_finite() { + tolerance.clamp(0.0, 1.0) + } else { + 0.5 + }; + doc.header.sketch_type = sketch_type; + doc.header.sketch_increment = increment; + doc.header.sketch_tolerance = tolerance; + set_drawing_variable(doc, "SKPOLY", &sketch_type.to_string()); + set_drawing_variable(doc, "SKETCHINC", &increment.to_string()); + set_drawing_variable(doc, "SKTOLERANCE", &tolerance.to_string()); +} + /// The layout tab that was active when the drawing was saved — the `CTAB` /// system variable (stored in the variable dictionary as a `DICTIONARYVAR`). /// `None` when the file recorded no current tab. @@ -1919,6 +1971,10 @@ fn sync_current_styles_on_save(doc: &mut CadDocument) { set_drawing_variable(doc, "CMLEADERSTYLE", &mleader); let annotation = doc.header.current_annotation_scale.clone(); set_drawing_variable(doc, "CANNOSCALE", &annotation); + let sketch_type = doc.header.sketch_type; + let increment = doc.header.sketch_increment; + let tolerance = doc.header.sketch_tolerance; + set_sketch_settings(doc, sketch_type, increment, tolerance); } // ── Corrupt-entity guard ────────────────────────────────────────────────── diff --git a/src/locale_catalog.rs b/src/locale_catalog.rs index c4ec293b..249e2234 100644 --- a/src/locale_catalog.rs +++ b/src/locale_catalog.rs @@ -2230,6 +2230,19 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static "SETVAR: {name} requires 0, 1, or 2." => Some(("draw", "setvar-frame-range")), "SHEETSET: Sheet Set Manager not yet implemented." => Some(("view", "sheetset-sheet-set-manager-not-yet-implemented")), "SHX" => Some(("properties", "shx")), + "Connect" => Some(("draw", "sketch-connect")), + "Exit" => Some(("draw", "sketch-exit")), + "Increment" => Some(("draw", "sketch-increment")), + "Pen" => Some(("draw", "sketch-pen")), + "Quit" => Some(("draw", "sketch-quit")), + "Record" => Some(("draw", "sketch-record")), + "Stop erasing" => Some(("draw", "sketch-stop-erasing")), + "SKETCH Object type [Line/Polyline/Spline]:" => Some(("draw", "sketch-object-type")), + "SKETCH Record increment <%{value}>:" => Some(("draw", "sketch-record-increment")), + "SKETCH Spline fit tolerance <%{value}>:" => Some(("draw", "sketch-spline-fit-tolerance")), + "SKETCH Erase — move backward over the temporary sketch:" => Some(("draw", "sketch-erase-move-backward")), + "SKETCH (%{type}, increment %{increment}) Pen down — move to sketch:" => Some(("draw", "sketch-pen-down-move")), + "SKETCH (%{type}, increment %{increment}) Pen up — click to lower the pen:" => Some(("draw", "sketch-pen-up-click")), "SKETCH Pen down — move to sketch, click to lift, Enter to record:" => Some(("draw", "sketch-pen-down-move-to-sketch-click-to-lift-e")), "SKETCH Pen up — click to lower the pen, Enter to record:" => Some(("draw", "sketch-pen-up-click-to-lower-the-pen-enter-to")), "SLICE cutting-plane axis [X / Y / Z] (add TOP/BOTTOM by typing):" => Some(("draw", "slice-cutting-plane-axis-x-y-z-add-top-bottom")), diff --git a/src/modules/draw/draw/sketch.rs b/src/modules/draw/draw/sketch.rs index cc56eb51..434a4bfb 100644 --- a/src/modules/draw/draw/sketch.rs +++ b/src/modules/draw/draw/sketch.rs @@ -1,33 +1,19 @@ -// Freehand sketch tool — interactive command. -// -// Command: SKETCH — freehand sketching. A click toggles the pen down/up. While -// the pen is DOWN, moving the cursor records its position into the current -// stroke whenever it has travelled more than a small fixed threshold, and a -// cyan preview wire of the current stroke is shown. A click while the pen is -// down lifts it, ending the current stroke; the next click starts a fresh one. -// Enter commits every recorded stroke (each stroke of two or more points -// becomes one lightweight polyline of straight segments); Esc cancels. -// -// Geometry is built like the wide-line / ring tools: each stroke is one -// LwPolyline whose vertices carry the sampled XY positions and whose elevation -// is the first sample's Z. +// Freehand sketch tool. -use acadrust::entities::{LwPolyline, LwVertex}; +use acadrust::entities::{Line, LwPolyline, LwVertex, Spline}; use acadrust::types::{Vector2, Vector3}; use acadrust::EntityType; -use crate::t; - -use crate::command::{CadCommand, CmdResult, WorkingPlane}; -use crate::modules::{IconKind, ModuleEvent, ToolDef}; -use crate::scene::model::wire_model::WireModel; +use cadkernel::geom2d::NurbsCurve; use glam::DVec3; -// Minimum cursor travel (drawing units) before a new sample is recorded. -const SAMPLE_EPSILON: f64 = 0.5; +use crate::command::{CadCommand, CmdOption, CmdResult, WorkingPlane}; +use crate::modules::{IconKind, ModuleEvent, ToolDef}; +use crate::scene::model::wire_model::WireModel; +use crate::t; -// ── Ribbon definition ───────────────────────────────────────────────────── +const MIN_INCREMENT: f64 = 1.0e-9; -#[allow(dead_code)] // ribbon definition ready for wiring; command works via the command line +#[allow(dead_code)] pub fn tool() -> ToolDef { ToolDef { id: "SKETCH", @@ -37,54 +23,336 @@ pub fn tool() -> ToolDef { } } -// ── Command implementation ──────────────────────────────────────────────── +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum SketchType { + Line, + Polyline, + Spline, +} + +impl SketchType { + fn from_header(value: i16) -> Self { + match value { + 0 => Self::Line, + 2 => Self::Spline, + _ => Self::Polyline, + } + } + + fn header_value(self) -> i16 { + match self { + Self::Line => 0, + Self::Polyline => 1, + Self::Spline => 2, + } + } + + fn label(self) -> &'static str { + match self { + Self::Line => "Line", + Self::Polyline => "Polyline", + Self::Spline => "Spline", + } + } + + fn parse(value: &str) -> Option { + match value.trim().to_ascii_uppercase().as_str() { + "L" | "LINE" => Some(Self::Line), + "P" | "PLINE" | "POLYLINE" => Some(Self::Polyline), + "S" | "SPLINE" => Some(Self::Spline), + _ => None, + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum InputStage { + Drawing, + Type, + Increment, + Tolerance, +} pub struct SketchCommand { - /// All completed strokes plus, while the pen is down, the one being drawn - /// as the last element. Each stroke is its own list of sampled points. strokes: Vec>, - /// True while the pen is down and samples accumulate into the current stroke. pen_down: bool, + erasing: bool, + erase_engaged: bool, + stage: InputStage, + sketch_type: SketchType, + increment: f64, + tolerance: f64, + last_cursor: Option, + last_recorded_endpoint: Option, plane: WorkingPlane, } impl SketchCommand { - pub fn new() -> Self { + pub fn new(sketch_type: i16, increment: f64, tolerance: f64) -> Self { Self { strokes: Vec::new(), pen_down: false, + erasing: false, + erase_engaged: false, + stage: InputStage::Drawing, + sketch_type: SketchType::from_header(sketch_type), + increment: valid_increment(increment), + tolerance: valid_tolerance(tolerance), + last_cursor: None, + last_recorded_endpoint: None, plane: WorkingPlane::default(), } } - /// Build one lightweight polyline from a stroke's sampled points, or `None` - /// when the stroke has too few points to form a segment. - fn build_stroke(&self, points: &[DVec3]) -> Option { - if points.len() < 2 { - return None; + fn begin_stroke(&mut self, point: DVec3) { + self.strokes.push(vec![point]); + self.pen_down = true; + self.erasing = false; + self.erase_engaged = false; + } + + /// Record real pointer positions at the configured minimum spacing. + fn sample_to(&mut self, point: DVec3) { + let Some(stroke) = self.strokes.last_mut() else { + return; + }; + let Some(cursor) = stroke.last().copied() else { + stroke.push(point); + return; + }; + let distance = cursor.distance(point); + if distance.is_finite() && distance + 1.0e-12 >= self.increment { + stroke.push(point); } - let points: Vec = points + } + + fn finish_stroke(&mut self, point: DVec3) { + self.sample_to(point); + if let Some(stroke) = self.strokes.last_mut() { + if stroke + .last() + .is_some_and(|last| last.distance(point) > 1.0e-9) + { + stroke.push(point); + } + } + self.pen_down = false; + } + + fn lift_pen(&mut self) { + if !self.pen_down { + return; + } + if let Some(cursor) = self.last_cursor { + self.finish_stroke(cursor); + } else { + self.pen_down = false; + } + } + + fn connect(&mut self) { + self.lift_pen(); + let endpoint = self + .strokes + .iter() + .rev() + .find_map(|stroke| stroke.last().copied()) + .or(self.last_recorded_endpoint); + if let Some(endpoint) = endpoint { + self.begin_stroke(endpoint); + } + } + + /// Erasing is armed when the pointer reaches the latest temporary sample. + /// Moving back over the recorded path then removes samples one by one. + fn erase_at(&mut self, point: DVec3) { + let threshold = (self.increment * 0.45).max(1.0e-6); + loop { + let Some(stroke) = self.strokes.last_mut() else { + self.erase_engaged = false; + break; + }; + let Some(last) = stroke.last().copied() else { + self.strokes.pop(); + continue; + }; + if !self.erase_engaged { + if last.distance(point) <= threshold { + self.erase_engaged = true; + } else { + break; + } + } + if last.distance(point) <= threshold { + stroke.pop(); + if stroke.is_empty() { + self.strokes.pop(); + self.erase_engaged = false; + } + } else { + break; + } + } + } + + fn fit_curve(&self, points: &[DVec3]) -> Option<(NurbsCurve, f64)> { + let local: Vec = points .iter() .map(|point| self.plane.to_local(*point)) .collect(); - let elevation = points[0].z; - let mut pl = LwPolyline::new(); - pl.is_closed = false; - pl.elevation = elevation; - pl.vertices = points - .iter() - .map(|p| LwVertex::new(Vector2::new(p.x, p.y))) - .collect(); - pl.normal = Vector3::UNIT_Z; - Some(self.plane.place_entity(EntityType::LwPolyline(pl))) + let elevation = local.first()?.z; + let points: Vec<[f64; 2]> = local.iter().map(|point| [point.x, point.y]).collect(); + NurbsCurve::fit_polyline(&points, self.fit_distance()) + .map(|curve| (curve, elevation)) } - /// All strokes that have enough points, as committable entities. - fn build_all(&self) -> Vec { - self.strokes + fn fit_distance(&self) -> f64 { + self.tolerance * self.increment + } + + fn build_stroke(&self, points: &[DVec3]) -> Vec { + if points.len() < 2 { + return Vec::new(); + } + let local: Vec = points .iter() - .filter_map(|stroke| self.build_stroke(stroke)) - .collect() + .map(|point| self.plane.to_local(*point)) + .collect(); + match self.sketch_type { + SketchType::Line => local + .windows(2) + .map(|pair| { + self.plane.place_entity(EntityType::Line(Line::from_points( + to_vector3(pair[0]), + to_vector3(pair[1]), + ))) + }) + .collect(), + SketchType::Polyline => { + let mut polyline = LwPolyline::new(); + polyline.elevation = local[0].z; + polyline.normal = Vector3::UNIT_Z; + polyline.vertices = local + .iter() + .map(|point| LwVertex::new(Vector2::new(point.x, point.y))) + .collect(); + vec![self + .plane + .place_entity(EntityType::LwPolyline(polyline))] + } + SketchType::Spline => { + let Some((curve, elevation)) = self.fit_curve(points) else { + return Vec::new(); + }; + let mut spline = Spline { + degree: curve.degree() as i32, + knots: curve.knots().to_vec(), + control_points: curve + .control_points() + .iter() + .map(|point| Vector3::new(point[0], point[1], elevation)) + .collect(), + fit_tolerance: self.fit_distance(), + normal: Vector3::UNIT_Z, + ..Default::default() + }; + spline.flags.planar = true; + vec![self.plane.place_entity(EntityType::Spline(spline))] + } + } + } + + fn take_entities(&mut self) -> Vec { + self.lift_pen(); + let mut entities = Vec::new(); + let mut endpoint = None; + for stroke in &self.strokes { + let built = self.build_stroke(stroke); + if !built.is_empty() { + endpoint = stroke.last().copied(); + entities.extend(built); + } + } + if !entities.is_empty() { + self.last_recorded_endpoint = endpoint; + } + self.strokes.clear(); + self.pen_down = false; + self.erasing = false; + self.erase_engaged = false; + entities + } + + fn preview(&self, cursor: Option) -> Option { + let mut combined = Vec::<[f64; 3]>::new(); + for (index, stroke) in self.strokes.iter().enumerate() { + let mut points = stroke.clone(); + if self.pen_down && index + 1 == self.strokes.len() { + if let Some(cursor) = cursor { + if points + .last() + .is_some_and(|last| last.distance(cursor) > 1.0e-9) + { + points.push(cursor); + } + } + } + if points.len() < 2 { + continue; + } + let display = if self.sketch_type == SketchType::Spline { + match self.fit_curve(&points) { + Some((curve, elevation)) => curve + .tessellate(16) + .into_iter() + .map(|point| { + self.plane + .to_world(DVec3::new(point[0], point[1], elevation)) + }) + .collect(), + None => points, + } + } else { + points + }; + if display.len() < 2 { + continue; + } + if !combined.is_empty() { + combined.push([f64::NAN; 3]); + } + combined.extend(display.into_iter().map(|point| [point.x, point.y, point.z])); + } + (combined.len() >= 2).then(|| { + WireModel::solid_f64("sketch_preview".to_string(), combined, WireModel::CYAN, false) + }) + } + + fn toggle_pen(&mut self) { + if self.pen_down { + self.lift_pen(); + } else if let Some(cursor) = self.last_cursor { + self.begin_stroke(cursor); + } + } + + fn drawing_options(&self) -> Vec { + let mut options = vec![ + CmdOption::new("Pen", "PEN"), + CmdOption::new("Type", "TYPE"), + CmdOption::new("Increment", "INCREMENT"), + ]; + if self.sketch_type == SketchType::Spline { + options.push(CmdOption::new("Tolerance", "TOLERANCE")); + } + options.extend([ + CmdOption::new("Record", "RECORD"), + CmdOption::new(if self.erasing { "Stop erasing" } else { "Erase" }, "ERASE"), + CmdOption::new("Connect", "CONNECT"), + CmdOption::new("Exit", "EXIT"), + CmdOption::new("Quit", "QUIT"), + ]); + options } } @@ -98,72 +366,227 @@ impl CadCommand for SketchCommand { } fn prompt(&self) -> String { - if self.pen_down { - t!("SKETCH Pen down — move to sketch, click to lift, Enter to record:").into_owned() - } else { - t!("SKETCH Pen up — click to lower the pen, Enter to record:").into_owned() + match self.stage { + InputStage::Type => t!("SKETCH Object type [Line/Polyline/Spline]:").into_owned(), + InputStage::Increment => t!( + "SKETCH Record increment <%{value}>:", + value = self.increment + ) + .into_owned(), + InputStage::Tolerance => t!( + "SKETCH Spline fit tolerance <%{value}>:", + value = self.tolerance + ) + .into_owned(), + InputStage::Drawing if self.erasing => { + t!("SKETCH Erase — move backward over the temporary sketch:").into_owned() + } + InputStage::Drawing if self.pen_down => t!( + "SKETCH (%{type}, increment %{increment}) Pen down — move to sketch:", + type = t!(self.sketch_type.label()), + increment = self.increment + ) + .into_owned(), + InputStage::Drawing => t!( + "SKETCH (%{type}, increment %{increment}) Pen up — click to lower the pen:", + type = t!(self.sketch_type.label()), + increment = self.increment + ) + .into_owned(), } } - fn on_point(&mut self, pt: DVec3) -> CmdResult { - if self.pen_down { - // Lift the pen: the current stroke is finished. A new click later - // begins a fresh stroke. - self.pen_down = false; + fn options(&self) -> Vec { + match self.stage { + InputStage::Drawing => self.drawing_options(), + InputStage::Type => vec![ + CmdOption::new("Line", "LINE"), + CmdOption::new("Polyline", "POLYLINE"), + CmdOption::new("Spline", "SPLINE"), + ], + InputStage::Increment | InputStage::Tolerance => Vec::new(), + } + } + + fn on_point(&mut self, point: DVec3) -> CmdResult { + self.last_cursor = Some(point); + if self.stage != InputStage::Drawing { + return CmdResult::NeedPoint; + } + if self.erasing { + self.erasing = false; + self.erase_engaged = false; + } else if self.pen_down { + self.finish_stroke(point); } else { - // Lower the pen: start a new stroke seeded with this point. - self.strokes.push(vec![pt]); - self.pen_down = true; + self.begin_stroke(point); } CmdResult::NeedPoint } fn on_enter(&mut self) -> CmdResult { - let entities = self.build_all(); - match entities.len() { - 0 => CmdResult::Cancel, - 1 => CmdResult::CommitAndExit(entities.into_iter().next().unwrap()), - _ => CmdResult::ReplaceMany(vec![], entities), + if self.stage != InputStage::Drawing { + self.stage = InputStage::Drawing; + return CmdResult::NeedPoint; + } + let entities = self.take_entities(); + if entities.is_empty() { + CmdResult::Cancel + } else { + CmdResult::CommitEntitiesAndExit(entities) } } fn on_escape(&mut self) -> CmdResult { + self.strokes.clear(); CmdResult::Cancel } - fn on_mouse_move(&mut self, pt: DVec3) -> Option { - if !self.pen_down { - return None; - } - // The current stroke is the last one pushed while the pen is down. - let stroke = self.strokes.last_mut()?; - // Record the sample only once it has moved past the threshold from the - // last recorded point, so the polyline isn't flooded with near-duplicate - // vertices. - let record = match stroke.last() { - Some(last) => last.distance(pt) > SAMPLE_EPSILON, - None => true, - }; - if record { - stroke.push(pt); - } - // Preview the current stroke, including the (possibly unrecorded) cursor - // position so the line tracks the pointer smoothly. - let mut pts: Vec<[f64; 3]> = stroke.iter().map(|p| [p.x, p.y, p.z]).collect(); - if !record { - pts.push([pt.x, pt.y, pt.z]); - } - if pts.len() < 2 { - return None; - } - Some(WireModel::solid_f64( - "rubber_band".to_string(), - pts, - WireModel::CYAN, - false, + fn wants_text_input(&self) -> bool { + true + } + + fn point_step_accepts_keywords(&self) -> bool { + self.stage == InputStage::Drawing + } + + fn sketch_settings(&self) -> Option<(i16, f64, f64)> { + Some(( + self.sketch_type.header_value(), + self.increment, + self.tolerance, )) } + + fn on_text_input(&mut self, text: &str) -> Option { + let input = text.trim(); + if input.is_empty() { + return None; + } + match self.stage { + InputStage::Type => { + if let Some(sketch_type) = SketchType::parse(input) { + self.sketch_type = sketch_type; + self.stage = InputStage::Drawing; + } + Some(CmdResult::NeedPoint) + } + InputStage::Increment => { + if let Ok(value) = input.parse::() { + if value.is_finite() && value > 0.0 { + self.increment = value.max(MIN_INCREMENT); + self.stage = InputStage::Drawing; + } + } + Some(CmdResult::NeedPoint) + } + InputStage::Tolerance => { + if let Ok(value) = input.parse::() { + if value.is_finite() && (0.0..=1.0).contains(&value) { + self.tolerance = value; + self.stage = InputStage::Drawing; + } + } + Some(CmdResult::NeedPoint) + } + InputStage::Drawing => { + let keyword = input.to_ascii_uppercase(); + let result = match keyword.as_str() { + "P" | "PEN" => { + self.toggle_pen(); + CmdResult::NeedPoint + } + "T" | "TYPE" => { + self.lift_pen(); + self.erasing = false; + self.stage = InputStage::Type; + CmdResult::NeedPoint + } + "I" | "INCREMENT" => { + self.lift_pen(); + self.erasing = false; + self.stage = InputStage::Increment; + CmdResult::NeedPoint + } + "TO" | "TOLERANCE" if self.sketch_type == SketchType::Spline => { + self.lift_pen(); + self.erasing = false; + self.stage = InputStage::Tolerance; + CmdResult::NeedPoint + } + "R" | "RECORD" => { + let entities = self.take_entities(); + if entities.is_empty() { + CmdResult::NeedPoint + } else { + CmdResult::CommitEntities(entities) + } + } + "E" | "ERASE" => { + self.lift_pen(); + self.erasing = !self.erasing; + self.erase_engaged = false; + CmdResult::NeedPoint + } + "C" | "CONNECT" => { + self.connect(); + CmdResult::NeedPoint + } + "X" | "EXIT" => { + let entities = self.take_entities(); + if entities.is_empty() { + CmdResult::Cancel + } else { + CmdResult::CommitEntitiesAndExit(entities) + } + } + "Q" | "QUIT" => { + self.strokes.clear(); + CmdResult::Cancel + } + _ => { + if let Some(sketch_type) = SketchType::parse(&keyword) { + self.sketch_type = sketch_type; + } + CmdResult::NeedPoint + } + }; + Some(result) + } + } + } + + fn on_mouse_move(&mut self, point: DVec3) -> Option { + self.last_cursor = Some(point); + if self.stage == InputStage::Drawing { + if self.pen_down { + self.sample_to(point); + } else if self.erasing { + self.erase_at(point); + } + } + self.preview(Some(point)) + } } -// ── Autocomplete registry ───────────────────────────────── -inventory::submit!(crate::command::CommandRegistration { names: &["SKETCH"] }); // SketchCommand +fn valid_increment(value: f64) -> f64 { + if value.is_finite() && value > 0.0 { + value.max(MIN_INCREMENT) + } else { + 0.1 + } +} + +fn valid_tolerance(value: f64) -> f64 { + if value.is_finite() && (0.0..=1.0).contains(&value) { + value + } else { + 0.5 + } +} + +fn to_vector3(point: DVec3) -> Vector3 { + Vector3::new(point.x, point.y, point.z) +} + +inventory::submit!(crate::command::CommandRegistration { names: &["SKETCH"] });