"""
Copyright (c) 2016-2021, CodeLV.
Distributed under the terms of the GPL v3 License.
The full license is in the file LICENSE, distributed with this software.
Created on Dec 23, 2021
@author: jrm
"""
from atom.api import Dict, set_default
from OCCT.BRepBuilderAPI import BRepBuilderAPI_MakeFace
from OCCT.BRepFilletAPI import (
BRepFilletAPI_MakeChamfer,
BRepFilletAPI_MakeFillet,
BRepFilletAPI_MakeFillet2d,
)
from OCCT.BRepTools import BRepTools
from OCCT.gp import gp_Pnt2d
from OCCT.ShapeFix import ShapeFix_Shape
from OCCT.TColgp import TColgp_Array1OfPnt2d
from OCCT.TopoDS import TopoDS_Edge, TopoDS_Face, TopoDS_Shape, TopoDS_Wire
from declaracad.core.utils import log
from declaracad.occ.algo import ChamferData, ProxyChamfer
from .occ_algo import OccOperation
from .topology import Topology
[docs]
class OccChamfer(OccOperation, ProxyChamfer):
reference = set_default(
"https://dev.opencascade.org/doc/refman/html/"
"class_b_rep_fillet_a_p_i___make_chamfer.html"
)
context = Dict()
def update_shape(self, change=None):
d = self.declaration
#: Get the shape to apply the fillet to
child = self.get_first_child()
# Ignore this operation
if d.disabled:
self.shape = child.shape
return
if isinstance(child.shape, (TopoDS_Wire, TopoDS_Face)):
shape = self.chamfer_2d(child)
elif self.has_profile_operations():
shape = self.chamfer_profile(child)
else:
shape = self.chamfer_3d(child)
if d.fix:
fixer = ShapeFix_Shape(shape)
if fixer.Perform():
shape = fixer.Shape()
self.shape = shape
def has_profile_operations(self) -> bool:
T = (tuple, list)
for item in self.declaration.operations:
if isinstance(item, T) and isinstance(item[0], T):
return True
return False
def chamfer_2d(self, child) -> TopoDS_Shape:
d = self.declaration
shape = child.shape
was_wire = isinstance(shape, TopoDS_Wire)
if was_wire:
shape = BRepBuilderAPI_MakeFace(shape).Face()
builder = BRepFilletAPI_MakeFillet2d(shape)
operations = d.operations if d.operations else child.topology.vertices
for item in operations:
d1, d2 = d.distance, d.distance2 or d.distance
if isinstance(item, (tuple, list)):
n = len(item)
if n == 2:
e1, e2 = item
builder.AddChamfer(e1, e2, d1, d2)
elif n == 3:
d1, e1, e2 = item
builder.AddChamfer(e1, e2, d1, d1)
elif n == 4:
d1, d2, e1, e2 = item
builder.AddChamfer(e1, e2, d1, d2)
else:
log.warning(f"Invalid chamfer {item}")
else:
log.warning(f"Invalid chamfer {item}")
shape = Topology.cast_shape(builder.Shape())
if was_wire:
shape = BRepTools.OuterWire_(shape)
return shape
def chamfer_3d(self, child) -> TopoDS_Shape:
d = self.declaration
shape = child.shape
operations = d.operations if d.operations else child.topology.faces
if self.has_profile_operations():
return self.chamfer_profile(shape, d.operations)
chamfer = BRepFilletAPI_MakeChamfer(shape)
for item in operations:
edge = None
angle: float = 0
d1, d2 = d.distance, d.distance2 or d.distance
if isinstance(item, (tuple, list)):
face = item[-1]
n = len(item)
if n > 1:
i = 0
if isinstance(item[-2], TopoDS_Edge):
edge = item[-2]
i += 1
if n == i + 2:
d1 = d2 = item[0]
elif n == i + 3:
d1, d2 = item[0:2]
elif isinstance(item, ChamferData):
angle = item.angle
d1 = item.distance
d2 = item.distance or item.distance2
face = item.face
edge = item.edge
else:
face = item
if isinstance(face, TopoDS_Edge) and d1 == d2:
edge = face
chamfer.Add(d1, edge)
elif edge is None:
for edge in child.topology.edges_from_face(face):
if angle:
chamfer.AddDA(d1, angle, edge, face)
else:
chamfer.Add(d1, d2, edge, face)
elif angle:
chamfer.AddDA(d1, angle, edge, face)
else:
chamfer.Add(d1, d2, edge, face)
return chamfer.Shape()
def chamfer_profile(self, child) -> TopoDS_Shape:
"""Use fillet with custom shape. Only supports 45 deg chamfers."""
d = self.declaration
if d.distance2:
raise ValueError("Cannot mix profile and two distances")
from OCCT.ChFi3d import ChFi3d_Linear
builder = BRepFilletAPI_MakeFillet(child.shape)
builder.SetFilletShape(ChFi3d_Linear)
T = (tuple, list)
for item in d.operations:
if isinstance(item, T):
if isinstance(item[0], T):
profile, edge = item
array = TColgp_Array1OfPnt2d(1, len(profile))
for i, pt in enumerate(profile):
array.SetValue(i + 1, gp_Pnt2d(*pt))
builder.Add(array, edge)
elif len(item) == 2:
r, e = item
builder.Add(r, e)
else:
raise ValueError("Cannot mix profile and two distances")
else:
builder.Add(d.distance, e)
return builder.Shape()
def set_distance(self, d):
self.update_shape()
def set_distance2(self, d):
self.update_shape()
def set_operations(self, operations):
self.update_shape()