175 lines
7.5 KiB
Python
175 lines
7.5 KiB
Python
#/***************************************************************************
|
|
# * Copyright (c) Victor Titov (DeepSOIC) *
|
|
# * (vv.titov@gmail.com) 2016 *
|
|
# * *
|
|
# * This file is part of the FreeCAD CAx development system. *
|
|
# * *
|
|
# * This library is free software; you can redistribute it and/or *
|
|
# * modify it under the terms of the GNU Library General Public *
|
|
# * License as published by the Free Software Foundation; either *
|
|
# * version 2 of the License, or (at your option) any later version. *
|
|
# * *
|
|
# * This library is distributed in the hope that it will be useful, *
|
|
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
|
# * GNU Library General Public License for more details. *
|
|
# * *
|
|
# * You should have received a copy of the GNU Library General Public *
|
|
# * License along with this library; see the file COPYING.LIB. If not, *
|
|
# * write to the Free Software Foundation, Inc., 59 Temple Place, *
|
|
# * Suite 330, Boston, MA 02111-1307, USA *
|
|
# * *
|
|
# ***************************************************************************/
|
|
|
|
__title__="BOPTools.JoinAPI module"
|
|
__author__ = "DeepSOIC"
|
|
__url__ = "http://www.freecadweb.org"
|
|
__doc__ = "JoinFeatures functions that operate on shapes."
|
|
|
|
import Part
|
|
from . import ShapeMerge
|
|
from . import generalFuseIsAvailable
|
|
from .GeneralFuseResult import GeneralFuseResult
|
|
from .Utils import compoundLeaves
|
|
|
|
def shapeOfMaxSize(list_of_shapes):
|
|
"""shapeOfMaxSize(list_of_shapes): finds the shape that has the largest mass in the list and returns it. The shapes in the list must be of same dimension."""
|
|
#first, check if shapes can be compared by size
|
|
ShapeMerge.dimensionOfShapes(list_of_shapes)
|
|
|
|
rel_precision = 1e-8
|
|
|
|
#find it!
|
|
max_size = -1e100 # max size encountered so far
|
|
count_max = 0 # number of shapes with size equal to max_size
|
|
shape_max = None # shape of max_size
|
|
for sh in list_of_shapes:
|
|
v = abs(Part.cast_to_shape(sh).Mass)
|
|
if v > max_size*(1 + rel_precision) :
|
|
max_size = v
|
|
shape_max = sh
|
|
count_max = 1
|
|
elif (1-rel_precision) * max_size <= v and v <= (1+rel_precision) * max_size :
|
|
count_max = count_max + 1
|
|
if count_max > 1 :
|
|
raise ValueError("There is more than one largest piece!")
|
|
return shape_max
|
|
|
|
def connect(list_of_shapes, tolerance = 0.0):
|
|
"""connect(list_of_shapes, tolerance = 0.0): connects solids (walled objects), shells and
|
|
wires by throwing off small parts that result when splitting them at intersections.
|
|
|
|
Compounds in list_of_shapes are automatically exploded, so self-intersecting compounds
|
|
are valid for connect."""
|
|
|
|
# explode all compounds before GFA.
|
|
new_list_of_shapes = []
|
|
for sh in list_of_shapes:
|
|
new_list_of_shapes.extend( compoundLeaves(sh) )
|
|
list_of_shapes = new_list_of_shapes
|
|
|
|
#test if shapes are compatible for connecting
|
|
dim = ShapeMerge.dimensionOfShapes(list_of_shapes)
|
|
if dim == 0:
|
|
raise TypeError("Cannot connect vertices!")
|
|
|
|
if len(list_of_shapes) < 2:
|
|
return Part.makeCompound(list_of_shapes)
|
|
|
|
if not generalFuseIsAvailable(): #fallback to legacy
|
|
result = list_of_shapes[0]
|
|
for i in range(1, len(list_of_shapes)):
|
|
result = connect_legacy(result, list_of_shapes[i], tolerance)
|
|
return result
|
|
|
|
pieces, map = list_of_shapes[0].generalFuse(list_of_shapes[1:], tolerance)
|
|
ao = GeneralFuseResult(list_of_shapes, (pieces, map))
|
|
ao.splitAggregates()
|
|
#print len(ao.pieces)," pieces total"
|
|
|
|
keepers = []
|
|
all_danglers = [] # debug
|
|
|
|
#add all biggest dangling pieces
|
|
for src in ao.source_shapes:
|
|
danglers = [piece for piece in ao.piecesFromSource(src) if len(ao.sourcesOfPiece(piece)) == 1]
|
|
all_danglers.extend(danglers)
|
|
largest = shapeOfMaxSize(danglers)
|
|
if largest is not None:
|
|
keepers.append(largest)
|
|
|
|
touch_test_list = Part.Compound(keepers)
|
|
#add all intersection pieces that touch danglers, triple intersection pieces that touch duals, and so on
|
|
for ii in range(2, ao.largestOverlapCount()+1):
|
|
list_ii_pieces = [piece for piece in ao.pieces if len(ao.sourcesOfPiece(piece)) == ii]
|
|
keepers_2_add = []
|
|
for piece in list_ii_pieces:
|
|
if ShapeMerge.isConnected(piece, touch_test_list):
|
|
keepers_2_add.append(piece)
|
|
if len(keepers_2_add) == 0:
|
|
break
|
|
keepers.extend(keepers_2_add)
|
|
touch_test_list = Part.Compound(keepers_2_add)
|
|
|
|
|
|
#merge, and we are done!
|
|
#print len(keepers)," pieces to keep"
|
|
return ShapeMerge.mergeShapes(keepers)
|
|
|
|
def connect_legacy(shape1, shape2, tolerance = 0.0):
|
|
"""connect_legacy(shape1, shape2, tolerance = 0.0): alternative implementation of
|
|
connect, without use of generalFuse. Slow. Provided for backwards compatibility, and
|
|
for older OCC."""
|
|
|
|
if tolerance>0.0:
|
|
import FreeCAD as App
|
|
App.Console.PrintWarning("connect_legacy does not support tolerance (yet).\n")
|
|
cut1 = shape1.cut(shape2)
|
|
cut1 = shapeOfMaxSize(cut1.childShapes())
|
|
cut2 = shape2.cut(shape1)
|
|
cut2 = shapeOfMaxSize(cut2.childShapes())
|
|
return cut1.multiFuse([cut2, shape2.common(shape1)])
|
|
|
|
#def embed(shape_base, shape_tool, tolerance = 0.0):
|
|
# (TODO)
|
|
|
|
def embed_legacy(shape_base, shape_tool, tolerance = 0.0):
|
|
"""embed_legacy(shape_base, shape_tool, tolerance = 0.0): alternative implementation of
|
|
embed, without use of generalFuse. Slow. Provided for backwards compatibility, and
|
|
for older OCC."""
|
|
if tolerance>0.0:
|
|
import FreeCAD as App
|
|
App.Console.PrintWarning("embed_legacy does not support tolerance (yet).\n")
|
|
|
|
# using legacy implementation, except adding support for shells
|
|
pieces = compoundLeaves(shape_base.cut(shape_tool))
|
|
piece = shapeOfMaxSize(pieces)
|
|
result = piece.fuse(shape_tool)
|
|
dim = ShapeMerge.dimensionOfShapes(pieces)
|
|
if dim == 2:
|
|
# fusing shells returns shells that are still split. Reassemble them
|
|
result = ShapeMerge.mergeShapes(result.Faces)
|
|
elif dim == 1:
|
|
result = ShapeMerge.mergeShapes(result.Edges)
|
|
return result
|
|
|
|
def cutout_legacy(shape_base, shape_tool, tolerance = 0.0):
|
|
"""cutout_legacy(shape_base, shape_tool, tolerance = 0.0): alternative implementation of
|
|
cutout, without use of generalFuse. Slow. Provided for backwards compatibility, and
|
|
for older OCC."""
|
|
|
|
if tolerance>0.0:
|
|
import FreeCAD as App
|
|
App.Console.PrintWarning("cutout_legacy does not support tolerance (yet).\n")
|
|
#if base is multi-piece, work on per-piece basis
|
|
shapes_base = compoundLeaves(shape_base)
|
|
if len(shapes_base) > 1:
|
|
result = []
|
|
for sh in shapes_base:
|
|
result.append(cutout(sh, shape_tool))
|
|
return Part.Compound(result)
|
|
|
|
shape_base = shapes_base[0]
|
|
pieces = compoundLeaves(shape_base.cut(shape_tool))
|
|
return shapeOfMaxSize(pieces)
|