forked from 1b-agent/opencadstudio-upstream
feat(solid history): booleans keep the solids they joined editable
With SOLIDHIST on, UNION, SUBTRACT and INTERSECT no longer reduce the result's history to a frozen body: each solid's history joins the result's under a boolean step, in the order the result applied them. The Properties palette lists a composite's solids under Operand; the one chosen shows its own geometry rows and grips, placed where it sits in the composite, and editing it replays the whole tree. A composite read from a file keeps its saved body; the tree is replayed only on edits. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d1f3bf3013
commit
d3033caf24
28 files changed
+383
-52
No files matched your search
@@ -2342,6 +2342,7 @@ common =
|
|||||||
.requires-two-positive-values = يتطلب قيمتين موجبتين (x,y) أو قيمة واحدة لكليهما.
|
.requires-two-positive-values = يتطلب قيمتين موجبتين (x,y) أو قيمة واحدة لكليهما.
|
||||||
.snap-marker-color = لون علامة الالتقاط
|
.snap-marker-color = لون علامة الالتقاط
|
||||||
.command-line-text-color = لون نص سطر الأوامر
|
.command-line-text-color = لون نص سطر الأوامر
|
||||||
|
.operand = المعامل
|
||||||
.linetypes-loaded = تم تحميل __ocs_fmt_0__ من أنواع الخطوط.
|
.linetypes-loaded = تم تحميل __ocs_fmt_0__ من أنواع الخطوط.
|
||||||
.geometric-center-snap = المركز الهندسي
|
.geometric-center-snap = المركز الهندسي
|
||||||
.no-trim-option = بدون تشذيب
|
.no-trim-option = بدون تشذيب
|
||||||
|
|||||||
@@ -2329,6 +2329,7 @@ common =
|
|||||||
.requires-two-positive-values = Изискват се две положителни стойности (x,y) или една за двете.
|
.requires-two-positive-values = Изискват се две положителни стойности (x,y) или една за двете.
|
||||||
.snap-marker-color = Цвят на маркера за прихващане
|
.snap-marker-color = Цвят на маркера за прихващане
|
||||||
.command-line-text-color = Цвят на текста в командния ред
|
.command-line-text-color = Цвят на текста в командния ред
|
||||||
|
.operand = Операнд
|
||||||
.linetypes-loaded = Заредени типове линии: __ocs_fmt_0__.
|
.linetypes-loaded = Заредени типове линии: __ocs_fmt_0__.
|
||||||
.geometric-center-snap = Геометричен център
|
.geometric-center-snap = Геометричен център
|
||||||
.no-trim-option = Без подрязване
|
.no-trim-option = Без подрязване
|
||||||
|
|||||||
@@ -2329,6 +2329,7 @@ common =
|
|||||||
.requires-two-positive-values = Vyžaduje dvě kladné hodnoty (x,y) nebo jednu pro obě.
|
.requires-two-positive-values = Vyžaduje dvě kladné hodnoty (x,y) nebo jednu pro obě.
|
||||||
.snap-marker-color = Barva značky uchopení
|
.snap-marker-color = Barva značky uchopení
|
||||||
.command-line-text-color = Barva textu příkazového řádku
|
.command-line-text-color = Barva textu příkazového řádku
|
||||||
|
.operand = Operand
|
||||||
.linetypes-loaded = Načteno typů čar: __ocs_fmt_0__.
|
.linetypes-loaded = Načteno typů čar: __ocs_fmt_0__.
|
||||||
.geometric-center-snap = Geometrický střed
|
.geometric-center-snap = Geometrický střed
|
||||||
.no-trim-option = Neoříznout
|
.no-trim-option = Neoříznout
|
||||||
|
|||||||
@@ -2330,6 +2330,7 @@ common =
|
|||||||
.requires-two-positive-values = Erfordert zwei positive Werte (x,y) oder einen für beide.
|
.requires-two-positive-values = Erfordert zwei positive Werte (x,y) oder einen für beide.
|
||||||
.snap-marker-color = Farbe der Fangmarkierung
|
.snap-marker-color = Farbe der Fangmarkierung
|
||||||
.command-line-text-color = Textfarbe der Befehlszeile
|
.command-line-text-color = Textfarbe der Befehlszeile
|
||||||
|
.operand = Operand
|
||||||
.linetypes-loaded = __ocs_fmt_0__ Linientyp(en) geladen.
|
.linetypes-loaded = __ocs_fmt_0__ Linientyp(en) geladen.
|
||||||
.geometric-center-snap = Geometrischer Mittelpunkt
|
.geometric-center-snap = Geometrischer Mittelpunkt
|
||||||
.no-trim-option = Nicht stutzen
|
.no-trim-option = Nicht stutzen
|
||||||
|
|||||||
@@ -2329,6 +2329,7 @@ common =
|
|||||||
.requires-two-positive-values = Απαιτούνται δύο θετικές τιμές (x,y) ή μία και για τις δύο.
|
.requires-two-positive-values = Απαιτούνται δύο θετικές τιμές (x,y) ή μία και για τις δύο.
|
||||||
.snap-marker-color = Χρώμα δείκτη έλξης
|
.snap-marker-color = Χρώμα δείκτη έλξης
|
||||||
.command-line-text-color = Χρώμα κειμένου γραμμής εντολών
|
.command-line-text-color = Χρώμα κειμένου γραμμής εντολών
|
||||||
|
.operand = Τελεστέος
|
||||||
.linetypes-loaded = Φορτώθηκαν __ocs_fmt_0__ τύποι γραμμής.
|
.linetypes-loaded = Φορτώθηκαν __ocs_fmt_0__ τύποι γραμμής.
|
||||||
.geometric-center-snap = Γεωμετρικό κέντρο
|
.geometric-center-snap = Γεωμετρικό κέντρο
|
||||||
.no-trim-option = Χωρίς περικοπή
|
.no-trim-option = Χωρίς περικοπή
|
||||||
|
|||||||
@@ -2330,6 +2330,7 @@ common =
|
|||||||
.requires-two-positive-values = Requires two positive values (x,y) or one for both.
|
.requires-two-positive-values = Requires two positive values (x,y) or one for both.
|
||||||
.snap-marker-color = Snap marker color
|
.snap-marker-color = Snap marker color
|
||||||
.command-line-text-color = Command line text color
|
.command-line-text-color = Command line text color
|
||||||
|
.operand = Operand
|
||||||
.linetypes-loaded = __ocs_fmt_0__ linetype(s) loaded.
|
.linetypes-loaded = __ocs_fmt_0__ linetype(s) loaded.
|
||||||
.geometric-center-snap = Geometric Center
|
.geometric-center-snap = Geometric Center
|
||||||
.no-trim-option = No trim
|
.no-trim-option = No trim
|
||||||
|
|||||||
@@ -2330,6 +2330,7 @@ common =
|
|||||||
.requires-two-positive-values = Requiere dos valores positivos (x,y) o uno para ambos.
|
.requires-two-positive-values = Requiere dos valores positivos (x,y) o uno para ambos.
|
||||||
.snap-marker-color = Color del marcador de referencia
|
.snap-marker-color = Color del marcador de referencia
|
||||||
.command-line-text-color = Color del texto de la línea de comandos
|
.command-line-text-color = Color del texto de la línea de comandos
|
||||||
|
.operand = Operando
|
||||||
.linetypes-loaded = __ocs_fmt_0__ tipo(s) de línea cargado(s).
|
.linetypes-loaded = __ocs_fmt_0__ tipo(s) de línea cargado(s).
|
||||||
.geometric-center-snap = Centro geométrico
|
.geometric-center-snap = Centro geométrico
|
||||||
.no-trim-option = No recortar
|
.no-trim-option = No recortar
|
||||||
|
|||||||
@@ -2329,6 +2329,7 @@ common =
|
|||||||
.requires-two-positive-values = Vaatii kaksi positiivista arvoa (x,y) tai yhden molemmille.
|
.requires-two-positive-values = Vaatii kaksi positiivista arvoa (x,y) tai yhden molemmille.
|
||||||
.snap-marker-color = Kohdistusmerkin väri
|
.snap-marker-color = Kohdistusmerkin väri
|
||||||
.command-line-text-color = Komentorivin tekstin väri
|
.command-line-text-color = Komentorivin tekstin väri
|
||||||
|
.operand = Operandi
|
||||||
.linetypes-loaded = Ladattu __ocs_fmt_0__ viivatyyppiä.
|
.linetypes-loaded = Ladattu __ocs_fmt_0__ viivatyyppiä.
|
||||||
.geometric-center-snap = Geometrinen keskipiste
|
.geometric-center-snap = Geometrinen keskipiste
|
||||||
.no-trim-option = Ei trimmausta
|
.no-trim-option = Ei trimmausta
|
||||||
|
|||||||
@@ -2330,6 +2330,7 @@ common =
|
|||||||
.requires-two-positive-values = Nécessite deux valeurs positives (x,y) ou une seule pour les deux.
|
.requires-two-positive-values = Nécessite deux valeurs positives (x,y) ou une seule pour les deux.
|
||||||
.snap-marker-color = Couleur du marqueur d'accrochage
|
.snap-marker-color = Couleur du marqueur d'accrochage
|
||||||
.command-line-text-color = Couleur du texte de la ligne de commande
|
.command-line-text-color = Couleur du texte de la ligne de commande
|
||||||
|
.operand = Opérande
|
||||||
.linetypes-loaded = __ocs_fmt_0__ type(s) de ligne chargé(s).
|
.linetypes-loaded = __ocs_fmt_0__ type(s) de ligne chargé(s).
|
||||||
.geometric-center-snap = Centre géométrique
|
.geometric-center-snap = Centre géométrique
|
||||||
.no-trim-option = Sans ajuster
|
.no-trim-option = Sans ajuster
|
||||||
|
|||||||
@@ -2318,6 +2318,7 @@ common =
|
|||||||
.requires-two-positive-values = दो धनात्मक मान (x,y) या दोनों के लिए एक मान आवश्यक है।
|
.requires-two-positive-values = दो धनात्मक मान (x,y) या दोनों के लिए एक मान आवश्यक है।
|
||||||
.snap-marker-color = स्नैप मार्कर का रंग
|
.snap-marker-color = स्नैप मार्कर का रंग
|
||||||
.command-line-text-color = कमांड लाइन टेक्स्ट रंग
|
.command-line-text-color = कमांड लाइन टेक्स्ट रंग
|
||||||
|
.operand = ऑपरेंड
|
||||||
.linetypes-loaded = __ocs_fmt_0__ लाइनटाइप लोड हुए।
|
.linetypes-loaded = __ocs_fmt_0__ लाइनटाइप लोड हुए।
|
||||||
.geometric-center-snap = ज्यामितीय केंद्र
|
.geometric-center-snap = ज्यामितीय केंद्र
|
||||||
.no-trim-option = ट्रिम नहीं
|
.no-trim-option = ट्रिम नहीं
|
||||||
|
|||||||
@@ -2329,6 +2329,7 @@ common =
|
|||||||
.requires-two-positive-values = Két pozitív érték (x,y) vagy egy közös érték szükséges.
|
.requires-two-positive-values = Két pozitív érték (x,y) vagy egy közös érték szükséges.
|
||||||
.snap-marker-color = Illesztési jelölő színe
|
.snap-marker-color = Illesztési jelölő színe
|
||||||
.command-line-text-color = Parancssor szövegszíne
|
.command-line-text-color = Parancssor szövegszíne
|
||||||
|
.operand = Operandus
|
||||||
.linetypes-loaded = __ocs_fmt_0__ vonaltípus betöltve.
|
.linetypes-loaded = __ocs_fmt_0__ vonaltípus betöltve.
|
||||||
.geometric-center-snap = Geometriai középpont
|
.geometric-center-snap = Geometriai középpont
|
||||||
.no-trim-option = Nincs vágás
|
.no-trim-option = Nincs vágás
|
||||||
|
|||||||
@@ -2329,6 +2329,7 @@ common =
|
|||||||
.requires-two-positive-values = Richiede due valori positivi (x,y) o uno per entrambi.
|
.requires-two-positive-values = Richiede due valori positivi (x,y) o uno per entrambi.
|
||||||
.snap-marker-color = Colore del marcatore di snap
|
.snap-marker-color = Colore del marcatore di snap
|
||||||
.command-line-text-color = Colore del testo della riga di comando
|
.command-line-text-color = Colore del testo della riga di comando
|
||||||
|
.operand = Operando
|
||||||
.linetypes-loaded = __ocs_fmt_0__ tipo/i di linea caricato/i.
|
.linetypes-loaded = __ocs_fmt_0__ tipo/i di linea caricato/i.
|
||||||
.geometric-center-snap = Centro geometrico
|
.geometric-center-snap = Centro geometrico
|
||||||
.no-trim-option = Non tagliare
|
.no-trim-option = Non tagliare
|
||||||
|
|||||||
@@ -2320,6 +2320,7 @@ common =
|
|||||||
.requires-two-positive-values = 2つの正の値 (x,y)、または両方に共通の1つの値が必要です。
|
.requires-two-positive-values = 2つの正の値 (x,y)、または両方に共通の1つの値が必要です。
|
||||||
.snap-marker-color = スナップ マーカーの色
|
.snap-marker-color = スナップ マーカーの色
|
||||||
.command-line-text-color = コマンドラインの文字色
|
.command-line-text-color = コマンドラインの文字色
|
||||||
|
.operand = オペランド
|
||||||
.linetypes-loaded = __ocs_fmt_0__ 個の線種をロードしました。
|
.linetypes-loaded = __ocs_fmt_0__ 個の線種をロードしました。
|
||||||
.geometric-center-snap = 図心
|
.geometric-center-snap = 図心
|
||||||
.no-trim-option = トリムしない
|
.no-trim-option = トリムしない
|
||||||
|
|||||||
@@ -2329,6 +2329,7 @@ common =
|
|||||||
.requires-two-positive-values = 양수 값 두 개(x,y) 또는 둘 다에 쓸 값 하나가 필요합니다.
|
.requires-two-positive-values = 양수 값 두 개(x,y) 또는 둘 다에 쓸 값 하나가 필요합니다.
|
||||||
.snap-marker-color = 스냅 마커 색상
|
.snap-marker-color = 스냅 마커 색상
|
||||||
.command-line-text-color = 명령줄 텍스트 색상
|
.command-line-text-color = 명령줄 텍스트 색상
|
||||||
|
.operand = 피연산자
|
||||||
.linetypes-loaded = 선종류 __ocs_fmt_0__개를 로드했습니다.
|
.linetypes-loaded = 선종류 __ocs_fmt_0__개를 로드했습니다.
|
||||||
.geometric-center-snap = 기하학적 중심
|
.geometric-center-snap = 기하학적 중심
|
||||||
.no-trim-option = 자르지 않기
|
.no-trim-option = 자르지 않기
|
||||||
|
|||||||
@@ -2330,6 +2330,7 @@ common =
|
|||||||
.requires-two-positive-values = Vereist twee positieve waarden (x,y) of één voor beide.
|
.requires-two-positive-values = Vereist twee positieve waarden (x,y) of één voor beide.
|
||||||
.snap-marker-color = Kleur van snapmarkering
|
.snap-marker-color = Kleur van snapmarkering
|
||||||
.command-line-text-color = Tekstkleur opdrachtregel
|
.command-line-text-color = Tekstkleur opdrachtregel
|
||||||
|
.operand = Operand
|
||||||
.linetypes-loaded = __ocs_fmt_0__ lijntype(n) geladen.
|
.linetypes-loaded = __ocs_fmt_0__ lijntype(n) geladen.
|
||||||
.geometric-center-snap = Geometrisch middelpunt
|
.geometric-center-snap = Geometrisch middelpunt
|
||||||
.no-trim-option = Niet trimmen
|
.no-trim-option = Niet trimmen
|
||||||
|
|||||||
@@ -2329,6 +2329,7 @@ common =
|
|||||||
.requires-two-positive-values = Wymaga dwóch wartości dodatnich (x,y) lub jednej dla obu.
|
.requires-two-positive-values = Wymaga dwóch wartości dodatnich (x,y) lub jednej dla obu.
|
||||||
.snap-marker-color = Kolor znacznika przyciągania
|
.snap-marker-color = Kolor znacznika przyciągania
|
||||||
.command-line-text-color = Kolor tekstu wiersza poleceń
|
.command-line-text-color = Kolor tekstu wiersza poleceń
|
||||||
|
.operand = Operand
|
||||||
.linetypes-loaded = Wczytano rodzaje linii: __ocs_fmt_0__.
|
.linetypes-loaded = Wczytano rodzaje linii: __ocs_fmt_0__.
|
||||||
.geometric-center-snap = Środek geometryczny
|
.geometric-center-snap = Środek geometryczny
|
||||||
.no-trim-option = Bez przycinania
|
.no-trim-option = Bez przycinania
|
||||||
|
|||||||
@@ -2330,6 +2330,7 @@ common =
|
|||||||
.requires-two-positive-values = Requer dois valores positivos (x,y) ou um para ambos.
|
.requires-two-positive-values = Requer dois valores positivos (x,y) ou um para ambos.
|
||||||
.snap-marker-color = Cor do marcador de snap
|
.snap-marker-color = Cor do marcador de snap
|
||||||
.command-line-text-color = Cor do texto da linha de comando
|
.command-line-text-color = Cor do texto da linha de comando
|
||||||
|
.operand = Operando
|
||||||
.linetypes-loaded = __ocs_fmt_0__ tipo(s) de linha carregado(s).
|
.linetypes-loaded = __ocs_fmt_0__ tipo(s) de linha carregado(s).
|
||||||
.geometric-center-snap = Centro geométrico
|
.geometric-center-snap = Centro geométrico
|
||||||
.no-trim-option = Não aparar
|
.no-trim-option = Não aparar
|
||||||
|
|||||||
@@ -2333,6 +2333,7 @@ common =
|
|||||||
.requires-two-positive-values = Требуются два положительных значения (x,y) или одно для обоих.
|
.requires-two-positive-values = Требуются два положительных значения (x,y) или одно для обоих.
|
||||||
.snap-marker-color = Цвет маркера привязки
|
.snap-marker-color = Цвет маркера привязки
|
||||||
.command-line-text-color = Цвет текста командной строки
|
.command-line-text-color = Цвет текста командной строки
|
||||||
|
.operand = Операнд
|
||||||
.linetypes-loaded = Загружено типов линий: __ocs_fmt_0__.
|
.linetypes-loaded = Загружено типов линий: __ocs_fmt_0__.
|
||||||
.geometric-center-snap = Геометрический центр
|
.geometric-center-snap = Геометрический центр
|
||||||
.no-trim-option = Без обрезки
|
.no-trim-option = Без обрезки
|
||||||
|
|||||||
@@ -2314,6 +2314,7 @@ common =
|
|||||||
.requires-two-positive-values = İki pozitif değer (x,y) ya da ikisi için tek değer gerekir.
|
.requires-two-positive-values = İki pozitif değer (x,y) ya da ikisi için tek değer gerekir.
|
||||||
.snap-marker-color = Yakalama işaretçisi rengi
|
.snap-marker-color = Yakalama işaretçisi rengi
|
||||||
.command-line-text-color = Komut satırı metin rengi
|
.command-line-text-color = Komut satırı metin rengi
|
||||||
|
.operand = İşlenen
|
||||||
.linetypes-loaded = __ocs_fmt_0__ çizgi tipi yüklendi.
|
.linetypes-loaded = __ocs_fmt_0__ çizgi tipi yüklendi.
|
||||||
.geometric-center-snap = Geometrik merkez
|
.geometric-center-snap = Geometrik merkez
|
||||||
.no-trim-option = Budama yok
|
.no-trim-option = Budama yok
|
||||||
|
|||||||
@@ -2317,6 +2317,7 @@ common =
|
|||||||
.requires-two-positive-values = 需要两个正值 (x,y),或一个同时用于两者的值。
|
.requires-two-positive-values = 需要两个正值 (x,y),或一个同时用于两者的值。
|
||||||
.snap-marker-color = 捕捉标记颜色
|
.snap-marker-color = 捕捉标记颜色
|
||||||
.command-line-text-color = 命令行文字颜色
|
.command-line-text-color = 命令行文字颜色
|
||||||
|
.operand = 操作对象
|
||||||
.linetypes-loaded = 已加载 __ocs_fmt_0__ 个线型。
|
.linetypes-loaded = 已加载 __ocs_fmt_0__ 个线型。
|
||||||
.geometric-center-snap = 几何中心
|
.geometric-center-snap = 几何中心
|
||||||
.no-trim-option = 不修剪
|
.no-trim-option = 不修剪
|
||||||
|
|||||||
@@ -2329,6 +2329,7 @@ common =
|
|||||||
.requires-two-positive-values = 需要兩個正值 (x,y),或一個同時用於兩者的值。
|
.requires-two-positive-values = 需要兩個正值 (x,y),或一個同時用於兩者的值。
|
||||||
.snap-marker-color = 鎖點標記顏色
|
.snap-marker-color = 鎖點標記顏色
|
||||||
.command-line-text-color = 命令列文字顏色
|
.command-line-text-color = 命令列文字顏色
|
||||||
|
.operand = 運算元
|
||||||
.linetypes-loaded = 已載入 __ocs_fmt_0__ 個線型。
|
.linetypes-loaded = 已載入 __ocs_fmt_0__ 個線型。
|
||||||
.geometric-center-snap = 幾何中心
|
.geometric-center-snap = 幾何中心
|
||||||
.no-trim-option = 不修剪
|
.no-trim-option = 不修剪
|
||||||
|
|||||||
+79
-7
@@ -4,7 +4,7 @@ use codec::{
|
|||||||
entities::{
|
entities::{
|
||||||
AcisData, EntityCommon, Region, Solid3D, Surface, SurfaceData, SurfaceKind, Wire,
|
AcisData, EntityCommon, Region, Solid3D, Surface, SurfaceData, SurfaceKind, Wire,
|
||||||
},
|
},
|
||||||
objects::SolidHistoryOperation,
|
objects::{SolidHistoryBoolean, SolidHistoryOperation},
|
||||||
EntityType, Handle,
|
EntityType, Handle,
|
||||||
};
|
};
|
||||||
use kernel::brep::{Body, EdgeKey, FaceKey};
|
use kernel::brep::{Body, EdgeKey, FaceKey};
|
||||||
@@ -1006,6 +1006,32 @@ impl super::OpenCADStudio {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Intersect compatible selected solids, Regions, and coplanar Surfaces.
|
/// Intersect compatible selected solids, Regions, and coplanar Surfaces.
|
||||||
|
/// Before a boolean replaces `retained` with `entity`: with SOLIDHIST on,
|
||||||
|
/// join the histories of the solids it combined (`steps`, in the order
|
||||||
|
/// the result applied them) into `retained`'s and hand that history to
|
||||||
|
/// `entity`; otherwise drop it. True when the history was kept.
|
||||||
|
fn join_boolean_history(
|
||||||
|
&mut self,
|
||||||
|
retained: Handle,
|
||||||
|
entity: &mut EntityType,
|
||||||
|
steps: &[(Handle, u8)],
|
||||||
|
bodies: &HashMap<Handle, Body>,
|
||||||
|
) -> bool {
|
||||||
|
let scene = &mut self.tabs[self.active_tab].scene;
|
||||||
|
let joined = scene.document.header.record_solid_history
|
||||||
|
&& scene.join_boolean_histories(retained, retained, steps, bodies);
|
||||||
|
if !joined {
|
||||||
|
scene.delete_solid_history(retained);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if let (EntityType::Solid3D(entity), Some(EntityType::Solid3D(current))) =
|
||||||
|
(entity, scene.document.get_entity(retained))
|
||||||
|
{
|
||||||
|
entity.history_handle = current.history_handle;
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) fn solid_intersect(&mut self) -> Task<Message> {
|
pub(super) fn solid_intersect(&mut self) -> Task<Message> {
|
||||||
let i = self.active_tab;
|
let i = self.active_tab;
|
||||||
let mut handles = self.selected_intersect_handles();
|
let mut handles = self.selected_intersect_handles();
|
||||||
@@ -1139,11 +1165,17 @@ impl super::OpenCADStudio {
|
|||||||
match group.outcome {
|
match group.outcome {
|
||||||
PreparedIntersectOutcome::Replace {
|
PreparedIntersectOutcome::Replace {
|
||||||
retained,
|
retained,
|
||||||
entity,
|
mut entity,
|
||||||
body,
|
body,
|
||||||
display,
|
display,
|
||||||
} => {
|
} => {
|
||||||
self.tabs[i].scene.delete_solid_history(retained);
|
let steps = group
|
||||||
|
.handles
|
||||||
|
.iter()
|
||||||
|
.filter(|handle| **handle != retained)
|
||||||
|
.map(|handle| (*handle, SolidHistoryBoolean::INTERSECT))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let joined = self.join_boolean_history(retained, &mut entity, &steps, &bodies);
|
||||||
if !self.tabs[i].scene.update_entity(entity) {
|
if !self.tabs[i].scene.update_entity(entity) {
|
||||||
self.command_line.push_error(
|
self.command_line.push_error(
|
||||||
crate::t!("INTERSECT: the retained object could not be updated.")
|
crate::t!("INTERSECT: the retained object could not be updated.")
|
||||||
@@ -1152,6 +1184,7 @@ impl super::OpenCADStudio {
|
|||||||
return Task::none();
|
return Task::none();
|
||||||
}
|
}
|
||||||
if record_history
|
if record_history
|
||||||
|
&& !joined
|
||||||
&& matches!(
|
&& matches!(
|
||||||
self.tabs[i].scene.document.get_entity(retained),
|
self.tabs[i].scene.document.get_entity(retained),
|
||||||
Some(EntityType::Solid3D(_))
|
Some(EntityType::Solid3D(_))
|
||||||
@@ -1328,11 +1361,37 @@ impl super::OpenCADStudio {
|
|||||||
let history = solid_history::brep_op(&group.body);
|
let history = solid_history::brep_op(&group.body);
|
||||||
let mut entity = Solid3D::new();
|
let mut entity = Solid3D::new();
|
||||||
entity.common = group.common;
|
entity.common = group.common;
|
||||||
self.add_solid_model_preserving_style(
|
let handle = self.add_solid_model_preserving_style(
|
||||||
EntityType::Solid3D(entity),
|
EntityType::Solid3D(entity),
|
||||||
group.body,
|
group.body,
|
||||||
history,
|
history,
|
||||||
)
|
);
|
||||||
|
// The new solid's history becomes the joined histories
|
||||||
|
// of the solids it unites.
|
||||||
|
let record = self.tabs[i].scene.document.header.record_solid_history;
|
||||||
|
if !handle.is_null() && record {
|
||||||
|
let bodies = group
|
||||||
|
.handles
|
||||||
|
.iter()
|
||||||
|
.filter_map(|operand| {
|
||||||
|
let body = self.tabs[i].scene.solid_models.get(operand)?;
|
||||||
|
Some((*operand, body.clone()))
|
||||||
|
})
|
||||||
|
.collect::<HashMap<_, _>>();
|
||||||
|
let steps = group
|
||||||
|
.handles
|
||||||
|
.iter()
|
||||||
|
.skip(1)
|
||||||
|
.map(|operand| (*operand, SolidHistoryBoolean::UNION))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let scene = &mut self.tabs[i].scene;
|
||||||
|
if !scene.join_boolean_histories(handle, group.handles[0], &steps, &bodies) {
|
||||||
|
if let Some(body) = scene.solid_models.get(&handle).cloned() {
|
||||||
|
scene.create_solid_history(handle, solid_history::brep_op(&body));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
handle
|
||||||
}
|
}
|
||||||
UnionEntityKind::Region => {
|
UnionEntityKind::Region => {
|
||||||
let mut entity = Region::new();
|
let mut entity = Region::new();
|
||||||
@@ -1588,8 +1647,20 @@ impl super::OpenCADStudio {
|
|||||||
let mut retained = Vec::new();
|
let mut retained = Vec::new();
|
||||||
let mut consumed = Vec::new();
|
let mut consumed = Vec::new();
|
||||||
for group in prepared {
|
for group in prepared {
|
||||||
if let Some((handle, entity, body, display)) = group.result {
|
if let Some((handle, mut entity, body, display)) = group.result {
|
||||||
self.tabs[i].scene.delete_solid_history(handle);
|
let steps = group
|
||||||
|
.bases
|
||||||
|
.iter()
|
||||||
|
.skip(1)
|
||||||
|
.map(|base| (*base, SolidHistoryBoolean::UNION))
|
||||||
|
.chain(
|
||||||
|
group
|
||||||
|
.cutters
|
||||||
|
.iter()
|
||||||
|
.map(|cutter| (*cutter, SolidHistoryBoolean::SUBTRACT)),
|
||||||
|
)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let joined = self.join_boolean_history(handle, &mut entity, &steps, &operand_bodies);
|
||||||
if !self.tabs[i].scene.update_entity(entity) {
|
if !self.tabs[i].scene.update_entity(entity) {
|
||||||
self.command_line.push_error(
|
self.command_line.push_error(
|
||||||
crate::t!("SUBTRACT: the retained base object could not be updated.").as_ref(),
|
crate::t!("SUBTRACT: the retained base object could not be updated.").as_ref(),
|
||||||
@@ -1597,6 +1668,7 @@ impl super::OpenCADStudio {
|
|||||||
return Task::none();
|
return Task::none();
|
||||||
}
|
}
|
||||||
if record_history
|
if record_history
|
||||||
|
&& !joined
|
||||||
&& matches!(
|
&& matches!(
|
||||||
self.tabs[i].scene.document.get_entity(handle),
|
self.tabs[i].scene.document.get_entity(handle),
|
||||||
Some(EntityType::Solid3D(_))
|
Some(EntityType::Solid3D(_))
|
||||||
|
|||||||
@@ -2732,6 +2732,19 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
|||||||
self.tabs[i].properties.edit_choice_open = false;
|
self.tabs[i].properties.edit_choice_open = false;
|
||||||
return Task::none();
|
return Task::none();
|
||||||
}
|
}
|
||||||
|
if field == crate::scene::model::solid_history::PROP_OPERAND {
|
||||||
|
// Which solid of a composite to edit: palette state only.
|
||||||
|
for handle in &handles {
|
||||||
|
crate::scene::model::solid_history::select_operand(
|
||||||
|
&self.tabs[i].scene.document,
|
||||||
|
*handle,
|
||||||
|
&value,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
self.tabs[i].properties.edit_choice_open = false;
|
||||||
|
self.refresh_properties();
|
||||||
|
return Task::none();
|
||||||
|
}
|
||||||
if crate::scene::model::solid_history::is_history_choice(field) {
|
if crate::scene::model::solid_history::is_history_choice(field) {
|
||||||
self.apply_property_op(i, "CHPROP", &handles, |app, handle| {
|
self.apply_property_op(i, "CHPROP", &handles, |app, handle| {
|
||||||
if app.tabs[i].scene.is_layer_locked(handle) {
|
if app.tabs[i].scene.is_layer_locked(handle) {
|
||||||
|
|||||||
@@ -149,7 +149,7 @@ fn entity_history_handle(entity: &EntityType) -> Option<Handle> {
|
|||||||
.filter(|handle| handle.is_valid())
|
.filter(|handle| handle.is_valid())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn history_operation_name(operation: &SolidHistoryOperation) -> &'static str {
|
pub(crate) fn history_operation_name(operation: &SolidHistoryOperation) -> &'static str {
|
||||||
match operation {
|
match operation {
|
||||||
SolidHistoryOperation::Unknown => "Unknown",
|
SolidHistoryOperation::Unknown => "Unknown",
|
||||||
SolidHistoryOperation::Box(_) => "Box",
|
SolidHistoryOperation::Box(_) => "Box",
|
||||||
|
|||||||
@@ -4895,6 +4895,7 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static
|
|||||||
"Requires two positive values (x,y) or one for both." => Some(("common", "requires-two-positive-values")),
|
"Requires two positive values (x,y) or one for both." => Some(("common", "requires-two-positive-values")),
|
||||||
"Snap marker color" => Some(("common", "snap-marker-color")),
|
"Snap marker color" => Some(("common", "snap-marker-color")),
|
||||||
"Command line text color" => Some(("common", "command-line-text-color")),
|
"Command line text color" => Some(("common", "command-line-text-color")),
|
||||||
|
"Operand" => Some(("common", "operand")),
|
||||||
"{added} linetype(s) loaded." => Some(("common", "linetypes-loaded")),
|
"{added} linetype(s) loaded." => Some(("common", "linetypes-loaded")),
|
||||||
"Geometric Center" => Some(("common", "geometric-center-snap")),
|
"Geometric Center" => Some(("common", "geometric-center-snap")),
|
||||||
"No trim" => Some(("common", "no-trim-option")),
|
"No trim" => Some(("common", "no-trim-option")),
|
||||||
|
|||||||
+14
-6
@@ -756,11 +756,12 @@ impl Scene {
|
|||||||
.iter()
|
.iter()
|
||||||
.filter(|handle| !self.solid_models.contains_key(handle))
|
.filter(|handle| !self.solid_models.contains_key(handle))
|
||||||
.filter_map(|&handle| {
|
.filter_map(|&handle| {
|
||||||
let from_history = self
|
let tree = self.document.solid_history_tree(handle);
|
||||||
.document
|
let rebuilt = || {
|
||||||
.solid_history_operations(handle)
|
tree.as_ref()
|
||||||
.and_then(|operations| kernel::acis::rebuild_history(&operations).ok());
|
.and_then(|tree| kernel::acis::rebuild_history_tree(tree).ok())
|
||||||
let body = from_history.or_else(|| match self.document.get_entity(handle) {
|
};
|
||||||
|
let stored = || match self.document.get_entity(handle) {
|
||||||
Some(EntityType::Solid3D(solid)) => {
|
Some(EntityType::Solid3D(solid)) => {
|
||||||
crate::scene::convert::solid3d_tess::kernel_body(solid)
|
crate::scene::convert::solid3d_tess::kernel_body(solid)
|
||||||
}
|
}
|
||||||
@@ -771,7 +772,14 @@ impl Scene {
|
|||||||
crate::scene::convert::solid3d_tess::kernel_surface_body(surface)
|
crate::scene::convert::solid3d_tess::kernel_surface_body(surface)
|
||||||
}
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
})?;
|
};
|
||||||
|
// A composite's saved body is what its booleans produced;
|
||||||
|
// replaying them is for edits, not for reading the file.
|
||||||
|
let body = if tree.as_ref().is_some_and(|tree| tree.has_boolean()) {
|
||||||
|
stored().or_else(rebuilt)
|
||||||
|
} else {
|
||||||
|
rebuilt().or_else(stored)
|
||||||
|
}?;
|
||||||
Some((handle, body))
|
Some((handle, body))
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|||||||
@@ -94,6 +94,112 @@ pub const PROP_LOFT_CLOSED: &str = "solid_history_loft_closed";
|
|||||||
pub const PROP_LOFT_PERIODIC: &str = "solid_history_loft_periodic";
|
pub const PROP_LOFT_PERIODIC: &str = "solid_history_loft_periodic";
|
||||||
pub const PROP_HISTORY: &str = "solid_history_record";
|
pub const PROP_HISTORY: &str = "solid_history_record";
|
||||||
pub const PROP_SHOW_HISTORY: &str = "solid_history_show";
|
pub const PROP_SHOW_HISTORY: &str = "solid_history_show";
|
||||||
|
/// Which of a composite's solids the palette and grips edit.
|
||||||
|
pub const PROP_OPERAND: &str = "solid_history_operand";
|
||||||
|
|
||||||
|
thread_local! {
|
||||||
|
/// The operand edited inside each composite solid, by history node id.
|
||||||
|
static EDIT_OPERANDS: std::cell::RefCell<rustc_hash::FxHashMap<codec::Handle, i32>> =
|
||||||
|
std::cell::RefCell::new(rustc_hash::FxHashMap::default());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The solids a composite's booleans joined, as (node id, palette label) in
|
||||||
|
/// operand order. Empty for a solid with no boolean step.
|
||||||
|
pub fn composite_operands(
|
||||||
|
document: &codec::CadDocument,
|
||||||
|
handle: codec::Handle,
|
||||||
|
) -> Vec<(i32, String)> {
|
||||||
|
let Some(tree) = document
|
||||||
|
.solid_history_tree(handle)
|
||||||
|
.filter(|tree| tree.has_boolean())
|
||||||
|
else {
|
||||||
|
return Vec::new();
|
||||||
|
};
|
||||||
|
tree.leaves()
|
||||||
|
.into_iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter_map(|(index, operation)| {
|
||||||
|
let name = crate::entities::object_data::history_operation_name(operation);
|
||||||
|
Some((operation.base()?.node_id(), format!("{}: {}", index + 1, t!(name))))
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Edit the operand the palette lists as `label` from now on.
|
||||||
|
pub fn select_operand(document: &codec::CadDocument, handle: codec::Handle, label: &str) {
|
||||||
|
if let Some((id, _)) = composite_operands(document, handle)
|
||||||
|
.into_iter()
|
||||||
|
.find(|(_, candidate)| candidate == label)
|
||||||
|
{
|
||||||
|
EDIT_OPERANDS.with(|operands| operands.borrow_mut().insert(handle, id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The composite's history and the node id of the operand being edited: the
|
||||||
|
/// one last chosen, or the first.
|
||||||
|
fn selected_operand(
|
||||||
|
document: &codec::CadDocument,
|
||||||
|
handle: codec::Handle,
|
||||||
|
) -> Option<(codec::objects::SolidHistoryTree, i32)> {
|
||||||
|
let tree = document
|
||||||
|
.solid_history_tree(handle)
|
||||||
|
.filter(|tree| tree.has_boolean())?;
|
||||||
|
let ids = tree
|
||||||
|
.leaves()
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|operation| Some(operation.base()?.node_id()))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let chosen = EDIT_OPERANDS.with(|operands| operands.borrow().get(&handle).copied());
|
||||||
|
let id = chosen.filter(|id| ids.contains(id)).or(ids.first().copied())?;
|
||||||
|
Some((tree, id))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The placement the steps above node `id` add to it: the product of their
|
||||||
|
/// transforms, outermost first.
|
||||||
|
fn ancestors_frame(tree: &codec::objects::SolidHistoryTree, id: i32) -> Option<glam::DMat4> {
|
||||||
|
if tree.operation.base().is_some_and(|base| base.node_id() == id) {
|
||||||
|
return Some(glam::DMat4::IDENTITY);
|
||||||
|
}
|
||||||
|
let own = matrix(tree.operation.base()?.transform)?;
|
||||||
|
tree.operands
|
||||||
|
.iter()
|
||||||
|
.find_map(|operand| ancestors_frame(operand, id))
|
||||||
|
.map(|inner| own * inner)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The operand of a composite solid that the palette and grips edit, placed
|
||||||
|
/// where it sits in the composite. None for a solid with no boolean step.
|
||||||
|
pub fn edit_operand(
|
||||||
|
document: &codec::CadDocument,
|
||||||
|
handle: codec::Handle,
|
||||||
|
) -> Option<SolidHistoryOperation> {
|
||||||
|
let (tree, id) = selected_operand(document, handle)?;
|
||||||
|
let frame = ancestors_frame(&tree, id)?;
|
||||||
|
let mut operation = tree.find(id)?.operation.clone();
|
||||||
|
let base = operation.base_mut()?;
|
||||||
|
base.transform = (frame * matrix(base.transform)?).to_cols_array();
|
||||||
|
Some(operation)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An operand from [`edit_operand`], edited where it sits, back in the frame
|
||||||
|
/// its composite stores it in. `operation` itself when the solid has no
|
||||||
|
/// boolean step.
|
||||||
|
pub fn operand_to_history(
|
||||||
|
document: &codec::CadDocument,
|
||||||
|
handle: codec::Handle,
|
||||||
|
mut operation: SolidHistoryOperation,
|
||||||
|
) -> Option<SolidHistoryOperation> {
|
||||||
|
let Some(tree) = document
|
||||||
|
.solid_history_tree(handle)
|
||||||
|
.filter(|tree| tree.has_boolean())
|
||||||
|
else {
|
||||||
|
return Some(operation);
|
||||||
|
};
|
||||||
|
let frame = ancestors_frame(&tree, operation.base()?.node_id())?;
|
||||||
|
let base = operation.base_mut()?;
|
||||||
|
base.transform = (frame.inverse() * matrix(base.transform)?).to_cols_array();
|
||||||
|
Some(operation)
|
||||||
|
}
|
||||||
pub const PROP_SURFACE_TYPE: &str = "surface_type";
|
pub const PROP_SURFACE_TYPE: &str = "surface_type";
|
||||||
pub const PROP_SURFACE_WIREFRAME_TYPE: &str = "srf_wireframe_type";
|
pub const PROP_SURFACE_WIREFRAME_TYPE: &str = "srf_wireframe_type";
|
||||||
pub const PROP_SURFACE_U_ISOLINES: &str = "srf_u_isolines";
|
pub const PROP_SURFACE_U_ISOLINES: &str = "srf_u_isolines";
|
||||||
@@ -250,6 +356,9 @@ pub fn primitive_property_operation(
|
|||||||
document: &codec::CadDocument,
|
document: &codec::CadDocument,
|
||||||
handle: codec::Handle,
|
handle: codec::Handle,
|
||||||
) -> Option<SolidHistoryOperation> {
|
) -> Option<SolidHistoryOperation> {
|
||||||
|
if let Some(operand) = edit_operand(document, handle) {
|
||||||
|
return Some(operand);
|
||||||
|
}
|
||||||
document
|
document
|
||||||
.solid_history_operations(handle)
|
.solid_history_operations(handle)
|
||||||
.and_then(|operations| operations.into_iter().next())
|
.and_then(|operations| operations.into_iter().next())
|
||||||
@@ -1619,7 +1728,33 @@ pub fn primitive_properties(
|
|||||||
return brep_properties(document, handle);
|
return brep_properties(document, handle);
|
||||||
};
|
};
|
||||||
corner_frame(&mut operation);
|
corner_frame(&mut operation);
|
||||||
match &operation {
|
let mut sections = operation_properties(document, handle, &operation);
|
||||||
|
let operands = composite_operands(document, handle);
|
||||||
|
let selected = operation
|
||||||
|
.base()
|
||||||
|
.and_then(|base| operands.iter().find(|(id, _)| *id == base.node_id()));
|
||||||
|
if let (Some((_, selected)), Some(first)) = (selected, sections.first_mut()) {
|
||||||
|
first.props.insert(
|
||||||
|
0,
|
||||||
|
Property {
|
||||||
|
label: t!("Operand").into_owned(),
|
||||||
|
field: PROP_OPERAND,
|
||||||
|
value: PropValue::Choice {
|
||||||
|
selected: selected.clone(),
|
||||||
|
options: operands.iter().map(|(_, label)| label.clone()).collect(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
sections
|
||||||
|
}
|
||||||
|
|
||||||
|
fn operation_properties(
|
||||||
|
document: &codec::CadDocument,
|
||||||
|
handle: codec::Handle,
|
||||||
|
operation: &SolidHistoryOperation,
|
||||||
|
) -> Vec<PropSection> {
|
||||||
|
match operation {
|
||||||
SolidHistoryOperation::Box(value) => {
|
SolidHistoryOperation::Box(value) => {
|
||||||
rectangular_properties(document, handle, value, "Box")
|
rectangular_properties(document, handle, value, "Box")
|
||||||
}
|
}
|
||||||
@@ -1706,7 +1841,7 @@ pub fn is_loft_geometry_choice(field: &str) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_specialized_property(field: &str) -> bool {
|
pub fn is_specialized_property(field: &str) -> bool {
|
||||||
matches!(field, "solid_history_type" | PROP_BANK | PROP_SWEEP_LENGTH
|
matches!(field, "solid_history_type" | PROP_OPERAND | PROP_BANK | PROP_SWEEP_LENGTH
|
||||||
| PROP_LOFT_TYPE | PROP_LOFT_SECTION_COUNT
|
| PROP_LOFT_TYPE | PROP_LOFT_SECTION_COUNT
|
||||||
| PROP_EXTRUSION_DIRECTION_X | PROP_EXTRUSION_DIRECTION_Y | PROP_EXTRUSION_DIRECTION_Z
|
| PROP_EXTRUSION_DIRECTION_X | PROP_EXTRUSION_DIRECTION_Y | PROP_EXTRUSION_DIRECTION_Z
|
||||||
| PROP_SURFACE_TYPE | PROP_SURFACE_WIREFRAME_TYPE
|
| PROP_SURFACE_TYPE | PROP_SURFACE_WIREFRAME_TYPE
|
||||||
@@ -3372,18 +3507,14 @@ pub fn chamfer_distance_grips(
|
|||||||
handle: codec::Handle,
|
handle: codec::Handle,
|
||||||
value: &SolidHistoryChamfer,
|
value: &SolidHistoryChamfer,
|
||||||
) -> Vec<GripDef> {
|
) -> Vec<GripDef> {
|
||||||
let Some(operations) = document.solid_history_operations(handle) else {
|
// The chamfer's edge ordinals name edges of the solid it was cut from.
|
||||||
return Vec::new();
|
let Some(source) = document
|
||||||
};
|
.solid_history_tree(handle)
|
||||||
let Some(chamfer_index) = operations
|
.and_then(|tree| tree.find(value.base.node_id())?.operands.first().cloned())
|
||||||
.iter()
|
.and_then(|operand| kernel::acis::rebuild_history_tree(&operand).ok())
|
||||||
.rposition(|operation| matches!(operation, SolidHistoryOperation::Chamfer(_)))
|
|
||||||
else {
|
else {
|
||||||
return Vec::new();
|
return Vec::new();
|
||||||
};
|
};
|
||||||
let Ok(source) = kernel::acis::rebuild_history(&operations[..chamfer_index]) else {
|
|
||||||
return Vec::new();
|
|
||||||
};
|
|
||||||
let edges = source.edge_keys().collect::<Vec<_>>();
|
let edges = source.edge_keys().collect::<Vec<_>>();
|
||||||
let faces = source.face_keys().collect::<Vec<_>>();
|
let faces = source.face_keys().collect::<Vec<_>>();
|
||||||
let Some(edge) = value
|
let Some(edge) = value
|
||||||
@@ -3469,10 +3600,11 @@ pub fn primitive_grips(
|
|||||||
document: &codec::CadDocument,
|
document: &codec::CadDocument,
|
||||||
handle: codec::Handle,
|
handle: codec::Handle,
|
||||||
) -> Vec<GripDef> {
|
) -> Vec<GripDef> {
|
||||||
let Some(operation) = document.solid_history_operation(handle) else {
|
let Some(mut operation) = edit_operand(document, handle)
|
||||||
|
.or_else(|| document.solid_history_operation(handle).cloned())
|
||||||
|
else {
|
||||||
return Vec::new();
|
return Vec::new();
|
||||||
};
|
};
|
||||||
let mut operation = operation.clone();
|
|
||||||
corner_frame(&mut operation);
|
corner_frame(&mut operation);
|
||||||
let operation = &operation;
|
let operation = &operation;
|
||||||
if let SolidHistoryOperation::Fillet(value) = operation {
|
if let SolidHistoryOperation::Fillet(value) = operation {
|
||||||
|
|||||||
+109
-25
@@ -913,6 +913,87 @@ impl Scene {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Make `target`'s history the boolean steps that built it: starting from
|
||||||
|
/// `first`'s history, each of `steps` joins a solid's history into the
|
||||||
|
/// result so far. When `first` is `target` its own history is kept; a
|
||||||
|
/// solid with no history starts from its body in `bodies`.
|
||||||
|
///
|
||||||
|
/// False, with `target` left without a history, when one of the solids
|
||||||
|
/// cannot carry one.
|
||||||
|
pub fn join_boolean_histories(
|
||||||
|
&mut self,
|
||||||
|
target: Handle,
|
||||||
|
first: Handle,
|
||||||
|
steps: &[(Handle, u8)],
|
||||||
|
bodies: &std::collections::HashMap<Handle, kernel::brep::Body>,
|
||||||
|
) -> bool {
|
||||||
|
let solids = std::iter::once(target)
|
||||||
|
.chain(std::iter::once(first))
|
||||||
|
.chain(steps.iter().map(|(handle, _)| *handle))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let solid = |handle: &Handle| {
|
||||||
|
matches!(self.document.get_entity(*handle), Some(EntityType::Solid3D(_)))
|
||||||
|
};
|
||||||
|
if !solids.iter().all(solid) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for handle in solids.iter().skip(1) {
|
||||||
|
if self.document.solid_history_graph(*handle).is_some() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let Some(body) = bodies.get(handle) else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
if !self.create_solid_history(
|
||||||
|
*handle,
|
||||||
|
crate::scene::model::solid_history::brep_op(body),
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if first != target {
|
||||||
|
self.delete_solid_history(target);
|
||||||
|
if self.is_recording_undo() {
|
||||||
|
let before = self.document.get_entity_arc(target);
|
||||||
|
self.record_undo_before(target, before);
|
||||||
|
}
|
||||||
|
if !self.copy_solid_history(first, target) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for &(tool, operation) in steps {
|
||||||
|
if !self.merge_solid_history_boolean(target, tool, operation) {
|
||||||
|
self.delete_solid_history(target);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn merge_solid_history_boolean(&mut self, target: Handle, tool: Handle, operation: u8) -> bool {
|
||||||
|
let previous: Vec<Handle> = [target, tool]
|
||||||
|
.iter()
|
||||||
|
.filter_map(|handle| self.document.solid_history_graph(*handle))
|
||||||
|
.flat_map(|graph| graph.nodes.into_iter().chain(graph.evaluation_graph))
|
||||||
|
.collect();
|
||||||
|
self.record_solid_history_before(target);
|
||||||
|
self.record_solid_history_before(tool);
|
||||||
|
if self.is_recording_undo() {
|
||||||
|
// The tool loses its history link; undo must give it back.
|
||||||
|
let before = self.document.get_entity_arc(tool);
|
||||||
|
self.record_undo_before(tool, before);
|
||||||
|
}
|
||||||
|
let Some(graph) = self.document.merge_solid_history_boolean(target, tool, operation) else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
for node in graph.nodes.into_iter().chain(graph.evaluation_graph) {
|
||||||
|
if !previous.contains(&node) {
|
||||||
|
self.record_undo_object_before(node, None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn sync_solid_reference_point(&mut self, handle: Handle) {
|
pub(crate) fn sync_solid_reference_point(&mut self, handle: Handle) {
|
||||||
let reference = self
|
let reference = self
|
||||||
.document
|
.document
|
||||||
@@ -971,26 +1052,9 @@ impl Scene {
|
|||||||
kernel::acis::rebuild_body(operation).ok()
|
kernel::acis::rebuild_body(operation).ok()
|
||||||
}
|
}
|
||||||
(EntityType::Solid3D(_), _) => {
|
(EntityType::Solid3D(_), _) => {
|
||||||
let mut operations = self.document.solid_history_operations(handle)?;
|
let mut tree = self.document.solid_history_tree(handle)?;
|
||||||
let replacement_id = operation.base().map(|base| {
|
tree.find_mut(operation.base()?.node_id())?.operation = operation.clone();
|
||||||
if base.eval.node_id > 0 {
|
kernel::acis::rebuild_history_tree(&tree).ok()
|
||||||
base.eval.node_id
|
|
||||||
} else {
|
|
||||||
base.step_id
|
|
||||||
}
|
|
||||||
})?;
|
|
||||||
let target = operations.iter_mut().find(|candidate| {
|
|
||||||
candidate.base().is_some_and(|base| {
|
|
||||||
let node_id = if base.eval.node_id > 0 {
|
|
||||||
base.eval.node_id
|
|
||||||
} else {
|
|
||||||
base.step_id
|
|
||||||
};
|
|
||||||
node_id == replacement_id
|
|
||||||
})
|
|
||||||
})?;
|
|
||||||
*target = operation.clone();
|
|
||||||
kernel::acis::rebuild_history(&operations).ok()
|
|
||||||
}
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
@@ -1126,11 +1190,18 @@ impl Scene {
|
|||||||
if self.history_surface_data(handle, &operation).is_none() {
|
if self.history_surface_data(handle, &operation).is_none() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if self
|
// An operand inside a composite is a step below the active one.
|
||||||
|
let active = self
|
||||||
.document
|
.document
|
||||||
.update_solid_history(handle, operation)
|
.solid_history_operation(handle)
|
||||||
.is_none()
|
.and_then(|active| active.base())
|
||||||
{
|
.map(|base| base.node_id());
|
||||||
|
let updated = if operation.base().map(|base| base.node_id()) == active {
|
||||||
|
self.document.update_solid_history(handle, operation).is_some()
|
||||||
|
} else {
|
||||||
|
self.document.update_solid_history_step(handle, operation).is_some()
|
||||||
|
};
|
||||||
|
if !updated {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
let (isoline_counts, planar_isolines) = match self.document.get_entity(handle) {
|
let (isoline_counts, planar_isolines) = match self.document.get_entity(handle) {
|
||||||
@@ -1270,7 +1341,10 @@ impl Scene {
|
|||||||
grip_id: usize,
|
grip_id: usize,
|
||||||
apply: GripApply,
|
apply: GripApply,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let Some(mut operation) = self.document.solid_history_operation(handle).cloned() else {
|
let Some(mut operation) =
|
||||||
|
crate::scene::model::solid_history::edit_operand(&self.document, handle)
|
||||||
|
.or_else(|| self.document.solid_history_operation(handle).cloned())
|
||||||
|
else {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
if grip_id == crate::scene::model::solid_history::GRIP_FILLET_RADIUS {
|
if grip_id == crate::scene::model::solid_history::GRIP_FILLET_RADIUS {
|
||||||
@@ -1345,6 +1419,11 @@ impl Scene {
|
|||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
let Some(operation) =
|
||||||
|
crate::scene::model::solid_history::operand_to_history(&self.document, handle, operation)
|
||||||
|
else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
self.preview_solid_history(handle, operation)
|
self.preview_solid_history(handle, operation)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1367,6 +1446,11 @@ impl Scene {
|
|||||||
) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
let Some(operation) =
|
||||||
|
crate::scene::model::solid_history::operand_to_history(&self.document, handle, operation)
|
||||||
|
else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
self.rebuild_solid_history(handle, operation)
|
self.rebuild_solid_history(handle, operation)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user