diff --git a/Cargo.lock b/Cargo.lock index 189c1b223..5f4c4930a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -878,7 +878,7 @@ checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" [[package]] name = "cadkernel" version = "0.1.0" -source = "git+https://github.com/HakanSeven12/cadkernel.git?rev=76de3b3#76de3b35e55a45f3ffb813c499cf3affe176351d" +source = "git+https://github.com/HakanSeven12/cadkernel.git?rev=b2b1d4b#b2b1d4b0cd7def2d26a0d519991fc89a89f916d3" dependencies = [ "acadrust", "cavalier_contours", diff --git a/Cargo.toml b/Cargo.toml index 4cc5ae75c..7254993d8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ rfd = "0.17" clap = { version = "4", features = ["derive"] } env_logger = "0.11" acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "94df2c3", features = ["serde"] } -cadkernel = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "76de3b3", features = ["acis", "offset"] } +cadkernel = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "b2b1d4b", 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/locales/de-DE/opencadstudio.ftl b/locales/de-DE/opencadstudio.ftl index ac4eb8dc8..78ed63e07 100644 --- a/locales/de-DE/opencadstudio.ftl +++ b/locales/de-DE/opencadstudio.ftl @@ -29,8 +29,8 @@ action = ribbon-tab = .draw = Zeichnen .annotate = Beschriften - .insert = Einsatz - .model = Modellierung + .insert = Einfügen + .model = Modell .layout = Layout .manage = Verwalten .view = Ansicht @@ -53,7 +53,7 @@ options = .browser-storage = Browserspeicherung .dark = Dunkel .default-application = Standardanwendung - .iced-theme = Eisthema: + .iced-theme = Iced-Motiv: .theme = Motiv command-line = @@ -68,9 +68,9 @@ start = .new-drawing = Neue Zeichnung .open-file = Datei öffnen… .donate = Spenden - .send-feedback = Feedback senden + .send-feedback = Rückmeldung senden .sponsors = Sponsoren - .tutorials = Lernprogramme + .tutorials = Lerneinheiten .loading-videos = Videos werden geladen… .videos-online = Videos werden aus dem Internet geladen. .open-playlist = Wiedergabeliste auf YouTube öffnen @@ -94,21 +94,21 @@ modal = .keyboard-shortcuts = Tastenkombinationen .command-aliases = Befehlsaliase .find-replace = Suchen und Ersetzen - .plugin-manager = Plug-in-Manager + .plugin-manager = Plug-in-Verwaltung .update-available = Aktualisierung verfügbar - .layer-manager = Layer-Manager - .layer-state-manager = Layerstatus-Manager + .layer-manager = Layer-Verwaltung + .layer-state-manager = Layerstatus-Verwaltung .edit-layer-state = Layerstatus bearbeiten .plot = Plotten - .layout-manager = Layout-Manager - .scale-manager = Maßstabs-Manager + .layout-manager = Layoutverwaltung + .scale-manager = Maßstabsverwaltung .annotation-object-scale = Maßstab des Beschriftungsobjekts .plot-style-editor = Plotstileditor - .text-style-manager = Textstil-Manager - .multiline-style-manager = Mehrfachlinienstil-Manager - .table-style-manager = Tabellenstil-Manager - .multileader-style-manager = Multi-Führungslinienstil-Manager - .dimension-style-manager = Bemaßungsstil-Manager + .text-style-manager = Textstil-Verwaltung + .multiline-style-manager = Mehrfachlinienstil-Verwaltung + .table-style-manager = Tabellenstil-Verwaltung + .multileader-style-manager = Multi-Führungslinienstil-Verwaltung + .dimension-style-manager = Bemaßungsstil-Verwaltung .default-application = Standardanwendung .save-warning = Speicherwarnung .unable-save = Zeichnung kann nicht gespeichert werden @@ -136,13 +136,13 @@ modal = .not-now = Nicht jetzt .open-dwg-and-dxf-drawings-in-open-cad-studio-b = Öffnen Sie .dwg und .dxf Zeichnungen in Open CAD Studio standardmäßig. .path-path = Pfad: __ocs_arg_path__ - .reload-from-disk = Reload von Disk - .save-as = Sparen Sie sich als... - .saving-now-could-destroy-those-external-change = Speichern Sie jetzt diese externen Änderungen zerstören könnte, laden Sie die Festplattenkopie, speichern Sie Ihre lokale Arbeit an anderer Stelle, oder explizit überschreiben. - .the-file-is-open-or-being-used-by-another-appl = Die Datei ist geöffnet oder wird von einer anderen Anwendung verwendet. Schließen Sie sie dort und versuchen Sie es erneut oder speichern Sie diese Zeichnung unter einem anderen Namen. + .reload-from-disk = Von Festplatte neu laden + .save-as = Speichern unter... + .saving-now-could-destroy-those-external-change = Wenn Sie jetzt speichern, könnten diese externen Änderungen verloren gehen. Laden Sie die Festplattenkopie neu, speichern Sie Ihre lokale Arbeit an anderer Stelle oder überschreiben Sie sie ausdrücklich. + .the-file-is-open-or-being-used-by-another-appl = Die Datei ist geöffnet oder wird von einer anderen Anwendung verwendet. Schließen Sie die andere Anwendung und versuchen Sie es erneut oder speichern Sie diese Zeichnung unter einem anderen Namen. .the-selected-layer-state-is-no-longer-availabl = Der ausgewählte Ebenenzustand steht nicht mehr zur Verfügung. .this-drawing-contains-count-aec-civil-objects = - Diese Zeichnung enthält __ocs_arg_count__ AEC/Zivilobjekte, die "__ocs_arg_target__" nicht speichern können, so dass sie nicht gespeichert werden. + Diese Zeichnung enthält __ocs_arg_count__ AEC/Civilobjekte, die "__ocs_arg_target__" nicht speichern können, so dass sie nicht gespeichert werden. Um sie zu behalten, speichern Sie in der Quellversion (__ocs_arg_src_version__). .yes-set-as-default = Ja, Standardwert .has = enthält @@ -157,13 +157,13 @@ clipboard = .occupied = Die Systemzwischenablage wird gerade verwendet. Versuchen Sie es erneut. .conversion-failed = Der Text in der Zwischenablage konnte nicht gelesen werden. .copybase-specify-base-point = COPYBASE Basispunkt angeben: - .copy-to-clipboard = Kopie an Clipboard + .copy-to-clipboard = Kopie in die Zwischenablage .cut = Ausschneiden - .cut-to-clipboard = Zugeschnitten auf Clipboard - .pasteclip-pick-insertion-point = PASTECLIP Einsteckpunkt: - .paste-as-block = Paste als Block - .paste-from-clipboard = Paste aus Clipboard - .paste-to-original-coordinates = Paste zu Originalkoordinaten + .cut-to-clipboard = Ausschnitt in die Zwischenablage + .pasteclip-pick-insertion-point = PASTECLIP Einfügepunkt: + .paste-as-block = Einfügen als Block + .paste-from-clipboard = Einfügen aus der Zwischenablage + .paste-to-original-coordinates = Einfügen auf Originalkoordinaten viewport = .split-vertical = Ansichtsfenster vertikal teilen @@ -189,7 +189,7 @@ recovery = .show-log = Wiederherstellungsprotokoll anzeigen .save-new-file-required = Eine reparierte Zeichnung muss als neue Datei gespeichert werden. .prompt-heading = Normales Öffnen ist fehlgeschlagen - .prompt-description = Die Zeichnung wurde nicht verändert. Der Wiederherstellungsmodus kann versuchen, verwendbare Daten zu retten; beschädigte Datensätze können dabei übersprungen werden. Möchten Sie fortfahren? + .prompt-description = Die Zeichnung wurde nicht verändert. Der Wiederherstellungsmodus kann versuchen verwendbare Daten zu retten; beschädigte Datensätze können dabei übersprungen werden. Möchten Sie fortfahren? .attempt = Wiederherstellung versuchen .decline = Abbrechen @@ -256,14 +256,14 @@ common = .value-2nd-arrowhead-dimblk2 = 2. Pfeilspitze (DIMBLK2) .value-3d-wireframe = 3D-Drahtmodell .value-3dalign-aligned-the-solid-to-the-destination-p = 3DALIGN: Ausrichtung des Festkörpers auf die Zielpunkte. - .value-3dalign-each-point-triple-must-be-non-coincide = 3DALIGN: Jeder Punkt dreifach muss nicht zusammenfallend und nicht kolinear sein. - .value-3dalign-select-exactly-one-solid-created-this = 3DALIGN: Wählen Sie genau einen Solid, der diese Sitzung erstellt hat. - .value-3dmirror-added-a-mirror-across-the-plane = 3DMIRROR: Ein Spiegel über der Ebene __ocs_fmt_0__ wurde hinzugefügt. - .value-3dmirror-could-not-determine-the-solid-s-exten = 3DMIRROR: konnte die Ausdehnung des Feststoffs nicht bestimmen. - .value-3dmirror-select-exactly-one-solid-created-this = 3DMIRROR: Wählen Sie genau einen Solid, der diese Sitzung erstellt hat. - .value-3drotate-could-not-determine-the-solid-s-exten = 3DROTATE: konnte die Ausdehnung des Feststoffs nicht bestimmen. + .value-3dalign-each-point-triple-must-be-non-coincide = 3DALIGN: Jedes Punkte-Triple muss nicht zusammenfallend und nicht kolinear sein. + .value-3dalign-select-exactly-one-solid-created-this = 3DALIGN: Wählen Sie genau einen erstellten Volumenkörper. + .value-3dmirror-added-a-mirror-across-the-plane = 3DMIRROR: Eine Spiegelung über der Ebene __ocs_fmt_0__ wurde hinzugefügt. + .value-3dmirror-could-not-determine-the-solid-s-exten = 3DMIRROR: konnte die Ausdehnung des Volumenkörpers nicht bestimmen. + .value-3dmirror-select-exactly-one-solid-created-this = 3DMIRROR: Wählen Sie genau einen erstellten Volumenkörper. + .value-3drotate-could-not-determine-the-solid-s-exten = 3DROTATE: konnte die Ausdehnung des Volumenkörpers nicht bestimmen. .value-3drotate-rotated-angle-deg-about-the-axis = 3DROTATE: gedreht __ocs_fmt_0__° um die Achse __ocs_fmt_1__. - .value-3drotate-select-exactly-one-solid-created-this = 3DROTATE: Wählen Sie genau einen Solid, der diese Sitzung erstellt hat. + .value-3drotate-select-exactly-one-solid-created-this = 3DROTATE: Wählen Sie genau einen erstellten Volumenkörper. .align-applied = ALIGN: angewendet. .above = Über .accept = Akzeptieren @@ -274,26 +274,26 @@ common = .alignment-title-case = Ausrichtung .all = Alle .alternate = Alternativ - .alternate-units = Wechseleinheiten + .alternate-units = Alternative Einheiten .angle = Winkel .angular-decimals-dimadec = Winkeldezimalstellen (DIMADEC) .angular-unit-dimaunit = Winkeleinheit (DIMAUNIT) .angular-zero-supp-dimazin = Winkelnullpunkt supp (DIMAZIN) .annotation = Anmerkung - .annotative = Anmerkung + .annotative = Erläuternd .apparent-intersection = Sichtbarer Schnitt .apply = Beantragen .apply-cell = Anwendungszelle - .apply-margins = Anwendung von Einschusszahlungen + .apply-margins = Ränder anwenden .arc = Bogen .arch = Bogen - .architectural = Architektur + .architectural = Architektonisch .area = Fläche .arrow-size = Pfeilgröße .arrowhead = Pfeilkopf .arrowhead-size = Größe des Pfeilkopfes .as-displayed = Wie dargestellt - .attachment-direction = Anlagerichtung: + .attachment-direction = Richtung der Anlage: .attribute = Attribut .attributes = Attribute .auto = auto @@ -315,19 +315,19 @@ common = .blocks = Blöcke .blue = Blau .bulge = Wölbung - .boolean-failed-the-solids-may-not-overlap = Boolean versagte - die Feststoffe überlappen sich möglicherweise nicht. - .boolean-select-exactly-two-solids-created-this = Boolean: Wählen Sie genau zwei Feststoffe erstellt diese Sitzung. + .boolean-failed-the-solids-may-not-overlap = Boolean versagte - die Volumenkörper überlappen sich möglicherweise nicht. + .boolean-select-exactly-two-solids-created-this = Boolean: Wählen Sie genau zwei erstellte Volumenkörper. .bottom = Unten .bottom-center-title-case = Unten zentriert .bottom-left-title-case = Unten links .bottom-right-title-case = Unten rechts - .bottom-attachment = Bodenbefestigung: - .bottom-center-sentence-case = untere Mitte + .bottom-attachment = Befestigung unten: + .bottom-center-sentence-case = Unten mittig .bottom-left-sentence-case = Unten links - .bottom-right-sentence-case = Rechts unten rechts - .bottomcenter = untere Mitte + .bottom-right-sentence-case = Unten rechts + .bottomcenter = Untere Mitte .bottomleft = Unten links - .bottomright = Rechts unten rechts + .bottomright = Unten rechts .box = Quader .break = Bruch .brightness = Helligkeit @@ -335,42 +335,42 @@ common = .byblock = Block .bylayer = ByLayer .convtosurface-converted-n-solid-s-to-surface-s = CONVTOSURFACE: Umgerechnet __ocs_fmt_0__ Feststoff(e) in Oberfläche(n). - .convtosurface-select-a-solid-created-this-sess = CONVTOSURFACE: Wählen Sie einen Solid, der diese Sitzung erstellt hat. + .convtosurface-select-a-solid-created-this-sess = CONVTOSURFACE: Wählen Sie einen erstellten Volumenkörper. .coords-mode-label = COORDS = __ocs_fmt_0__ (__ocs_fmt_1__) .copy-specify-destination-count-copies-so-far-a = COPY Ziel angeben [bisher __ocs_arg_count__ Kopien | Reihe | Eingabe=Fertig | Basis __ocs_arg_bx__,__ocs_arg_by__]: .cancel = Abbrechen - .cancel-active-command = Aktives Kommando abbrechen - .cannot-delete-a-style-that-is-current-or-in-us = Kann einen Stil, der aktuell oder in Gebrauch ist, nicht löschen. - .cannot-delete-the-model-layout = Das Layout des Modells kann nicht gelöscht werden. + .cancel-active-command = Aktiven Befehl abbrechen + .cannot-delete-a-style-that-is-current-or-in-us = Der Stil ist aktuell in Verwendung und kann nicht gelöscht werden. + .cannot-delete-the-model-layout = Das Modell-Layout kann nicht gelöscht werden. .cannot-delete-the-standard-style = Der Standardstil kann nicht gelöscht werden. - .cannot-rename-the-model-layout = Kann das Layout des Modells nicht umbenennen. - .cannot-rename-the-standard-style = Kann den Standard-Stil nicht umbenennen. + .cannot-rename-the-model-layout = Das Modell-Layout kann nicht umbenannt werden. + .cannot-rename-the-standard-style = Der Standard-Stil kann nicht umbenannt werden. .catchment-tagged-successfully = Catchment erfolgreich verschlagwortet. .center = Mitte .center-mark-dimcen = Mittelmarke (DIMCEN) .centered = Zentriert .centimeters = Zentimeter - .chamfer = Scheide + .chamfer = Fase .changelog = Änderungsprotokoll - .changing-a-base-colour-switches-to-custom-iced = Durch Wechseln einer Grundfarbe wird auf Custom umgestellt. Iced erzeugt aus diesen sechs Farben jeden Farbton. + .changing-a-base-colour-switches-to-custom-iced = Durch Wechseln einer Grundfarbe wird auf benutzerdefiniert umgestellt. Iced erzeugt aus diesen sechs Farben jeden Farbton. .circle = Kreis .class-version = Klassenversion .clear = Klar - .clear-all = Alles klar + .clear-all = Alles löschen .clip = Zuschneiden .clipboard = Zwischenablage .clipboard-is-empty = Die Zwischenablage ist leer. .close = Schließen .closed-filled = Geschlossen gefüllt .color = Farbe - .command-cancelled-because-the-active-drawing-s = Befehl abgesagt, weil sich der aktive Zeichenraum geändert hat. - .command-history-navigation = Durch den Befehlsverlauf navigieren - .command-label-title-case = Kommando: + .command-cancelled-because-the-active-drawing-s = Befehl abgebrochen, weil sich der aktive Zeichenraum geändert hat. + .command-history-navigation = Navigation im Befehlsverlauf + .command-label-title-case = Befehl: .conceptual = Konzeptionell - .cone = Zapfen + .cone = Kegel .content = Inhalt .content-type = Inhaltsart: - .continue = Weiterfahren + .continue = Fortsetzen .contrast = Kontrast .coordinates = Koordinaten .copied-path = Kopierter Pfad: __ocs_fmt_0__ @@ -387,7 +387,7 @@ common = .cyan = Cyan .cylinder = Zylinder .dataextraction-rows-rows-fname = DATAEXTRACTION __ocs_fmt_0__ rows → "__ocs_fmt_1__" - .dataextraction-write-failed-e = DATAEXTRACTION: Schreibe fehlgeschlagen: __ocs_fmt_0__ + .dataextraction-write-failed-e = DATAEXTRACTION: Schreiben fehlgeschlagen: __ocs_fmt_0__ .ddedit-entity-type-not-supported = DDEDIT: Objekttyp wird nicht unterstützt. .ddedit-text-updated = DDEDIT: Text aktualisiert. .dimbreak-not-yet-implemented-nothing-changed = DIMBREAK: noch nicht umgesetzt – nichts hat sich geändert. @@ -406,7 +406,7 @@ common = .decimals-dimdec = Dezimalstellen (DIMDEC) .default-title-case = Standard .default-text = Standardtext: - .deg-min-sec = Grad/Min/sec + .deg-min-sec = Grad/Min/Sek .delete = Löschen .delete-selected-entities = Ausgewählte Objekte löschen .description-title-case = Beschreibung @@ -415,23 +415,23 @@ common = .dim-style = Bemaßungsstil .dimension = Bemaßung .dimensions = Bemaßungen - .discard = Rückwurf + .discard = Verwerfen .discussions = Diskussionen - .discussions-load-from-github = Diskussionslast von GitHub. + .discussions-load-from-github = Diskussionen von GitHub laden. .display = Anzeige - .distance = Distanz + .distance = Entfernung .donate = Spenden .done = Fertig .double = Doppelt .draft = Entwurf .draw = Zeichnen .drawing = Zeichnung - .drawing-units = Zeichenwerke - .drop-0-ft-keep-0-in = Unterdrücken Sie Null Fuß, halten Sie Null Zoll + .drawing-units = Zeichnungseinheiten + .drop-0-ft-keep-0-in = Unterdrücken Sie Null Fuß, behalten Sie Null Zoll .dynamic = Dynamik .extrude-could-not-build-profile-select-a-close = EXTRUDE: konnte kein Profil erstellen. Wählen Sie eine geschlossene 2D-Entität (Circle, LwPolyline, etc.). .extrude-entity-not-found = EXTRUDE: Objekt nicht gefunden. - .extrude-solid-created = EXTRUDE: solide erstellt. + .extrude-solid-created = EXTRUDE: Volumenkörper erstellt. .edge = Rand .editing-selected = Bearbeiten: __ocs_arg_selected__ .elements = Elemente: @@ -442,37 +442,37 @@ common = .engineering = Technisch .enter = Eingabetaste .expected-coordinates-x-y-or-a-number-got-text = Erwartete Koordinaten (x,y) oder eine Zahl, erhalten: "__ocs_fmt_0__" - .extension = Verlängerung + .extension = Erweiterung .extension-dimdle = Erweiterung (DIMDLE) .extension-dimexe = Erweiterung (DIMEXE) - .extents = Umfang + .extents = Ausmaße .find-replace-replaced-1-occurrence-of-search = FIND/REPLACE: ersetzt 1 Vorkommen von "__ocs_fmt_0__". .find-replace-replaced-replaced-occurrence-s-of = FIND/REPLACE: ersetzt durch __ocs_fmt_0__ Vorkommen von "__ocs_fmt_1__". .flatshot-created-n-projected-edge-s-at-z-0 = FLATSHOT: erstellt __ocs_fmt_0__ projizierte Kante(n) bei Z=0. - .flatshot-select-a-solid-created-this-session = FLATSHOT: Wählen Sie einen Solid, der diese Sitzung erstellt hat. - .fade = Ausblendung + .flatshot-select-a-solid-created-this-session = FLATSHOT: Wählen Sie einen erstellten Volumenkörper. + .fade = Abblendung .failed-to-create-layout-e = Fehler beim Erstellen des Layouts: __ocs_fmt_0__ - .feet = Füße + .feet = Fuß .fence = Zaun .file-locations-are-unavailable-in-the-web-appl = Dateispeicherorte sind in der Webanwendung nicht verfügbar. .fill-color-sentence-case = Füllfarbe .fill-color-aci-dimtfillclr = Farbe ausfüllen ACI (DIMTFILLCLR) - .fillet = Filet - .finalize-command-repeat-last = Befehl finalisieren / Last Repeat - .finalizing = Finalisierung... - .find-and-replace = Finden und Ersetzen - .first-seg-angle = erster Nähwinkel: - .fit = Anpassen + .fillet = Hohlkehle + .finalize-command-repeat-last = Befehl abschließen / Letzten wiederholen + .finalizing = Fertigstellung... + .find-and-replace = Suchen und Ersetzen + .first-seg-angle = erster Segmentwinkel: + .fit = Einpassen .fit-dimatfit = Einpassen (DIMATFIT) .fit-legacy-dimfit = Einpassen (veraltet: DIMFIT) .fixed-height = Feste Höhe: .fixed-length-dimfxl = Feste Länge (DIMFXL) .fixed-length-ext-lines-dimfxlon = Länge der Ext-Linien (DIMFXLON) - .flat-shaded = Flach beschattet + .flat-shaded = Flach schattiert .flat-shaded-edges = Flache schattiert + Kanten - .flow-direction = Strömungsrichtung: - .font-load-failed-script-e = Schriftladefehler (__ocs_fmt_0__): __ocs_fmt_1__ - .force-text-inside-dimtix = Text drinnen erzwingen (DIMTIX) + .flow-direction = Fließrichtung: + .font-load-failed-script-e = Ladefehler Schrift (__ocs_fmt_0__): __ocs_fmt_1__ + .force-text-inside-dimtix = Text innen erzwingen (DIMTIX) .format = Format .format-dimlunit = Format (DIMLUNIT) .fraction-format-dimfrac = Format der Fraktion (DIMFRAC) @@ -501,7 +501,7 @@ common = .height-dimtxt = Höhe (DIMTXT) .helix = Helix .hidden = Verdeckt - .hidden-line = Versteckte Linie + .hidden-line = Verdeckte Linie .high = hoch .horizontal = Horizontal .horizontal-inside-dimtih = Horizontal innen (DIMTIH) @@ -513,25 +513,25 @@ common = .importobj-imported-file-stem-as-mesh = IMPORTOBJ: importiertes "__ocs_fmt_0__" als mesh. .importobj-target-drawing-was-closed = IMPORTOBJ: Zielzeichnung wurde geschlossen. .importobj-error = IMPORTOBJ: __ocs_fmt_0__ - .interfere-created-an-interference-solid-from-t = INTERFERE: erzeugte einen Interferenzkörper aus der Überlappung. - .interfere-select-exactly-two-solids-created-th = INTERFERE: Wählen Sie genau zwei Festkörper aus, die diese Sitzung erstellt haben. - .interfere-the-selected-solids-do-not-overlap = INTERFERE: die ausgewählten Feststoffe überlappen sich nicht. - .import = Einfuhr + .interfere-created-an-interference-solid-from-t = INTERFERE: erzeugte einen Schnittkörper aus der Überlappung. + .interfere-select-exactly-two-solids-created-th = INTERFERE: Wählen Sie genau zwei erstellte Volumenkörper aus. + .interfere-the-selected-solids-do-not-overlap = INTERFERE: die ausgewählten Volumenkörper überlappen sich nicht. + .import = Import .inches = Zoll .index = Index - .insert = Einsatz + .insert = Einfügen .insert-block = Block einfügen - .insertion = Einsetzen + .insertion = Einfügung .installed = Installiert .intersection = Schnittpunkt .invert-selection-object-s-selected = Invertierte Auswahl: __ocs_fmt_0__ Objekt(e) ausgewählt. .invisible = Unsichtbar .jis = JIS .join-count-in-object-s-joined-into-count-out = JOIN: __ocs_fmt_0__ Objekt(e), das zu __ocs_fmt_1__ verbunden ist. - .jog-angle-dimjogang = Jogwinkel ° (DIMJOGANG) - .join = Beitritt + .jog-angle-dimjogang = Schrägwinkel ° (DIMJOGANG) + .join = Verbindung .keep-0-ft-drop-0-in = Halten Sie Null Fuß, unterdrücken Sie Null Zoll - .keep-recent-files = Bewahren Sie aktuelle Dateien auf + .keep-recent-files = Erhalten Sie aktuelle Dateien .kilometers = Kilometer .layerstate-deleted-name = LAYERSTATE: gelöscht "__ocs_fmt_0__". .layerstate-restored-name-restored-layer-s = LAYERSTATE: wiederhergestellt "__ocs_fmt_0__" (__ocs_fmt_1__ Layer(s)). @@ -540,8 +540,8 @@ common = .layout-e = LAYOUT: __ocs_fmt_0__ .lengthen-applied = LENGTHEN: angewendet. .lengthen-entity-type-not-supported = LENGTHEN: Objekttyp wird nicht unterstützt. - .loft-could-not-loft-profiles-ensure-sections-h = LOFT: konnte keine Profile hochladen. Stellen Sie sicher, dass Abschnitte die gleiche Kantenzahl haben und kompatibel sind. - .loft-solid-created-from-profiles = LOFT: Solid erstellt aus __ocs_fmt_0__ Profilen. + .loft-could-not-loft-profiles-ensure-sections-h = LOFT: konnte keine Profile hochladen. Stellen Sie sicher, dass Schnitte die gleiche Kantenzahl haben und kompatibel sind. + .loft-solid-created-from-profiles = LOFT: Volumenkörper aus __ocs_fmt_0__ Profilen erstellt. .landing-distance-sentence-case = Anlandestrecke .landing-distance-label-sentence-case = Landestrecke: .landing-gap-sentence-case = Landelücke @@ -550,25 +550,25 @@ common = .language-title-case = Sprache .language-label-title-case = Sprache: .layer = Ebene - .layer-matched-to-layer = Layer mit "__ocs_fmt_0__" abgestimmt. + .layer-matched-to-layer = Ebene mit "__ocs_fmt_0__" abgestimmt. .layer-state-name-already-exists = Der Ebenenzustand "__ocs_fmt_0__" existiert bereits. .layer-state-name-was-not-found = Ebenenzustand "__ocs_fmt_0__" wurde nicht gefunden. - .layer-state-name-cannot-be-empty = Der Name des Layer-Status kann nicht leer sein. - .layers = Layer + .layer-state-name-cannot-be-empty = Der Name des Ebenen-Status kann nicht leer sein. + .layers = Ebenen .layout = Layout .layout-name-silindi = Layout „__ocs_fmt_0__“ gelöscht .layout-new-name-created-use-mview-to-add-a-vie = Layout „__ocs_fmt_0__“ erstellt — mit MVIEW kann ein Ansichtsfenster hinzugefügt werden - .layout-orig-new-name = Layout__ocs_fmt_0__" → "__ocs_fmt_1__" - .layout-name-created = Layout '__ocs_fmt_0__' erstellt. - .layout-name-deleted = Layout '__ocs_fmt_0__' gelöscht. - .layout-manager = Layout-Manager + .layout-orig-new-name = Layout „__ocs_fmt_0__“ → „__ocs_fmt_1__“ + .layout-name-created = Layout „__ocs_fmt_0__“ erstellt. + .layout-name-deleted = Layout „__ocs_fmt_0__“ gelöscht. + .layout-manager = Layoutverwaltung .layout-name-cannot-be-empty = Der Layoutname kann nicht leer sein. - .layout-renamed-old-name-new-name = Layout umbenannt: '__ocs_fmt_0__' → '__ocs_fmt_1__' + .layout-renamed-old-name-new-name = Layout umbenannt: „__ocs_fmt_0__“ → „__ocs_fmt_1__“ .leader = Führungslinie .leader-format = Führungslinienformat .leader-structure = Struktur der Führungslinie .leaders = Führungslinien - .leading-trailing = Leading und Trailing + .leading-trailing = Führung und Verlauf .left = Links .left-attachment = linke Befestigung: .left-to-right-dimtxtdirection = Links nach rechts (DIMTXTDIRECTION) @@ -582,91 +582,91 @@ common = .linetype-scale = Streckenmaßstab .lineweight-title-case = Linienstärke .list = Liste - .loaded = Beladen - .loading-discussions = Ladegespräche... - .loading-videos = Videos laden... - .load = Belastung ... + .loaded = Geladen + .loading-discussions = Lade Diskussionen... + .loading-videos = Lade Videos... + .load = Laden ... .low = niedrig .measure-entity-type-not-supported-or-distance = MEASURE: Objekttyp wird nicht unterstützt oder Abstand ist zu groß. .measure-count-point-s-placed = MEASURE: __ocs_fmt_0__ Punkt(e) platziert. .mleaderalign-leaders-aligned = MLEADERALIGN Führungslinien ausgerichtet. .mleadercollect-leaders-collected = MLEADERCOLLECT Führungslinien gesammelt. .model-uppercase = MODEL - .ms-is-only-available-in-paper-space-layouts = MS ist nur in Papierraumlayouts verfügbar. + .ms-is-only-available-in-paper-space-layouts = MS ist nur in Papierraum-Layouts verfügbar. .mspace = MSPACE .mtext = Mtext - .mview-the-clipping-boundary-has-no-usable-area = MVIEW: Die Clipping-Grenze hat keine nutzbare Fläche. - .magenta = Magen + .mview-the-clipping-boundary-has-no-usable-area = MVIEW: Die Schnittgrenze hat keine nutzbare Fläche. + .magenta = Magenta .manage = Verwalten - .meters = Messgeräte + .meters = Meter .middle = Mitte .middle-center-title-case = Mitte zentriert .middle-left-title-case = Mitte links .middle-right-title-case = Mitte rechts - .middle-center-sentence-case = Mittleres Zentrum + .middle-center-sentence-case = Mitte zentriert .middle-left-sentence-case = Mitte links .middle-right-sentence-case = Mitte rechts .middleleft = Mitte links .middleright = Mitte rechts - .midpoint = Mittelwert + .midpoint = Mittelpunkt .miles = Meilen .millimeters = Millimeter .model-title-case = Modell - .modify = Modifizieren - .move = Bewegung + .modify = Ändern + .move = Bewegen .multiple = Mehrfach .multiplier-dimaltf = Multiplikator (DIMALTF) .name-title-case = Name .name-label-title-case = Name: - .nearest = Nächste + .nearest = Nächstgelegen .new = Neu .no = Nein .no-drawing-open-use-new-or-open-first = Keine Zeichnung geöffnet – zuerst Neu oder Öffnen verwenden. .no-groups-found-for-selected-objects = Keine Gruppen für ausgewählte Objekte gefunden. - .no-plugin-handled-command = Kein Plugin wird bearbeitet: __ocs_fmt_0__ + .no-plugin-handled-command = Kein Plugin bearbeitet: __ocs_fmt_0__ .no-viewport-found-in-this-layout = In diesem Layout wurde kein Ansichtsfenster gefunden. .node = Knotenpunkt .none-title-case = Keine .normal = Normal .nothing-found-at-that-point = Nichts gefunden an diesem Punkt. .nothing-to-redo = Nichts zu wiederholen. - .nothing-to-undo = Nichts zu rückgängig machen. + .nothing-to-undo = Nichts rückgängig zu machen. .ok = OK .object-title-case = Objekt .object-is-on-locked-layer-layer-unlock-the-layer-to-edit-it = Objekt befindet sich auf gesperrter Ebene "__ocs_fmt_0__" - entsperren Sie die Ebene, um es zu bearbeiten. .object-is-on-locked-layer-layer-unlock-the-layer-to-select-or-edit-it = Objekt befindet sich auf gesperrter Ebene "__ocs_fmt_0__" - entsperren Sie die Ebene, um sie auszuwählen oder zu bearbeiten. .oblique = Schräglage (°): .oblique-angle = Schrägwinkel - .off = Inaktiv - .offset = Offset + .off = Aus + .offset = Versatz .on = Ein .open = Öffnen .open-dwg-dxf-not-yet-implemented = DWG/DXF öffnen: noch nicht implementiert. .open-cancelled = Geöffnet abgesagt: "__ocs_fmt_0__" - .open-failed-e = Offen fehlgeschlagen: __ocs_fmt_0__ + .open-failed-e = Öffnen fehlgeschlagen: __ocs_fmt_0__ .open-or-create-a-drawing-to-add-a-layout = Öffnen oder erstellen Sie eine Zeichnung, um ein Layout hinzuzufügen. .open-or-create-a-drawing-to-manage-layer-state = Öffnen oder erstellen Sie eine Zeichnung, um Ebenenzustände zu verwalten. .open-or-create-a-drawing-to-manage-layouts = Öffnen oder erstellen Sie eine Zeichnung, um Layouts zu verwalten. - .open-or-create-a-drawing-to-switch-layouts = Öffnen oder erstellen Sie eine Zeichnung, um Layouts zu wechseln. - .opened-file-location = Geöffneter Dateispeicher: __ocs_fmt_0__ + .open-or-create-a-drawing-to-switch-layouts = Öffnen oder erstellen Sie eine Zeichnung, um zwischen Layouts zu wechseln. + .opened-file-location = Geöffneter Dateispeicherort: __ocs_fmt_0__ .opening-name-size-label = Öffnen "__ocs_fmt_0__" (__ocs_fmt_1__)... .options-title-case = Optionen .origin = Ursprung - .ortho = Orthopädie + .ortho = Lotrecht .output = Ausgabe .outside = Außen .outside-limits = Außerhalb der Grenzen. .overall-scale = Globale Skalierung .overwrite = Überschreiben .pedit-applied = PEDIT: angewendet. - .pedit-cannot-convert-this-entity = PEDIT: Dieses Objekt kann nicht konvertiert werden. + .pedit-cannot-convert-this-entity = PEDIT: Dieses Objekt kann nicht umgewandelt werden. .pedit-operation-not-applicable-to-this-entity = PEDIT: Der Vorgang ist auf dieses Objekt nicht anwendbar. .perf = PERF .polysolid-built-a-wall-solid-from-used-segment = POLYSOLID: eine Wand aus __ocs_fmt_0__ Segment(s) gebaut (Breite __ocs_fmt_1__, Höhe __ocs_fmt_2__). .polysolid-select-a-polyline-first = POLYSOLID: Wählen Sie zuerst eine Polylinie aus. .polysolid-the-polyline-has-no-usable-segments = POLYSOLID: Die Polylinie hat keine verwertbaren Segmente. .pspace = PSPACE - .pyramid-could-not-build-all-faces = PYRAMID: konnte nicht alle Gesichter bauen. + .pyramid-could-not-build-all-faces = PYRAMID: konnte nicht alle Seitenflächen bauen. .pyramid-created-a-n-sided-pyramid-radius-radiu = PYRAMID: eine __ocs_fmt_0__-seitige Pyramide (Radius __ocs_fmt_1__, Höhe __ocs_fmt_2__) erstellt. .pyramid-radius-and-height-must-be-positive = PYRAMID: Radius und Höhe müssen positiv sein. .pyramid-tessellation-failed = PYRAMID: Tessellation fehlgeschlagen. @@ -674,13 +674,13 @@ common = .page-setup = Seiteneinrichtung .paper = Papier .parallel = Parallel - .parsing-entities = Objekte werden analysiert… + .parsing-entities = Objekte werden ausgewertet… .paste = Einfügen .perpendicular = Rechtwinklig - .photometric = Photometrische + .photometric = Photometrisch .pick-a-point = Wählen Sie einen Punkt .pick-an-object = Wählen Sie ein Objekt - .pinned = gepinnt + .pinned = angeheftet .place-text-manually-dimupt = Text manuell einfügen (DIMUPT) .plot = Plotten .plot-style = Plotstil @@ -690,7 +690,7 @@ common = .plot-window-x0-2-y0-2-to-x1-2-y1-2 = Plot-Fenster: __ocs_fmt_0__,__ocs_fmt_1__ bis __ocs_fmt_2__,__ocs_fmt_3__ .plot-window-x1-2-y1-2-to-x2-2-y2-2 = Plot-Fenster: __ocs_fmt_0__,__ocs_fmt_1__ bis __ocs_fmt_2__,__ocs_fmt_3__ .plugin-id-failed-to-load-e = Plugin '__ocs_fmt_0__' konnte nicht geladen werden: __ocs_fmt_1__ - .plugin-manager = Plug-in-Manager + .plugin-manager = Plug-in-Verwaltung .point = Punkt .point-cloud = Punktwolken .polyline = Polylinie @@ -714,13 +714,13 @@ common = .quality = Qualität .revolve-could-not-revolve-profile = REVOLVE: konnte das Profil nicht drehen. .revolve-entity-not-found = REVOLVE: Objekt nicht gefunden. - .revolve-solid-created-0 = REVOLVE: solide erzeugt (__ocs_fmt_0__°). + .revolve-solid-created-0 = REVOLVE: Volumenkörper erzeugt (__ocs_fmt_0__°). .radians = Radiant .radius = Radius .ray = Strahl - .read-only-session-read-only-saving-is-disabled = Read-only Session (--read-only): Speichern ist deaktiviert. + .read-only-session-read-only-saving-is-disabled = Schreibgeschütze Sitzung (--read-only): Speichern ist deaktiviert. .realistic = Realistisch - .recent-documents = Jüngste Dokumente + .recent-documents = Letzte Dokumente .recent-files = Zuletzt geöffnete Dateien .recent-file-no-longer-exists = Aktuelle Datei existiert nicht mehr: __ocs_fmt_0__ .red = Rot @@ -737,27 +737,27 @@ common = .requires-single-or-multiple = Erfordert Single oder Multiple. .restore = Wiederherstellung .retry = Versuch - .reverse = Rückwärtsgang + .reverse = Rückwärts .right = Rechts - .right-attachment = Rechten Befestigung: + .right-attachment = Rechte Befestigung: .rotate = Rotieren .rotation = Rotation .round-off-dimaltrnd = Abrunden (DIMALTRND) .round-off-dimrnd = Abrunden (DIMRND) - .section-could-not-build-the-cutting-plane = SECTION: konnte die Schnittebene nicht bauen. - .section-could-not-determine-the-solid-s-extent = SECTION: konnte die Ausdehnung des Feststoffs nicht bestimmen. + .section-could-not-build-the-cutting-plane = SECTION: konnte die Schnittebene nicht erstellen. + .section-could-not-determine-the-solid-s-extent = SECTION: konnte die Ausdehnung des Volumenkörpers nicht bestimmen. .section-created-section-line-s-at-value = SECTION: erstellt __ocs_fmt_0__ Schnittlinie(n) bei __ocs_fmt_1__=__ocs_fmt_2__. - .section-select-exactly-one-solid-created-this = SECTION: Wählen Sie genau einen Solid, der diese Sitzung erstellt hat. + .section-select-exactly-one-solid-created-this = SECTION: Wählen Sie genau einen erstellten Volumenkörper. .section-solid-modelling-is-unavailable-in-this = SECTION: Solid Modeling ist in diesem Build nicht verfügbar. - .section-the-plane-does-not-cross-the-solid = SECTION: Die Ebene kreuzt den Feststoff nicht. - .slice-failed-the-plane-may-not-cross-the-solid = SLICE fehlgeschlagen - das Flugzeug darf den Feststoff nicht kreuzen. - .slice-could-not-determine-the-solid-s-extent = SLICE: konnte die Ausdehnung des Feststoffs nicht bestimmen. + .section-the-plane-does-not-cross-the-solid = SECTION: Die Ebene kreuzt den Volumenkörper nicht. + .slice-failed-the-plane-may-not-cross-the-solid = SLICE fehlgeschlagen - die Ebene darf den Volumenkörper nicht kreuzen. + .slice-could-not-determine-the-solid-s-extent = SLICE: konnte die Ausdehnung des Volumenkörper nicht bestimmen. .slice-cut-at-ax-value-kept-the-half = SLICE: geschnitten bei __ocs_fmt_0__=__ocs_fmt_1__, die Hälfte von __ocs_fmt_2__ beibehalten. - .slice-select-exactly-one-solid-created-this-se = SLICE: Wählen Sie genau einen Solid, der diese Sitzung erstellt hat. - .slice-the-plane-does-not-cross-the-solid-on-th = SLICE: Die Ebene kreuzt den Festkörper auf der gehaltenen Seite nicht. + .slice-select-exactly-one-solid-created-this-se = SLICE: Wählen Sie genau einen erstellten Volumenkörper. + .slice-the-plane-does-not-cross-the-solid-on-th = SLICE: Die Ebene kreuzt den Volumenkörper auf der gehaltenen Seite nicht. .splinefit-fit-a-spline-through-n-points = SPLINEFIT: Passen Sie einen Spline durch __ocs_fmt_0__ Punkte. - .splinefit-need-at-least-3-points = SPLINEFIT: mindestens 3 Punkte benötigen. - .splinefit-select-a-polyline-to-fit-a-spline-th = SPLINEFIT: Wählen Sie eine Polylinie, um eine Spline durchzupassen. + .splinefit-need-at-least-3-points = SPLINEFIT: mindestens 3 Punkte werden benötigt. + .splinefit-select-a-polyline-to-fit-a-spline-th = SPLINEFIT: Wählen Sie eine Polylinie, um eine Spline einzupassen. .stepout-exported-to = STEPOUT: exportiert nach "__ocs_fmt_0__" .stepout-no-3d-mesh-data-in-this-drawing = STEPOUT: keine 3D-Mesh-Daten in dieser Zeichnung. .stepout-error = STEPOUT: __ocs_fmt_0__ @@ -766,15 +766,15 @@ common = .stlout-error = STLOUT: __ocs_fmt_0__ .stretch-nothing-crosses-the-window = STRETCH: Nichts durchquert das Fenster. .stretch-count-entity-ies-stretched = STRETCH: __ocs_fmt_0__ Objekt(e) gestreckt. - .sweep-could-not-sweep-profile-along-path-use-a = SWEEP: Profil entlang des Pfades nicht kehren konnte, ein geschlossenes 2D-Profil und einen Linien- oder Polylinienpfad verwenden. - .sweep-solid-created = SWEEP: solide erstellt. + .sweep-could-not-sweep-profile-along-path-use-a = SWEEP: Profil entlang des Pfades nicht erkannt, verwende ein geschlossenes 2D-Profil und ein Linien- oder Polylinienpfad. + .sweep-solid-created = SWEEP: Volumenkörper erstellt. .save = Speichern - .save-as = Sparen Sie + .save-as = Speichern als .save-drawing-as = Zeichnung speichern als .save-already-running-for-this-drawing = Diese Zeichnung wird bereits gespeichert. - .save-anyway = Auf jeden Fall sparen + .save-anyway = In jeden Fall speichern .save-error-e = Fehler speichern: __ocs_fmt_0__ - .save-in-source-version = Speichern in der Source Version + .save-in-source-version = Speichern in der Quellversion .save-the-drawing-before-copying-its-file-path = Speichern Sie die Zeichnung, bevor Sie den Dateipfad kopieren. .save-the-drawing-before-opening-its-file-locat = Speichern Sie die Zeichnung, bevor Sie den Dateispeicherort öffnen. .saved-download-but-recent-copy-could-not-be-st = Gespeicherter Download, aber die aktuelle Kopie konnte nicht gespeichert werden: __ocs_fmt_0__ @@ -783,20 +783,20 @@ common = .scene-cleared-standard-linetypes-loaded = Szene geleert; Standardlinientypen geladen. .scientific = Wissenschaftlich .search-layers = Ebenen suchen… - .second-seg-angle = zweiter Nähwinkel: + .second-seg-angle = zweiter Segmentwinkel: .select-all = Alle auswählen .select-similar-added = Wählen Sie Ähnliches: __ocs_fmt_0__ addiert. .select-a-tangent-object = Wählen Sie ein Tangentenobjekt. .select-one-object-first-then-run-objectscale = Wählen Sie zuerst ein Objekt aus und führen Sie dann OBJECTSCALE aus. - .send-feedback = Senden Sie Feedback + .send-feedback = Eine Rückmeldung senden .sent-to-printer-printer = Gesendet an Drucker: __ocs_fmt_0__ .set-current = Aktuell setzen - .shade-plot = Schattenfläche - .shadow-flags = Schattenflaggen + .shade-plot = Schattierter Plot + .shadow-flags = Schattenfahnen .single = Einzel .size = Größe - .snap-override-label-next-pick-only = Snap override: __ocs_fmt_0__ (nur nächster Pick). - .solid-created = Solid geschaffen. + .snap-override-label-next-pick-only = Snap Übersteuerung: __ocs_fmt_0__ (nur nächste Auswahl). + .solid-created = Volumenkörper erstellt. .source-object-not-found = Quellobjekt nicht gefunden. .spacing = Abstand .spacing-dimdli = Abstand (DIMDLI) @@ -817,14 +817,14 @@ common = .support-on-patreon = Unterstützung für Patreon .supporters = Unterstützer .suppress-0-ft-0-in = Unterdrücken Sie Null Fuß und Null Zoll - .suppress-leading = Führende Unterdrückung + .suppress-leading = Leitzeichen unterdrücken .suppress-leading-trailing = Unterdrückung von führenden und nachlaufenden Nullen .suppress-leading-zeros = Unterdrücken Sie führende Nullen .suppress-none = Keine Unterdrückung .suppress-outside-arrows-dimsoxd = Unterdrücken Sie äußere Pfeile (DIMSOXD) - .suppress-trailing = Suppress Trailing - .suppress-trailing-zeros = Unterdrücken Sie nacheilende Nullen - .switched-to-layout-name = Umgeschalten auf Layout '__ocs_fmt_0__'. + .suppress-trailing = Nachlauf unterdrücken + .suppress-trailing-zeros = Unterdrücken Sie nachlaufende Nullen + .switched-to-layout-name = Zu Layout „__ocs_fmt_0__“ gewechselt. .textedit-selected-entity-is-not-text = TEXTEDIT: Das ausgewählte Objekt ist kein Text. .texteditmode-set-to-display-val = TEXTEDITMODE auf __ocs_fmt_0__ gesetzt .table = Tabelle @@ -844,11 +844,11 @@ common = .text-height-label-sentence-case = Texthöhe: .text-movement = Textbewegung .text-movement-dimtmove = Textbewegung (DIMTMOVE) - .text-out-first = Text zuerst raus + .text-out-first = Text abheben .text-style = Textstil - .text-to-find = Text zu finden - .the-selected-object-does-not-support-annotatio = Das ausgewählte Objekt unterstützt keine Annotationsskalen. - .tick-size-dimtsz = Tickgröße (DIMTSZ) + .text-to-find = Text finden + .the-selected-object-does-not-support-annotatio = Das ausgewählte Objekt unterstützt keine Beschriftungsskalierung. + .tick-size-dimtsz = Hakengröße (DIMTSZ) .title = Titel .title-row = Titelzeile: .title-row-suppressed = Titelzeile unterdrückt @@ -857,11 +857,11 @@ common = .toggle-object-snap-tracking = Objektfangspur umschalten .toggle-ortho = Orthomodus umschalten .toggle-polar-tracking = Polarspur umschalten - .tol-decimals-dimalttd = Tol-Dezimalstellen (DIMALTTD) - .tol-zero-supp-dimalttz = Nullunterdrückung der Toleranz (DIMALTTZ) - .tol-decimals-dimtdec = Tol. Dezimalstellen (DIMTDEC) + .tol-decimals-dimalttd = Toleranz - Dezimalstellen (DIMALTTD) + .tol-zero-supp-dimalttz = Toleranz - Nullen ausblenden (DIMALTTZ) + .tol-decimals-dimtdec = Toleranz - Dezimalstellen (DIMTDEC) .tol-vert-just-dimtolj = Vertikale Toleranzausrichtung (DIMTOLJ) - .tol-zero-supp-dimtzin = Nullunterdrückung der Toleranz (DIMTZIN) + .tol-zero-supp-dimtzin = Toleranz - Nullen ausblenden (DIMTZIN) .tolerance = Toleranz .tolerance-precision = Toleranzgenauigkeit .tolerances = Toleranzen @@ -872,23 +872,23 @@ common = .top-center-title-case = Oben zentriert .top-left-title-case = Oben links .top-right-title-case = Oben rechts - .top-attachment = Oberbefestigung: - .top-center-sentence-case = Oberes Zentrum + .top-attachment = Befestigung oben: + .top-center-sentence-case = Oben mittig .top-left-sentence-case = Oben links .top-right-sentence-case = Oben rechts .top-to-bottom = Von oben nach unten - .topcenter = Oberes Zentrum + .topcenter = Oben mittig .topleft = Oben links .topright = Oben rechts .torus = Torus .transparency = Transparenz .truetype-system = TrueType (System) .ttr = Ttr - .tutorials = Lernprogramme + .tutorials = Lerneinheiten .type = Typ - .ucs-world = Welt-BKS + .ucs-world = WKS .ucs = UCS: __ocs_fmt_0__ - .unable-to-change-ui-language-error = Unfähig, die UI Sprache zu ändern: __ocs_fmt_0__ + .unable-to-change-ui-language-error = Unmöglich die UI Sprache zu ändern: __ocs_fmt_0__ .undo = Rückgängig .undo-last-label = Rückgängig: __ocs_fmt_0__ .uniform-scale = Einheitliche Skala @@ -900,7 +900,7 @@ common = .unsaved-changes = Ungespeicherte Änderungen .unsupported-file-type = Nicht unterstützter Dateityp: __ocs_fmt_0__ .up = Aufwärts - .update-available = Update verfügbar + .update-available = Aktualisierung verfügbar .upside-down = Kopfüber nach unten .v-margin = Vertikaler Rand: .vplayer-frozen-count-layer-s-frozen-in-viewpor = VPLAYER: __ocs_fmt_0__ Ebene(n) im Ansichtsfenster eingefroren. @@ -911,7 +911,7 @@ common = .vertical-pos-dimtad = Vertikale Pos (DIMTAD) .vertical-pos-dimtvp = Vertikale Pos (DIMTVP) .videos = Videos - .videos-load-from-the-internet = Videos aus dem Internet geladen. + .videos-load-from-the-internet = Videos aus dem Internet laden. .view = Ansicht .view-home = Ansicht: Start .view-value = Ansicht: __ocs_fmt_0__ @@ -929,7 +929,7 @@ common = .width = Breite .width-factor-title-case = Breitenfaktor .width-factor-label-title-case = Breitenfaktor: - .width-factor-sentence-case = Witwenfaktor + .width-factor-sentence-case = Breitenfaktor .window = Fenster .wipeout = Abdeckung .wireframe = Drahtmodell @@ -939,19 +939,19 @@ common = .xline = XLine .xref-not-found = XREF Nicht gefunden: "__ocs_fmt_0__" (__ocs_fmt_1__) .xref-unloaded-skipped = XREF Unloaded (übersprungen): "__ocs_fmt_0__" - .yards = Gärten + .yards = Yards .yellow = Gelb .yes = Ja - .zero-suppress-dimaltz = Nullunterdrückung (DIMALTZ) - .zero-suppress-dimzin = Nullunterdrückung (DIMZIN) - .zoom-extents = Zoomausdehnungen - .zoom-in = In den Warenkorb - .zoom-out = Ausverkauft - .zoom-window = Zoomfenster + .zero-suppress-dimaltz = Nullen ausblenden (DIMALTZ) + .zero-suppress-dimzin = Nullen ausblenden (DIMZIN) + .zoom-extents = Ausdehnung anpassen + .zoom-in = Vergrößern + .zoom-out = Verkleinern + .zoom-window = Ausdehnungsfenster .command-lowercase = Befehl .e-g-arial = z. B. Arial - .linetype-lowercase = linienförmig - .lineweight-lowercase = Streckengewicht + .linetype-lowercase = Linientyp + .lineweight-lowercase = Liniengewicht .object-lowercase = Objekt .options-lowercase = Optionen .units = Einheiten @@ -960,7 +960,7 @@ common = .label-count-noun-created = __ocs_fmt_0__: __ocs_fmt_1__ __ocs_fmt_2__ erstellt. .acquired = __ocs_fmt_0__ erworben. .alias-es-applied = __ocs_fmt_0__ Aliasname(n) angewendet. - .group-s-dissolved = __ocs_fmt_0__ Gruppe(n) gelöst. + .group-s-dissolved = __ocs_fmt_0__ Gruppe(n) aufgelöst. .layer-s-turned = __ocs_fmt_0__ Layer(s) gedreht __ocs_fmt_1__ .layer-s = __ocs_fmt_0__ Ebene(n) __ocs_fmt_1__ .mod-shortcuts = ── __ocs_arg_MOD__-Tastenkürzel ──────────────────────── @@ -982,22 +982,22 @@ command = .cancel = *Abbrechen* .add-alias = + Aliasnamen hinzufügen .alias-title-case = Aliasname - .cannot-add-a-vertex-here = Kann hier keinen Scheitelpunkt hinzufügen. - .cannot-break-at-this-vertex = Kann nicht an diesem Scheitelpunkt brechen. - .cannot-place-the-new-vertex = Kann den neuen Scheitelpunkt nicht platzieren. + .cannot-add-a-vertex-here = Kann hier keinen Stützpunkt hinzufügen. + .cannot-break-at-this-vertex = Kann nicht an diesem Stützpunkt brechen. + .cannot-place-the-new-vertex = Kann den neuen Stützpunkt nicht platzieren. .command = Befehl - .command-aliases = Befehls Aliasnamen + .command-aliases = Befehls-Aliasnamen .key = Schlüssel .keyboard-shortcuts = Tastenkombinationen .layer-0-and-the-current-layer-can-t-be-deleted = Layer "0" und die aktuelle Ebene können nicht gelöscht werden - übersprungen. .no-drawing-open-use-new-or-open-to-start-a-dra = Verwenden Sie NEW oder OPEN, um eine Zeichnung zu starten. .object-is-on-locked-layer-layer-unlock-the-lay = Objekt befindet sich auf gesperrter Ebene "__ocs_fmt_0__" - sperren Sie die Ebene frei, um ihre Attribute zu bearbeiten. .pan-ended = PAN beendet. - .polyline-broken-at-vertex = Polylinie gebrochen an Scheitelpunkt. + .polyline-broken-at-vertex = Polylinie am Stützpunkt gebrochen. .rename-select-the-object-type-to-rename = RENAME Wählen Sie den umzubenennenden Objekttyp: .rename-type-enter-the-current-name = RENAME __ocs_arg_type__ Geben Sie den aktuellen Namen ein: .rename-type-rename-old-to = RENAME __ocs_arg_type__ „__ocs_arg_old__“ umbenennen in: - .specify-new-vertex-location = Neue Scheitelpunkt-Position angeben: + .specify-new-vertex-location = Neue Stützpunktposition angeben: .type-shortcuts-set-key-cmd-to-add-custom-short = Geben Sie SHORTCUTS SET ein, um benutzerdefinierte Verknüpfungen hinzuzufügen. .type-an-alias-and-the-command-it-runs-e-g-l-li = Alias und zugehörigen Befehl eingeben (z. B. L → LINE). Mit Anwenden in ocad.pgp speichern; Schließen verwirft nicht angewendete Änderungen. .unknown-command-cmd = Unbekannter Befehl: __ocs_fmt_0__ @@ -1010,21 +1010,21 @@ command = .tcount-placement-overwrite-prefix-suffix-overw = TCOUNT Platzierung [Überschreiben/Präfix/Suffix] <Überschreiben>: file = - .parse-ms-purge-ms-caches-ms-xref-ms-total-ms = parse __ocs_fmt_0__ms · Purge __ocs_fmt_1__ms · Caches __ocs_fmt_2__ms · xref __ocs_fmt_3__ms · total __ocs_fmt_4__ms + .parse-ms-purge-ms-caches-ms-xref-ms-total-ms = analysiere __ocs_fmt_0__ms · Säubere __ocs_fmt_1__ms · Puffer __ocs_fmt_2__ms · xref __ocs_fmt_3__ms · gesamt __ocs_fmt_4__ms .archive-save-the-drawing-first-it-has-no-file = ARCHIVE: Speichern Sie die Zeichnung zuerst (sie hat noch keinen Dateipfad). .autosaved-1-drawing = 1 Zeichnung automatisch gespeichert .background-colour-default-black-darkgray-gray = BACKGROUND Farbe [Standard/Black/DarkGray/Gray/LightGray/White] oder R G B (0–255): - .background-reset-to-default = Background auf Standard zurückgesetzt. + .background-reset-to-default = Hintergrund auf Standard zurückgesetzt. .background-rgb = Hintergrund: rgb(__ocs_fmt_0__, __ocs_fmt_1__, __ocs_fmt_2__) .created-new-ctb-table-aci-aci-updated = Neue CTB Tabelle erstellt, ACI __ocs_fmt_0__ aktualisiert. .default-save-format = Standard-Speicherformat: .do-you-want-to-save-changes-to-name = Möchten Sie Änderungen an "__ocs_arg_name__" speichern? .drawing-changed-on-disk = Zeichnung auf dem Datenträger geändert .drawing-units-insunits-click-to-change = - Zeicheneinheiten (INSUNITS) - Klicken Sie auf Change - .drawing-changed-on-disk-while-it-was-opening-s = Zeichnen auf der Festplatte geändert, während es geöffnet wurde; Speichern erfordert Konfliktlösung. - .edit-lease-active-warning-external-change-chec = Leasing aktiv bearbeiten; __ocs_fmt_0__. Externe Wechselprüfungen bleiben aktiv. + Zeichnungseinheiten (INSUNITS) + Klicken Sie auf Ändern + .drawing-changed-on-disk-while-it-was-opening-s = Zeichnung während es geöffnet wurde auf der Festplatte geändert; Speichern erfordert Konfliktlösung. + .edit-lease-active-warning-external-change-chec = Pacht aktiv bearbeiten; __ocs_fmt_0__. Externe Wechselprüfungen bleiben aktiv. .files-you-open-will-show-up-here = Dateien, die Sie öffnen, werden hier angezeigt. .new-drawing = Neue Zeichnung .no-plot-style-table-loaded-load-or-create-one = Keine Plotstiltabelle geladen. Laden oder erstellen Sie eine Tabelle. @@ -1032,22 +1032,22 @@ file = .oops-nothing-to-restore = OOPS: nichts zu restaurieren. .oops-restored-n-object-s = OOPS: Wiederherstellung von __ocs_fmt_0__ Objekt(en). .open-cad-studio-ready = Open CAD Studio ist bereit. - .open-discussions-on-github = Offene Diskussionen zu GitHub - .open-file-title-case = Offene Datei + .open-discussions-on-github = Öffne Diskussionen auf GitHub + .open-file-title-case = Öffne Datei .open-file-picker-title-case = Datei öffnen… .open-and-save = Öffnen und Speichern .open-playlist-on-youtube = Wiedergabeliste auf YouTube öffnen .opened-name-entity-count-entities = „__ocs_fmt_0__“ geöffnet — __ocs_fmt_1__ Objekte .opened-printer-properties = Geöffnete Druckereigenschaften. - .opened-read-only-against-other-editors-error = Read-only gegen andere Editoren geöffnet: __ocs_fmt_0__ + .opened-read-only-against-other-editors-error = Nur-Lese-Zugriff von anderem Bearbeiter geöffnet: __ocs_fmt_0__ .page-setup-applied-to-the-layout = Seiteneinrichtung auf das Layout angewendet. - .path-type = Art der Trassen: + .path-type = Art des Pfades: .plot-area-is-empty-pick-a-larger-window = Die Fläche ist leer. Wählen Sie ein größeres Fenster. - .plot-style-aci-aci-updated = Plotstyle ACI __ocs_fmt_0__ aktualisiert. - .plot-style-table-is-not-loaded = Die Plot-Tabelle '__ocs_fmt_0__' wird nicht geladen. + .plot-style-aci-aci-updated = Plotstil ACI __ocs_fmt_0__ aktualisiert. + .plot-style-table-is-not-loaded = Die Plotstil-Tabelle '__ocs_fmt_0__' wird nicht geladen. .quick-print-works-in-model-space = Schnelldruck ist nur im Modellbereich verfügbar. - .reload-failed-error = Reload fehlgeschlagen: __ocs_fmt_0__ - .saveall-save-each-tab-individually-in-the-web = SAVEALL: Speichern Sie jeden Tab einzeln im Web Build. + .reload-failed-error = Nachladen fehlgeschlagen: __ocs_fmt_0__ + .saveall-save-each-tab-individually-in-the-web = SAVEALL: Speichern Sie jeden Reiter einzeln im Web Build. .saveall-saved-saved-drawing-s = SAVEALL: gespeichert __ocs_fmt_0__ Zeichnung(en)__ocs_fmt_1__. .saveall-already-has-a-save-running = SAVEALL: Für __ocs_fmt_0__ wird bereits gespeichert .saveall-failed-e = SAVEALL: __ocs_fmt_0__ fehlgeschlagen: __ocs_fmt_1__ @@ -1063,15 +1063,15 @@ file = .selection-has-no-printable-area = Die Auswahl hat keinen bedruckbaren Bereich. .selection-has-no-printable-geometry = Die Auswahl hat keine druckbare Geometrie. .sending-to-system-printer = Senden an Systemdrucker... - .type-a-command-or-use-the-ribbon-open-obj-inse = Geben Sie einen Befehl ein oder verwenden Sie das Menüband. Öffnen Sie den Tab OBJ: INSERT. + .type-a-command-or-use-the-ribbon-open-obj-inse = Geben Sie einen Befehl ein oder verwenden Sie das Menüband. Öffnen Sie den Reiter OBJ: INSERT. .unable-to-acquire-edit-lock-error = Unfähig, edit lock zu erwerben: __ocs_fmt_0__ .unable-to-save-file-name-file-is-in-use-by-ano = Kann "__ocs_fmt_0__" nicht speichern: Die Datei wird von einer anderen Anwendung verwendet. .unable-to-save-error = Kann "__ocs_fmt_0__" nicht speichern: __ocs_fmt_1__ - .usage-script-path-to-scr-file = Verwendung: SCRIPT - .usage-undo-number-of-steps = Verwendung: UNDO [number of steps] + .usage-script-path-to-scr-file = Anwendung: SCRIPT + .usage-undo-number-of-steps = Anwendung: UNDO [number of steps] .used-for-the-first-save-of-a-new-drawing-exist = Wird für die erste Speicherung einer neuen Zeichnung verwendet. Bestehende Zeichnungen behalten ihren Dateityp und ihre Version bei. .visual-style-label = Visueller Stil: __ocs_fmt_0__. - .warning-corrupt-entities-dropped-parser-junk-b = Warnung: __ocs_fmt_0__ beschädigte Objekte verworfen (ungültige Parserdaten — fehlerhafte Normalen oder Anzahlen) + .warning-corrupt-entities-dropped-parser-junk-b = Warnung: __ocs_fmt_0__ beschädigte Objekte verworfen (ungültige Analysedaten — fehlerhafte Normalen oder Anzahlen) .warning-corrupt-xref-entities-dropped = Warnung: __ocs_fmt_0__ beschädigte XRef-Objekte verworfen .xref-loaded = XREF Geladen "__ocs_fmt_0__" .cmd-cannot-create-folder-e = __ocs_fmt_0__: kann keinen Ordner erstellen (__ocs_fmt_1__). @@ -1094,11 +1094,11 @@ inquiry = .addselected-selected-object-not-found = ADDSELECTED: ausgewähltes Objekt nicht gefunden. .add-selected-drawing-a-new-kind-on-layer-layer = Auswahl hinzufügen: Neues Objekt vom Typ __ocs_fmt_0__ auf Ebene „__ocs_fmt_1__“ zeichnen. .angle-angle = Winkel = __ocs_arg_angle__° - .angle-0-0000-degenerate-rays = Winkel = 0.0000° (entartete Strahlen) + .angle-0-0000-degenerate-rays = Winkel = 0.0000° (zerfallende Strahlen) .area-area-perimeter-perimeter = Fläche = __ocs_arg_area__, Umfang = __ocs_arg_perimeter__ .bedit-block-edit-discarded = BEDIT: Block "__ocs_fmt_0__" edit verworfen. .bedit-block-saved-all-references-updated = BEDIT: Block "__ocs_fmt_0__" gespeichert. Alle Referenzen werden aktualisiert. - .bedit-editing-block-use-save-block-or-discard = BEDIT: Bearbeiten des Blocks "__ocs_fmt_0__". Verwenden Sie Speicherblock oder Discard, um ihn zu beenden. + .bedit-editing-block-use-save-block-or-discard = BEDIT: Bearbeiten des Blocks "__ocs_fmt_0__". Verwenden Sie Block speichern oder verwerfen um ihn zu beenden. .bedit-block-not-found = BEDIT: Block "__ocs_fmt_0__" nicht gefunden. .bedit-cannot-edit-an-external-reference-xref = BEDIT: kann keine externe Referenz (xref) bearbeiten. .bedit-finish-the-active-refedit-refclose-first = BEDIT: Beenden Sie zuerst die aktive REFEDIT (REFCLOSE). @@ -1114,10 +1114,10 @@ inquiry = .dblist-drawing-has-no-entities = DBLIST: Zeichnung enthält keine Objekte. .dblist-count-entit = DBLIST: __ocs_fmt_0__ Objekt(e). .dist-specify-first-point = DIST Geben Sie den ersten Punkt an: - .dist-specify-second-point = DIST Zweiter Punkt angeben: + .dist-specify-second-point = DIST Zweiten Punkt angeben: .divide-enter-number-of-segments = DIVIDE Geben Sie die Anzahl der Segmente ein: .divide-select-object-to-divide = DIVIDE Objekt auswählen, um zu teilen: - .deselected = Ausgewählt. + .deselected = Abgewählt. .distance-dist-angle-in-xy-plane-angle-xy-delta = Abstand = __ocs_arg_dist__, Winkel in XY Ebene = __ocs_arg_angle_xy__° Delta X = __ocs_arg_dx__, Delta Y = __ocs_arg_dy__, Delta Z = __ocs_arg_dz__ @@ -1128,82 +1128,82 @@ inquiry = .find-not-found = FIND: "__ocs_fmt_0__" nicht gefunden. .find-specify-search-text = FIND: Suchtext angeben. .find-match-es-for = FIND: __ocs_fmt_0__ match(es) für "__ocs_fmt_1__". - .findall-text-to-find-add-replace-text-by-typin = FINDALL Text zu finden (REPLACE durch Eingabe hinzufügen): + .findall-text-to-find-add-replace-text-by-typin = FINDALL Text auffinden (REPLACE durch Eingabe hinzufügen): .flatten-no-entities = FLATTEN: Keine Objekte ausgewählt. .flatten-entity-ies-moved-to-z-0 = FLATTEN: __ocs_fmt_0__ Objekt(e) auf Z=0 verschoben. .id-specify-point = ID Anzugeben ist: - .list-no-entities-selected-select-entities-firs = LIST: keine Entitäten ausgewählt. + .list-no-entities-selected-select-entities-firs = LIST: keine Objekte ausgewählt. .massprop-no-entities-selected-select-entities = MASSPROP: Keine Objekte ausgewählt. .measure-select-object-to-measure = MEASURE Objekt auswählen, um zu messen: .measure-specify-segment-length = MEASURE Segmentlänge angeben: - .measuregeom-enter-an-option-distance-radius-an = MEASUREGEOM Geben Sie eine Option ein [Abstand/Radius/Angle/ARea]: + .measuregeom-enter-an-option-distance-radius-an = MEASUREGEOM Geben Sie eine Option ein [Distance/Radius/Angle/ARea]: .measuregeom-select-arc-or-circle = MEASUREGEOM Bogen oder Kreis auswählen: .measuregeom-specify-first-corner-point-enter-t = MEASUREGEOM Ersten Eckpunkt angeben (Eingabe zum Abbrechen): .measuregeom-specify-first-point = MEASUREGEOM Geben Sie den ersten Punkt an: .measuregeom-specify-first-ray-point = MEASUREGEOM Erststrahlpunkt angeben: .measuregeom-specify-next-point-n-picked-enter = MEASUREGEOM Nächster Punkt angeben (__ocs_arg_n__ ausgewählt, Geben Sie ein, um zu berechnen): - .measuregeom-specify-second-point = MEASUREGEOM Zweiter Punkt angeben: - .measuregeom-specify-second-ray-point = MEASUREGEOM Zweiter Strahlpunkt angeben: - .measuregeom-specify-vertex-point = MEASUREGEOM Scheitelpunkt-Punkt angeben: + .measuregeom-specify-second-point = MEASUREGEOM Zweiten Punkt angeben: + .measuregeom-specify-second-ray-point = MEASUREGEOM Zweiten Strahlpunkt angeben: + .measuregeom-specify-vertex-point = MEASUREGEOM Stützpunkt angeben: .measure-geometry = Geometrie der Messwerte - .qselect-no-matching-entities = QSELECT: keine passenden Entitäten. + .qselect-no-matching-entities = QSELECT: keine passenden Objekte. .qselect-entity-ies-selected = QSELECT: __ocs_fmt_0__ Objekt(e) ausgewählt. .refclose-block-saved-all-references-updated = REFCLOSE: Block "__ocs_fmt_0__" gespeichert. Alle Referenzen werden aktualisiert. .refclose-changes-discarded = REFCLOSE: Änderungen verworfen. .refclose-no-refedit-session-active = REFCLOSE: keine REFEDIT Sitzung aktiv. - .refedit-editing-block-run-refclose-to-save-ref = REFEDIT: Bearbeiten Block "__ocs_fmt_0__". Laufen Sie REFCLOSE zu speichern, REFCLOSE_DISCARD zu annullieren. - .refedit-a-session-is-already-active-use-refclo = REFEDIT: Eine Sitzung ist bereits aktiv. Verwenden Sie REFCLOSE zuerst. + .refedit-editing-block-run-refclose-to-save-ref = REFEDIT: Block "__ocs_fmt_0__" earbeiten. REFCLOSE aufrufen zum speichern, REFCLOSE_DISCARD zum verwerfen. + .refedit-a-session-is-already-active-use-refclo = REFEDIT: Eine Sitzung ist bereits aktiv. Verwenden Sie zuerst REFCLOSE. .refedit-block-not-found = REFEDIT: Block "__ocs_fmt_0__" nicht gefunden. .refedit-block-is-empty = REFEDIT: Block ist leer. .refedit-selected-object-is-not-an-insert = REFEDIT: Ausgewähltes Objekt ist kein INSERT. .radius-radius-diameter-diameter = Radius = __ocs_arg_radius__, Durchmesser = __ocs_arg_diameter__ .selectall-object-s-selected = SELECTALL: __ocs_fmt_0__ Objekt(e) ausgewählt. - .selhandles-no-entities-selected-select-entitie = SELHANDLES: keine Entitäten ausgewählt. - .usage-cal-expression-e-g-cal-2-3-4 = Verwendung: CAL e.g. CAL (2+3)*4 - .usage-qselect-type-layer-color-linetype-value = Verwendung: QSELECT TYPE|LAYER|COLOR|LINETYPE + .selhandles-no-entities-selected-select-entitie = SELHANDLES: keine Objekte ausgewählt. + .usage-cal-expression-e-g-cal-2-3-4 = Anwendung: CAL e.g. CAL (2+3)*4 + .usage-qselect-type-layer-color-linetype-value = Anwendung: QSELECT TYPE|LAYER|COLOR|LINETYPE .x-x-y-y-z-z = X = __ocs_arg_x__, Y = __ocs_arg_y__, Z = __ocs_arg_z__ .type-name-handle-x-layer-color-lt = __ocs_fmt_0__ Griff:__ocs_fmt_1__ Ebene:__ocs_fmt_2__ Farbe:__ocs_fmt_3__ LT:__ocs_fmt_4____ocs_fmt_5__ - .area-4-perimeter-4-centroid-4-4 = __ocs_fmt_0__ Fläche=__ocs_fmt_1__ Perimeter=__ocs_fmt_2__ Centroid=(__ocs_fmt_3__,__ocs_fmt_4__) + .area-4-perimeter-4-centroid-4-4 = __ocs_fmt_0__ Fläche=__ocs_fmt_1__ Umfang=__ocs_fmt_2__ Schwerpunkt=(__ocs_fmt_3__,__ocs_fmt_4__) draw = .insert-x-no-attributes = __ocs_fmt_0__ einfügen: keine Attribute. .tag-val = [__ocs_fmt_0__] = __ocs_fmt_1__ .no-closed-boundary-found = ⚠ Keine geschlossene Begrenzung gefunden. - .inverted = , invertiert + .inverted = , vertauscht .value-2-point = 2-Punkt - .value-2d-solid = 2D Festkörper + .value-2d-solid = 2D Volumenkörper .value-3-point = 3-Punkt - .value-3d-polyline = 3D-Polylin - .value-3dmirror-mirror-plane-x-y-z = 3DMIRROR Spiegelebene [X/Y/Z]: + .value-3d-polyline = 3D-Polylinie + .value-3dmirror-mirror-plane-x-y-z = 3DMIRROR Spiegelebene [X/Y/Z]: .value-3dpoly-specify-next-point-close-undo = 3DPOLY Nächsten Punkt angeben [Schließen/Zurück]: .value-3dpoly-specify-next-point-undo = 3DPOLY Nächsten Punkt angeben [Zurück]: .value-3dpoly-specify-start-point-of-polyline = 3DPOLY Beginn der Polylinie angeben: - .value-3drotate-angle-in-degrees = 3DROTATE Winkel in Grad: - .value-3drotate-rotation-axis-x-y-z = 3DROTATE Drehachse [X/Y/Z]: + .value-3drotate-angle-in-degrees = 3DROTATE Winkel in Grad: + .value-3drotate-rotation-axis-x-y-z = 3DROTATE Drehachse [X/Y/Z]: .arc-specify-center = ARC Zentrum angeben: .arc-specify-end-point-c-cx-cy-r-r = ARC Endpunkt angeben [c=(__ocs_arg_cx__,__ocs_arg_cy__) r=__ocs_arg_r__]: .arc-specify-start-point = ARC Startpunkt angeben: .arc-3p-specify-end-point = ARC 3P Endpunkt angeben: - .arc-3p-specify-second-point-on-arc = ARC 3P Zweiter Punkt auf dem Bogen angeben: + .arc-3p-specify-second-point-on-arc = ARC 3P Zweiten Punkt auf dem Bogen angeben: .arc-3p-specify-start-point = ARC 3P Startpunkt angeben: - .arc-csa-click-end-direction-or-type-arc-span-i = ARC CSA Klicken Sie auf die Endrichtung oder Typ Bogenspanne in Grad [start=__ocs_arg_sa__]: - .arc-csa-specify-center = ARC CSA Zentrum angeben: + .arc-csa-click-end-direction-or-type-arc-span-i = ARC CSA Klicken Sie auf die Endrichtung oder Typ Bogen in Grad [start=__ocs_arg_sa__]: + .arc-csa-specify-center = ARC CSA Mittelpunkt angeben: .arc-csa-specify-start-point = ARC CSA Startpunkt angeben: - .arc-csl-click-chord-end-or-type-chord-length-r = ARC CSL Akkordende oder Akkordlänge eingeben [r=__ocs_arg_r__]: - .arc-csl-specify-center = ARC CSL Zentrum angeben: + .arc-csl-click-chord-end-or-type-chord-length-r = ARC CSL Sehnenende oder Sehnenlänge eingeben [r=__ocs_arg_r__]: + .arc-csl-specify-center = ARC CSL Mittelpunkt angeben: .arc-csl-specify-start-point = ARC CSL Startpunkt angeben: - .arc-continue-no-previous-line-or-arc-to-contin = ARC Fortfahren Keine vorherige Linie oder Bogen, um fortzufahren. - .arc-continue-specify-end-point-ctrl-flip-direc = ARC Weiter Endpunkt angeben [Strg = Flip-Richtung]: - .arc-sca-click-end-direction-or-type-arc-span-i = ARC SCA Klicken Sie auf die Endrichtung oder Typ Bogenspanne in Grad [start=__ocs_arg_sa__]: - .arc-sca-specify-center = ARC SCA Zentrum angeben: + .arc-continue-no-previous-line-or-arc-to-contin = ARC Fortfahren, keine vorherige Linie oder Bogen, um fortzufahren. + .arc-continue-specify-end-point-ctrl-flip-direc = ARC Forfahren, Endpunkt angeben [Strg = Flip-Richtung]: + .arc-sca-click-end-direction-or-type-arc-span-i = ARC SCA Klicken Sie auf die Endrichtung oder Typ Bogen in Grad [start=__ocs_arg_sa__]: + .arc-sca-specify-center = ARC SCA Mittelpunkt angeben: .arc-sca-specify-start-point = ARC SCA Startpunkt angeben: - .arc-sce-specify-center = ARC SCE Zentrum angeben: + .arc-sce-specify-center = ARC SCE Mittelpunkt angeben: .arc-sce-specify-end-point-r-r = ARC SCE Endpunkt angeben [r=__ocs_arg_r__]: .arc-sce-specify-start-point = ARC SCE Startpunkt angeben: - .arc-scl-click-chord-end-or-type-chord-length-r = ARC SCL Akkordende oder Akkordlänge eingeben [r=__ocs_arg_r__]: - .arc-scl-specify-center = ARC SCL Zentrum angeben: + .arc-scl-click-chord-end-or-type-chord-length-r = ARC SCL Sehnenende oder Sehnenlänge eingeben [r=__ocs_arg_r__]: + .arc-scl-specify-center = ARC SCL Mittelpunkt angeben: .arc-scl-specify-start-point = ARC SCL Startpunkt angeben: - .arc-sea-specify-angle-move-cursor-perpendicula = ARC SEA Winkel angeben (Cursor senkrecht zum Akkord verschieben): + .arc-sea-specify-angle-move-cursor-perpendicula = ARC SEA Winkel angeben (Eingabezeiger senkrecht zum Bezug verschieben): .arc-sea-specify-end-point = ARC SEA Endpunkt angeben: .arc-sea-specify-start-point = ARC SEA Startpunkt angeben: .arc-sed-specify-end-point = ARC SED Endpunkt angeben: @@ -1220,7 +1220,7 @@ draw = .attdef-specify-insertion-point-for-tag = ATTDEF Einfügungspunkt für '__ocs_arg_tag__' angeben: .attdisp-attribute-display-on-off-normal = ATTDISP Attributanzeige [Ein/Aus/Normal]: .attdisp-sub-count-attribute-definition-s-updat = ATTDISP __ocs_fmt_0__: __ocs_fmt_1__ Attributdefinition(en) aktualisiert. - .attedit-no-insert-entities-in-selection = ATTEDIT: keine Entitäten in die Auswahl einfügen. + .attedit-no-insert-entities-in-selection = ATTEDIT: keine Objekte in die Auswahl einfügen. .attedit-select-an-insert-entity-first = ATTEDIT: Zuerst eine Blockreferenz auswählen. .attedit-tag-tag-up-not-found-in-selection = ATTEDIT: tag '__ocs_fmt_0__' nicht in der Auswahl gefunden. .attedit-updated-changed-attribute-s-tag-up-new = ATTEDIT: aktualisiertes __ocs_fmt_0__ Attribut(s) [__ocs_fmt_1__] = __ocs_fmt_2__. @@ -1247,8 +1247,8 @@ draw = .circle-ttt-select-second-tangent-object = CIRCLE TTT Wähle das zweite Tangentenobjekt aus: .circle-ttt-select-third-tangent-object = CIRCLE TTT Wähle drittes Tangentenobjekt: .copy-specify-base-point-count-objects = COPY Basispunkt angeben [__ocs_arg_count__ Objekte]: - .center-line = Zentrumlinie - .center-mark = Zentrum Mark + .center-line = Mittellinie + .center-mark = Mittelpunktmarkierung .center-axes = Zentrum, Achsen .center-diameter = Mitte, Durchmesser .center-radius = Mitte, Radius @@ -1262,14 +1262,14 @@ draw = .donut-specify-center-of-donut-enter-to-exit = DONUT Mittelpunkt des Rings angeben (Eingabe zum Beenden): .donut-specify-inside-diameter-0 = DONUT Innendurchmesser <0> angeben: .donut-specify-outside-diameter = DONUT Außendurchmesser angeben: - .draw-manually = Handzeichen - .ellipse-specify-center = ELLIPSE Zentrum angeben: - .ellipse-specify-major-axis-endpoint = ELLIPSE Endpunkt der Hauptachse angeben: - .ellipse-specify-minor-axis-point-or-type-half = ELLIPSE Kleiner Achsenpunkt oder Typhalblänge angeben [Hauptachse = __ocs_fmt_0__]: + .draw-manually = Manuell zeichnen + .ellipse-specify-center = ELLIPSE Mittelpunkt angeben: + .ellipse-specify-major-axis-endpoint = ELLIPSE Endpunkt der Hauptachse angeben: + .ellipse-specify-minor-axis-point-or-type-half = ELLIPSE Kleiner Achsenpunkt oder Typhalblänge angeben [Hauptachse = __ocs_fmt_0__]: .ellipse-axis-specify-first-endpoint-of-major-a = ELLIPSE (Achse) Geben Sie den ersten Endpunkt der Hauptachse an: - .ellipse-axis-specify-minor-axis-point-or-type = ELLIPSE (Achse) Kleinerer Achsenpunkt oder Typhalblänge angeben [großes r=__ocs_fmt_0__]: + .ellipse-axis-specify-minor-axis-point-or-type = ELLIPSE (Achse) Kleineren Achsenpunkt oder Typhalblänge angeben [großes r=__ocs_fmt_0__]: .ellipse-axis-specify-second-endpoint-of-major = ELLIPSE (Achse) Geben Sie den zweiten Endpunkt der Hauptachse an: - .ellipse-arc-specify-center = ELLIPSE ARC Zentrum angeben: + .ellipse-arc-specify-center = ELLIPSE ARC Mittelpunkt angeben: .ellipse-arc-specify-end-angle-point-or-type-de = ELLIPSE ARC Endwinkelpunkt oder Typgrad angeben [start=__ocs_arg_sa__]: .ellipse-arc-specify-major-axis-endpoint = ELLIPSE ARC Endpunkt der Hauptachse angeben: .ellipse-arc-specify-minor-axis-point-or-type-h = ELLIPSE ARC Kleinachsenpunkt oder Typhalblänge angeben [Hauptachse r=__ocs_arg_r__]: @@ -1292,18 +1292,18 @@ draw = .helix-specify-centre-point-of-base = HELIX Geben Sie den Mittelpunkt der Basis an: .helix-specify-helix-height = HELIX Helixhöhe angeben: .helix-specify-top-radius-same-as-base = HELIX Geben Sie den oberen Radius an: - .image-specify-insertion-point-name = IMAGE Einführstelle angeben (__ocs_arg_name__): + .image-specify-insertion-point-name = IMAGE Einfügepunkt angeben (__ocs_arg_name__): .image-specify-width-drag-right = IMAGE Breite angeben (rechts abziehen): - .invert-off = Invertiert: ausgeschaltet - .invert-on = Invertieren: am + .invert-off = Invertiert: aus + .invert-on = Invertieren: an .line-specify-first-point = LINE Geben Sie den ersten Punkt an: .line-specify-next-point-close-undo = LINE Nächsten Punkt angeben [Schließen/Zurück]: .line-specify-next-point-undo = LINE Nächsten Punkt angeben [Zurück]: .line-color-aci = Linienfarbe (ACI): .line-type = Linientyp: - .line-weight = Streckengewicht: + .line-weight = Liniengewicht: .linear-factor-dimlfac = Linearer Faktor (DIMLFAC) - .literal-spaces-space-stays-in-the-line-instead = Wörtlich definierte Leerzeichen: Leerzeichen bleiben in der Zeile, anstatt den Befehl auszuführen (das gleiche wie das Eingeben eines führenden '>'). Bleibt ein, bis man abschaltet. + .literal-spaces-space-stays-in-the-line-instead = Wörtlich definierte Leerzeichen: Leerzeichen verbleiben in der Zeile, anstatt den Befehl auszuführen (das gleiche wie das Eingeben eines führenden '>'). Aktiv bis zur Abschaltung. .mline-enter-scale-factor = MLINE Skalierungsfaktor eingeben: .mline-specify-next-point-count-pts-enter-to-fi = MLINE Nächsten Punkt angeben (__ocs_arg_count__ Punkte, Eingabe zum Beenden, C zum Schließen, S zum Festlegen des Maßstabs): .mline-specify-start-point-scale-scale = MLINE Startpunkt angeben (scale=__ocs_arg_scale__): @@ -1312,10 +1312,10 @@ draw = .point-specify-point = POINT Anzugeben ist: .polygon-enter-number-of-sides = POLYGON Geben Sie die Anzahl der Seiten <__ocs_fmt_0__> ein: .polygon-specify-center-sides = POLYGON Mitte angeben [__ocs_fmt_0__ Seiten]: - .polygon-specify-vertex-on-circle-sides-inscrib = POLYGON Scheitelpunkt im Kreis angeben [__ocs_fmt_0__ Seiten eingeschrieben]: - .polygon-c-enter-number-of-sides = POLYGON C Geben Sie die Anzahl der Seiten <__ocs_fmt_0__> ein: - .polygon-c-specify-center-sides = POLYGON C Mitte angeben [__ocs_fmt_0__ Seiten]: - .polygon-c-specify-edge-midpoint-radius-sides-c = POLYGON C Kanten-Mittelpunkt-Radius angeben [__ocs_fmt_0__ Seiten umschrieben]: + .polygon-specify-vertex-on-circle-sides-inscrib = POLYGON Stützpunkt im Kreis angeben [__ocs_fmt_0__ Seiten eingeschrieben]: + .polygon-c-enter-number-of-sides = POLYGON C Geben Sie die Anzahl der Seiten <__ocs_fmt_0__> ein: + .polygon-c-specify-center-sides = POLYGON C Mitte angeben [__ocs_fmt_0__ Seiten]: + .polygon-c-specify-edge-midpoint-radius-sides-c = POLYGON C Kanten-Mittelpunkt-Radius angeben [__ocs_fmt_0__ Seiten umschrieben]: .polygon-e-enter-number-of-sides-n = POLYGON E Geben Sie die Anzahl der Seiten ein <__ocs_arg_n__>: .polygon-e-specify-first-endpoint-of-edge-n-sid = POLYGON E Geben Sie den ersten Endpunkt der Kante an [__ocs_arg_n__ Seiten]: .polygon-e-specify-second-endpoint-of-edge-n-si = POLYGON E Geben Sie den zweiten Endpunkt der Kante an [__ocs_arg_n__ Seiten]: @@ -1325,7 +1325,7 @@ draw = .pyramid-height-add-sides-by-typing-a-3rd-numbe = PYRAMID Höhe (Seiten hinzufügen durch Eingabe einer 3. Zahl): .polygon-circumscribed = Polygon – umschrieben .polygon-edge = Polygon - Rand - .polygon-inscribed = Polygon - Inschrift + .polygon-inscribed = Polygon – eingeschrieben .ray-specify-start-point = RAY Startpunkt angeben: .ray-specify-through-point = RAY Geben Sie durch Punkt an: .rect-specify-first-corner = RECT Geben Sie die erste Ecke an: @@ -1356,7 +1356,7 @@ draw = .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-guide-cursor = REVCLOUD Eingabezeiger führen, dann klicken oder Eingabe 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__ @@ -1366,9 +1366,9 @@ draw = .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-polyline-not-planar = Die ausgewählte Polylinie ist nicht eben. + .revcloud-corners-non-zero-area = Die beiden Ecken müssen eine Fläche ungleich Null ergeben. + .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. @@ -1380,12 +1380,12 @@ draw = .rectangle-rotated = Rechteck - rotiert .rectangle-two-corners = Rechteck - Zwei Ecken .rev-cloud = Rev. Cloud - .section-cutting-plane-axis-x-y-z = SECTION Schnittebenenachse [X/Y/Z]: + .section-cutting-plane-axis-x-y-z = SECTION Schnittebenenachse [X/Y/Z]: .section-offset-along-x = SECTION versetzt entlang X: .section-offset-along-y = SECTION längs Y versetzt: .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-down-move-to-sketch-click-to-lift-e = SKETCH Stift unten — zum Skizzieren bewegen, zum anheben klicken, Eingabe zum Aufzeichnen: + .sketch-pen-up-click-to-lower-the-pen-enter-to = SKETCH Stift oben — klicken Sie auf den Stift zum absenken, Eingabe um aufzuzeichnen: .sketch-pen = Stift .sketch-increment = Schrittweite .sketch-record = Aufzeichnen @@ -1427,17 +1427,17 @@ draw = .tan-tan-tan = Tangente, Tangente, Tangente .trace = Band .ttt = Ttt - .usage-attdisp-on-off-normal = Verwendung: ATTDISP ON | OFF | NORMAL - .usage-polysolid-width-height-select-a-polyline = Verwendung: POLYSOLID (select a polyline first) - .usage-pyramid-radius-height-sides-default-4-si = Verwendung: PYRAMID [sides] (default 4 sides) + .usage-attdisp-on-off-normal = Anwendung: ATTDISP ON | OFF | NORMAL + .usage-polysolid-width-height-select-a-polyline = Anwendung: POLYSOLID (select a polyline first) + .usage-pyramid-radius-height-sides-default-4-si = Anwendung: PYRAMID [sides] (default 4 sides) .wipeout-specify-first-point-or-polyline = WIPEOUT Geben Sie den ersten Punkt oder [Rahmen/Polylinie] an : .wipeout-specify-next-point-or-undo-close-n-poi = WIPEOUT Nächsten Punkt angeben oder [Zurück/Schließen] (__ocs_arg_n__ Punkte): - .wipeout-polyline-select-a-closed-planar-polyli = WIPEOUT Polylinie Wählen Sie eine geschlossene planare Polylinie: + .wipeout-polyline-select-a-closed-planar-polyli = WIPEOUT Wählen Sie eine geschlossene planare Polylinie: .wipeout-rectangular-specify-first-corner = WIPEOUT Rechteckig Geben Sie die erste Ecke an: .wipeout-rectangular-specify-opposite-corner = WIPEOUT Rechteckig Angeben der gegenüberliegenden Ecke: - .wipeout-frames-enter-frame-mode = WIPEOUT Rahmen Rahmenmodus eingeben [Aus/Ein/AnzeigenAberNichtPlotten] <__ocs_arg_mode__>: - .wipeout-polyline-erase-source = WIPEOUT Polylinie Quellpolylinie löschen? [Ja/Nein] : - .wipeout-polyline-invalid = WIPEOUT Polylinie: Wählen Sie eine gerade, geschlossene, planare 2D-Polylinie mit mindestens 3 sich nicht schneidenden Scheitelpunkten. + .wipeout-frames-enter-frame-mode = WIPEOUT Modus des Rahmens eingeben [Aus/Ein/AnzeigenAberNichtPlotten] <__ocs_arg_mode__>: + .wipeout-polyline-erase-source = WIPEOUT Quellpolylinie löschen? [Ja/Nein] : + .wipeout-polyline-invalid = WIPEOUT Wählen Sie eine gerade, geschlossene, planare 2D-Polylinie mit mindestens 3 sich nicht schneidenden Stützpunkten. .setvar-frame-range = SETVAR: __ocs_fmt_0__ erfordert 0, 1 oder 2. .frames = Rahmen .display-but-not-plot = Anzeigen, aber nicht plotten @@ -1445,7 +1445,7 @@ draw = .image-adjust = Bildanpassung .x-ray = Röntgenstrahlen .xline-specify-a-point = XLINE Geben Sie einen Punkt an: - .xline-specify-through-point = XLINE Geben Sie durch Punkt an: + .xline-specify-through-point = XLINE Geben Sie den Durchgangspunkt an: .n-object-s-erased = __ocs_fmt_0__ Objekt(e) gelöscht. modify = @@ -1648,9 +1648,9 @@ layers = .layuniso-all-layers-restored = LAYUNISO: Alle Ebenen wiederhergestellt. .layer-freeze = Ebene frieren .layer-lock = Ebene sperren - .layer-manager = Layer-Manager + .layer-manager = Ebenenverwaltung .layer-off = Ebene ausschalten - .layer-state-manager = Layerstatus-Manager + .layer-state-manager = Ebenenstatus-Verwaltung .layer-unlock = Layer Entsperrung .layer-state-name = Name des Ebenenzustands .layer-states-are-stored-inside-the-drawing-and = Ebenenzustände werden innerhalb der Zeichnung gespeichert und bleiben nach dem Wiederöffnen verfügbar. @@ -1938,7 +1938,7 @@ insert = .attsync-no-block-named-arg = ATTSYNC: kein Block mit dem Namen "__ocs_fmt_0__". .attsync-synchronised-synced-insert-s-of-block = ATTSYNC: synchronisierte __ocs_fmt_0__ Einfügen(e) von "__ocs_fmt_1__" gegen __ocs_fmt_2__ Attributdefinition(en). .adjust = Anpassung - .array-insert = Array-Einsatz + .array-insert = Array einfügen .attach = Anlage .attach-xref = XREF anhängen .attribute-editor = Attribut Editor @@ -3012,7 +3012,7 @@ styles = .dim-line-weight-dimlwd = Dim Line Gewicht (DIMLWD) .dimstyle-updated = DimStyle '__ocs_fmt_0__' aktualisiert. .dimension-line = Bemaßungslinie - .dimension-style-manager = Bemaßungsstil-Manager + .dimension-style-manager = Bemaßungsstil-Verwaltung .double-click-a-scale-to-rename-it-edit-its-pap = Zum Umbenennen auf einen Maßstab doppelklicken. Hier wird das Verhältnis zwischen Papier- und Zeichnungseinheiten bearbeitet. Neu oder Kopieren fügt einen Maßstab hinzu. Änderungen werden erst nach Anwenden gespeichert. .down = nach unten .drawing-background = Zeichnungshintergrund @@ -3068,8 +3068,8 @@ styles = .middleoftext = Mitte des Textes .middleoftopline = Mitte der oberen Linie .minus-tolerance-dimtm = Minustoleranz (DIMTM) - .multileader-style-manager = Multiführungslinienstil-Manager - .multiline-style-manager = Mehrfachlinienstil-Manager + .multileader-style-manager = Multiführungslinienstil-Verwaltung + .multiline-style-manager = Mehrfachlinienstil-Verwaltung .no-linetypes-defined = Keine Linientypen definiert. .not-stacked = nicht gestapelt .overkill-deleted-n-duplicate-object-s = OVERKILL: gelöscht __ocs_fmt_0__ doppeltes Objekt(e). @@ -3129,7 +3129,7 @@ styles = .styles-title-case = Stile .texteditmode-v = TEXTEDITMODE = __ocs_fmt_0__ .texteditmode-set-to-n = TEXTEDITMODE auf __ocs_fmt_0__ gesetzt - .table-style-manager = Tabellenstil-Manager + .table-style-manager = Tabellenstil-Verwaltung .text-style-manager = Textstilverwaltung .text-style-label-sentence-case = Textstil: .topoftopline = Obere Linie diff --git a/locales/en-US/opencadstudio.ftl b/locales/en-US/opencadstudio.ftl index 71bd8ff26..6fbd00eee 100644 --- a/locales/en-US/opencadstudio.ftl +++ b/locales/en-US/opencadstudio.ftl @@ -1271,6 +1271,8 @@ draw = .donut-specify-center-of-donut-enter-to-exit = DONUT Specify center of donut (Enter to exit): .donut-specify-inside-diameter-0 = DONUT Specify inside diameter <0>: .donut-specify-outside-diameter = DONUT Specify outside diameter: + .donut-specify-second-point-for-inside-diameter = DONUT Specify second point for inside diameter: + .donut-specify-second-point-for-outside-diameter = DONUT Specify second point for outside diameter: .draw-manually = Draw manually .ellipse-specify-center = ELLIPSE Specify center: .ellipse-specify-major-axis-endpoint = ELLIPSE Specify major axis endpoint: @@ -2573,6 +2575,7 @@ properties = .ext-line-lineweight = Ext line lineweight .ext-line-offset = Ext line offset .extended-entity = Extended Entity + .extended-data = Extended Data .extended-lighting = Extended Lighting .extents-max = Extents Max .extents-min = Extents Min diff --git a/locales/tr-TR/opencadstudio.ftl b/locales/tr-TR/opencadstudio.ftl index 2f44e3700..8a85cf230 100644 --- a/locales/tr-TR/opencadstudio.ftl +++ b/locales/tr-TR/opencadstudio.ftl @@ -1253,6 +1253,8 @@ draw = .donut-specify-center-of-donut-enter-to-exit = DONUT Halkanın merkezini belirtin (çıkmak için Enter): .donut-specify-inside-diameter-0 = DONUT İç çapı belirtin <0>: .donut-specify-outside-diameter = DONUT Dış çapı belirtin: + .donut-specify-second-point-for-inside-diameter = DONUT İç çap için ikinci noktayı belirtin: + .donut-specify-second-point-for-outside-diameter = DONUT Dış çap için ikinci noktayı belirtin: .draw-manually = Elle çiz .ellipse-specify-center = ELLIPSE Merkezi belirtin: .ellipse-specify-major-axis-endpoint = ELLIPSE Büyük eksen uç noktasını belirtin: diff --git a/src/app/commands/styleprops.rs b/src/app/commands/styleprops.rs index 8f746a2a2..043aff806 100644 --- a/src/app/commands/styleprops.rs +++ b/src/app/commands/styleprops.rs @@ -935,6 +935,8 @@ impl OpenCADStudio { | "SKETCHINC" | "SKPOLY" | "SKTOLERANCE" + | "DONUTID" + | "DONUTOD" | "CENTEREXE" | "CENTERLAYER" | "CENTERLTYPE" @@ -965,7 +967,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 SKETCHINC SKPOLY SKTOLERANCE CENTEREXE CENTERLAYER CENTERLTYPE CENTERLTSCALE CENTERLTYPEFILE CENTERCROSSSIZE CENTERCROSSGAP CENTERMARKEXE | 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 DONUTID DONUTOD CENTEREXE CENTERLAYER CENTERLTYPE CENTERLTSCALE CENTERLTYPEFILE CENTERCROSSSIZE CENTERCROSSGAP CENTERMARKEXE | CLAYER CELTYPE TEXTSTYLE (read-only)", ); } else { let frame_kind = crate::scene::frame::kind_for_name(&name); @@ -1081,6 +1083,49 @@ impl OpenCADStudio { } return Some(self.finish_dispatch(cmd)); } + if matches!(name.as_str(), "DONUTID" | "DONUTOD") { + let current = if name == "DONUTID" { + crate::modules::draw::defaults::get_donut_inner_diameter() + } else { + crate::modules::draw::defaults::get_donut_outer_diameter() + }; + if let Some(value) = &value { + let parsed = value + .trim() + .replace(',', ".") + .parse::() + .ok() + .filter(|number| number.is_finite()); + let valid = parsed.filter(|number| { + if name == "DONUTID" { + *number >= 0.0 + } else { + *number > 0.0 + } + }); + if let Some(number) = valid { + if name == "DONUTID" { + crate::modules::draw::defaults::set_donut_inner_diameter(number); + } else { + crate::modules::draw::defaults::set_donut_outer_diameter(number); + } + self.command_line + .push_output(&format!("{name} = {number}")); + } else { + self.command_line.push_error(if name == "DONUTID" { + "SETVAR: DONUTID requires a finite value greater than or equal to zero." + } else { + "SETVAR: DONUTOD requires a finite value greater than zero." + }); + } + } else { + self.command_line.push_output(crate::tf!( + "Enter new value for {name} <{current}>:" + ).as_ref()); + self.pending_setvar = Some(name.clone()); + } + return Some(self.finish_dispatch(cmd)); + } // Parse a boolean given as 0/1 or ON/OFF. let parse_bool = |s: &str| match s.to_uppercase().as_str() { "1" | "ON" | "TRUE" => Some(true), @@ -1849,6 +1894,9 @@ impl OpenCADStudio { } else { self.tabs[i].dirty = true; } + if name == "FILLMODE" { + self.tabs[i].scene.bump_geometry(); + } self.command_line.push_output(&msg); } else { // Queried with no value (`changed == false`): diff --git a/src/app/properties.rs b/src/app/properties.rs index a7a5088b3..a7256c286 100644 --- a/src/app/properties.rs +++ b/src/app/properties.rs @@ -671,6 +671,31 @@ impl OpenCADStudio { entity, )); + { + use crate::entities::common::ro_prop; + let xd = &source_entity.common().extended_data; + if !xd.is_empty() { + let mut xd_props = Vec::new(); + for rec in xd.records() { + let value_text = rec + .values + .iter() + .map(|value| format!("{value:?}")) + .collect::>() + .join(", "); + xd_props.push(ro_prop( + t!("Application").as_ref(), + "xdata_value", + format!("{}: {value_text}", rec.application_name), + )); + } + sections.push(crate::scene::model::object::PropSection { + title: t!("Extended Data").into_owned(), + props: xd_props, + }); + } + } + // Uniform-scale checkbox for block references (#427): // while the three scale factors are equal (and the user // hasn't opted into per-axis editing) the panel shows one diff --git a/src/app/update/viewport.rs b/src/app/update/viewport.rs index 379eddbf4..338b05571 100644 --- a/src/app/update/viewport.rs +++ b/src/app/update/viewport.rs @@ -2242,6 +2242,7 @@ impl OpenCADStudio { let guide = crate::scene::WireModel { point_marker: None, taper_widths: Vec::new(), + pattern_stations: Vec::new(), world_width: 0.0, depth_override: None, display_visible: true, diff --git a/src/entities/common.rs b/src/entities/common.rs index f8417b75f..5e4f98efc 100644 --- a/src/entities/common.rs +++ b/src/entities/common.rs @@ -1,6 +1,8 @@ use std::cell::Cell; +use std::collections::HashSet; use crate::scene::model::object::{GripDef, GripShape, PropValue, Property}; +use crate::scene::model::wire_model::PatternStationPiece; /// Linear / angular unit format pulled from the document header so the /// per-thread properties pipeline can format values consistently without @@ -789,6 +791,158 @@ pub(crate) fn tapered_band_points( (pts, widths) } +pub(crate) struct WideBandOutline { + pub points: Vec<[f64; 3]>, + pub stations: Vec, + pub point_segments: Vec, + pub station_pieces: Vec, + pub source_length: f32, +} + +/// Builds the boundary of a variable-width band through the kernel. +pub(crate) fn wide_band_outline( + verts: &[([f64; 2], f64, f64, f64)], + is_closed: bool, + restart_per_segment: bool, + to_wcs: &dyn Fn(f64, f64) -> (f64, f64, f64), +) -> WideBandOutline { + let source = cadkernel::geom2d::Polyline { + vertices: verts + .iter() + .map(|(position, bulge, _, _)| cadkernel::geom2d::PolylineVertex { + position: *position, + bulge: *bulge, + }) + .collect(), + closed: is_closed, + }; + let segment_count = if is_closed { + verts.len() + } else { + verts.len().saturating_sub(1) + }; + let widths: Vec<[f64; 2]> = verts + .iter() + .take(segment_count) + .map(|(_, _, start, end)| [*start, *end]) + .collect(); + let boundary = cadkernel::geom2d::polyline_band_boundary( + &source, + &widths, + cadkernel::tessellation::DEFAULT_ANGLE, + ); + let mut points = Vec::new(); + let mut stations = Vec::new(); + let mut point_segments = Vec::new(); + for edge in boundary.edges { + if !points.is_empty() { + points.push([f64::NAN; 3]); + stations.push(0.0); + point_segments.push(-1); + } + points.extend(edge.points.into_iter().map(|point| { + let (x, y, z) = to_wcs(point[0], point[1]); + [x, y, z] + })); + stations.extend( + if restart_per_segment { + edge.segment_distances + } else { + edge.source_distances + } + .map(|distance| distance as f32), + ); + point_segments.extend([edge.source_segment as i32; 2]); + } + let station_pieces = boundary + .station_pieces + .into_iter() + .map(|piece| { + let start = to_wcs(piece.points[0][0], piece.points[0][1]); + let end = to_wcs(piece.points[1][0], piece.points[1][1]); + PatternStationPiece { + source_segment: piece.source_segment as u32, + source_distances: piece.source_distances.map(|distance| distance as f32), + segment_distances: piece.segment_distances.map(|distance| distance as f32), + vector: [ + (end.0 - start.0) as f32, + (end.1 - start.1) as f32, + (end.2 - start.2) as f32, + ], + } + }) + .collect(); + WideBandOutline { + points, + stations, + point_segments, + station_pieces, + source_length: boundary.source_length as f32, + } +} + +pub(crate) fn extrude_wide_band_outline( + outline: WideBandOutline, + extrusion: [f64; 3], +) -> WideBandOutline { + let base_points = outline.points; + let base_stations = outline.stations; + let base_segments = outline.point_segments; + let mut points = base_points.clone(); + let mut stations = base_stations.clone(); + let mut point_segments = base_segments.clone(); + points.push([f64::NAN; 3]); + stations.push(0.0); + point_segments.push(-1); + points.extend(base_points.iter().map(|point| { + if point[0].is_finite() { + [ + point[0] + extrusion[0], + point[1] + extrusion[1], + point[2] + extrusion[2], + ] + } else { + *point + } + })); + stations.extend_from_slice(&base_stations); + point_segments.extend_from_slice(&base_segments); + + let mut emitted = HashSet::new(); + for ((&point, &station), &segment) in base_points + .iter() + .zip(&base_stations) + .zip(&base_segments) + { + if !point[0].is_finite() { + continue; + } + let key = (point.map(f64::to_bits), station.to_bits(), segment); + if !emitted.insert(key) { + continue; + } + points.push([f64::NAN; 3]); + stations.push(0.0); + point_segments.push(-1); + points.push(point); + points.push([ + point[0] + extrusion[0], + point[1] + extrusion[1], + point[2] + extrusion[2], + ]); + stations.push(station); + stations.push(station); + point_segments.extend([segment; 2]); + } + WideBandOutline { + points, + stations, + point_segments, + station_pieces: outline.station_pieces, + source_length: outline.source_length, + } +} + /// Compute the filled boundary polygon for one polyline segment. /// For straight segments: a rectangle/trapezoid. /// For arc segments: an arc band (outer arc + reversed inner arc). diff --git a/src/entities/dimension.rs b/src/entities/dimension.rs index adda3e8f2..c3ff26cf7 100644 --- a/src/entities/dimension.rs +++ b/src/entities/dimension.rs @@ -2068,6 +2068,7 @@ fn tessellate_dimension_inner( wires.push(WireModel { point_marker: None, taper_widths: Vec::new(), + pattern_stations: Vec::new(), world_width: 0.0, depth_override: None, display_visible: true, @@ -2102,6 +2103,7 @@ fn tessellate_dimension_inner( wires.push(WireModel { point_marker: None, taper_widths: Vec::new(), + pattern_stations: Vec::new(), world_width: 0.0, depth_override: None, display_visible: true, @@ -2136,6 +2138,7 @@ fn tessellate_dimension_inner( wires.push(WireModel { point_marker: None, taper_widths: Vec::new(), + pattern_stations: Vec::new(), world_width: 0.0, depth_override: None, display_visible: true, @@ -2171,6 +2174,7 @@ fn tessellate_dimension_inner( wires.push(WireModel { point_marker: None, taper_widths: Vec::new(), + pattern_stations: Vec::new(), world_width: 0.0, depth_override: None, display_visible: true, @@ -2222,6 +2226,7 @@ fn tessellate_dimension_inner( wires.push(WireModel { point_marker: None, taper_widths: Vec::new(), + pattern_stations: Vec::new(), world_width: 0.0, depth_override: None, display_visible: true, diff --git a/src/entities/leader.rs b/src/entities/leader.rs index 535259180..58937cda6 100644 --- a/src/entities/leader.rs +++ b/src/entities/leader.rs @@ -676,6 +676,7 @@ impl LeaderTess for Leader { return WireModel { point_marker: None, taper_widths: Vec::new(), + pattern_stations: Vec::new(), world_width: 0.0, depth_override: None, display_visible: true, @@ -855,6 +856,7 @@ impl LeaderTess for Leader { WireModel { point_marker: None, taper_widths: Vec::new(), + pattern_stations: Vec::new(), world_width: 0.0, depth_override: None, display_visible: true, diff --git a/src/entities/lwpolyline.rs b/src/entities/lwpolyline.rs index f74588c50..77a185326 100644 --- a/src/entities/lwpolyline.rs +++ b/src/entities/lwpolyline.rs @@ -16,6 +16,15 @@ 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; +const WIDTH_EPSILON: f64 = 1.0e-9; + +fn effective_width(width: f64, constant_width: f64) -> f64 { + if width > WIDTH_EPSILON { + width + } else { + constant_width + } +} /// Midpoint position on an arc segment defined by its bulge. fn arc_midpoint(p0: [f64; 2], p1: [f64; 2], bulge: f64) -> [f64; 2] { @@ -251,7 +260,7 @@ fn thick_wide_band( } } -fn to_render(pline: &LwPolyline) -> RenderEntity { +fn to_render(pline: &LwPolyline, fill_mode: bool) -> RenderEntity { let verts = &pline.vertices; if verts.is_empty() { return RenderEntity { @@ -268,9 +277,6 @@ fn to_render(pline: &LwPolyline) -> RenderEntity { let normal = (pline.normal.x, pline.normal.y, pline.normal.z); let count = verts.len(); let seg_count = if pline.is_closed { count } else { count - 1 }; - let mut tangents: Vec = Vec::new(); - let mut key_verts: Vec<[f64; 3]> = Vec::new(); - // Convert OCS (x, y, elevation) to a WCS point. let to_wcs = |x: f64, y: f64| -> (f64, f64, f64) { crate::scene::view::transform::ocs_point_to_wcs((x, y, elev), normal) @@ -280,6 +286,44 @@ fn to_render(pline: &LwPolyline) -> RenderEntity { [wx, wy, wz] }; + let band_verts = band_verts(pline); + if !fill_mode { + let mut boundary = crate::entities::common::wide_band_outline( + &band_verts, + pline.is_closed, + !pline.plinegen, + &to_wcs, + ); + if !boundary.points.is_empty() { + if pline.thickness.abs() > 1e-10 { + boundary = crate::entities::common::extrude_wide_band_outline( + boundary, + [ + pline.thickness * normal.0, + pline.thickness * normal.1, + pline.thickness * normal.2, + ], + ); + } + let (tangent_geoms, key_vertices) = centerline_metadata(pline, &to_wcs); + return RenderEntity { + pick_tris: Vec::new(), + object: RenderObject::BoundaryLines { + points: boundary.points, + stations: boundary.stations, + point_segments: boundary.point_segments, + station_pieces: boundary.station_pieces, + source_length: boundary.source_length, + plinegen: pline.plinegen, + }, + snap_pts: vec![], + tangent_geoms, + key_vertices, + fill_tris: vec![], + }; + } + } + if pline.thickness.abs() > 1e-10 { let mut path: Vec<[f64; 3]> = Vec::new(); let mut kv: Vec<[f64; 3]> = Vec::new(); @@ -341,7 +385,7 @@ fn to_render(pline: &LwPolyline) -> RenderEntity { // A wide polyline whose per-vertex widths VARY renders a smooth taper — // handled here, before the PLINEGEN split, so both cases get it. A // uniform-width polyline falls through to the constant-band paths below. - if let Some(band_verts) = tapered_band_verts(pline) { + if tapered_band_verts(&band_verts).is_some() { let mut kv: Vec<[f64; 3]> = Vec::new(); let mut tgs: Vec = Vec::new(); for i in 0..seg_count { @@ -370,8 +414,11 @@ fn to_render(pline: &LwPolyline) -> RenderEntity { }); } } - let (pts, widths) = - crate::entities::common::tapered_band_points(&band_verts, pline.is_closed, &to_wcs); + let (pts, widths) = crate::entities::common::tapered_band_points( + &band_verts, + pline.is_closed, + &to_wcs, + ); let (fill_origin, fills) = wide_fills(pline); return RenderEntity { pick_tris: crate::entities::common::wide_band_tris(fill_origin, &fills), @@ -444,36 +491,7 @@ fn to_render(pline: &LwPolyline) -> RenderEntity { }; } - for i in 0..seg_count { - let v0 = &verts[i]; - let v1 = &verts[(i + 1) % count]; - let p0 = to_pt(v0); - let p1 = to_pt(v1); - let bulge = v0.bulge; - - if bulge.abs() < 1e-9 { - tangents.push(TangentGeom::Line { - p1: [p0[0] as f32, p0[1] as f32, p0[2] as f32], - p2: [p1[0] as f32, p1[1] as f32, p1[2] as f32], - }); - } else if let Some(arc) = crate::entities::common::BulgeArc::from_bulge( - [v0.location.x, v0.location.y], - [v1.location.x, v1.location.y], - bulge as f64, - ) { - let (wcx, wcy, wcz) = to_wcs(arc.center[0], arc.center[1]); - tangents.push(TangentGeom::Circle { - center: [wcx as f32, wcy as f32, wcz as f32], - radius: arc.radius as f32, - }); - } - - if i == 0 { - key_verts.push([p0[0], p0[1], p0[2]]); - } - key_verts.push([p1[0], p1[1], p1[2]]); - } - + let (tangents, key_verts) = centerline_metadata(pline, &to_wcs); let (fill_origin, fills) = wide_fills(pline); RenderEntity { pick_tris: crate::entities::common::wide_band_tris(fill_origin, &fills), @@ -491,20 +509,23 @@ fn to_render(pline: &LwPolyline) -> RenderEntity { } } -/// The per-vertex `(location, bulge, start_width, end_width)` band description -/// for a wide LwPolyline whose width VARIES — `None` when the width is uniform -/// (a constant band) so the caller keeps the cheaper constant-width path. -fn tapered_band_verts(pline: &LwPolyline) -> Option> { +/// Effective segment widths for an LwPolyline band. +fn band_verts(pline: &LwPolyline) -> Vec<([f64; 2], f64, f64, f64)> { let c = pline.constant_width; - let band: Vec<([f64; 2], f64, f64, f64)> = pline + pline .vertices .iter() .map(|v| { - let sw = if v.start_width > 1e-9 { v.start_width } else { c }; - let ew = if v.end_width > 1e-9 { v.end_width } else { c }; + let sw = effective_width(v.start_width, c); + let ew = effective_width(v.end_width, c); ([v.location.x, v.location.y], v.bulge, sw, ew) }) - .collect(); + .collect() +} + +fn tapered_band_verts( + band: &[([f64; 2], f64, f64, f64)], +) -> Option<&[([f64; 2], f64, f64, f64)]> { let w0 = band.first().map_or(0.0, |v| v.2); let varies = band .iter() @@ -517,6 +538,47 @@ fn tapered_band_verts(pline: &LwPolyline) -> Option (f64, f64, f64), +) -> (Vec, Vec<[f64; 3]>) { + let count = pline.vertices.len(); + let segment_count = if pline.is_closed { + count + } else { + count.saturating_sub(1) + }; + let mut tangents = Vec::with_capacity(segment_count); + let mut key_vertices = Vec::with_capacity(segment_count + 1); + for index in 0..segment_count { + let start = &pline.vertices[index]; + let end = &pline.vertices[(index + 1) % count]; + let p0 = to_wcs(start.location.x, start.location.y); + let p1 = to_wcs(end.location.x, end.location.y); + if start.bulge.abs() < 1e-9 { + tangents.push(TangentGeom::Line { + p1: [p0.0 as f32, p0.1 as f32, p0.2 as f32], + p2: [p1.0 as f32, p1.1 as f32, p1.2 as f32], + }); + } else if let Some(arc) = crate::entities::common::BulgeArc::from_bulge( + [start.location.x, start.location.y], + [end.location.x, end.location.y], + start.bulge, + ) { + let center = to_wcs(arc.center[0], arc.center[1]); + tangents.push(TangentGeom::Circle { + center: [center.0 as f32, center.1 as f32, center.2 as f32], + radius: arc.radius as f32, + }); + } + if index == 0 { + key_vertices.push([p0.0, p0.1, p0.2]); + } + key_vertices.push([p1.0, p1.1, p1.2]); + } + (tangents, key_vertices) +} + /// Split at vertex `idx`: a closed polyline re-opens there (one piece); an /// open one splits into two (interior vertices only). `None` when invalid. pub(crate) fn break_at_vertex( @@ -852,8 +914,12 @@ fn properties(pline: &LwPolyline) -> Vec { let v = pline.vertices.get(vi); let vx = v.map_or(0.0, |v| v.location.x); let vy = v.map_or(0.0, |v| v.location.y); - 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 start_w = v.map_or(0.0, |v| { + effective_width(v.start_width, pline.constant_width) + }); + let end_w = v.map_or(0.0, |v| { + effective_width(v.end_width, pline.constant_width) + }); let mp = ::mass_props(pline); let cloud_arc_length = revision_cloud_arc_length(pline); let vertex_label = if n == 0 { @@ -964,16 +1030,7 @@ fn apply_geom_prop(pline: &mut LwPolyline, field: &str, value: &str) { }; match field { "elevation" => pline.elevation = v, - "global_width" if v.is_finite() && v >= 0.0 => { - pline.constant_width = v; - // Global width replaces every segment override. Keeping old - // start/end values would make this editable row appear to work - // while the renderer continued using the stale per-segment data. - for vertex in &mut pline.vertices { - vertex.start_width = 0.0; - vertex.end_width = 0.0; - } - } + "global_width" if v.is_finite() && v >= 0.0 => pline.constant_width = v, "vertex_x" => { if let Some(vtx) = pline.vertices.get_mut(vi) { vtx.location.x = v; @@ -985,27 +1042,11 @@ fn apply_geom_prop(pline: &mut LwPolyline, field: &str, value: &str) { } } "start_width" if v.is_finite() && v >= 0.0 => { - if pline.constant_width > 0.0 { - let width = pline.constant_width; - for vertex in &mut pline.vertices { - vertex.start_width = width; - vertex.end_width = width; - } - pline.constant_width = 0.0; - } if let Some(vtx) = pline.vertices.get_mut(vi) { vtx.start_width = v; } } "end_width" if v.is_finite() && v >= 0.0 => { - if pline.constant_width > 0.0 { - let width = pline.constant_width; - for vertex in &mut pline.vertices { - vertex.start_width = width; - vertex.end_width = width; - } - pline.constant_width = 0.0; - } if let Some(vtx) = pline.vertices.get_mut(vi) { vtx.end_width = v; } @@ -1104,8 +1145,8 @@ fn apply_transform(pline: &mut LwPolyline, t: &EntityTransform) { } impl RenderConvertible for LwPolyline { - fn to_render(&self, _document: &acadrust::CadDocument) -> Option { - Some(to_render(self)) + fn to_render(&self, document: &acadrust::CadDocument) -> Option { + Some(to_render(self, document.header.fill_mode)) } } @@ -1226,16 +1267,8 @@ impl crate::entities::traits::Grippable for LwPolyline { new_v.location.x = midpoint[0]; new_v.location.y = midpoint[1]; new_v.vertex_id = 0; - let effective_start = if v0.start_width > 1e-9 { - v0.start_width - } else { - self.constant_width - }; - let effective_end = if v0.end_width > 1e-9 { - v0.end_width - } else { - self.constant_width - }; + let effective_start = effective_width(v0.start_width, self.constant_width); + let effective_end = effective_width(v0.end_width, self.constant_width); let middle_width = (effective_start + effective_end) * 0.5; self.vertices[i0].end_width = middle_width; new_v.start_width = middle_width; @@ -1296,7 +1329,6 @@ pub(crate) fn wide_fills(pl: &acadrust::entities::LwPolyline) -> ([f64; 2], Vec< // Feeding the stored width in whole draws every wide polyline twice as wide // as the file asks for, which on a donut (a closed 2-vertex bulge-1 polyline) // shows up as a disc 1.5× its real radius. - let hw_const = pl.constant_width as f32 * 0.5; let verts = &pl.vertices; let n = verts.len(); if n < 2 { @@ -1308,16 +1340,8 @@ pub(crate) fn wide_fills(pl: &acadrust::entities::LwPolyline) -> ([f64; 2], Vec< for i in 0..seg_count { let v0 = &verts[i]; let v1 = &verts[(i + 1) % n]; - let hw0 = if v0.start_width > 1e-9 { - v0.start_width as f32 * 0.5 - } else { - hw_const - }; - let hw1 = if v0.end_width > 1e-9 { - v0.end_width as f32 * 0.5 - } else { - hw_const - }; + let hw0 = effective_width(v0.start_width, pl.constant_width) as f32 * 0.5; + let hw1 = effective_width(v0.end_width, pl.constant_width) as f32 * 0.5; if hw0 < 1e-6 && hw1 < 1e-6 { continue; } diff --git a/src/entities/multileader.rs b/src/entities/multileader.rs index d25680135..2c483ea54 100644 --- a/src/entities/multileader.rs +++ b/src/entities/multileader.rs @@ -1505,6 +1505,7 @@ impl MultiLeaderTess for MultiLeader { wires.push(WireModel { point_marker: None, taper_widths: Vec::new(), + pattern_stations: Vec::new(), world_width: 0.0, depth_override: None, display_visible: true, @@ -1845,6 +1846,7 @@ impl MultiLeaderTess for MultiLeader { wires.push(WireModel { point_marker: None, taper_widths: Vec::new(), + pattern_stations: Vec::new(), world_width: 0.0, depth_override: None, display_visible: true, @@ -1918,6 +1920,7 @@ impl MultiLeaderTess for MultiLeader { wires.push(WireModel { point_marker: None, taper_widths: Vec::new(), + pattern_stations: Vec::new(), world_width: 0.0, depth_override: None, display_visible: true, @@ -2006,6 +2009,7 @@ impl MultiLeaderTess for MultiLeader { wires.push(WireModel { point_marker: None, taper_widths: Vec::new(), + pattern_stations: Vec::new(), world_width: 0.0, depth_override: None, display_visible: true, @@ -2049,6 +2053,7 @@ impl MultiLeaderTess for MultiLeader { wires.push(WireModel { point_marker: None, taper_widths: Vec::new(), + pattern_stations: Vec::new(), world_width: 0.0, depth_override: None, display_visible: true, diff --git a/src/entities/polyline.rs b/src/entities/polyline.rs index 6e63b5588..54d98ab77 100644 --- a/src/entities/polyline.rs +++ b/src/entities/polyline.rs @@ -195,7 +195,7 @@ pub fn drawn_vertices2d( (kept.len() >= 2).then_some(kept) } -fn tessellate_polyline2d(pl: &Polyline2D) -> RenderEntity { +fn tessellate_polyline2d(pl: &Polyline2D, fill_mode: bool) -> RenderEntity { let filtered = drawn_vertices2d(pl); let verts: &[acadrust::entities::Vertex2D] = filtered.as_deref().unwrap_or(&pl.vertices); if verts.is_empty() { @@ -224,6 +224,47 @@ fn tessellate_polyline2d(pl: &Polyline2D) -> RenderEntity { [wx, wy, wz] }; + if !fill_mode { + let continuous = pl.flags.bits() + & acadrust::entities::PolylineFlags::LINETYPE_CONTINUOUS.bits() + != 0; + let mut boundary = crate::entities::common::wide_band_outline( + &band_verts_2d(pl), + pl.is_closed(), + !continuous, + &to_wcs, + ); + if !boundary.points.is_empty() { + if pl.thickness.abs() > 1e-10 { + boundary = crate::entities::common::extrude_wide_band_outline( + boundary, + [ + pl.thickness * normal.0, + pl.thickness * normal.1, + pl.thickness * normal.2, + ], + ); + } + let (tangent_geoms, key_vertices) = + centerline_metadata_2d(verts, pl.is_closed(), &to_wcs); + return RenderEntity { + pick_tris: Vec::new(), + object: RenderObject::BoundaryLines { + points: boundary.points, + stations: boundary.stations, + point_segments: boundary.point_segments, + station_pieces: boundary.station_pieces, + source_length: boundary.source_length, + plinegen: continuous, + }, + snap_pts: vec![], + tangent_geoms, + key_vertices, + fill_tris: vec![], + }; + } + } + if pl.thickness.abs() > 1e-10 { let (nx, ny, nz) = normal; let t = pl.thickness; @@ -348,13 +389,14 @@ fn tessellate_polyline2d(pl: &Polyline2D) -> RenderEntity { key_verts.push([p1[0], p1[1], p1[2]]); } + let band_verts = band_verts_2d(pl); let (fill_origin, fills) = wide_fills(pl); // A wide Polyline2D whose per-vertex widths VARY renders a smooth taper; a // uniform-width one keeps the constant-band Contour. - let object = match tapered_band_verts_2d(pl) { + let object = match tapered_band_verts_2d(&band_verts) { Some(band_verts) => { let (pts, widths) = crate::entities::common::tapered_band_points( - &band_verts, + band_verts, pl.is_closed(), &to_wcs, ); @@ -376,16 +418,15 @@ fn tessellate_polyline2d(pl: &Polyline2D) -> RenderEntity { } } -/// Per-vertex `(location, bulge, start_width, end_width)` band description for a -/// wide Polyline2D whose width VARIES — `None` when the width is uniform. -fn tapered_band_verts_2d( +/// Effective segment widths for a Polyline2D band. +fn band_verts_2d( pl: &acadrust::entities::Polyline2D, -) -> Option> { +) -> Vec<([f64; 2], f64, f64, f64)> { let default_start = pl.start_width; let default_end = pl.end_width; let filtered = drawn_vertices2d(pl); let verts: &[acadrust::entities::Vertex2D] = filtered.as_deref().unwrap_or(&pl.vertices); - let band: Vec<([f64; 2], f64, f64, f64)> = verts + verts .iter() .map(|v| { let sw = if v.start_width > 1e-9 { @@ -400,7 +441,12 @@ fn tapered_band_verts_2d( }; ([v.location.x, v.location.y], v.bulge, sw, ew) }) - .collect(); + .collect() +} + +fn tapered_band_verts_2d( + band: &[([f64; 2], f64, f64, f64)], +) -> Option<&[([f64; 2], f64, f64, f64)]> { let w0 = band.first().map_or(0.0, |v| v.2); let varies = band .iter() @@ -412,9 +458,51 @@ fn tapered_band_verts_2d( } } +fn centerline_metadata_2d( + verts: &[acadrust::entities::Vertex2D], + closed: bool, + to_wcs: &dyn Fn(f64, f64) -> (f64, f64, f64), +) -> (Vec, Vec<[f64; 3]>) { + let count = verts.len(); + let segment_count = if closed { + count + } else { + count.saturating_sub(1) + }; + let mut tangents = Vec::with_capacity(segment_count); + let mut key_vertices = Vec::with_capacity(segment_count + 1); + for index in 0..segment_count { + let start = &verts[index]; + let end = &verts[(index + 1) % count]; + let p0 = to_wcs(start.location.x, start.location.y); + let p1 = to_wcs(end.location.x, end.location.y); + if start.bulge.abs() < 1e-9 { + tangents.push(TangentGeom::Line { + p1: [p0.0 as f32, p0.1 as f32, p0.2 as f32], + p2: [p1.0 as f32, p1.1 as f32, p1.2 as f32], + }); + } else if let Some(arc) = crate::entities::common::BulgeArc::from_bulge( + [start.location.x, start.location.y], + [end.location.x, end.location.y], + start.bulge, + ) { + let center = to_wcs(arc.center[0], arc.center[1]); + tangents.push(TangentGeom::Circle { + center: [center.0 as f32, center.1 as f32, center.2 as f32], + radius: arc.radius as f32, + }); + } + if index == 0 { + key_vertices.push([p0.0, p0.1, p0.2]); + } + key_vertices.push([p1.0, p1.1, p1.2]); + } + (tangents, key_vertices) +} + impl RenderConvertible for Polyline2D { - fn to_render(&self, _document: &acadrust::CadDocument) -> Option { - Some(tessellate_polyline2d(self)) + fn to_render(&self, document: &acadrust::CadDocument) -> Option { + Some(tessellate_polyline2d(self, document.header.fill_mode)) } } diff --git a/src/entities/table.rs b/src/entities/table.rs index 67acd76b9..498f428bc 100644 --- a/src/entities/table.rs +++ b/src/entities/table.rs @@ -1669,6 +1669,7 @@ pub fn tessellate_table( WireModel { point_marker: None, taper_widths: Vec::new(), + pattern_stations: Vec::new(), world_width: 0.0, depth_override: None, display_visible: true, diff --git a/src/io/pdf_export.rs b/src/io/pdf_export.rs index ed1c160bd..3f8c5d2d7 100644 --- a/src/io/pdf_export.rs +++ b/src/io/pdf_export.rs @@ -593,6 +593,43 @@ fn append_pdf_page( // Linetype dash pattern. Without this every wire exported as a solid // line regardless of its linetype (dashed / centre / dash-dot). (#155) let dash_arr = dash_array_from_pattern(wire.pattern_length, &wire.pattern, MM_TO_PT); + let stationed = + !dash_arr.is_empty() && wire.pattern_stations.len() > wire.points.len(); + if stationed { + if last_dash.as_ref().is_none_or(|dash| !dash.is_empty()) { + ops.push(Op::SetLineDashPattern { + dash: LineDashPattern::default(), + }); + last_dash = Some(Vec::new()); + } + for index in 0..wire.points.len().saturating_sub(1) { + if !wire.points[index][0].is_finite() + || !wire.points[index + 1][0].is_finite() + { + continue; + } + let start = wire.point_world(index, paper_h as f64 / scale.max(1e-6) as f64); + let end = wire.point_world(index + 1, paper_h as f64 / scale.max(1e-6) as f64); + for [from, to] in visible_station_ranges( + wire.pattern_stations[index], + wire.pattern_stations[index + 1], + wire.pattern_length, + &wire.pattern, + ) { + let point = |t: f32| { + LinePoint { + p: Point::new( + Mm((start.x + (end.x - start.x) * t as f64 + ox) as f32), + Mm((start.y + (end.y - start.y) * t as f64 + oy) as f32), + ), + bezier: false, + } + }; + flush_line(&mut ops, &[point(from), point(to)], None); + } + } + continue; + } if last_dash.as_deref() != Some(dash_arr.as_slice()) { let dash = if dash_arr.is_empty() { LineDashPattern::default() @@ -600,7 +637,7 @@ fn append_pdf_page( LineDashPattern::from_array(&dash_arr, 0) }; ops.push(Op::SetLineDashPattern { dash }); - last_dash = Some(dash_arr); + last_dash = Some(dash_arr.clone()); } // Emit segments (NaN = pen-up). Points are the "high" half of a @@ -670,6 +707,76 @@ fn dash_array_from_pattern(pattern_length: f32, pattern: &[f32; 8], mm_to_pt: f3 .collect() } +#[cfg(not(target_arch = "wasm32"))] +fn visible_station_ranges( + start: f32, + end: f32, + pattern_length: f32, + pattern: &[f32; 8], +) -> Vec<[f32; 2]> { + let count = pattern.iter().rposition(|value| *value != 0.0).map_or(0, |i| i + 1); + if count == 0 || pattern_length <= 1e-6 { + return vec![[0.0, 1.0]]; + } + let dot = 1.0 / MM_TO_PT; + let mut elements: Vec<(f32, bool)> = pattern[..count] + .iter() + .map(|value| (if *value == 0.0 { dot } else { value.abs() }, *value >= 0.0)) + .collect(); + let total: f32 = elements.iter().map(|(length, _)| *length).sum(); + if total <= 1e-6 { + return vec![[0.0, 1.0]]; + } + let factor = pattern_length / total; + for (length, _) in &mut elements { + *length *= factor; + } + let delta = end - start; + let state = |station: f32, forward: bool| { + let mut phase = station.rem_euclid(pattern_length); + if !forward && phase <= 1e-6 { + phase = pattern_length; + } + let mut offset = 0.0; + if forward { + for &(length, drawn) in &elements { + let end = offset + length; + if phase < end - 1e-6 { + return (drawn, end - phase); + } + offset = end; + } + (elements[0].1, elements[0].0) + } else { + offset = pattern_length; + for &(length, drawn) in elements.iter().rev() { + offset -= length; + if phase > offset + 1e-6 { + return (drawn, phase - offset); + } + } + let last = elements[elements.len() - 1]; + (last.1, last.0) + } + }; + if delta.abs() <= 1e-6 { + return state(start, true).0.then_some([0.0, 1.0]).into_iter().collect(); + } + + let mut ranges = Vec::new(); + let mut t = 0.0; + while t < 1.0 - 1e-6 { + let station = start + delta * t; + let (drawn, remaining) = state(station, delta > 0.0); + let next = (t + remaining / delta.abs()).clamp(t + 1e-6, 1.0); + if drawn { + ranges.push([t, next]); + } + t = next; + } + ranges +} + #[cfg(not(target_arch = "wasm32"))] fn flush_line(ops: &mut Vec, pts: &[LinePoint], dot_radius: Option) { if pts.len() < 2 { diff --git a/src/locale_catalog.rs b/src/locale_catalog.rs index 3a7fce6f2..e5f04731f 100644 --- a/src/locale_catalog.rs +++ b/src/locale_catalog.rs @@ -786,6 +786,8 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static "DONUT Specify center of donut (Enter to exit):" => Some(("draw", "donut-specify-center-of-donut-enter-to-exit")), "DONUT Specify inside diameter <0>:" => Some(("draw", "donut-specify-inside-diameter-0")), "DONUT Specify outside diameter:" => Some(("draw", "donut-specify-outside-diameter")), + "DONUT Specify second point for inside diameter:" => Some(("draw", "donut-specify-second-point-for-inside-diameter")), + "DONUT Specify second point for outside diameter:" => Some(("draw", "donut-specify-second-point-for-outside-diameter")), "DRAWORDER Select reference object (move selection above):" => Some(("view", "draworder-select-reference-object-move-selection-above")), "DRAWORDER Select reference object (move selection under):" => Some(("view", "draworder-select-reference-object-move-selection-under")), "DRAWORDER [Front / Back] (Above/Under by typing):" => Some(("view", "draworder-front-back-above-under-handle-by-typ")), @@ -1015,6 +1017,7 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static "Ext line offset" => Some(("properties", "ext-line-offset")), "Ext line weight (DIMLWE)" => Some(("draw", "ext-line-weight-dimlwe")), "Extend" => Some(("modify", "extend")), + "Extended Data" => Some(("properties", "extended-data")), "Extended Entity" => Some(("properties", "extended-entity")), "Extended Lighting" => Some(("properties", "extended-lighting")), "Extension" => Some(("common", "extension")), diff --git a/src/modules/annotate/aligned_dim.rs b/src/modules/annotate/aligned_dim.rs index 5a4154262..81fa87865 100644 --- a/src/modules/annotate/aligned_dim.rs +++ b/src/modules/annotate/aligned_dim.rs @@ -222,6 +222,7 @@ impl CadCommand for AlignedDimensionCommand { Some(WireModel { point_marker: None, taper_widths: Vec::new(), + pattern_stations: Vec::new(), world_width: 0.0, depth_override: None, display_visible: true, @@ -281,6 +282,7 @@ fn preview_aligned(p1: DVec3, p2: DVec3, dim_pt: DVec3) -> WireModel { WireModel { point_marker: None, taper_widths: Vec::new(), + pattern_stations: Vec::new(), world_width: 0.0, depth_override: None, display_visible: true, diff --git a/src/modules/annotate/angular_dim.rs b/src/modules/annotate/angular_dim.rs index b526532d5..36a917e45 100644 --- a/src/modules/annotate/angular_dim.rs +++ b/src/modules/annotate/angular_dim.rs @@ -163,6 +163,7 @@ fn preview_wire(points: Vec) -> WireModel { WireModel { point_marker: None, taper_widths: Vec::new(), + pattern_stations: Vec::new(), world_width: 0.0, depth_override: None, display_visible: true, diff --git a/src/modules/annotate/diameter_dim.rs b/src/modules/annotate/diameter_dim.rs index f0a570d65..1fe7ccfa5 100644 --- a/src/modules/annotate/diameter_dim.rs +++ b/src/modules/annotate/diameter_dim.rs @@ -194,6 +194,7 @@ fn preview_line(a: Vec3, b: Vec3) -> WireModel { WireModel { point_marker: None, taper_widths: Vec::new(), + pattern_stations: Vec::new(), world_width: 0.0, depth_override: None, display_visible: true, diff --git a/src/modules/annotate/dim_baseline.rs b/src/modules/annotate/dim_baseline.rs index b38718395..207e9c352 100644 --- a/src/modules/annotate/dim_baseline.rs +++ b/src/modules/annotate/dim_baseline.rs @@ -182,6 +182,7 @@ impl CadCommand for DimBaselineCommand { Some(WireModel { point_marker: None, taper_widths: Vec::new(), + pattern_stations: Vec::new(), world_width: 0.0, depth_override: None, display_visible: true, diff --git a/src/modules/annotate/dim_continue.rs b/src/modules/annotate/dim_continue.rs index d2371aac9..b7eb336b7 100644 --- a/src/modules/annotate/dim_continue.rs +++ b/src/modules/annotate/dim_continue.rs @@ -162,6 +162,7 @@ impl CadCommand for DimContinueCommand { Some(WireModel { point_marker: None, taper_widths: Vec::new(), + pattern_stations: Vec::new(), world_width: 0.0, depth_override: None, display_visible: true, diff --git a/src/modules/annotate/dimjogline.rs b/src/modules/annotate/dimjogline.rs index ebaaa8b49..168b631da 100644 --- a/src/modules/annotate/dimjogline.rs +++ b/src/modules/annotate/dimjogline.rs @@ -88,6 +88,7 @@ impl CadCommand for DimJogLineCommand { Some(WireModel { point_marker: None, taper_widths: Vec::new(), + pattern_stations: Vec::new(), world_width: 0.0, depth_override: None, display_visible: true, diff --git a/src/modules/annotate/dimtedit.rs b/src/modules/annotate/dimtedit.rs index e84213202..6c5a60b98 100644 --- a/src/modules/annotate/dimtedit.rs +++ b/src/modules/annotate/dimtedit.rs @@ -105,6 +105,7 @@ impl CadCommand for DimTeditCommand { Some(WireModel { point_marker: None, taper_widths: Vec::new(), + pattern_stations: Vec::new(), world_width: 0.0, depth_override: None, display_visible: true, diff --git a/src/modules/annotate/leader_cmd.rs b/src/modules/annotate/leader_cmd.rs index 1e13d936f..c8dfece71 100644 --- a/src/modules/annotate/leader_cmd.rs +++ b/src/modules/annotate/leader_cmd.rs @@ -302,6 +302,7 @@ fn preview_wire(pts: &[Vec3], arrow_size: f32) -> WireModel { WireModel { point_marker: None, taper_widths: Vec::new(), + pattern_stations: Vec::new(), world_width: 0.0, depth_override: None, display_visible: true, diff --git a/src/modules/annotate/linear_dim.rs b/src/modules/annotate/linear_dim.rs index f2af91e0a..68efd6668 100644 --- a/src/modules/annotate/linear_dim.rs +++ b/src/modules/annotate/linear_dim.rs @@ -239,6 +239,7 @@ fn preview_wire(points: Vec) -> WireModel { WireModel { point_marker: None, taper_widths: Vec::new(), + pattern_stations: Vec::new(), world_width: 0.0, depth_override: None, display_visible: true, diff --git a/src/modules/annotate/mleader_cmd.rs b/src/modules/annotate/mleader_cmd.rs index 383a8d666..b319daa0b 100644 --- a/src/modules/annotate/mleader_cmd.rs +++ b/src/modules/annotate/mleader_cmd.rs @@ -229,6 +229,7 @@ fn preview_wire(pts: &[Vec3], arrow_size: f32) -> WireModel { WireModel { point_marker: None, taper_widths: Vec::new(), + pattern_stations: Vec::new(), world_width: 0.0, depth_override: None, display_visible: true, diff --git a/src/modules/annotate/mleader_edit.rs b/src/modules/annotate/mleader_edit.rs index e3f19c235..fe4727970 100644 --- a/src/modules/annotate/mleader_edit.rs +++ b/src/modules/annotate/mleader_edit.rs @@ -464,6 +464,7 @@ fn preview_wire(pts: &[Vec3]) -> WireModel { WireModel { point_marker: None, taper_widths: Vec::new(), + pattern_stations: Vec::new(), world_width: 0.0, depth_override: None, display_visible: true, diff --git a/src/modules/annotate/ordinate_dim.rs b/src/modules/annotate/ordinate_dim.rs index 331677a34..203659bf9 100644 --- a/src/modules/annotate/ordinate_dim.rs +++ b/src/modules/annotate/ordinate_dim.rs @@ -139,6 +139,7 @@ fn preview_wire(points: Vec) -> WireModel { WireModel { point_marker: None, taper_widths: Vec::new(), + pattern_stations: Vec::new(), world_width: 0.0, depth_override: None, display_visible: true, diff --git a/src/modules/annotate/qdim.rs b/src/modules/annotate/qdim.rs index 4c1596a1d..151cfd4af 100644 --- a/src/modules/annotate/qdim.rs +++ b/src/modules/annotate/qdim.rs @@ -95,6 +95,7 @@ impl CadCommand for QdimCommand { Some(WireModel { point_marker: None, taper_widths: Vec::new(), + pattern_stations: Vec::new(), world_width: 0.0, depth_override: None, display_visible: true, diff --git a/src/modules/annotate/radius_dim.rs b/src/modules/annotate/radius_dim.rs index e91ca64e9..6bb9ec58e 100644 --- a/src/modules/annotate/radius_dim.rs +++ b/src/modules/annotate/radius_dim.rs @@ -199,6 +199,7 @@ fn preview_wire(points: Vec) -> WireModel { WireModel { point_marker: None, taper_widths: Vec::new(), + pattern_stations: Vec::new(), world_width: 0.0, depth_override: None, display_visible: true, diff --git a/src/modules/annotate/table_cmd.rs b/src/modules/annotate/table_cmd.rs index c243e2b60..458f1ee99 100644 --- a/src/modules/annotate/table_cmd.rs +++ b/src/modules/annotate/table_cmd.rs @@ -201,6 +201,7 @@ impl CadCommand for TableCommand { Some(WireModel { point_marker: None, taper_widths: Vec::new(), + pattern_stations: Vec::new(), world_width: 0.0, depth_override: None, display_visible: true, diff --git a/src/modules/annotate/tolerance_cmd.rs b/src/modules/annotate/tolerance_cmd.rs index 3033e969e..d27927eee 100644 --- a/src/modules/annotate/tolerance_cmd.rs +++ b/src/modules/annotate/tolerance_cmd.rs @@ -105,6 +105,7 @@ impl CadCommand for ToleranceCommand { Some(WireModel { point_marker: None, taper_widths: Vec::new(), + pattern_stations: Vec::new(), world_width: 0.0, depth_override: None, display_visible: true, diff --git a/src/modules/draw/defaults.rs b/src/modules/draw/defaults.rs index a3598fa8b..f21a40f89 100644 --- a/src/modules/draw/defaults.rs +++ b/src/modules/draw/defaults.rs @@ -26,6 +26,8 @@ thread_local! { static RECT_CHAMFER1: Cell = Cell::new(0.0); static RECT_CHAMFER2: Cell = Cell::new(0.0); static RECT_FILLET: Cell = Cell::new(0.0); + static DONUT_INNER_DIAMETER: Cell = Cell::new(0.5); + static DONUT_OUTER_DIAMETER: Cell = Cell::new(1.0); } macro_rules! accessors { @@ -68,3 +70,13 @@ accessors!(get_rect_rotation, set_rect_rotation, RECT_ROTATION); accessors!(get_rect_chamfer1, set_rect_chamfer1, RECT_CHAMFER1); accessors!(get_rect_chamfer2, set_rect_chamfer2, RECT_CHAMFER2); accessors!(get_rect_fillet, set_rect_fillet, RECT_FILLET); +accessors!( + get_donut_inner_diameter, + set_donut_inner_diameter, + DONUT_INNER_DIAMETER +); +accessors!( + get_donut_outer_diameter, + set_donut_outer_diameter, + DONUT_OUTER_DIAMETER +); diff --git a/src/modules/draw/draw/attdef.rs b/src/modules/draw/draw/attdef.rs index 33551f5a7..5d6c46db4 100644 --- a/src/modules/draw/draw/attdef.rs +++ b/src/modules/draw/draw/attdef.rs @@ -203,6 +203,7 @@ impl CadCommand for AttdefCommand { Some(WireModel { point_marker: None, taper_widths: Vec::new(), + pattern_stations: Vec::new(), world_width: 0.0, depth_override: None, display_visible: true, diff --git a/src/modules/draw/draw/donut.rs b/src/modules/draw/draw/donut.rs index c459492a6..fe1fb8412 100644 --- a/src/modules/draw/draw/donut.rs +++ b/src/modules/draw/draw/donut.rs @@ -1,21 +1,19 @@ -// DONUT command — create a filled circular ring (thick LwPolyline). -// -// A donut is an LwPolyline with: -// - 2 vertices at (cx ± r_avg, 0), both with bulge = 1.0 (two 180° CCW arcs) -// - start_width = end_width = (outer - inner) / 2 -// - is_closed = true -// -// Workflow: -// 1. Type inner diameter (or 0 for a filled circle) -// 2. Type outer diameter -// 3. Click center point(s); Enter to finish +// DONUT creates a ring as a closed, constant-width LwPolyline. use acadrust::entities::{LwPolyline, LwVertex}; use acadrust::EntityType; +use cadkernel::geom2d::{ + Circle as KernelCircle, Curve as KernelCurve, Vec2 as KernelVec2, +}; use glam::DVec3; + use crate::t; use crate::command::{CadCommand, CmdResult, WorkingPlane}; +use crate::modules::draw::defaults; +use crate::scene::model::wire_model::WireModel; + +const TAU: f64 = std::f64::consts::TAU; pub struct DonutCommand { state: DonutState, @@ -24,21 +22,64 @@ pub struct DonutCommand { plane: WorkingPlane, } +#[derive(Clone, Copy)] enum DonutState { AskInner, + AskInnerSecond(DVec3), AskOuter, + AskOuterSecond(DVec3), PlaceCenter, } impl DonutCommand { pub fn new() -> Self { + let mut inner_diameter = defaults::get_donut_inner_diameter(); + let mut outer_diameter = defaults::get_donut_outer_diameter(); + if inner_diameter > outer_diameter { + std::mem::swap(&mut inner_diameter, &mut outer_diameter); + } Self { state: DonutState::AskInner, - inner_r: 0.0, - outer_r: 1.0, + inner_r: inner_diameter * 0.5, + outer_r: outer_diameter * 0.5, plane: WorkingPlane::default(), } } + + fn set_inner_diameter(&mut self, diameter: f64) { + self.inner_r = diameter * 0.5; + self.state = DonutState::AskOuter; + } + + fn set_outer_diameter(&mut self, diameter: f64) { + self.outer_r = diameter * 0.5; + if self.inner_r > self.outer_r { + std::mem::swap(&mut self.inner_r, &mut self.outer_r); + } + defaults::set_donut_inner_diameter(self.inner_r * 2.0); + defaults::set_donut_outer_diameter(self.outer_r * 2.0); + self.state = DonutState::PlaceCenter; + } + + fn point_distance(&self, first: DVec3, second: DVec3) -> f64 { + let first = self.plane.to_local(first); + let second = self.plane.to_local(second); + KernelVec2::new(first.x, first.y).distance(KernelVec2::new(second.x, second.y)) + } + + fn project_to_plane(&self, point: DVec3) -> DVec3 { + let local = self.plane.to_local(point); + self.plane.to_world(DVec3::new(local.x, local.y, 0.0)) + } +} + +fn prompt_with_default(prompt: &str, value: f64) -> String { + match prompt.char_indices().next_back() { + Some((index, suffix @ (':' | ':'))) => { + format!("{} <{value:.4}>{suffix}", &prompt[..index]) + } + _ => format!("{prompt} <{value:.4}>"), + } } impl CadCommand for DonutCommand { @@ -51,9 +92,19 @@ impl CadCommand for DonutCommand { } fn prompt(&self) -> String { - match &self.state { - DonutState::AskInner => t!("DONUT Specify inside diameter <0>:").into_owned(), - DonutState::AskOuter => t!("DONUT Specify outside diameter:").into_owned(), + match self.state { + DonutState::AskInner => t!("DONUT Specify inside diameter <0>:") + .replace("<0>", &format!("<{:.4}>", self.inner_r * 2.0)), + DonutState::AskInnerSecond(_) => { + t!("DONUT Specify second point for inside diameter:").into_owned() + } + DonutState::AskOuter => prompt_with_default( + t!("DONUT Specify outside diameter:").as_ref(), + self.outer_r * 2.0, + ), + DonutState::AskOuterSecond(_) => { + t!("DONUT Specify second point for outside diameter:").into_owned() + } DonutState::PlaceCenter => { t!("DONUT Specify center of donut (Enter to exit):").into_owned() } @@ -78,22 +129,20 @@ impl CadCommand for DonutCommand { .replace(',', ".") .parse() .ok() - .filter(|&v: &f64| v >= 0.0)?; - match &self.state { + .filter(|v: &f64| v.is_finite())?; + match self.state { DonutState::AskInner => { - self.inner_r = val / 2.0; - self.state = DonutState::AskOuter; + if val < 0.0 { + return Some(CmdResult::NeedPoint); + } + self.set_inner_diameter(val); Some(CmdResult::NeedPoint) } DonutState::AskOuter => { if val <= 0.0 { - return None; + return Some(CmdResult::NeedPoint); } - self.outer_r = val / 2.0; - if self.inner_r > self.outer_r { - std::mem::swap(&mut self.inner_r, &mut self.outer_r); - } - self.state = DonutState::PlaceCenter; + self.set_outer_diameter(val); Some(CmdResult::NeedPoint) } _ => None, @@ -101,29 +150,107 @@ impl CadCommand for DonutCommand { } fn on_point(&mut self, pt: DVec3) -> CmdResult { - match &self.state { + match self.state { + DonutState::AskInner => { + self.state = DonutState::AskInnerSecond(self.project_to_plane(pt)); + CmdResult::NeedPoint + } + DonutState::AskInnerSecond(first) => { + let diameter = self.point_distance(first, pt); + if diameter <= 1.0e-9 { + return CmdResult::NeedPoint; + } + self.set_inner_diameter(diameter); + CmdResult::NeedPoint + } + DonutState::AskOuter => { + self.state = DonutState::AskOuterSecond(self.project_to_plane(pt)); + CmdResult::NeedPoint + } + DonutState::AskOuterSecond(first) => { + let diameter = self.point_distance(first, pt); + if diameter <= 1.0e-9 { + return CmdResult::NeedPoint; + } + self.set_outer_diameter(diameter); + CmdResult::NeedPoint + } DonutState::PlaceCenter => { let center = self.plane.to_local(pt); let entity = make_donut(center.x, center.y, center.z, self.inner_r, self.outer_r); // Keep command active so user can place more donuts. CmdResult::CommitEntity(self.plane.place_entity(entity)) } - _ => CmdResult::NeedPoint, } } fn on_enter(&mut self) -> CmdResult { - match &self.state { + match self.state { DonutState::AskInner => { - // Accept default 0 for inner diameter - self.inner_r = 0.0; self.state = DonutState::AskOuter; CmdResult::NeedPoint } + DonutState::AskOuter => { + self.set_outer_diameter(self.outer_r * 2.0); + CmdResult::NeedPoint + } DonutState::PlaceCenter => CmdResult::Cancel, _ => CmdResult::Cancel, } } + + fn on_mouse_move(&mut self, pt: DVec3) -> Option { + match self.state { + DonutState::AskInnerSecond(first) | DonutState::AskOuterSecond(first) => { + let point = self.project_to_plane(pt); + Some(WireModel::solid_f64( + "rubber_band".into(), + vec![first.to_array(), point.to_array()], + WireModel::CYAN, + false, + )) + } + DonutState::PlaceCenter => Some(donut_wire( + pt, + self.inner_r, + self.outer_r, + self.plane, + )), + _ => None, + } + } +} + +fn donut_wire( + center: DVec3, + inner_r: f64, + outer_r: f64, + plane: WorkingPlane, +) -> WireModel { + let local_center = plane.to_local(center); + let mut points = Vec::new(); + for radius in [outer_r, inner_r] { + if radius <= 1.0e-9 { + continue; + } + if !points.is_empty() { + points.push([f64::NAN; 3]); + } + points.extend( + KernelCurve::Circle(KernelCircle { + centre: [local_center.x, local_center.y], + radius, + }) + .tessellate_angle(TAU / 64.0) + .into_iter() + .map(|point| { + plane + .to_world(DVec3::new(point[0], point[1], local_center.z)) + .to_array() + }), + ); + } + WireModel::solid_f64("rubber_band".into(), points, WireModel::CYAN, false) } fn make_donut(cx: f64, cy: f64, elevation: f64, inner_r: f64, outer_r: f64) -> EntityType { @@ -136,17 +263,12 @@ fn make_donut(cx: f64, cy: f64, elevation: f64, inner_r: f64, outer_r: f64) -> E p.constant_width = width; p.elevation = elevation; - // Vertex at (cx - r, cy) with bulge 1.0 (180° CCW arc to next vertex) + // Segment widths inherit the polyline's constant width. let mut v0 = LwVertex::new(Vector2::new(cx - r_avg, cy)); v0.bulge = 1.0; - v0.start_width = width; - v0.end_width = width; - // Vertex at (cx + r, cy) with bulge 1.0 (second 180° arc back to v0) let mut v1 = LwVertex::new(Vector2::new(cx + r_avg, cy)); v1.bulge = 1.0; - v1.start_width = width; - v1.end_width = width; p.vertices = vec![v0, v1]; EntityType::LwPolyline(p) diff --git a/src/modules/draw/draw/mline.rs b/src/modules/draw/draw/mline.rs index 9e03c4571..c476b2358 100644 --- a/src/modules/draw/draw/mline.rs +++ b/src/modules/draw/draw/mline.rs @@ -182,6 +182,7 @@ impl MlineCommand { Some(WireModel { point_marker: None, taper_widths: Vec::new(), + pattern_stations: Vec::new(), world_width: 0.0, depth_override: None, display_visible: true, diff --git a/src/modules/draw/draw/raster_image.rs b/src/modules/draw/draw/raster_image.rs index ac973aca2..f2001719b 100644 --- a/src/modules/draw/draw/raster_image.rs +++ b/src/modules/draw/draw/raster_image.rs @@ -125,6 +125,7 @@ impl CadCommand for ImageCommand { Some(WireModel { point_marker: None, taper_widths: Vec::new(), + pattern_stations: Vec::new(), world_width: 0.0, depth_override: None, display_visible: true, diff --git a/src/modules/draw/draw/ray.rs b/src/modules/draw/draw/ray.rs index 47bcf9a81..63d374d00 100644 --- a/src/modules/draw/draw/ray.rs +++ b/src/modules/draw/draw/ray.rs @@ -87,6 +87,7 @@ impl CadCommand for RayCommand { Some(WireModel { point_marker: None, taper_widths: Vec::new(), + pattern_stations: Vec::new(), world_width: 0.0, depth_override: None, display_visible: true, @@ -190,6 +191,7 @@ impl CadCommand for XLineCommand { Some(WireModel { point_marker: None, taper_widths: Vec::new(), + pattern_stations: Vec::new(), world_width: 0.0, depth_override: None, display_visible: true, diff --git a/src/modules/draw/inquiry/area.rs b/src/modules/draw/inquiry/area.rs index 6ae6386e4..de7330314 100644 --- a/src/modules/draw/inquiry/area.rs +++ b/src/modules/draw/inquiry/area.rs @@ -364,6 +364,7 @@ impl CadCommand for AreaCommand { Some(WireModel { point_marker: None, taper_widths: Vec::new(), + pattern_stations: Vec::new(), world_width: 0.0, depth_override: None, display_visible: true, diff --git a/src/modules/draw/inquiry/dist.rs b/src/modules/draw/inquiry/dist.rs index d0a3ac0f4..a05274858 100644 --- a/src/modules/draw/inquiry/dist.rs +++ b/src/modules/draw/inquiry/dist.rs @@ -87,6 +87,7 @@ impl CadCommand for DistCommand { Some(WireModel { point_marker: None, taper_widths: Vec::new(), + pattern_stations: Vec::new(), world_width: 0.0, depth_override: None, display_visible: true, diff --git a/src/modules/draw/inquiry/measuregeom.rs b/src/modules/draw/inquiry/measuregeom.rs index 17d5d72cb..25e47ac68 100644 --- a/src/modules/draw/inquiry/measuregeom.rs +++ b/src/modules/draw/inquiry/measuregeom.rs @@ -72,6 +72,7 @@ impl MeasureGeomCommand { WireModel { point_marker: None, taper_widths: Vec::new(), + pattern_stations: Vec::new(), world_width: 0.0, depth_override: None, display_visible: true, diff --git a/src/modules/draw/modify/trim.rs b/src/modules/draw/modify/trim.rs index a04b94aaf..039db2b9a 100644 --- a/src/modules/draw/modify/trim.rs +++ b/src/modules/draw/modify/trim.rs @@ -3721,6 +3721,7 @@ fn preview_wire(points: Vec<[f32; 3]>, color: [f32; 4], name: &str) -> WireModel WireModel { point_marker: None, taper_widths: Vec::new(), + pattern_stations: Vec::new(), world_width: 0.0, depth_override: None, display_visible: true, diff --git a/src/modules/model/primitive_cmd.rs b/src/modules/model/primitive_cmd.rs index 0d43d22b6..b7dc9d25e 100644 --- a/src/modules/model/primitive_cmd.rs +++ b/src/modules/model/primitive_cmd.rs @@ -541,6 +541,7 @@ fn wire(name: &str, points: Vec<[f32; 3]>) -> WireModel { WireModel { point_marker: None, taper_widths: Vec::new(), + pattern_stations: Vec::new(), world_width: 0.0, depth_override: None, display_visible: true, diff --git a/src/modules/view/plot_window.rs b/src/modules/view/plot_window.rs index 4f86e8cf6..03341c085 100644 --- a/src/modules/view/plot_window.rs +++ b/src/modules/view/plot_window.rs @@ -56,6 +56,7 @@ impl CadCommand for PlotWindowCommand { Some(WireModel { point_marker: None, taper_widths: Vec::new(), + pattern_stations: Vec::new(), world_width: 0.0, depth_override: None, display_visible: true, diff --git a/src/modules/view/zoom_window.rs b/src/modules/view/zoom_window.rs index 3e666cc77..a22f030e5 100644 --- a/src/modules/view/zoom_window.rs +++ b/src/modules/view/zoom_window.rs @@ -138,6 +138,7 @@ impl CadCommand for ZoomWindowCommand { Some(WireModel { point_marker: None, taper_widths: Vec::new(), + pattern_stations: Vec::new(), world_width: 0.0, depth_override: None, display_visible: true, diff --git a/src/scene/cache/block_cache.rs b/src/scene/cache/block_cache.rs index 95886daf0..73f3dbca5 100644 --- a/src/scene/cache/block_cache.rs +++ b/src/scene/cache/block_cache.rs @@ -23,7 +23,10 @@ use acadrust::{CadDocument, EntityType, Handle}; use cadkernel::space::{Plane as KernelPlane, Vec3 as KernelVec3}; use crate::scene::convert::tessellate; -use crate::scene::model::wire_model::{PointMarker, SnapHint, TangentGeom, WireModel}; +use crate::scene::model::wire_model::{ + decode_pattern_station_map, encode_pattern_stations, pattern_station_values, PatternStationMap, + PatternStationPiece, PointMarker, SnapHint, TangentGeom, WireModel, +}; const MAX_NESTING_DEPTH: usize = 32; /// Skip wires whose world-AABB projects to fewer than this many pixels in @@ -70,6 +73,7 @@ pub struct LocalWire { pub pattern_length: f32, pub pattern: [f32; 8], pub line_weight_px: f32, + pub pattern_stations: Vec, /// World-space band width for a wide polyline (see `WireModel.world_width`). /// Block-local; the expand-time transform scales it by the insert so the /// shader band grows with a scaled insert. `0.0` = a normal wire. @@ -865,6 +869,7 @@ fn tessellate_sub_local( pattern_length: pat_len, pattern: pat, line_weight_px: lw_px, + pattern_stations: wire.pattern_stations, world_width: wire.world_width, plinegen: wire.plinegen, plot_visible: frame_mode.is_none_or(|mode| mode == 1) @@ -1433,6 +1438,7 @@ struct StyleKey { pattern_length: u32, pattern: [u32; 8], line_weight_px: u32, + source_length: u32, /// Wide-polyline band width (bit-cast). Keeps bands of different widths — and /// bands vs thin wires of the same colour/style — in separate batches so the /// finalized WireModel carries one correct `world_width`. @@ -1475,6 +1481,10 @@ struct BatchEntry { pattern_length: f32, pattern: [f32; 8], line_weight_px: f32, + pattern_stations: Vec, + pattern_station_segments: Vec, + pattern_station_pieces: Vec, + source_length: f32, world_width: f32, point_marker: Option, aci: u8, @@ -1555,6 +1565,7 @@ impl BatchEntry { pat_len: f32, pat: [f32; 8], lw_px: f32, + source_length: f32, world_width: f32, point_marker: Option, aci: u8, @@ -1578,6 +1589,7 @@ impl BatchEntry { pattern_length: pat_len, pattern: pat, line_weight_px: lw_px, + source_length, world_width, point_marker, aci, @@ -1606,6 +1618,9 @@ impl Batches { if b.hide_unselected && !selected { b.points.clear(); b.points_low.clear(); + b.pattern_stations.clear(); + b.pattern_station_segments.clear(); + b.pattern_station_pieces.clear(); } let aabb = if b.min_x.is_infinite() { WireModel::UNBOUNDED_AABB @@ -1628,9 +1643,18 @@ impl Batches { ); } } + if !b.pattern_stations.is_empty() { + b.pattern_stations = encode_pattern_stations( + std::mem::take(&mut b.pattern_stations), + b.source_length, + &b.pattern_station_segments, + &b.pattern_station_pieces, + ); + } WireModel { point_marker: b.point_marker, taper_widths: Vec::new(), + pattern_stations: b.pattern_stations, world_width: b.world_width, depth_override: b.local_depth, display_visible: !b.hide_unselected || selected, @@ -1681,6 +1705,7 @@ fn style_key( pat_len: f32, pat: [f32; 8], lw_px: f32, + source_length: f32, world_width: f32, point_marker: Option, aci: u8, @@ -1714,6 +1739,7 @@ fn style_key( pat[7].to_bits(), ], line_weight_px: lw_px.to_bits(), + source_length: source_length.to_bits(), world_width: world_width.to_bits(), point_marker: point_marker.map(|marker| PointMarkerKey { origin: marker.origin.to_array().map(f64::to_bits), @@ -2060,6 +2086,116 @@ fn transformed_wire_length_scale(lw: &LocalWire, xform: &Transform) -> f32 { } } +struct TransformedStationData { + values: Vec, + source_length: f32, + point_segments: Vec, + pieces: Vec, +} + +fn transformed_station_data( + values: &[f32], + map: &PatternStationMap, + plinegen: bool, + xform: &Transform, +) -> Option { + if values.len() != map.point_segments.len() || map.pieces.is_empty() { + return None; + } + + let mut pieces = Vec::with_capacity(map.pieces.len()); + let mut global = 0.0_f32; + let mut local = 0.0_f32; + let mut current_segment = None; + for piece in &map.pieces { + if current_segment != Some(piece.source_segment) { + current_segment = Some(piece.source_segment); + local = 0.0; + } + let vector = Vector3::new( + piece.vector[0] as f64, + piece.vector[1] as f64, + piece.vector[2] as f64, + ); + let mapped = xform.matrix.transform_direction(vector); + let chord = vector.length(); + let mapped_chord = mapped.length(); + let original_length = (piece.source_distances[1] - piece.source_distances[0]).abs(); + let length = if chord > 1e-12 && mapped_chord.is_finite() { + original_length * (mapped_chord / chord) as f32 + } else { + original_length + }; + pieces.push(PatternStationPiece { + source_segment: piece.source_segment, + source_distances: [global, global + length], + segment_distances: [local, local + length], + vector: [mapped.x as f32, mapped.y as f32, mapped.z as f32], + }); + global += length; + local += length; + } + + let fallback_scale = map + .pieces + .last() + .map(|piece| piece.source_distances[1]) + .filter(|length| length.abs() > 1e-12) + .map_or(1.0, |length| global / length); + let mapped_values = values + .iter() + .zip(&map.point_segments) + .map(|(&value, &segment)| { + if segment < 0 { + return 0.0; + } + let segment = segment as u32; + let range = |piece: &PatternStationPiece| { + if plinegen { + piece.source_distances + } else { + piece.segment_distances + } + }; + let distance_to = |piece: &PatternStationPiece| { + let [start, end] = range(piece); + if value < start { + start - value + } else if value > end { + value - end + } else { + 0.0 + } + }; + let Some((old_piece, new_piece)) = map + .pieces + .iter() + .zip(&pieces) + .filter(|(piece, _)| piece.source_segment == segment) + .min_by(|(a, _), (b, _)| distance_to(a).total_cmp(&distance_to(b))) + else { + return value * fallback_scale; + }; + let old = range(old_piece); + let new = range(new_piece); + let span = old[1] - old[0]; + let t = if span.abs() > 1e-12 { + ((value - old[0]) / span).clamp(0.0, 1.0) + } else { + 0.0 + }; + new[0] + (new[1] - new[0]) * t + }) + .collect(); + + Some(TransformedStationData { + values: mapped_values, + source_length: global, + point_segments: map.point_segments.clone(), + pieces, + }) +} + fn transformed_point_marker(marker: PointMarker, xform: &Transform) -> (PointMarker, f64) { let origin = xform.apply(Vector3::new(marker.origin.x, marker.origin.y, marker.origin.z)); let map_direction = |direction: glam::DVec3| { @@ -2163,16 +2299,38 @@ fn emit_wire( lw.line_weight_px }; - // Pattern distances are stored in block-local units. Scale them by the - // wire's actual path-length ratio so uniform and non-uniform INSERTs both - // stay dimensionally consistent with their transformed geometry. - let pattern_scale = if final_pat_len > 0.0 { - transformed_wire_length_scale(lw, accum_xform) - } else { - 1.0 - }; + let station_values = pattern_station_values(&lw.pattern_stations, lw.points.len()); + let station_map = decode_pattern_station_map(&lw.pattern_stations, lw.points.len()); + let transformed_stations = station_values.and_then(|(values, _)| { + station_map.as_ref().and_then(|map| { + transformed_station_data(values, map, lw.plinegen, accum_xform) + }) + }); + let has_stations = station_values.is_some(); + let pattern_scale = transformed_stations.as_ref().map_or_else( + || { + if final_pat_len > 0.0 || has_stations { + transformed_wire_length_scale(lw, accum_xform) + } else { + 1.0 + } + }, + |data| { + station_values.map_or(1.0, |(_, source_length)| { + if source_length.abs() > 1e-12 { + data.source_length / source_length + } else { + 1.0 + } + }) + }, + ); let final_pat_len = final_pat_len * ctx.pslt_factor * pattern_scale; let final_pat = final_pat.map(|v| v * ctx.pslt_factor * pattern_scale); + let source_length = transformed_stations.as_ref().map_or_else( + || station_values.map_or(0.0, |(_, length)| length * pattern_scale), + |data| data.source_length, + ); // A wide polyline's band width is baked in block-local units; scale it by // the insert transform so the shader band matches the scaled geometry. @@ -2211,6 +2369,7 @@ fn emit_wire( final_pat_len, final_pat, final_lw_px, + source_length, final_world_width, point_marker, final_aci, @@ -2223,6 +2382,12 @@ fn emit_wire( local_depth, ); + if transformed_stations.is_some() { + if let Some(closed) = out.by_style.remove(&key) { + out.closed.push(closed); + } + } + // If the open batch for this style would exceed wgpu's per-buffer limit // after appending this wire, finalize it now and start a fresh batch. if let Some(existing) = out.by_style.get(&key) { @@ -2241,6 +2406,7 @@ fn emit_wire( final_pat_len, final_pat, final_lw_px, + source_length, final_world_width, point_marker, final_aci, @@ -2253,6 +2419,9 @@ fn emit_wire( ) }); entry.local_depth = local_depth; + if let Some(data) = &transformed_stations { + entry.pattern_station_pieces.clone_from(&data.pieces); + } // NaN separator between previously-appended geometry and this wire so the // GPU shader treats them as disconnected polylines within one buffer. @@ -2263,14 +2432,33 @@ fn emit_wire( if needs_sep { entry.points.push([f32::NAN; 3]); entry.points_low.push([0.0; 3]); + if has_stations { + entry.pattern_stations.push(0.0); + } + if transformed_stations.is_some() { + entry.pattern_station_segments.push(-1); + } } // Iterate paired with the matching low residual so the GPU keeps // sub-f32 precision once the INSERT transform lands the wire in // world space at UTM-scale coordinates. for (idx, p) in lw.points.iter().enumerate() { + let station = transformed_stations + .as_ref() + .map(|data| data.values[idx]) + .or_else(|| has_stations.then(|| lw.pattern_stations[idx] * pattern_scale)); + let station_segment = transformed_stations + .as_ref() + .map(|data| data.point_segments[idx]); if p[0].is_nan() { entry.points.push([f32::NAN; 3]); entry.points_low.push([0.0; 3]); + if let Some(station) = station { + entry.pattern_stations.push(station); + } + if let Some(segment) = station_segment { + entry.pattern_station_segments.push(segment); + } continue; } let pl = lw.points_low.get(idx).copied().unwrap_or([0.0; 3]); @@ -2311,6 +2499,12 @@ fn emit_wire( } entry.points.push(q); entry.points_low.push([qx_l, qy_l, qz_l]); + if let Some(station) = station { + entry.pattern_stations.push(station); + } + if let Some(segment) = station_segment { + entry.pattern_station_segments.push(segment); + } } } diff --git a/src/scene/convert/acad_to_render.rs b/src/scene/convert/acad_to_render.rs index f7f1bdb9f..4ebc04a7a 100644 --- a/src/scene/convert/acad_to_render.rs +++ b/src/scene/convert/acad_to_render.rs @@ -61,6 +61,15 @@ pub enum RenderObject { Lines(Vec<[f64; 3]>), /// Like Lines but linetype pattern restarts at each NaN-separated segment (plinegen=false). SegmentedLines(Vec<[f64; 3]>), + /// Kernel band boundary with centreline-based linetype stations. + BoundaryLines { + points: Vec<[f64; 3]>, + stations: Vec, + point_segments: Vec, + station_pieces: Vec, + source_length: f32, + plinegen: bool, + }, /// A wide polyline whose band width VARIES (a taper): a continuous WCS point /// list paired index-for-index with a per-point full band width. The wire /// shader interpolates the two endpoint widths of each segment so the band diff --git a/src/scene/convert/tess.rs b/src/scene/convert/tess.rs index eab11c215..623d0db70 100644 --- a/src/scene/convert/tess.rs +++ b/src/scene/convert/tess.rs @@ -750,6 +750,7 @@ fn tessellate_entity_inner( return vec![WireModel { point_marker: None, taper_widths: Vec::new(), + pattern_stations: Vec::new(), world_width: 0.0, depth_override: None, display_visible: true, @@ -1110,6 +1111,7 @@ fn tessellate_entity_inner( let marker = WireModel { point_marker: None, taper_widths: Vec::new(), + pattern_stations: Vec::new(), world_width: 0.0, depth_override: None, display_visible: true, @@ -1508,6 +1510,7 @@ fn lod_stub_wire( WireModel { point_marker: None, taper_widths: Vec::new(), + pattern_stations: Vec::new(), world_width: 0.0, depth_override: None, display_visible: true, @@ -1602,6 +1605,7 @@ fn lod_stub_wire_3d( WireModel { point_marker: None, taper_widths: Vec::new(), + pattern_stations: Vec::new(), world_width: 0.0, depth_override: None, display_visible: true, diff --git a/src/scene/convert/tessellate.rs b/src/scene/convert/tessellate.rs index be7cfa206..d474c5f15 100644 --- a/src/scene/convert/tessellate.rs +++ b/src/scene/convert/tessellate.rs @@ -147,6 +147,7 @@ fn point_cloud_wires( let mut wires = vec![WireModel { point_marker: None, taper_widths: Vec::new(), + pattern_stations: Vec::new(), world_width: 0.0, depth_override: None, display_visible: true, @@ -400,6 +401,7 @@ pub fn tessellate( out.push(WireModel { point_marker: None, taper_widths: Vec::new(), + pattern_stations: Vec::new(), world_width: 0.0, depth_override: None, display_visible: true, @@ -562,6 +564,7 @@ pub fn tessellate( elem_wires.push(WireModel { point_marker: None, taper_widths: Vec::new(), + pattern_stations: Vec::new(), world_width: 0.0, depth_override: None, display_visible: true, @@ -870,6 +873,7 @@ pub fn tessellate( wires.push(WireModel { point_marker: None, taper_widths: Vec::new(), + pattern_stations: Vec::new(), world_width: 0.0, depth_override: None, display_visible: true, @@ -920,6 +924,7 @@ pub fn tessellate( wires.push(WireModel { point_marker: None, taper_widths: Vec::new(), + pattern_stations: Vec::new(), world_width: 0.0, depth_override: None, display_visible: true, @@ -972,6 +977,7 @@ pub fn tessellate( wires.push(WireModel { point_marker: None, taper_widths: Vec::new(), + pattern_stations: Vec::new(), world_width: 0.0, depth_override: None, display_visible: true, @@ -1005,6 +1011,7 @@ pub fn tessellate( wires.push(WireModel { point_marker: None, taper_widths: Vec::new(), + pattern_stations: Vec::new(), world_width: 0.0, depth_override: None, display_visible: true, @@ -1068,6 +1075,7 @@ pub fn tessellate( out.push(WireModel { point_marker: None, taper_widths: Vec::new(), + pattern_stations: Vec::new(), world_width: 0.0, depth_override: None, display_visible: true, @@ -1113,6 +1121,7 @@ pub fn tessellate( out.push(WireModel { point_marker: None, taper_widths: Vec::new(), + pattern_stations: Vec::new(), world_width: 0.0, depth_override: None, display_visible: true, @@ -1155,6 +1164,7 @@ pub fn tessellate( out.push(WireModel { point_marker: None, taper_widths: Vec::new(), + pattern_stations: Vec::new(), world_width: 0.0, depth_override: None, display_visible: true, @@ -1190,6 +1200,7 @@ pub fn tessellate( out.push(WireModel { point_marker: None, taper_widths: Vec::new(), + pattern_stations: Vec::new(), world_width: 0.0, depth_override: None, display_visible: true, @@ -1259,6 +1270,7 @@ pub fn tessellate( return vec![WireModel { point_marker: None, taper_widths: Vec::new(), + pattern_stations: Vec::new(), world_width: 0.0, depth_override: None, display_visible: true, @@ -1355,8 +1367,7 @@ pub fn tessellate( } else { color }; - // Basic curve entities use the Lines path for large-coordinate precision, - // but they must still preserve the entity's resolved linetype pattern. + // Basic curves keep their resolved linetype. let (edge_pattern_length, edge_pattern) = if matches!( entity, @@ -1385,7 +1396,8 @@ pub fn tessellate( out.push(WireModel { point_marker, taper_widths: Vec::new(), - world_width: polyline_band_width(entity), + pattern_stations: Vec::new(), + world_width: polyline_band_width(entity, document.header.fill_mode), depth_override: None, display_visible: true, plot_visible: true, @@ -1429,6 +1441,7 @@ pub fn tessellate( out.push(WireModel { point_marker: None, taper_widths: Vec::new(), + pattern_stations: Vec::new(), world_width: 0.0, pick_tris: Vec::new(), pick_tris_low: Vec::new(), @@ -1464,6 +1477,7 @@ pub fn tessellate( out.push(WireModel { point_marker: None, taper_widths: Vec::new(), + pattern_stations: Vec::new(), world_width: 0.0, depth_override: None, display_visible: true, @@ -1498,6 +1512,61 @@ pub fn tessellate( return out; } + RenderObject::BoundaryLines { + points, + stations, + point_segments, + station_pieces, + source_length, + plinegen, + } => { + let (local_pts, local_pts_low) = points_to_ds(points); + let key_vertices = te + .key_vertices + .into_iter() + .map(|[x, y, z]| [x, y, z]) + .collect(); + let station_data = crate::scene::model::wire_model::encode_pattern_stations( + stations, + source_length, + &point_segments, + &station_pieces, + ); + return vec![WireModel { + point_marker: None, + taper_widths: Vec::new(), + pattern_stations: station_data, + world_width: 0.0, + depth_override: None, + display_visible: true, + plot_visible: true, + 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, + points: local_pts, + points_low: local_pts_low, + color, + selected, + pattern_length, + pattern, + line_weight_px, + snap_pts: te.snap_pts, + tangent_geoms: te.tangent_geoms, + aci: 0, + key_vertices, + plinegen, + aabb: WireModel::UNBOUNDED_AABB, + fill_tris: Vec::new(), + fill_tris_low: Vec::new(), + }]; + } + RenderObject::SegmentedLines(points) => { let (local_pts, local_pts_low) = points_to_ds(points); let snap_pts = te.snap_pts; @@ -1512,7 +1581,8 @@ pub fn tessellate( return vec![WireModel { point_marker: None, taper_widths: Vec::new(), - world_width: polyline_band_width(entity), + pattern_stations: Vec::new(), + world_width: polyline_band_width(entity, document.header.fill_mode), depth_override: None, display_visible: true, plot_visible: true, @@ -1561,6 +1631,7 @@ pub fn tessellate( return vec![WireModel { point_marker: None, taper_widths: widths, + pattern_stations: Vec::new(), world_width, depth_override: None, display_visible: true, @@ -1683,6 +1754,7 @@ pub fn tessellate( vec![WireModel { point_marker: None, taper_widths: Vec::new(), + pattern_stations: Vec::new(), world_width: 0.0, depth_override: None, display_visible: true, @@ -2140,14 +2212,11 @@ pub(crate) fn entity_z(entity: &EntityType) -> f32 { } } -/// Band width (drawing units) of a wide polyline whose flat band is drawn by -/// expanding its centre-line wire in the shader. `0.0` = a zero-width polyline, -/// a non-polyline (a normal screen-pixel wire), or a thickness-extruded one — an -/// LwPolyline thickens into a 3-D tube (`thick_wide_band`) and a Polyline2D -/// keeps its hatch band (only its centre-line extrudes; no tube yet), so neither -/// takes the flat shader band. A tapering polyline (per-vertex start/end widths) -/// collapses to its widest edge — the shader carries one width per wire. -fn polyline_band_width(entity: &EntityType) -> f32 { +/// Shader band width, or zero while FILLMODE draws the kernel boundary. +fn polyline_band_width(entity: &EntityType, fill_mode: bool) -> f32 { + if !fill_mode { + return 0.0; + } let w = match entity { EntityType::LwPolyline(p) if p.thickness.abs() <= 1e-10 => { let mut w = p.constant_width; diff --git a/src/scene/model/wire_model.rs b/src/scene/model/wire_model.rs index 5e16de1fd..bc5549f46 100644 --- a/src/scene/model/wire_model.rs +++ b/src/scene/model/wire_model.rs @@ -59,6 +59,72 @@ pub struct PointMarker { pub viewport_percent: f32, } +#[derive(Clone, Copy, Debug)] +pub struct PatternStationPiece { + pub source_segment: u32, + pub source_distances: [f32; 2], + pub segment_distances: [f32; 2], + pub vector: [f32; 3], +} + +#[derive(Clone, Debug)] +pub(crate) struct PatternStationMap { + pub point_segments: Vec, + pub pieces: Vec, +} + +pub(crate) fn pattern_station_values(data: &[f32], point_count: usize) -> Option<(&[f32], f32)> { + (data.len() >= point_count + 1).then(|| (&data[..point_count], data[point_count])) +} + +pub(crate) fn encode_pattern_stations( + mut stations: Vec, + source_length: f32, + point_segments: &[i32], + pieces: &[PatternStationPiece], +) -> Vec { + let point_count = stations.len(); + stations.push(source_length); + if point_segments.len() != point_count || pieces.is_empty() { + return stations; + } + stations.extend(point_segments.iter().map(|segment| *segment as f32)); + for piece in pieces { + stations.push(piece.source_segment as f32); + stations.extend(piece.source_distances); + stations.extend(piece.segment_distances); + stations.extend(piece.vector); + } + stations +} + +pub(crate) fn decode_pattern_station_map( + data: &[f32], + point_count: usize, +) -> Option { + let metadata_start = point_count.checked_mul(2)?.checked_add(1)?; + if data.len() < metadata_start || (data.len() - metadata_start) % 8 != 0 { + return None; + } + let point_segments = data[point_count + 1..metadata_start] + .iter() + .map(|value| *value as i32) + .collect(); + let pieces = data[metadata_start..] + .chunks_exact(8) + .map(|values| PatternStationPiece { + source_segment: values[0] as u32, + source_distances: [values[1], values[2]], + segment_distances: [values[3], values[4]], + vector: [values[5], values[6], values[7]], + }) + .collect(); + Some(PatternStationMap { + point_segments, + pieces, + }) +} + impl PointMarker { pub fn resolve(self, point: glam::DVec3, view_height: f64) -> glam::DVec3 { let normal = self.normal.normalize_or(glam::DVec3::Z); @@ -111,6 +177,8 @@ pub struct WireModel { /// constant band of `world_width`. The wire shader reads the two endpoint /// widths of each segment and interpolates, so the band tapers smoothly. pub taper_widths: Vec, + /// Centreline stations and optional source-piece transform metadata. + pub pattern_stations: Vec, /// ACI color index (1-255). 0 means true-color or unknown (no CTB lookup). pub aci: u8, /// Pre-baked snap candidates (Center, Node, Quadrant, Insertion). @@ -249,6 +317,7 @@ impl WireModel { Self { point_marker: None, taper_widths: Vec::new(), + pattern_stations: Vec::new(), world_width: 0.0, depth_override: None, display_visible: true, @@ -625,6 +694,7 @@ impl Default for WireModel { line_weight_px: 1.0, world_width: 0.0, taper_widths: Vec::new(), + pattern_stations: Vec::new(), aci: 0, snap_pts: Vec::new(), tangent_geoms: Vec::new(), diff --git a/src/scene/pick/xclip.rs b/src/scene/pick/xclip.rs index 2a060f135..40786c911 100644 --- a/src/scene/pick/xclip.rs +++ b/src/scene/pick/xclip.rs @@ -16,7 +16,9 @@ use acadrust::types::{Handle, Transform, Vector3}; use acadrust::CadDocument; use cadkernel::geom2d::{contains, Curve, Line, Tolerance}; -use crate::scene::model::wire_model::WireModel; +use crate::scene::model::wire_model::{ + decode_pattern_station_map, encode_pattern_stations, pattern_station_values, WireModel, +}; const NAN3: [f32; 3] = [f32::NAN, f32::NAN, f32::NAN]; @@ -144,6 +146,7 @@ pub fn clip_wires(wires: &mut Vec, poly: &[[f64; 2]]) { if keep_marker == Some(false) { w.points.clear(); w.points_low.clear(); + w.pattern_stations.clear(); w.point_marker = None; } else if keep_marker.is_none() && !w.points.is_empty() { // Absolute → local f32 (NaN separators preserved). @@ -158,7 +161,15 @@ pub fn clip_wires(wires: &mut Vec, poly: &[[f64; 2]]) { [(a[0] - rx) as f32, (a[1] - ry) as f32, a[2] as f32] }) .collect(); - let clipped = clip_polyline(&local, &lpoly); + let station_data = pattern_station_values(&w.pattern_stations, w.points.len()); + let source_length = station_data.map(|(_, source_length)| source_length); + let station_map = decode_pattern_station_map(&w.pattern_stations, w.points.len()); + let (clipped, clipped_stations, clipped_segments) = clip_polyline_stationed( + &local, + &lpoly, + station_data.map(|(stations, _)| stations), + station_map.as_ref().map(|map| map.point_segments.as_slice()), + ); // Local → absolute, re-split into double-single high/low. let mut hi = Vec::with_capacity(clipped.len()); let mut lo = Vec::with_capacity(clipped.len()); @@ -176,6 +187,16 @@ pub fn clip_wires(wires: &mut Vec, poly: &[[f64; 2]]) { } w.points = hi; w.points_low = lo; + if let Some(source_length) = source_length { + w.pattern_stations = encode_pattern_stations( + clipped_stations, + source_length, + &clipped_segments, + station_map.as_ref().map_or(&[], |map| map.pieces.as_slice()), + ); + } else { + w.pattern_stations.clear(); + } } if !w.fill_tris.is_empty() { let local: Vec<[f32; 3]> = (0..w.fill_tris.len()) @@ -277,6 +298,7 @@ pub fn frame_wire( WireModel { point_marker: None, taper_widths: Vec::new(), + pattern_stations: Vec::new(), world_width: 0.0, depth_override: None, display_visible: true, @@ -438,8 +460,20 @@ fn point_in_poly(x: f32, y: f32, poly: &[[f32; 2]]) -> bool { /// Clip a NaN-separated polyline to `poly`, returning a NaN-separated polyline /// of only the inside portions. +#[cfg(test)] fn clip_polyline(pts: &[[f32; 3]], poly: &[[f32; 2]]) -> Vec<[f32; 3]> { + clip_polyline_stationed(pts, poly, None, None).0 +} + +fn clip_polyline_stationed( + pts: &[[f32; 3]], + poly: &[[f32; 2]], + stations: Option<&[f32]>, + point_segments: Option<&[i32]>, +) -> (Vec<[f32; 3]>, Vec, Vec) { let mut out: Vec<[f32; 3]> = Vec::new(); + let mut out_stations = Vec::new(); + let mut out_segments = Vec::new(); let mut i = 0; while i < pts.len() { if !pts[i][0].is_finite() || !pts[i][1].is_finite() { @@ -453,32 +487,80 @@ fn clip_polyline(pts: &[[f32; 3]], poly: &[[f32; 2]]) -> Vec<[f32; 3]> { let seg = &pts[start..i]; let mut last: Option<[f32; 3]> = None; for j in 0..seg.len().saturating_sub(1) { - for (a, b) in clip_segment(seg[j], seg[j + 1], poly) { + for (ta, tb) in clip_segment_params(seg[j], seg[j + 1], poly) { + let lerp_point = |t: f32| { + [ + seg[j][0] + (seg[j + 1][0] - seg[j][0]) * t, + seg[j][1] + (seg[j + 1][1] - seg[j][1]) * t, + seg[j][2] + (seg[j + 1][2] - seg[j][2]) * t, + ] + }; + let a = lerp_point(ta); + let b = lerp_point(tb); + let station = |t: f32| { + stations.map_or(0.0, |values| { + let a = values[start + j]; + a + (values[start + j + 1] - a) * t + }) + }; let contiguous = last.is_some_and(|l| { (l[0] - a[0]).abs() <= 1e-4 && (l[1] - a[1]).abs() <= 1e-4 }); if !contiguous { if !out.is_empty() { out.push(NAN3); + if stations.is_some() { + out_stations.push(0.0); + } + if point_segments.is_some() { + out_segments.push(-1); + } } out.push(a); + if stations.is_some() { + out_stations.push(station(ta)); + } + if let Some(segments) = point_segments { + out_segments.push(segments[start + j]); + } } out.push(b); + if stations.is_some() { + out_stations.push(station(tb)); + } + if let Some(segments) = point_segments { + out_segments.push(segments[start + j]); + } last = Some(b); } } } - out + (out, out_stations, out_segments) } /// Return the inside-the-polygon sub-segments of `p0`→`p1` as endpoint pairs. /// Handles convex and concave boundaries by testing the midpoint of every /// interval between consecutive boundary crossings. +#[cfg(test)] fn clip_segment( p0: [f32; 3], p1: [f32; 3], poly: &[[f32; 2]], ) -> Vec<([f32; 3], [f32; 3])> { + let lerp = |t: f32| { + [ + p0[0] + (p1[0] - p0[0]) * t, + p0[1] + (p1[1] - p0[1]) * t, + p0[2] + (p1[2] - p0[2]) * t, + ] + }; + clip_segment_params(p0, p1, poly) + .into_iter() + .map(|(a, b)| (lerp(a), lerp(b))) + .collect() +} + +fn clip_segment_params(p0: [f32; 3], p1: [f32; 3], poly: &[[f32; 2]]) -> Vec<(f32, f32)> { let mut ts: Vec = vec![0.0, 1.0]; let n = poly.len(); let mut j = n - 1; @@ -493,7 +575,7 @@ fn clip_segment( ts.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)); ts.dedup_by(|a, b| (*a - *b).abs() < 1e-7); - let lerp = |t: f32| { + let point = |t: f32| { [ p0[0] + (p1[0] - p0[0]) * t, p0[1] + (p1[1] - p0[1]) * t, @@ -502,9 +584,9 @@ fn clip_segment( }; let mut out = Vec::new(); for w in ts.windows(2) { - let mid = lerp(0.5 * (w[0] + w[1])); + let mid = point(0.5 * (w[0] + w[1])); if point_in_poly(mid[0], mid[1], poly) { - out.push((lerp(w[0]), lerp(w[1]))); + out.push((w[0], w[1])); } } out diff --git a/src/scene/pipeline/uniforms.rs b/src/scene/pipeline/uniforms.rs index 29326992c..c493081bc 100644 --- a/src/scene/pipeline/uniforms.rs +++ b/src/scene/pipeline/uniforms.rs @@ -25,8 +25,7 @@ pub struct Uniforms { /// zooming inside MSPACE changes one scalar instead of re-tessellating and /// re-uploading every dashed wire. pub linetype_scale: f32, - /// FILLMODE toggle. Wire shaders use this slot to draw wide polylines as - /// boundary outlines when fills are disabled. + /// Pads the struct to the uniform alignment required by wgpu. pub _pad: f32, // ── Relative-to-eye (double-single) additions ─────────────────────────── diff --git a/src/scene/pipeline/wire_gpu.rs b/src/scene/pipeline/wire_gpu.rs index 7187320de..351889ddb 100644 --- a/src/scene/pipeline/wire_gpu.rs +++ b/src/scene/pipeline/wire_gpu.rs @@ -406,40 +406,45 @@ fn pack_color(color: [f32; 4]) -> [u8; 4] { /// non-dash-first patterns keep the legacy centred phase. fn wire_distances(wire: &WireModel) -> (Vec, f32, f32) { let n = wire.points.len(); - let mut dists = vec![0.0_f32; n]; - let mut has_break = false; - // Accumulate arc-length in f64 from double-single deltas (high + low). An - // f32-high-only delta `q[0] - p[0]` quantises ~0.1 at UTM coordinates - // (−1.2M), which shifts the dash phase and drifts parallel lines out of sync - // — the reason MLINE elements were CPU-dashed. `points_low` may be empty for - // non-RTE wires → treat the low half as zero (same as the old behaviour). - let mut acc = 0.0_f64; - for i in 1..n { - let p = wire.points[i - 1]; - let q = wire.points[i]; - if !p[0].is_finite() || !q[0].is_finite() { - has_break = true; - // plinegen=false: reset to 0 at the first real point after a NaN separator. - if !wire.plinegen && !p[0].is_finite() && q[0].is_finite() { - acc = 0.0; + let explicit = wire.pattern_stations.len() >= n + 1; + let (mut dists, has_break, total) = if explicit { + ( + wire.pattern_stations[..n].to_vec(), + !wire.plinegen, + wire.pattern_stations[n], + ) + } else { + let mut dists = vec![0.0_f32; n]; + let mut has_break = false; + // Accumulate arc-length in f64 from double-single deltas (high + low). + let mut acc = 0.0_f64; + for i in 1..n { + let p = wire.points[i - 1]; + let q = wire.points[i]; + if !p[0].is_finite() || !q[0].is_finite() { + has_break = true; + if !wire.plinegen && !p[0].is_finite() && q[0].is_finite() { + acc = 0.0; + } + dists[i] = acc as f32; + } else { + let pl = wire.points_low.get(i - 1).copied().unwrap_or([0.0; 3]); + let ql = wire.points_low.get(i).copied().unwrap_or([0.0; 3]); + let dx = (q[0] as f64 - p[0] as f64) + (ql[0] as f64 - pl[0] as f64); + let dy = (q[1] as f64 - p[1] as f64) + (ql[1] as f64 - pl[1] as f64); + let dz = (q[2] as f64 - p[2] as f64) + (ql[2] as f64 - pl[2] as f64); + acc += (dx * dx + dy * dy + dz * dz).sqrt(); + dists[i] = acc as f32; } - dists[i] = acc as f32; - } else { - let pl = wire.points_low.get(i - 1).copied().unwrap_or([0.0; 3]); - let ql = wire.points_low.get(i).copied().unwrap_or([0.0; 3]); - let dx = (q[0] as f64 - p[0] as f64) + (ql[0] as f64 - pl[0] as f64); - let dy = (q[1] as f64 - p[1] as f64) + (ql[1] as f64 - pl[1] as f64); - let dz = (q[2] as f64 - p[2] as f64) + (ql[2] as f64 - pl[2] as f64); - acc += (dx * dx + dy * dy + dz * dz).sqrt(); - dists[i] = acc as f32; } - } + let total = dists.last().copied().unwrap_or(0.0); + (dists, has_break, total) + }; let pat_len = wire.pattern_length; if pat_len <= 1e-6 || has_break || n < 2 { return (dists, 0.0, 0.0); } - let total = dists[n - 1]; if total <= 1e-6 { return (dists, 0.0, 0.0); } diff --git a/src/scene/project.rs b/src/scene/project.rs index 791529b97..786b11639 100644 --- a/src/scene/project.rs +++ b/src/scene/project.rs @@ -246,8 +246,17 @@ impl Scene { continue; } - let clipped = - clip_polyline_to_rect(&projected_pts, vp_x0, vp_y0, vp_x1, vp_y1, pcz); + let stationed = wire.pattern_stations.len() > wire.points.len(); + let source_length = stationed.then(|| wire.pattern_stations[wire.points.len()]); + let (clipped, mut clipped_stations) = clip_polyline_to_rect( + &projected_pts, + stationed.then_some(&wire.pattern_stations[..wire.points.len()]), + vp_x0, + vp_y0, + vp_x1, + vp_y1, + pcz, + ); if clipped.is_empty() && projected_text.is_empty() { continue; } @@ -312,6 +321,15 @@ impl Scene { } let mut out = wire.clone(); out.points = clipped; + if let Some(source_length) = source_length { + for station in &mut clipped_stations { + *station *= scale; + } + clipped_stations.push(source_length * scale); + out.pattern_stations = clipped_stations; + } else { + out.pattern_stations.clear(); + } out.text_verts = projected_text; // Paper coordinates are small sheet units — no relative-to-eye // residual is needed, and keeping the model wire's points_low @@ -980,14 +998,16 @@ fn cs_clip( /// Returns a new points vec with proper NaN separators at clip boundaries. fn clip_polyline_to_rect( pts: &[[f32; 3]], + stations: Option<&[f32]>, xmin: f32, ymin: f32, xmax: f32, ymax: f32, z: f32, -) -> Vec<[f32; 3]> { +) -> (Vec<[f32; 3]>, Vec) { const NAN3: [f32; 3] = [f32::NAN, f32::NAN, f32::NAN]; let mut result: Vec<[f32; 3]> = Vec::new(); + let mut result_stations = Vec::new(); let mut i = 0; while i < pts.len() { @@ -1016,19 +1036,50 @@ fn clip_polyline_to_rect( pen_down = false; } Some((cx0, cy0, cx1, cy1)) => { + let parameter = |x: f32, y: f32| { + let dx = x1 - x0; + let dy = y1 - y0; + if dx.abs() >= dy.abs() && dx.abs() > 1e-12 { + (x - x0) / dx + } else if dy.abs() > 1e-12 { + (y - y0) / dy + } else { + 0.0 + } + }; + let station = |t: f32| { + stations.map_or(0.0, |values| { + let start_station = values[start + j]; + start_station + + (values[start + j + 1] - start_station) * t + }) + }; if !pen_down { if !result.is_empty() { result.push(NAN3); + if stations.is_some() { + result_stations.push(0.0); + } } result.push([cx0, cy0, z]); + if stations.is_some() { + result_stations.push(station(parameter(cx0, cy0))); + } pen_down = true; } else if let Some(&[lx, ly, _]) = result.last() { if (lx - cx0).abs() > 1e-4 || (ly - cy0).abs() > 1e-4 { result.push(NAN3); result.push([cx0, cy0, z]); + if stations.is_some() { + result_stations.push(0.0); + result_stations.push(station(parameter(cx0, cy0))); + } } } result.push([cx1, cy1, z]); + if stations.is_some() { + result_stations.push(station(parameter(cx1, cy1))); + } // If the exit point was clipped, lift pen. if (cx1 - x1).abs() > 1e-4 || (cy1 - y1).abs() > 1e-4 { pen_down = false; @@ -1044,6 +1095,9 @@ fn clip_polyline_to_rect( .unwrap_or(false) { result.pop(); + if stations.is_some() { + result_stations.pop(); + } } - result + (result, result_stations) } diff --git a/src/scene/text/complex_lt.rs b/src/scene/text/complex_lt.rs index dfad53c7e..1cb8c7101 100644 --- a/src/scene/text/complex_lt.rs +++ b/src/scene/text/complex_lt.rs @@ -311,6 +311,7 @@ pub fn apply_along( .map(|pts| WireModel { point_marker: None, taper_widths: Vec::new(), + pattern_stations: Vec::new(), world_width: 0.0, depth_override: None, display_visible: true, @@ -358,6 +359,7 @@ pub fn apply_along( out.push(WireModel { point_marker: None, taper_widths: Vec::new(), + pattern_stations: Vec::new(), world_width: 0.0, depth_override: None, display_visible: true, diff --git a/src/scene/view/render.rs b/src/scene/view/render.rs index b09fb5dbd..29a9f25cc 100644 --- a/src/scene/view/render.rs +++ b/src/scene/view/render.rs @@ -3527,7 +3527,6 @@ impl Scene { }; let mut uniforms = Uniforms::new(&inst.camera, full_bounds, self.document.header.lineweight_display); - uniforms._pad = if self.document.header.fill_mode { 1.0 } else { 0.0 }; if self.document.header.paper_space_linetype_scaling && !inst.paper_sheet && inst.tile_idx.is_none() diff --git a/src/shaders/block_wire.wgsl b/src/shaders/block_wire.wgsl index 79231f87f..29e5bd256 100644 --- a/src/shaders/block_wire.wgsl +++ b/src/shaders/block_wire.wgsl @@ -5,7 +5,7 @@ struct Uniforms { flat_shade: f32, transparency_enable: f32, linetype_scale: f32, - fillmode_enable: f32, + _pad: f32, view_rot: mat4x4, eye_high: vec3, _pad_eh: f32, @@ -58,7 +58,6 @@ struct VertexOut { @location(7) @interpolate(flat) align_total: f32, @location(8) cap: vec2, @location(9) @interpolate(flat) cap_ends: vec3, - @location(10) @interpolate(flat) world_band: f32, } fn resolve_hw(taper_ratio: f32, world_hw: f32, px_hw: f32) -> f32 { @@ -176,11 +175,6 @@ fn vs_main(@builtin(vertex_index) vertex_index: u32, in: VertexIn) -> VertexOut half_width * side, ); out.cap_ends = vec3(segment_length, half_width_a, half_width_b); - out.world_band = select( - 0.0, - 1.0, - wire_const.world_half_width > 0.0 || in.taper_ratio.x > 0.0 || in.taper_ratio.y > 0.0 - ); return out; } @@ -250,28 +244,10 @@ fn clipped_cap(cap: vec2, ends: vec3) -> bool { return false; } -fn band_interior_clipped(in: VertexOut) -> bool { - if u.fillmode_enable > 0.5 || in.world_band < 0.5 { - return false; - } - if in.cap.x < 0.0 || in.cap.x > in.cap_ends.x { - return true; - } - var along = 0.0; - if in.cap_ends.x > 1e-4 { - along = clamp(in.cap.x / in.cap_ends.x, 0.0, 1.0); - } - let half_width = mix(in.cap_ends.y, in.cap_ends.z, along); - return abs(in.cap.y) < max(half_width - 1.0, 0.0); -} - fn visible_fragment(in: VertexOut) -> bool { if clipped_cap(in.cap, in.cap_ends) { return false; } - if band_interior_clipped(in) { - return false; - } if in.pattern_length > 0.0 && in.min_elem >= u.world_per_pixel { return in_dash( in.distance, diff --git a/src/shaders/wire.wgsl b/src/shaders/wire.wgsl index 747fb5740..c8fac6521 100644 --- a/src/shaders/wire.wgsl +++ b/src/shaders/wire.wgsl @@ -29,7 +29,7 @@ struct Uniforms { // every line opaque. transparency_enable: f32, linetype_scale: f32, - fillmode_enable: f32, + _pad: f32, // ── Relative-to-eye (double-single) ────────────────────────────────── // view_rot is the rotation-only view-projection; vertices subtract the eye // (eye_high + eye_low, two f32 emulating f64) before transforming, so the @@ -94,7 +94,6 @@ struct VertexOut { @location(8) cap: vec2, // (segment pixel length, end half-width at A, end half-width at B). @location(9) @interpolate(flat) cap_ends: vec3, - @location(10) @interpolate(flat) world_band: f32, } // Half-width of one segment end: a tapered band's own end width wins, then a @@ -216,7 +215,6 @@ fn marker_relative(position_high: vec3, position_low: vec3, instance: + ext * hw * u.world_per_pixel; out.cap = vec2(which_end * seg_len + ext * hw, hw * side); out.cap_ends = vec3(seg_len, hw_a, hw_b); - out.world_band = select(0.0, 1.0, in.misc.w > 0.0 || in.taper.x > 0.0 || in.taper.y > 0.0); out.pattern_length = in.dists.w * lt_scale; out.pat0 = in.pat0 * lt_scale; out.pat1 = in.pat1 * lt_scale; @@ -289,24 +287,6 @@ fn cap_clipped(cap: vec2, cap_ends: vec3) -> bool { return false; } -fn band_interior_clipped(in: VertexOut) -> bool { - if u.fillmode_enable > 0.5 || in.world_band < 0.5 { - return false; - } - // Wide-polyline fill off: retain a one-pixel line at both band edges. - // Segment cap overhangs are discarded; adjacent sampled segments meet at - // their shared endpoint without drawing an interior seam across the band. - if in.cap.x < 0.0 || in.cap.x > in.cap_ends.x { - return true; - } - var along = 0.0; - if in.cap_ends.x > 1e-4 { - along = clamp(in.cap.x / in.cap_ends.x, 0.0, 1.0); - } - let half_width = mix(in.cap_ends.y, in.cap_ends.z, along); - return abs(in.cap.y) < max(half_width - 1.0, 0.0); -} - @fragment fn fs_main(in: VertexOut) -> @location(0) vec4 { // Negative pattern length is the persistent-arena tombstone sentinel. // Discard before cap/alpha work so deleted slabs cannot write color/depth. @@ -316,9 +296,6 @@ fn band_interior_clipped(in: VertexOut) -> bool { if cap_clipped(in.cap, in.cap_ends) { discard; } - if band_interior_clipped(in) { - discard; - } if in.pattern_length > 0.0 { // LOD: once the pattern's smallest feature drops below ~1 px // on screen, dash gaps alias / shimmer (or vanish completely) @@ -346,9 +323,6 @@ fn band_interior_clipped(in: VertexOut) -> bool { if cap_clipped(in.cap, in.cap_ends) { discard; } - if band_interior_clipped(in) { - discard; - } if in.pattern_length > 0.0 { if in.min_elem >= u.world_per_pixel { if !in_dash(in.distance, in.pattern_length, in.pat0, in.pat1, in.align_end, in.align_total) { diff --git a/src/shaders/wire_indexed.wgsl b/src/shaders/wire_indexed.wgsl index 0e0acfdac..c62d92bd6 100644 --- a/src/shaders/wire_indexed.wgsl +++ b/src/shaders/wire_indexed.wgsl @@ -12,7 +12,7 @@ struct Uniforms { flat_shade: f32, transparency_enable: f32, linetype_scale: f32, - fillmode_enable: f32, + _pad: f32, view_rot: mat4x4, eye_high: vec3, _pad_eh: f32, @@ -71,7 +71,6 @@ struct VertexOut { @location(8) cap: vec2, // (segment pixel length, end half-width at A, end half-width at B). @location(9) @interpolate(flat) cap_ends: vec3, - @location(10) @interpolate(flat) world_band: f32, } // Half-width of one segment end: a tapered band's own end width wins, then a @@ -182,7 +181,6 @@ fn marker_relative(position_high: vec3, position_low: vec3, c: WireCon + ext * hw * u.world_per_pixel; out.cap = vec2(which_end * seg_len + ext * hw, hw * side); out.cap_ends = vec3(seg_len, hw_a, hw_b); - out.world_band = select(0.0, 1.0, c.world_half_width > 0.0 || in.taper_ratio.x > 0.0 || in.taper_ratio.y > 0.0); out.pattern_length = c.pattern_length * lt_scale; out.pat0 = c.pat0 * lt_scale; out.pat1 = c.pat1 * lt_scale; @@ -246,21 +244,6 @@ fn cap_clipped(cap: vec2, cap_ends: vec3) -> bool { return false; } -fn band_interior_clipped(in: VertexOut) -> bool { - if u.fillmode_enable > 0.5 || in.world_band < 0.5 { - return false; - } - if in.cap.x < 0.0 || in.cap.x > in.cap_ends.x { - return true; - } - var along = 0.0; - if in.cap_ends.x > 1e-4 { - along = clamp(in.cap.x / in.cap_ends.x, 0.0, 1.0); - } - let half_width = mix(in.cap_ends.y, in.cap_ends.z, along); - return abs(in.cap.y) < max(half_width - 1.0, 0.0); -} - @fragment fn fs_main(in: VertexOut) -> @location(0) vec4 { // Negative pattern length is the persistent-arena tombstone sentinel. // Discard before cap/alpha work so deleted slabs cannot write color/depth. @@ -270,9 +253,6 @@ fn band_interior_clipped(in: VertexOut) -> bool { if cap_clipped(in.cap, in.cap_ends) { discard; } - if band_interior_clipped(in) { - discard; - } if in.pattern_length > 0.0 { if in.min_elem >= u.world_per_pixel { if !in_dash(in.distance, in.pattern_length, in.pat0, in.pat1, in.align_end, in.align_total) { @@ -294,9 +274,6 @@ fn band_interior_clipped(in: VertexOut) -> bool { if cap_clipped(in.cap, in.cap_ends) { discard; } - if band_interior_clipped(in) { - discard; - } if in.pattern_length > 0.0 { if in.min_elem >= u.world_per_pixel { if !in_dash(in.distance, in.pattern_length, in.pat0, in.pat1, in.align_end, in.align_total) {