"""
Copyright (c) 2016-2018, CodeLV.
Distributed under the terms of the GPL v3 License.
The full license is in the file LICENSE, distributed with this software.
Created on Sep 27, 2016
@author: jrm
"""
import warnings
from atom.api import Instance, Str, Subclass, set_default
from OCCT.BRepAlgoAPI import (
BRepAlgoAPI_BooleanOperation,
BRepAlgoAPI_Common,
BRepAlgoAPI_Cut,
BRepAlgoAPI_Fuse,
)
from OCCT.BRepBuilderAPI import BRepBuilderAPI_Sewing
from OCCT.ShapeFix import ShapeFix_Shape
from OCCT.TopoDS import TopoDS_Shape
from OCCT.TopTools import TopTools_ListOfShape
try:
from OCCT.Voxel import Voxel_BooleanOperation
except ImportError as e:
warnings.warn(f"{e}")
Voxel_BooleanOperation = None
from declaracad.occ.algo import (
ProxyBooleanOperation,
ProxyCommon,
ProxyCut,
ProxyFuse,
ProxyGlue,
ProxyOperation,
ProxySew,
)
from declaracad.occ.voxel import Voxel
from .occ_shape import OccDependentShape, Topology, coerce_shape
class OccOperation(OccDependentShape, ProxyOperation):
"""Operation is a dependent shape that uses queuing to only
perform the operation once all changes have settled because
in general these operations are expensive.
"""
def set_fix(self, fix: bool):
self.update_shape()
class OccBooleanOperation(OccOperation, ProxyBooleanOperation):
"""Base class for a boolean shape operation."""
shape = Instance(TopoDS_Shape)
op = Subclass(BRepAlgoAPI_BooleanOperation)
op_name = Str()
def update_shape(self, change=None):
d = self.declaration
if isinstance(d.parent, Voxel):
return self.update_voxel()
shapes = []
unify = d.unify
fix = d.fix
if d.shape1 and d.shape2:
shapes = [coerce_shape(d.shape1), coerce_shape(d.shape2)]
else:
shapes = []
shapes.extend(list(self.child_shapes()))
shape, *other_shapes = shapes
if d.disabled:
self.shape = Topology.cast_shape(shape)
return
if d.parallel and other_shapes:
builder = self.op()
builder.SetFuzzyValue(d.tolerance)
shape_list = TopTools_ListOfShape()
shape_list.Append(shape)
tool_list = TopTools_ListOfShape()
for s in other_shapes:
if fix:
fixer = ShapeFix_Shape(s)
if fixer.Perform():
s = fixer.Shape()
tool_list.Append(s)
builder.SetArguments(shape_list)
builder.SetTools(tool_list)
builder.SetRunParallel(True)
builder.Build()
builder.Check()
if unify:
builder.SimplifyResult()
shape = builder.Shape()
else:
for other_shape in other_shapes:
builder = self.op(shape, other_shape)
if unify:
builder.SimplifyResult()
shape = builder.Shape()
if fix:
fixer = ShapeFix_Shape(shape)
if fixer.Perform():
shape = fixer.Shape()
if fix:
fixer = ShapeFix_Shape(shape)
if fixer.Perform():
shape = fixer.Shape()
self.shape = Topology.cast_shape(shape)
def update_voxel(self):
if not self.op_name:
raise RuntimeError(f"Cannot use {self} on voxels")
f = getattr(Voxel_BooleanOperation(), self.op_name)
d = self.declaration.parent
voxel = d.proxy.voxel
if voxel is None:
d.proxy.update_shape()
voxel = d.proxy.voxel
shape = d.proxy.shape # Triggers voxel creation
for other_shape in self.child_shapes():
other_voxel = Voxel(
source=other_shape,
splits=d.splits,
mode=d.mode,
bounds=d.bounds,
deflection=d.deflection,
)
other_voxel.render()
if not f(voxel, other_voxel.proxy.voxel):
raise RuntimeError("Could not perform operation. Size mismatch?")
self.shape = shape
def set_parallel(self, parallel):
self.update_shape()
def set_disabled(self, disabled):
self.update_shape()
def child_added(self, child):
d = self.declaration.parent
viewer = self.viewer
if isinstance(d, Voxel) and viewer:
f = getattr(Voxel_BooleanOperation(), self.op_name)
voxel = d.proxy.voxel
other_voxel = Voxel(
source=child.shape,
splits=d.splits,
mode=d.mode,
bounds=d.bounds,
deflection=d.deflection,
)
other_voxel.render()
if not f(voxel, other_voxel.proxy.voxel):
raise RuntimeError("Could not perform operation. Size mismatch?")
d.proxy.ais_shape.Redisplay()
else:
super().child_added(child)
[docs]
class OccCommon(OccBooleanOperation, ProxyCommon):
"""Common of all the child shapes together."""
reference = set_default(
"https://dev.opencascade.org/doc/refman/html/"
"class_b_rep_algo_a_p_i___common.html"
)
op = set_default(BRepAlgoAPI_Common)
[docs]
class OccCut(OccBooleanOperation, ProxyCut):
"""Cut all the child shapes from the first shape."""
reference = set_default(
"https://dev.opencascade.org/doc/refman/html/"
"class_b_rep_algo_a_p_i___cut.html"
)
op_name = "Cut"
op = set_default(BRepAlgoAPI_Cut)
[docs]
class OccFuse(OccBooleanOperation, ProxyFuse):
"""Fuse all the child shapes together."""
reference = set_default(
"https://dev.opencascade.org/doc/overview/html/"
"occt_user_guides__boolean_operations.html#occt_algorithms_7"
)
op_name = "Fuse"
op = set_default(BRepAlgoAPI_Fuse)
[docs]
class OccSew(OccOperation, ProxySew):
def update_shape(self, change=None):
builder = BRepBuilderAPI_Sewing()
for s in self.child_shapes():
builder.Add(Topology.cast_shape(s))
builder.Perform()
self.shape = Topology.cast_shape(builder.SewedShape())
class OccGlue(OccOperation, ProxyGlue):
def update_shape(self, change=None):
raise NotImplementedError # TODO: This