Source code for declaracad.occ.impl.occ_algo

"""
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