feat(model): SECTIONCULL erases what lies outside a section

SECTIONCULL keeps only what the selected section object (or the live
section of the space) shows: solids, surfaces and closed meshes are cut
to it with the same planes the live section uses, open meshes are
clipped face by face, and every other object wholly outside it is
erased. Objects across the boundary that cannot be cut stay whole. One
undo step.

The section's bounding planes are gathered once (section_planes) and
shared by the live section and the cull.

Refs #1407

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Hakan SevenandClaude Opus 5.5 committed 2026-10-13 21:59:25 +03:00
1 parent adbcac8a02
commit d4b4fc37e1
28 files changed
+450 -77

No files matched your search

+4
View File
@@ -908,6 +908,10 @@ common =
.slice-result-commit-failed = SLICE: a result could not be committed; all source objects were retained.
.slice-retained-update-failed = SLICE: a retained result could not be updated.
.slice-created-results = SLICE: created __ocs_arg_count__ result object(s).
.sectioncull-no-section = SECTIONCULL: حدد كائن مقطع أو شغّل المقطع الحي.
.sectioncull-prepare-failed = SECTIONCULL: تعذر تجهيز كائن مقطوع؛ لم يتغير شيء.
.sectioncull-nothing-outside = SECTIONCULL: لا يوجد شيء خارج المقطع.
.sectioncull-result = SECTIONCULL: تم حذف __ocs_arg_erased__ كائن وقص __ocs_arg_cut__ عند حد المقطع.
.splinefit-fit-a-spline-through-n-points = SPLINEFIT: تم تمرير منحنى عبر __ocs_fmt_0__ نقطة.
.splinefit-need-at-least-3-points = SPLINEFIT: يلزم 3 نقاط على الأقل.
.splinefit-select-a-polyline-to-fit-a-spline-th = SPLINEFIT: حدد خطًا متعددًا لتمرير منحنى عبره.
+4
View File
@@ -895,6 +895,10 @@ common =
.slice-result-commit-failed = SLICE: a result could not be committed; all source objects were retained.
.slice-retained-update-failed = SLICE: a retained result could not be updated.
.slice-created-results = SLICE: created __ocs_arg_count__ result object(s).
.sectioncull-no-section = SECTIONCULL: изберете обект за сечение или включете живо сечение.
.sectioncull-prepare-failed = SECTIONCULL: изрязан обект не можа да бъде подготвен; нищо не е променено.
.sectioncull-nothing-outside = SECTIONCULL: извън сечението няма нищо.
.sectioncull-result = SECTIONCULL: изтрити __ocs_arg_erased__ обекта и изрязани __ocs_arg_cut__ по границата на сечението.
.splinefit-fit-a-spline-through-n-points = SPLINEFIT: сплайн прекаран през __ocs_fmt_0__ точки.
.splinefit-need-at-least-3-points = SPLINEFIT: нужни са поне 3 точки.
.splinefit-select-a-polyline-to-fit-a-spline-th = SPLINEFIT: избери полилиния, през която да се прекара сплайн.
+4
View File
@@ -895,6 +895,10 @@ common =
.slice-result-commit-failed = SLICE: a result could not be committed; all source objects were retained.
.slice-retained-update-failed = SLICE: a retained result could not be updated.
.slice-created-results = SLICE: created __ocs_arg_count__ result object(s).
.sectioncull-no-section = SECTIONCULL: vyberte objekt řezu nebo zapněte živý řez.
.sectioncull-prepare-failed = SECTIONCULL: oříznutý objekt nelze připravit; nic nebylo změněno.
.sectioncull-nothing-outside = SECTIONCULL: mimo řez nic neleží.
.sectioncull-result = SECTIONCULL: vymazáno __ocs_arg_erased__ objekt(ů) a oříznuto __ocs_arg_cut__ na hranici řezu.
.splinefit-fit-a-spline-through-n-points = SPLINEFIT: proložení spline přes body __ocs_fmt_0__.
.splinefit-need-at-least-3-points =SPLINEFIT: potřebujete alespoň 3 body.
.splinefit-select-a-polyline-to-fit-a-spline-th = SPLINEFIT: vyberte křivku pro proložení spline.
+4
View File
@@ -896,6 +896,10 @@ common =
.slice-result-commit-failed = SLICE: a result could not be committed; all source objects were retained.
.slice-retained-update-failed = SLICE: a retained result could not be updated.
.slice-created-results = SLICE: created __ocs_arg_count__ result object(s).
.sectioncull-no-section = SECTIONCULL: Wählen Sie ein Schnittobjekt oder aktivieren Sie einen Live-Schnitt.
.sectioncull-prepare-failed = SECTIONCULL: ein geschnittenes Objekt konnte nicht vorbereitet werden; nichts wurde geändert.
.sectioncull-nothing-outside = SECTIONCULL: außerhalb des Schnitts liegt nichts.
.sectioncull-result = SECTIONCULL: __ocs_arg_erased__ Objekt(e) gelöscht und __ocs_arg_cut__ am Schnitt beschnitten.
.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 werden benötigt.
.splinefit-select-a-polyline-to-fit-a-spline-th = SPLINEFIT: Wählen Sie eine Polylinie, um eine Spline einzupassen.
+4
View File
@@ -897,6 +897,10 @@ common =
.slice-result-commit-failed = SLICE: a result could not be committed; all source objects were retained.
.slice-retained-update-failed = SLICE: a retained result could not be updated.
.slice-created-results = SLICE: created __ocs_arg_count__ result object(s).
.sectioncull-no-section = SECTIONCULL: επιλέξτε αντικείμενο τομής ή ενεργοποιήστε ζωντανή τομή.
.sectioncull-prepare-failed = SECTIONCULL: ένα κομμένο αντικείμενο δεν προετοιμάστηκε· τίποτα δεν άλλαξε.
.sectioncull-nothing-outside = SECTIONCULL: τίποτα δεν βρίσκεται εκτός της τομής.
.sectioncull-result = SECTIONCULL: διαγράφηκαν __ocs_arg_erased__ αντικείμενα και κόπηκαν __ocs_arg_cut__ στο όριο της τομής.
.splinefit-fit-a-spline-through-n-points = SPLINEFIT: προσαρμογή spline μέσα από __ocs_fmt_0__ σημεία.
.splinefit-need-at-least-3-points = SPLINEFIT: απαιτούνται τουλάχιστον 3 σημεία.
.splinefit-select-a-polyline-to-fit-a-spline-th = SPLINEFIT: επιλέξτε μια πολυγραμμή για προσαρμογή spline.
+4
View File
@@ -894,6 +894,10 @@ common =
.slice-result-commit-failed = SLICE: a result could not be committed; all source objects were retained.
.slice-retained-update-failed = SLICE: a retained result could not be updated.
.slice-created-results = SLICE: created __ocs_arg_count__ result object(s).
.sectioncull-no-section = SECTIONCULL: select a section object or turn on a live section.
.sectioncull-prepare-failed = SECTIONCULL: a cut object could not be prepared; nothing was changed.
.sectioncull-nothing-outside = SECTIONCULL: nothing lies outside the section.
.sectioncull-result = SECTIONCULL: erased __ocs_arg_erased__ object(s) and cut __ocs_arg_cut__ across the section.
.splinefit-fit-a-spline-through-n-points = SPLINEFIT: fit a spline through __ocs_fmt_0__ points.
.splinefit-need-at-least-3-points = SPLINEFIT: need at least 3 points.
.splinefit-select-a-polyline-to-fit-a-spline-th = SPLINEFIT: select a polyline to fit a spline through.
+4
View File
@@ -896,6 +896,10 @@ common =
.slice-result-commit-failed = SLICE: a result could not be committed; all source objects were retained.
.slice-retained-update-failed = SLICE: a retained result could not be updated.
.slice-created-results = SLICE: created __ocs_arg_count__ result object(s).
.sectioncull-no-section = SECTIONCULL: seleccione un objeto de sección o active una sección dinámica.
.sectioncull-prepare-failed = SECTIONCULL: no se pudo preparar un objeto cortado; no se cambió nada.
.sectioncull-nothing-outside = SECTIONCULL: no hay nada fuera de la sección.
.sectioncull-result = SECTIONCULL: se borraron __ocs_arg_erased__ objeto(s) y se cortaron __ocs_arg_cut__ en el límite de la sección.
.splinefit-fit-a-spline-through-n-points = SPLINEFIT: se ajustó una spline por __ocs_fmt_0__ puntos.
.splinefit-need-at-least-3-points = SPLINEFIT: se necesitan al menos 3 puntos.
.splinefit-select-a-polyline-to-fit-a-spline-th = SPLINEFIT: seleccione una polilínea por la que ajustar una spline.
+4
View File
@@ -895,6 +895,10 @@ common =
.slice-result-commit-failed = SLICE: a result could not be committed; all source objects were retained.
.slice-retained-update-failed = SLICE: a retained result could not be updated.
.slice-created-results = SLICE: created __ocs_arg_count__ result object(s).
.sectioncull-no-section = SECTIONCULL: valitse leikkausobjekti tai ota live-leikkaus käyttöön.
.sectioncull-prepare-failed = SECTIONCULL: leikattua objektia ei voitu valmistella; mitään ei muutettu.
.sectioncull-nothing-outside = SECTIONCULL: leikkauksen ulkopuolella ei ole mitään.
.sectioncull-result = SECTIONCULL: __ocs_arg_erased__ objekti(a) poistettu ja __ocs_arg_cut__ leikattu leikkauksen rajalla.
.splinefit-fit-a-spline-through-n-points = SPLINEFIT: sovita spline __ocs_fmt_0__-pisteiden läpi.
.splinefit-need-at-least-3-points =SPLINEFIT: tarvitset vähintään 3 pistettä.
.splinefit-select-a-polyline-to-fit-a-spline-th = SPLINEFIT: valitse polyline sovittaaksesi splinin läpi.
+4
View File
@@ -896,6 +896,10 @@ common =
.slice-result-commit-failed = SLICE: a result could not be committed; all source objects were retained.
.slice-retained-update-failed = SLICE: a retained result could not be updated.
.slice-created-results = SLICE: created __ocs_arg_count__ result object(s).
.sectioncull-no-section = SECTIONCULL: sélectionnez un objet de coupe ou activez une coupe dynamique.
.sectioncull-prepare-failed = SECTIONCULL: un objet coupé n'a pas pu être préparé ; rien n'a été modifié.
.sectioncull-nothing-outside = SECTIONCULL: rien ne se trouve hors de la coupe.
.sectioncull-result = SECTIONCULL: __ocs_arg_erased__ objet(s) effacé(s) et __ocs_arg_cut__ coupé(s) à la limite de la coupe.
.splinefit-fit-a-spline-through-n-points = SPLINEFIT: adaptez une élingue à des points __ocs_fmt_0__.
.splinefit-need-at-least-3-points = SPLINEFIT: nécessite au moins 3 points.
.splinefit-select-a-polyline-to-fit-a-spline-th = SPLINEFIT : sélectionnez une polyligne pour l'adapter à une spline.
+4
View File
@@ -884,6 +884,10 @@ common =
.slice-result-commit-failed = SLICE: a result could not be committed; all source objects were retained.
.slice-retained-update-failed = SLICE: a retained result could not be updated.
.slice-created-results = SLICE: created __ocs_arg_count__ result object(s).
.sectioncull-no-section = SECTIONCULL: एक सेक्शन ऑब्जेक्ट चुनें या लाइव सेक्शन चालू करें।
.sectioncull-prepare-failed = SECTIONCULL: कटा हुआ ऑब्जेक्ट तैयार नहीं हो सका; कुछ नहीं बदला गया।
.sectioncull-nothing-outside = SECTIONCULL: सेक्शन के बाहर कुछ नहीं है।
.sectioncull-result = SECTIONCULL: __ocs_arg_erased__ ऑब्जेक्ट मिटाए गए और सेक्शन की सीमा पर __ocs_arg_cut__ काटे गए।
.splinefit-fit-a-spline-through-n-points = SPLINEFIT: __ocs_fmt_0__ अंक के माध्यम से एक स्पैन फिट।
.splinefit-need-at-least-3-points = SPLINEFIT: कम से कम 3 अंक की आवश्यकता है।
.splinefit-select-a-polyline-to-fit-a-spline-th = SPLINEFIT: एक sline के माध्यम से फिट करने के लिए एक पॉलीलाइन का चयन करें।
+4
View File
@@ -900,6 +900,10 @@ common =
.slice-result-commit-failed = SLICE: az egyik eredmény nem véglegesíthető; az összes forrásobjektum megmaradt.
.slice-retained-update-failed = SLICE: az egyik megtartott eredmény nem frissíthető.
.slice-created-results = SLICE: __ocs_arg_count__ eredményobjektum létrehozva.
.sectioncull-no-section = SECTIONCULL: válasszon metszetobjektumot, vagy kapcsolja be az élő metszetet.
.sectioncull-prepare-failed = SECTIONCULL: egy vágott objektumot nem sikerült előkészíteni; semmi sem változott.
.sectioncull-nothing-outside = SECTIONCULL: a metszeten kívül nincs semmi.
.sectioncull-result = SECTIONCULL: __ocs_arg_erased__ objektum törölve, __ocs_arg_cut__ levágva a metszet határán.
.splinefit-fit-a-spline-through-n-points = SPLINEFIT: illesszen be egy spline-t a __ocs_fmt_0__ pontokon.
.splinefit-need-at-least-3-points = SPLINEFIT: legalább 3 pont szükséges.
.splinefit-select-a-polyline-to-fit-a-spline-th = SPLINEFIT: válasszon egy vonalláncot a spline átillesztéséhez.
+4
View File
@@ -895,6 +895,10 @@ common =
.slice-result-commit-failed = SLICE: impossibile ottenere il risultato; gli oggetti originali sono rimasti come prima.
.slice-retained-update-failed = SLICE: non riesco ad aggiornare il risultato ottenuto.
.slice-created-results = SLICE: created __ocs_arg_count__ result object(s).
.sectioncull-no-section = SECTIONCULL: selezionare un oggetto sezione o attivare una sezione dinamica.
.sectioncull-prepare-failed = SECTIONCULL: non è stato possibile preparare un oggetto tagliato; nulla è stato modificato.
.sectioncull-nothing-outside = SECTIONCULL: nulla si trova fuori dalla sezione.
.sectioncull-result = SECTIONCULL: cancellati __ocs_arg_erased__ oggetti e tagliati __ocs_arg_cut__ sul limite della sezione.
.splinefit-fit-a-spline-through-n-points = SPLINEFIT: adatta una spline attraverso i punti __ocs_fmt_0__.
.splinefit-need-at-least-3-points =SPLINEFIT: servono almeno 3 punti.
.splinefit-select-a-polyline-to-fit-a-spline-th = SPLINEFIT: seleziona una polilinea attraverso la quale inserire una spline.
+4
View File
@@ -886,6 +886,10 @@ common =
.slice-result-commit-failed = SLICE: a result could not be committed; all source objects were retained.
.slice-retained-update-failed = SLICE: a retained result could not be updated.
.slice-created-results = SLICE: created __ocs_arg_count__ result object(s).
.sectioncull-no-section = SECTIONCULL: 断面オブジェクトを選択するか、ライブ断面をオンにしてください。
.sectioncull-prepare-failed = SECTIONCULL: 切断したオブジェクトを準備できませんでした。何も変更されていません。
.sectioncull-nothing-outside = SECTIONCULL: 断面の外側には何もありません。
.sectioncull-result = SECTIONCULL: __ocs_arg_erased__ 個のオブジェクトを削除し、断面の境界で __ocs_arg_cut__ 個を切断しました。
.splinefit-fit-a-spline-through-n-points = SPLINEFIT: __ocs_fmt_0__ 点を通るスプラインを作成しました。
.splinefit-need-at-least-3-points = SPLINEFIT: 3 点以上必要です。
.splinefit-select-a-polyline-to-fit-a-spline-th = SPLINEFIT: スプラインを通すポリラインを選択してください。
+4
View File
@@ -895,6 +895,10 @@ common =
.slice-result-commit-failed = SLICE: a result could not be committed; all source objects were retained.
.slice-retained-update-failed = SLICE: a retained result could not be updated.
.slice-created-results = SLICE: created __ocs_arg_count__ result object(s).
.sectioncull-no-section = SECTIONCULL: 단면 객체를 선택하거나 라이브 단면을 켜십시오.
.sectioncull-prepare-failed = SECTIONCULL: 잘린 객체를 준비할 수 없습니다. 아무것도 변경되지 않았습니다.
.sectioncull-nothing-outside = SECTIONCULL: 단면 밖에 있는 객체가 없습니다.
.sectioncull-result = SECTIONCULL: 객체 __ocs_arg_erased__개를 지우고 단면 경계에서 __ocs_arg_cut__개를 잘랐습니다.
.splinefit-fit-a-spline-through-n-points = SPLINEFIT: __ocs_fmt_0__ 점을 통해 스플라인을 맞춥니다.
.splinefit-need-at-least-3-points =SPLINEFIT: 최소 3개의 포인트가 필요합니다.
.splinefit-select-a-polyline-to-fit-a-spline-th = SPLINEFIT: 스플라인에 맞게 폴리선을 선택합니다.
+4
View File
@@ -901,6 +901,10 @@ common =
.slice-result-commit-failed = SLICE: ບໍ່ສາມາດບັນທຶກຜົນຮັບໄດ້; ວັດຖຸຕົ້ນສະບັບທັງໝົດຍັງຖືກຮັກສາໄວ້.
.slice-retained-update-failed = SLICE: ບໍ່ສາມາດອັບເດດຜົນຮັບທີ່ຮັກສາໄວ້ໄດ້.
.slice-created-results = SLICE: ສ້າງ __ocs_arg_count__ ວັດຖຸຜົນຮັບແລ້ວ.
.sectioncull-no-section = SECTIONCULL: ເລືອກວັດຖຸພາກຕັດ ຫຼື ເປີດພາກຕັດສົດ.
.sectioncull-prepare-failed = SECTIONCULL: ບໍ່ສາມາດກະກຽມວັດຖຸທີ່ຖືກຕັດໄດ້; ບໍ່ມີຫຍັງປ່ຽນແປງ.
.sectioncull-nothing-outside = SECTIONCULL: ບໍ່ມີຫຍັງຢູ່ນອກພາກຕັດ.
.sectioncull-result = SECTIONCULL: ລຶບ __ocs_arg_erased__ ວັດຖຸ ແລະ ຕັດ __ocs_arg_cut__ ຢູ່ຂອບພາກຕັດ.
.splinefit-fit-a-spline-through-n-points = SPLINEFIT: ປັບເສັ້ນສະພລາຍຜ່ານ __ocs_fmt_0__ ຈຸດ.
.splinefit-need-at-least-3-points = SPLINEFIT: ຕ້ອງການຢ່າງໜ້ອຍ 3 ຈຸດ.
.splinefit-select-a-polyline-to-fit-a-spline-th = SPLINEFIT: ເລືອກໂພລີລາຍເພື່ອປັບເສັ້ນສະພລາຍຜ່ານ.
+4
View File
@@ -894,6 +894,10 @@ common =
.slice-result-commit-failed = SLICE: a result could not be committed; all source objects were retained.
.slice-retained-update-failed = SLICE: a retained result could not be updated.
.slice-created-results = SLICE: created __ocs_arg_count__ result object(s).
.sectioncull-no-section = SECTIONCULL: selecteer een doorsnedeobject of zet een live doorsnede aan.
.sectioncull-prepare-failed = SECTIONCULL: een gesneden object kon niet worden voorbereid; er is niets gewijzigd.
.sectioncull-nothing-outside = SECTIONCULL: er ligt niets buiten de doorsnede.
.sectioncull-result = SECTIONCULL: __ocs_arg_erased__ object(en) gewist en __ocs_arg_cut__ afgesneden op de doorsnede.
.splinefit-fit-a-spline-through-n-points = SPLINEFIT: pas een spline door __ocs_fmt_0__ punten.
.splinefit-need-at-least-3-points = SPLINEFIT: minimaal 3 punten nodig.
.splinefit-select-a-polyline-to-fit-a-spline-th = SPLINEFIT: selecteer een polylijn om een spline door te passen.
+4
View File
@@ -895,6 +895,10 @@ common =
.slice-result-commit-failed = SLICE: a result could not be committed; all source objects were retained.
.slice-retained-update-failed = SLICE: a retained result could not be updated.
.slice-created-results = SLICE: created __ocs_arg_count__ result object(s).
.sectioncull-no-section = SECTIONCULL: wybierz obiekt przekroju lub włącz przekrój dynamiczny.
.sectioncull-prepare-failed = SECTIONCULL: nie udało się przygotować przeciętego obiektu; nic nie zmieniono.
.sectioncull-nothing-outside = SECTIONCULL: nic nie leży poza przekrojem.
.sectioncull-result = SECTIONCULL: usunięto __ocs_arg_erased__ obiekt(ów) i przycięto __ocs_arg_cut__ na granicy przekroju.
.splinefit-fit-a-spline-through-n-points = SPLINEFIT: dopasuj splajn przez punkty __ocs_fmt_0__.
.splinefit-need-at-least-3-points =SPLINEFIT: potrzebne są co najmniej 3 punkty.
.splinefit-select-a-polyline-to-fit-a-spline-th = SPLINEFIT: wybierz polilinię, przez którą ma zostać dopasowany splajn.
+4
View File
@@ -896,6 +896,10 @@ common =
.slice-result-commit-failed = SLICE: a result could not be committed; all source objects were retained.
.slice-retained-update-failed = SLICE: a retained result could not be updated.
.slice-created-results = SLICE: created __ocs_arg_count__ result object(s).
.sectioncull-no-section = SECTIONCULL: selecione um objeto de seção ou ative uma seção dinâmica.
.sectioncull-prepare-failed = SECTIONCULL: não foi possível preparar um objeto cortado; nada foi alterado.
.sectioncull-nothing-outside = SECTIONCULL: nada está fora da seção.
.sectioncull-result = SECTIONCULL: __ocs_arg_erased__ objeto(s) apagado(s) e __ocs_arg_cut__ cortado(s) no limite da seção.
.splinefit-fit-a-spline-through-n-points = SPLINEFIT: spline ajustada através de __ocs_fmt_0__ pontos.
.splinefit-need-at-least-3-points = SPLINEFIT: são necessários pelo menos 3 pontos.
.splinefit-select-a-polyline-to-fit-a-spline-th = SPLINEFIT: selecione uma polilinha através da qual ajustar uma spline.
+4
View File
@@ -899,6 +899,10 @@ common =
.slice-result-commit-failed = SLICE: a result could not be committed; all source objects were retained.
.slice-retained-update-failed = SLICE: a retained result could not be updated.
.slice-created-results = SLICE: created __ocs_arg_count__ result object(s).
.sectioncull-no-section = SECTIONCULL: выберите объект сечения или включите динамическое сечение.
.sectioncull-prepare-failed = SECTIONCULL: не удалось подготовить обрезанный объект; ничего не изменено.
.sectioncull-nothing-outside = SECTIONCULL: за пределами сечения ничего нет.
.sectioncull-result = SECTIONCULL: удалено объектов: __ocs_arg_erased__, обрезано по границе сечения: __ocs_arg_cut__.
.splinefit-fit-a-spline-through-n-points = SPLINEFIT: проведите сплин через точки __ocs_fmt_0__.
.splinefit-need-at-least-3-points = SPLINEFIT: необходимо не менее 3 баллов.
.splinefit-select-a-polyline-to-fit-a-spline-th = SPLINEFIT: выберите полилин, чтобы соответствовать сплину.
+4
View File
@@ -884,6 +884,10 @@ common =
.slice-result-commit-failed = SLICE: Bir sonuç işlenemedi; tüm kaynak nesneler korundu.
.slice-retained-update-failed = SLICE: Korunan sonuç güncellenemedi.
.slice-created-results = SLICE: __ocs_arg_count__ sonuç nesnesi oluşturuldu.
.sectioncull-no-section = SECTIONCULL: bir kesit nesnesi seçin veya canlı kesiti açın.
.sectioncull-prepare-failed = SECTIONCULL: kesilen bir nesne hazırlanamadı; hiçbir şey değiştirilmedi.
.sectioncull-nothing-outside = SECTIONCULL: kesitin dışında kalan bir şey yok.
.sectioncull-result = SECTIONCULL: __ocs_arg_erased__ nesne silindi, kesit sınırındaki __ocs_arg_cut__ nesne kesildi.
.splinefit-fit-a-spline-through-n-points = SPLINEFIT: __ocs_fmt_0__ noktadan geçen bir spline uyduruldu.
.splinefit-need-at-least-3-points = SPLINEFIT: En az 3 nokta gerekir.
.splinefit-select-a-polyline-to-fit-a-spline-th = SPLINEFIT: İçinden spline geçirilecek bir çoklu çizgi seçin.
+4
View File
@@ -883,6 +883,10 @@ common =
.slice-result-commit-failed = SLICE: a result could not be committed; all source objects were retained.
.slice-retained-update-failed = SLICE: a retained result could not be updated.
.slice-created-results = SLICE: created __ocs_arg_count__ result object(s).
.sectioncull-no-section = SECTIONCULL: 请选择截面对象或打开活动截面。
.sectioncull-prepare-failed = SECTIONCULL: 无法准备剪切后的对象;未做任何更改。
.sectioncull-nothing-outside = SECTIONCULL: 截面之外没有任何对象。
.sectioncull-result = SECTIONCULL: 已删除 __ocs_arg_erased__ 个对象,并在截面边界处剪切 __ocs_arg_cut__ 个。
.splinefit-fit-a-spline-through-n-points = SPLINEFIT: 匹配一条通过__ocs_fmt_0__点的相片。
.splinefit-need-at-least-3-points = SPLINEFIT:至少需要3个点。
.splinefit-select-a-polyline-to-fit-a-spline-th = SPLINEFIT:选择一条多线以适应通过的分线。
+4
View File
@@ -895,6 +895,10 @@ common =
.slice-result-commit-failed = SLICE: a result could not be committed; all source objects were retained.
.slice-retained-update-failed = SLICE: a retained result could not be updated.
.slice-created-results = SLICE: created __ocs_arg_count__ result object(s).
.sectioncull-no-section = SECTIONCULL: 請選取剖面物件或開啟即時剖面。
.sectioncull-prepare-failed = SECTIONCULL: 無法準備剪切後的物件;未做任何變更。
.sectioncull-nothing-outside = SECTIONCULL: 剖面之外沒有任何物件。
.sectioncull-result = SECTIONCULL: 已刪除 __ocs_arg_erased__ 個物件,並在剖面邊界剪切 __ocs_arg_cut__ 個。
.splinefit-fit-a-spline-through-n-points = SPLINEFIT: 匹配一條通過__ocs_fmt_0__點的相片。
.splinefit-need-at-least-3-points = SPLINEFIT:至少需要3個點。
.splinefit-select-a-polyline-to-fit-a-spline-th = SPLINEFIT:選擇一條多線以適應通過的分線。
+5
View File
@@ -2148,6 +2148,11 @@ impl OpenCADStudio {
self.tabs[i].active_cmd = Some(Box::new(subtract));
}
// SECTIONCULL — erase what lies outside a section object.
"SECTIONCULL" => {
return Some(self.section_cull());
}
// INTERFERE — non-destructive intersect: solid from the overlap.
"INTERFERE" => {
return Some(self.solid_interfere());
+1
View File
@@ -621,6 +621,7 @@ inventory::submit!(crate::command::CommandRegistration {
"SL",
"INTERFERE",
"INF",
"SECTIONCULL",
"PRESSPULL",
"THICKEN",
"3DROTATE",
+70
View File
@@ -225,6 +225,8 @@ fn subtract_bodies(
Ok(result)
}
inventory::submit!(crate::command::CommandRegistration { names: &["SECTIONCULL"] });
fn entity_with_boolean_body(mut source: EntityType, body: &Body) -> Option<EntityType> {
let document = crate::scene::convert::acis_export::solid_to_sat(body)?;
let wires = solid_model::edge_wires(body);
@@ -2073,6 +2075,74 @@ impl super::OpenCADStudio {
self.solid_slice(&handles, plane, keep_point)
}
/// SECTIONCULL — keep only what lies inside the selected section object
/// (or the live section): bodies are cut to it, open meshes clipped, and
/// everything wholly outside it erased. One undo step.
pub(super) fn section_cull(&mut self) -> Task<Message> {
let i = self.active_tab;
let Some(section) = self.tabs[i].scene.cull_section() else {
self.command_line.push_error(
crate::t!("SECTIONCULL: select a section object or turn on a live section.")
.as_ref(),
);
return Task::none();
};
let Some(cull) = self.tabs[i].scene.section_cull(section) else {
return Task::none();
};
let mut cut = Vec::with_capacity(cull.cut.len());
for (handle, body) in cull.cut {
let scene = &self.tabs[i].scene;
let entity = scene
.document
.get_entity(handle)
.cloned()
.and_then(|source| entity_with_boolean_body(source, &body));
let display = scene
.prepare_solid_model_display(handle, &body)
.filter(|display| display.0.complete);
let (Some(entity), Some(display)) = (entity, display) else {
self.command_line.push_error(
crate::t!(
"SECTIONCULL: a cut object could not be prepared; nothing was changed."
)
.as_ref(),
);
return Task::none();
};
cut.push((handle, entity, body, display));
}
let changed = cut.len() + cull.clipped.len() + cull.outside.len();
if changed == 0 {
self.command_line
.push_output(crate::t!("SECTIONCULL: nothing lies outside the section.").as_ref());
return Task::none();
}
self.push_undo_snapshot(i, "SECTIONCULL");
let scene = &mut self.tabs[i].scene;
for (handle, entity, body, display) in cut {
scene.delete_solid_history(handle);
scene.update_entity(entity);
scene.register_prepared_solid_model(handle, body, display);
}
for mesh in cull.clipped {
scene.update_entity(mesh);
}
scene.erase_entities(&cull.outside);
scene.deselect_all();
self.tabs[i].dirty = true;
self.refresh_properties();
self.command_line.push_output(
crate::t!(
"SECTIONCULL: erased %{erased} object(s) and cut %{cut} across the section.",
erased = cull.outside.len(),
cut = changed - cull.outside.len()
)
.as_ref(),
);
Task::none()
}
/// INTERFERE — create a solid from the overlap of the two selected solids,
/// leaving the originals in place (a non-destructive boolean intersect).
pub(super) fn solid_interfere(&mut self) -> Task<Message> {
+4
View File
@@ -2885,6 +2885,10 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static
"SLICE: a result could not be committed; all source objects were retained." => Some(("common", "slice-result-commit-failed")),
"SLICE: a retained result could not be updated." => Some(("common", "slice-retained-update-failed")),
"SLICE: created %{count} result object(s)." => Some(("common", "slice-created-results")),
"SECTIONCULL: a cut object could not be prepared; nothing was changed." => Some(("common", "sectioncull-prepare-failed")),
"SECTIONCULL: erased %{erased} object(s) and cut %{cut} across the section." => Some(("common", "sectioncull-result")),
"SECTIONCULL: nothing lies outside the section." => Some(("common", "sectioncull-nothing-outside")),
"SECTIONCULL: select a section object or turn on a live section." => Some(("common", "sectioncull-no-section")),
"SOLID Specify first point:" => Some(("draw", "solid-specify-first-point")),
"SOLID Specify fourth point:" => Some(("draw", "solid-specify-fourth-point")),
"SOLID Specify second point:" => Some(("draw", "solid-specify-second-point")),
+53 -77
View File
@@ -8,6 +8,7 @@
pub mod annotative;
pub mod assoc_array;
pub mod compare;
pub mod section_cull;
pub mod block_service;
pub mod cache;
pub mod convert;
@@ -8899,16 +8900,29 @@ impl Scene {
slice_depth: Option<f64>,
body_to_world: Option<&codec::types::Transform>,
) -> Result<Option<kernel::brep::Body>, ()> {
let Some((origin, tangent, vertical, viewing)) = section_frame(data) else {
return Err(());
};
let front = section_plane(origin, tangent, viewing, body_to_world).ok_or(())?;
let mut clipped = keep_section_positive(body, front)?;
let Some(mut current) = clipped.take() else {
return Ok(None);
};
let planes = Self::section_planes(data, slice_depth, body_to_world).ok_or(())?;
let mut current = body.clone();
for plane in planes {
match keep_section_positive(&current, plane)? {
Some(next) => current = next,
None => return Ok(None),
}
}
Ok(Some(current))
}
/// The planes bounding what section `data` keeps, each facing inward:
/// the section plane, then the back of a slice, the ends of a boundary
/// and the top and bottom of a volume.
fn section_planes(
data: &codec::entities::SectionObjectData,
slice_depth: Option<f64>,
body_to_world: Option<&codec::types::Transform>,
) -> Option<Vec<kernel::space::Plane>> {
let (origin, tangent, vertical, viewing) = section_frame(data)?;
let mut planes = vec![section_plane(origin, tangent, viewing, body_to_world)?];
if data.state == 1 && slice_depth.is_none() {
return Ok(Some(current));
return Some(planes);
}
let depth = slice_depth.unwrap_or_else(|| {
@@ -8923,82 +8937,44 @@ impl Scene {
})
});
if depth <= 1e-12 {
return Ok(Some(current));
return Some(planes);
}
let back =
section_plane(origin + viewing * depth, tangent, -viewing, body_to_world).ok_or(())?;
let Some(next) = keep_section_positive(&current, back)? else {
return Ok(None);
};
current = next;
planes.push(section_plane(
origin + viewing * depth,
tangent,
-viewing,
body_to_world,
)?);
if data.state == 1 {
return Ok(Some(current));
return Some(planes);
}
let span = data
.vertices
.first()
.zip(data.vertices.last())
.map_or(0.0, |(first, last)| {
(glam::DVec3::new(last.x, last.y, last.z)
- glam::DVec3::new(first.x, first.y, first.z))
.length()
});
if span <= 1e-12 {
return Ok(Some(current));
let first = data.vertices.first()?;
let last = data.vertices.last()?;
let first = glam::DVec3::new(first.x, first.y, first.z);
let last = glam::DVec3::new(last.x, last.y, last.z);
if (last - first).length() <= 1e-12 {
return Some(planes);
}
let first = data
.vertices
.first()
.map(|point| glam::DVec3::new(point.x, point.y, point.z))
.ok_or(())?;
let last = data
.vertices
.last()
.map(|point| glam::DVec3::new(point.x, point.y, point.z))
.ok_or(())?;
for plane in [
section_plane(first, vertical, tangent, body_to_world),
section_plane(last, vertical, -tangent, body_to_world),
] {
let Some(plane) = plane else {
return Err(());
};
let Some(next) = keep_section_positive(&current, plane)? else {
return Ok(None);
};
current = next;
}
planes.push(section_plane(first, vertical, tangent, body_to_world)?);
planes.push(section_plane(last, vertical, -tangent, body_to_world)?);
if data.state != 4 {
return Ok(Some(current));
return Some(planes);
}
for plane in [
section_plane(
origin - vertical * data.bottom_height,
tangent,
vertical,
body_to_world,
),
section_plane(
origin + vertical * data.top_height,
tangent,
-vertical,
body_to_world,
),
] {
let Some(plane) = plane else {
return Err(());
};
let Some(next) = keep_section_positive(&current, plane)? else {
return Ok(None);
};
current = next;
}
Ok(Some(current))
planes.push(section_plane(
origin - vertical * data.bottom_height,
tangent,
vertical,
body_to_world,
)?);
planes.push(section_plane(
origin + vertical * data.top_height,
tangent,
-vertical,
body_to_world,
)?);
Some(planes)
}
/// True when `layer` is turned off or frozen — entities on it never render.
+229
View File
@@ -0,0 +1,229 @@
//! SECTIONCULL: keep only what lies inside a section object — the part a
//! live section shows — and remove the rest for good. Solids, surfaces and
//! closed meshes are cut to it, open meshes are clipped face by face, and
//! every other object wholly outside it is erased. Objects across its
//! boundary that cannot be cut stay whole.
use codec::{EntityType, Handle};
use glam::DVec3;
use rustc_hash::FxHashMap;
use super::Scene;
/// What culling to a section does to the space it lives in.
#[derive(Default)]
pub struct SectionCull {
/// Bodies the section crosses, with what stays of each.
pub cut: Vec<(Handle, kernel::brep::Body)>,
/// Open meshes the section crosses, clipped to it.
pub clipped: Vec<EntityType>,
/// Objects wholly outside the section.
pub outside: Vec<Handle>,
}
enum Place {
Inside,
Outside,
Across,
}
const TOLERANCE: f64 = 1e-9;
fn place(planes: &[kernel::space::Plane], points: &[[f64; 3]]) -> Place {
let mut inside = true;
for plane in planes {
let behind = points
.iter()
.filter(|point| plane.distance_to(**point).unwrap_or(0.0) < -TOLERANCE)
.count();
if behind == points.len() {
return Place::Outside;
}
inside &= behind == 0;
}
if inside {
Place::Inside
} else {
Place::Across
}
}
fn same_extent(a: &kernel::brep::Body, b: &kernel::brep::Body) -> bool {
match (kernel::brep::body_bounds(a), kernel::brep::body_bounds(b)) {
(Some(a), Some(b)) => (0..3).all(|axis| {
(a.min[axis] - b.min[axis]).abs() <= 1e-7 && (a.max[axis] - b.max[axis]).abs() <= 1e-7
}),
_ => false,
}
}
impl Scene {
/// The section SECTIONCULL works with: the selected section object, or
/// else the live section of the current space.
pub fn cull_section(&self) -> Option<Handle> {
let is_section = |handle: &Handle| {
matches!(
self.document.get_entity(*handle),
Some(EntityType::Extended(extended))
if matches!(
extended.data,
codec::entities::ExtendedEntityData::SectionObject(_)
)
)
};
if let Some(handle) = self.selected_handles_in_order().into_iter().find(is_section) {
return Some(handle);
}
let block = self.current_layout_block_handle();
self.document
.entities()
.filter(|entity| match entity {
EntityType::Extended(extended) => match &extended.data {
codec::entities::ExtendedEntityData::SectionObject(data) => {
let owner = extended.common.owner_handle;
data.flags & 1 != 0
&& !extended.common.invisible
&& (owner.is_null() || owner == block)
}
_ => false,
},
_ => false,
})
.map(|entity| entity.common().handle)
.last()
}
/// Plan culling the current space to section object `section`; `None`
/// when it is not a section object.
pub fn section_cull(&self, section: Handle) -> Option<SectionCull> {
let Some(EntityType::Extended(extended)) = self.document.get_entity(section) else {
return None;
};
let codec::entities::ExtendedEntityData::SectionObject(data) = &extended.data else {
return None;
};
let slice_depth = crate::entities::extended::section_is_slice(extended).then(|| {
crate::entities::extended::section_slice_depth(extended).unwrap_or(0.0)
});
let planes = Self::section_planes(data, slice_depth, None)?;
// Every drawn point of every object in the space, from its wires and
// its shaded mesh.
let block = self.current_layout_block_handle();
let mut points: FxHashMap<Handle, Vec<[f64; 3]>> = FxHashMap::default();
let wires = self.wires_for_block_culled(
block,
None,
None,
None,
None,
None,
true,
None,
);
for wire in &wires {
let Some(handle) = Self::handle_from_wire_name(&wire.name) else {
continue;
};
let entry = points.entry(handle).or_default();
for (index, &[x, y, z]) in wire.points.iter().enumerate() {
let [lx, ly, lz] = wire.points_low.get(index).copied().unwrap_or([0.0; 3]);
let point = DVec3::new(
f64::from(x) + f64::from(lx),
f64::from(y) + f64::from(ly),
f64::from(z) + f64::from(lz),
);
if point.is_finite() {
entry.push(point.to_array());
}
}
}
for set in self.interaction_meshes_arc().iter() {
let handle = set.entity_handle();
let (Some(handle), Some(aabb)) = (handle, super::mesh_interaction_aabb(set)) else {
continue;
};
let entry = points.entry(handle).or_default();
for corner in 0..8 {
entry.push([
aabb[if corner & 1 == 0 { 0 } else { 3 }],
aabb[if corner & 2 == 0 { 1 } else { 4 }],
aabb[if corner & 4 == 0 { 2 } else { 5 }],
]);
}
}
let mut cull = SectionCull::default();
// A live section hides what lies wholly outside it, so the space's
// own objects are walked too.
let mut handles: Vec<Handle> = points
.keys()
.copied()
.chain(
self.document
.entities()
.filter(|entity| entity.common().owner_handle == block)
.map(|entity| entity.common().handle),
)
.collect();
handles.sort_unstable_by_key(|handle| handle.value());
handles.dedup();
for handle in handles {
if handle == section || self.is_layer_locked(handle) {
continue;
}
let Some(entity) = self.document.get_entity(handle) else {
continue;
};
// Bodies are cut with the kernel: their drawn wires may already
// show the live cut, so they say nothing about the source.
if let Some(body) = self.section_source_body(handle) {
match Self::section_body(&body, data, slice_depth, None) {
Ok(None) => cull.outside.push(handle),
Ok(Some(kept)) if !same_extent(&body, &kept) => cull.cut.push((handle, kept)),
_ => {}
}
continue;
}
if let Some((vertices, _)) = crate::entities::mesh::mesh_polygons(entity) {
match place(&planes, &vertices) {
Place::Outside => cull.outside.push(handle),
Place::Inside => {}
Place::Across => {
let mut mesh = entity.clone();
for plane in &planes {
let Some((vertices, faces)) =
crate::entities::mesh::mesh_polygons(&mesh)
else {
break;
};
let side = |point: [f64; 3]| plane.distance_to(point).unwrap_or(0.0);
if vertices.iter().all(|point| side(*point) >= -TOLERANCE) {
continue;
}
let Some(mut clipped) = crate::entities::mesh::clipped_mesh(
&vertices,
&faces,
side,
true,
mesh.common(),
) else {
break;
};
clipped.common.handle = handle;
mesh = EntityType::Mesh(clipped);
}
cull.clipped.push(mesh);
}
}
continue;
}
if let Some(points) = points.get(&handle) {
if matches!(place(&planes, points), Place::Outside) {
cull.outside.push(handle);
}
}
}
Some(cull)
}
}