Part: boolean splitt tools, remove trailing whitspaces
This commit is contained in:
parent
6378ad3aa7
commit
7c8b9a4b2c
|
@ -31,15 +31,15 @@ from .Utils import HashableShape, HashableShape_Deep, FrozenClass
|
||||||
|
|
||||||
|
|
||||||
class GeneralFuseResult(FrozenClass):
|
class GeneralFuseResult(FrozenClass):
|
||||||
"""class GeneralFuseResult: helper object for obtaining info from results of
|
"""class GeneralFuseResult: helper object for obtaining info from results of
|
||||||
Part.Shape.generalFuse() method.
|
Part.Shape.generalFuse() method.
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
def myCustomFusionRoutine(list_of_shapes):
|
def myCustomFusionRoutine(list_of_shapes):
|
||||||
generalFuse_return = list_of_shapes[0].generalFuse(list_of_shapes[1:])
|
generalFuse_return = list_of_shapes[0].generalFuse(list_of_shapes[1:])
|
||||||
ao = GeneralFuseResult(list_of_shapes, generalFuse_return)
|
ao = GeneralFuseResult(list_of_shapes, generalFuse_return)
|
||||||
... (use attributes and methods of ao) ..."""
|
... (use attributes and methods of ao) ..."""
|
||||||
|
|
||||||
def __define_attributes(self):
|
def __define_attributes(self):
|
||||||
self.gfa_return = None #stores the data returned by generalFuse, supplied to class constructor
|
self.gfa_return = None #stores the data returned by generalFuse, supplied to class constructor
|
||||||
|
|
||||||
|
@ -51,24 +51,24 @@ def myCustomFusionRoutine(list_of_shapes):
|
||||||
|
|
||||||
self._pieces_of_source = [] #list of pieces (indexes) generated from a source shape, by index of source shape. List of lists of ints.
|
self._pieces_of_source = [] #list of pieces (indexes) generated from a source shape, by index of source shape. List of lists of ints.
|
||||||
self._sources_of_piece = [] #list of source shapes (indexes) the piece came from, by index of piece. List of lists of ints.
|
self._sources_of_piece = [] #list of source shapes (indexes) the piece came from, by index of piece. List of lists of ints.
|
||||||
|
|
||||||
self._element_to_source = {} #dictionary for finding, which source shapes did an element of pieces come from. key = HashableShape (element). Value = set of ints
|
self._element_to_source = {} #dictionary for finding, which source shapes did an element of pieces come from. key = HashableShape (element). Value = set of ints
|
||||||
|
|
||||||
self._freeze()
|
self._freeze()
|
||||||
|
|
||||||
def __init__(self, source_shapes, generalFuse_return):
|
def __init__(self, source_shapes, generalFuse_return):
|
||||||
self.__define_attributes()
|
self.__define_attributes()
|
||||||
|
|
||||||
self.gfa_return = generalFuse_return
|
self.gfa_return = generalFuse_return
|
||||||
self.source_shapes = source_shapes
|
self.source_shapes = source_shapes
|
||||||
self.parse()
|
self.parse()
|
||||||
|
|
||||||
def parse(self):
|
def parse(self):
|
||||||
"""Parses the result of generalFuse recorded into self.gfa_return. Recovers missing
|
"""Parses the result of generalFuse recorded into self.gfa_return. Recovers missing
|
||||||
information. Fills in data structures.
|
information. Fills in data structures.
|
||||||
|
|
||||||
It is called automatically by class constructor."""
|
It is called automatically by class constructor."""
|
||||||
|
|
||||||
#save things to be parsed and wipe out all other data
|
#save things to be parsed and wipe out all other data
|
||||||
gfa_return = self.gfa_return
|
gfa_return = self.gfa_return
|
||||||
source_shapes = self.source_shapes
|
source_shapes = self.source_shapes
|
||||||
|
@ -76,10 +76,10 @@ def myCustomFusionRoutine(list_of_shapes):
|
||||||
self.gfa_return = gfa_return
|
self.gfa_return = gfa_return
|
||||||
self.source_shapes = source_shapes
|
self.source_shapes = source_shapes
|
||||||
# and start filling in data structures...
|
# and start filling in data structures...
|
||||||
|
|
||||||
compound, map = self.gfa_return
|
compound, map = self.gfa_return
|
||||||
self.pieces = compound.childShapes()
|
self.pieces = compound.childShapes()
|
||||||
|
|
||||||
# create piece shape index
|
# create piece shape index
|
||||||
for iPiece in range(len(self.pieces)):
|
for iPiece in range(len(self.pieces)):
|
||||||
ha_piece = HashableShape(self.pieces[iPiece])
|
ha_piece = HashableShape(self.pieces[iPiece])
|
||||||
|
@ -87,20 +87,20 @@ def myCustomFusionRoutine(list_of_shapes):
|
||||||
self._piece_to_index[ha_piece] = iPiece
|
self._piece_to_index[ha_piece] = iPiece
|
||||||
else:
|
else:
|
||||||
raise ValueError("GeneralFuseAnalyzer.parse: duplicate piece shape detected.")
|
raise ValueError("GeneralFuseAnalyzer.parse: duplicate piece shape detected.")
|
||||||
# create source shape index
|
# create source shape index
|
||||||
for iSource in range(len(self.source_shapes)):
|
for iSource in range(len(self.source_shapes)):
|
||||||
ha_source = HashableShape(self.source_shapes[iSource])
|
ha_source = HashableShape(self.source_shapes[iSource])
|
||||||
if not ha_source in self._source_to_index:
|
if not ha_source in self._source_to_index:
|
||||||
self._source_to_index[ha_source] = iSource
|
self._source_to_index[ha_source] = iSource
|
||||||
else:
|
else:
|
||||||
raise ValueError("GeneralFuseAnalyzer.parse: duplicate source shape detected.")
|
raise ValueError("GeneralFuseAnalyzer.parse: duplicate source shape detected.")
|
||||||
|
|
||||||
#test if map has missing entries
|
#test if map has missing entries
|
||||||
map_needs_repairing = False
|
map_needs_repairing = False
|
||||||
for iSource in range(len(map)):
|
for iSource in range(len(map)):
|
||||||
if len(map[iSource]) == 0:
|
if len(map[iSource]) == 0:
|
||||||
map_needs_repairing = True
|
map_needs_repairing = True
|
||||||
|
|
||||||
if map_needs_repairing:
|
if map_needs_repairing:
|
||||||
aggregate_types = set(["Wire","Shell","CompSolid","Compound"])
|
aggregate_types = set(["Wire","Shell","CompSolid","Compound"])
|
||||||
nonaggregate_types = set(["Vertex","Edge","Face","Solid"])
|
nonaggregate_types = set(["Vertex","Edge","Face","Solid"])
|
||||||
|
@ -108,15 +108,15 @@ def myCustomFusionRoutine(list_of_shapes):
|
||||||
types = set()
|
types = set()
|
||||||
for piece in self.pieces:
|
for piece in self.pieces:
|
||||||
types.add(piece.ShapeType)
|
types.add(piece.ShapeType)
|
||||||
|
|
||||||
types_to_extract = types.intersection(nonaggregate_types)
|
types_to_extract = types.intersection(nonaggregate_types)
|
||||||
extractor = lambda(sh):(
|
extractor = lambda(sh):(
|
||||||
(sh.Vertexes if "Vertex" in types_to_extract else [])
|
(sh.Vertexes if "Vertex" in types_to_extract else [])
|
||||||
+ (sh.Edges if "Edge" in types_to_extract else [])
|
+ (sh.Edges if "Edge" in types_to_extract else [])
|
||||||
+ (sh.Faces if "Face" in types_to_extract else [])
|
+ (sh.Faces if "Face" in types_to_extract else [])
|
||||||
+ (sh.Solids if "Solid" in types_to_extract else [])
|
+ (sh.Solids if "Solid" in types_to_extract else [])
|
||||||
)
|
)
|
||||||
|
|
||||||
aggregate_sources_indexes = [self.indexOfSource(sh) for sh in self.source_shapes if sh.ShapeType in aggregate_types]
|
aggregate_sources_indexes = [self.indexOfSource(sh) for sh in self.source_shapes if sh.ShapeType in aggregate_types]
|
||||||
aggregate_pieces = [sh for sh in self.pieces if sh.ShapeType in aggregate_types]
|
aggregate_pieces = [sh for sh in self.pieces if sh.ShapeType in aggregate_types]
|
||||||
assert(len(aggregate_sources_indexes) == len(aggregate_pieces))
|
assert(len(aggregate_sources_indexes) == len(aggregate_pieces))
|
||||||
|
@ -132,13 +132,13 @@ def myCustomFusionRoutine(list_of_shapes):
|
||||||
#print "found piece {num} in compound {numc}".format(num= iPiece, numc= i_aggregate)
|
#print "found piece {num} in compound {numc}".format(num= iPiece, numc= i_aggregate)
|
||||||
if not map[iSource][-1].isSame(self.pieces[iPiece]):
|
if not map[iSource][-1].isSame(self.pieces[iPiece]):
|
||||||
map[iSource].append(self.pieces[iPiece])
|
map[iSource].append(self.pieces[iPiece])
|
||||||
|
|
||||||
# check the map was recovered successfully
|
# check the map was recovered successfully
|
||||||
for iSource in range(len(map)):
|
for iSource in range(len(map)):
|
||||||
if len(map[iSource]) == 0:
|
if len(map[iSource]) == 0:
|
||||||
import FreeCAD as App
|
import FreeCAD as App
|
||||||
App.Console.PrintWarning("Map entry {num} is empty. Source-to-piece correspondence information is probably incomplete.".format(num= iSource))
|
App.Console.PrintWarning("Map entry {num} is empty. Source-to-piece correspondence information is probably incomplete.".format(num= iSource))
|
||||||
|
|
||||||
self._pieces_of_source = [[] for i in range(len(self.source_shapes))]
|
self._pieces_of_source = [[] for i in range(len(self.source_shapes))]
|
||||||
self._sources_of_piece = [[] for i in range(len(self.pieces))]
|
self._sources_of_piece = [[] for i in range(len(self.pieces))]
|
||||||
assert(len(map) == len(self.source_shapes))
|
assert(len(map) == len(self.source_shapes))
|
||||||
|
@ -148,14 +148,14 @@ def myCustomFusionRoutine(list_of_shapes):
|
||||||
iPiece = self.indexOfPiece(piece)
|
iPiece = self.indexOfPiece(piece)
|
||||||
self._sources_of_piece[iPiece].append(iSource)
|
self._sources_of_piece[iPiece].append(iSource)
|
||||||
self._pieces_of_source[iSource].append(iPiece)
|
self._pieces_of_source[iSource].append(iPiece)
|
||||||
|
|
||||||
def parse_elements(self):
|
def parse_elements(self):
|
||||||
"""Fills element-to-source map. Potentially slow, so separated from general parse.
|
"""Fills element-to-source map. Potentially slow, so separated from general parse.
|
||||||
Needed for splitAggregates; called automatically from splitAggregates."""
|
Needed for splitAggregates; called automatically from splitAggregates."""
|
||||||
|
|
||||||
if len(self._element_to_source)>0:
|
if len(self._element_to_source)>0:
|
||||||
return #already parsed.
|
return #already parsed.
|
||||||
|
|
||||||
for iPiece in range(len(self.pieces)):
|
for iPiece in range(len(self.pieces)):
|
||||||
piece = self.pieces[iPiece]
|
piece = self.pieces[iPiece]
|
||||||
for element in piece.Vertexes + piece.Edges + piece.Faces + piece.Solids:
|
for element in piece.Vertexes + piece.Edges + piece.Faces + piece.Solids:
|
||||||
|
@ -164,79 +164,79 @@ def myCustomFusionRoutine(list_of_shapes):
|
||||||
self._element_to_source[el_h].update(set(self._sources_of_piece[iPiece]))
|
self._element_to_source[el_h].update(set(self._sources_of_piece[iPiece]))
|
||||||
else:
|
else:
|
||||||
self._element_to_source[el_h] = set(self._sources_of_piece[iPiece])
|
self._element_to_source[el_h] = set(self._sources_of_piece[iPiece])
|
||||||
|
|
||||||
def indexOfPiece(self, piece_shape):
|
def indexOfPiece(self, piece_shape):
|
||||||
"indexOfPiece(piece_shape): returns index of piece_shape in list of pieces"
|
"indexOfPiece(piece_shape): returns index of piece_shape in list of pieces"
|
||||||
return self._piece_to_index[HashableShape(piece_shape)]
|
return self._piece_to_index[HashableShape(piece_shape)]
|
||||||
def indexOfSource(self, source_shape):
|
def indexOfSource(self, source_shape):
|
||||||
"indexOfSource(source_shape): returns index of source_shape in list of arguments"
|
"indexOfSource(source_shape): returns index of source_shape in list of arguments"
|
||||||
return self._source_to_index[HashableShape(source_shape)]
|
return self._source_to_index[HashableShape(source_shape)]
|
||||||
|
|
||||||
def piecesFromSource(self, source_shape):
|
def piecesFromSource(self, source_shape):
|
||||||
"""piecesFromSource(source_shape): returns list of pieces (shapes) that came from
|
"""piecesFromSource(source_shape): returns list of pieces (shapes) that came from
|
||||||
given source shape.
|
given source shape.
|
||||||
|
|
||||||
Note: aggregate pieces (e.g. wire, shell, compound) always have only one source - the
|
Note: aggregate pieces (e.g. wire, shell, compound) always have only one source - the
|
||||||
shape they came directly from. Only after executing splitAggregates and
|
shape they came directly from. Only after executing splitAggregates and
|
||||||
explodeCompounds the source lists become completely populated."""
|
explodeCompounds the source lists become completely populated."""
|
||||||
|
|
||||||
ilist = self._pieces_of_source[self.indexOfSource(source_shape)]
|
ilist = self._pieces_of_source[self.indexOfSource(source_shape)]
|
||||||
return [self.pieces[i] for i in ilist]
|
return [self.pieces[i] for i in ilist]
|
||||||
|
|
||||||
def sourcesOfPiece(self, piece_shape):
|
def sourcesOfPiece(self, piece_shape):
|
||||||
"""sourcesOfPiece(piece_shape): returns list of source shapes given piece came from.
|
"""sourcesOfPiece(piece_shape): returns list of source shapes given piece came from.
|
||||||
|
|
||||||
Note: aggregate pieces (e.g. wire, shell, compound) always have only one source - the
|
Note: aggregate pieces (e.g. wire, shell, compound) always have only one source - the
|
||||||
shape they came directly from. Only after executing splitAggregates and
|
shape they came directly from. Only after executing splitAggregates and
|
||||||
explodeCompounds the source lists become completely populated."""
|
explodeCompounds the source lists become completely populated."""
|
||||||
|
|
||||||
ilist = self._sources_of_piece[self.indexOfPiece(piece_shape)]
|
ilist = self._sources_of_piece[self.indexOfPiece(piece_shape)]
|
||||||
return [self.source_shapes[i] for i in ilist]
|
return [self.source_shapes[i] for i in ilist]
|
||||||
|
|
||||||
def largestOverlapCount(self):
|
def largestOverlapCount(self):
|
||||||
"""largestOverlapCount(self): returns the largest overlap count. For example, if three
|
"""largestOverlapCount(self): returns the largest overlap count. For example, if three
|
||||||
spheres intersect and have some volume common to all three, largestOverlapCount
|
spheres intersect and have some volume common to all three, largestOverlapCount
|
||||||
returns 3.
|
returns 3.
|
||||||
|
|
||||||
Note: the return value may be incorrect if some of the pieces are wires/shells/
|
Note: the return value may be incorrect if some of the pieces are wires/shells/
|
||||||
compsolids/compounds. Please use explodeCompounds and splitAggregates before using this function."""
|
compsolids/compounds. Please use explodeCompounds and splitAggregates before using this function."""
|
||||||
|
|
||||||
return max([len(ilist) for ilist in self._sources_of_piece])
|
return max([len(ilist) for ilist in self._sources_of_piece])
|
||||||
|
|
||||||
def splitAggregates(self, pieces_to_split = None):
|
def splitAggregates(self, pieces_to_split = None):
|
||||||
"""splitAggregates(pieces_to_split = None): splits aggregate shapes (wires, shells,
|
"""splitAggregates(pieces_to_split = None): splits aggregate shapes (wires, shells,
|
||||||
compsolids) in pieces of GF result as cut by intersections. Also splits aggregates
|
compsolids) in pieces of GF result as cut by intersections. Also splits aggregates
|
||||||
inside compounds. After running this, 'self' is replaced with new data, where the
|
inside compounds. After running this, 'self' is replaced with new data, where the
|
||||||
pieces_to_split are split.
|
pieces_to_split are split.
|
||||||
|
|
||||||
'pieces_to_split': list of shapes (from self.pieces), that are to be processed. If
|
'pieces_to_split': list of shapes (from self.pieces), that are to be processed. If
|
||||||
None, all pieces will be split if possible.
|
None, all pieces will be split if possible.
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
* this routine is very important to functioning of Connect on shells and wires.
|
* this routine is very important to functioning of Connect on shells and wires.
|
||||||
* Warning: convoluted and slow."""
|
* Warning: convoluted and slow."""
|
||||||
|
|
||||||
if pieces_to_split is None:
|
if pieces_to_split is None:
|
||||||
pieces_to_split = self.pieces
|
pieces_to_split = self.pieces
|
||||||
pieces_to_split = [HashableShape(piece) for piece in pieces_to_split]
|
pieces_to_split = [HashableShape(piece) for piece in pieces_to_split]
|
||||||
pieces_to_split = set(pieces_to_split)
|
pieces_to_split = set(pieces_to_split)
|
||||||
|
|
||||||
self.parse_elements()
|
self.parse_elements()
|
||||||
new_data = GeneralFuseReturnBuilder(self.source_shapes)
|
new_data = GeneralFuseReturnBuilder(self.source_shapes)
|
||||||
changed = False
|
changed = False
|
||||||
|
|
||||||
#split pieces that are not compounds....
|
#split pieces that are not compounds....
|
||||||
for iPiece in range(len(self.pieces)):
|
for iPiece in range(len(self.pieces)):
|
||||||
piece = self.pieces[iPiece]
|
piece = self.pieces[iPiece]
|
||||||
|
|
||||||
if HashableShape(piece) in pieces_to_split:
|
if HashableShape(piece) in pieces_to_split:
|
||||||
new_pieces = self.makeSplitPieces(piece)
|
new_pieces = self.makeSplitPieces(piece)
|
||||||
changed = changed or len(new_pieces)>1
|
changed = changed or len(new_pieces)>1
|
||||||
for new_piece in new_pieces:
|
for new_piece in new_pieces:
|
||||||
new_data.addPiece(new_piece, self._sources_of_piece[iPiece])
|
new_data.addPiece(new_piece, self._sources_of_piece[iPiece])
|
||||||
else:
|
else:
|
||||||
new_data.addPiece(piece, self._sources_of_piece[iPiece])
|
new_data.addPiece(piece, self._sources_of_piece[iPiece])
|
||||||
|
|
||||||
#split pieces inside compounds
|
#split pieces inside compounds
|
||||||
#prepare index of existing pieces.
|
#prepare index of existing pieces.
|
||||||
existing_pieces = new_data._piece_to_index.copy()
|
existing_pieces = new_data._piece_to_index.copy()
|
||||||
|
@ -248,20 +248,20 @@ def myCustomFusionRoutine(list_of_shapes):
|
||||||
if ret is not None:
|
if ret is not None:
|
||||||
changed = True
|
changed = True
|
||||||
new_data.replacePiece(i_new_piece, ret)
|
new_data.replacePiece(i_new_piece, ret)
|
||||||
|
|
||||||
if len(new_data.pieces) > len(self.pieces) or changed:
|
if len(new_data.pieces) > len(self.pieces) or changed:
|
||||||
self.gfa_return = new_data.getGFReturn()
|
self.gfa_return = new_data.getGFReturn()
|
||||||
self.parse()
|
self.parse()
|
||||||
#else:
|
#else:
|
||||||
#print "Nothing was split"
|
#print "Nothing was split"
|
||||||
|
|
||||||
def _splitInCompound(self, compound, existing_pieces):
|
def _splitInCompound(self, compound, existing_pieces):
|
||||||
"""Splits aggregates inside compound. Returns None if nothing is split, otherwise
|
"""Splits aggregates inside compound. Returns None if nothing is split, otherwise
|
||||||
returns compound.
|
returns compound.
|
||||||
existing_pieces is a dict. Key is deep hash. Value is tuple (int, shape). It is
|
existing_pieces is a dict. Key is deep hash. Value is tuple (int, shape). It is
|
||||||
used to search for if this split piece was already generated, and re-use the old
|
used to search for if this split piece was already generated, and re-use the old
|
||||||
one."""
|
one."""
|
||||||
|
|
||||||
changed = False
|
changed = False
|
||||||
new_children = []
|
new_children = []
|
||||||
for piece in compound.childShapes():
|
for piece in compound.childShapes():
|
||||||
|
@ -274,7 +274,7 @@ def myCustomFusionRoutine(list_of_shapes):
|
||||||
changed = True
|
changed = True
|
||||||
else:
|
else:
|
||||||
new_pieces = self.makeSplitPieces(piece)
|
new_pieces = self.makeSplitPieces(piece)
|
||||||
changed = changed or len(new_pieces)>1
|
changed = changed or len(new_pieces)>1
|
||||||
for new_piece in new_pieces:
|
for new_piece in new_pieces:
|
||||||
hash = HashableShape_Deep(new_piece)
|
hash = HashableShape_Deep(new_piece)
|
||||||
dummy,ex_piece = existing_pieces.get(hash, (None, None))
|
dummy,ex_piece = existing_pieces.get(hash, (None, None))
|
||||||
|
@ -289,14 +289,14 @@ def myCustomFusionRoutine(list_of_shapes):
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def makeSplitPieces(self, shape):
|
def makeSplitPieces(self, shape):
|
||||||
"""makeSplitPieces(self, shape): splits a shell, wire or compsolid into pieces where
|
"""makeSplitPieces(self, shape): splits a shell, wire or compsolid into pieces where
|
||||||
it intersects with other shapes.
|
it intersects with other shapes.
|
||||||
|
|
||||||
Returns list of split pieces. If no splits were done, returns list containing the
|
Returns list of split pieces. If no splits were done, returns list containing the
|
||||||
original shape."""
|
original shape."""
|
||||||
|
|
||||||
if shape.ShapeType == "Wire":
|
if shape.ShapeType == "Wire":
|
||||||
bit_extractor = lambda(sh): sh.Edges
|
bit_extractor = lambda(sh): sh.Edges
|
||||||
joint_extractor = lambda(sh): sh.Vertexes
|
joint_extractor = lambda(sh): sh.Vertexes
|
||||||
|
@ -309,7 +309,7 @@ def myCustomFusionRoutine(list_of_shapes):
|
||||||
else:
|
else:
|
||||||
#can't split the shape
|
#can't split the shape
|
||||||
return [shape]
|
return [shape]
|
||||||
|
|
||||||
# for each joint, test if all bits it's connected to are from same number of sources. If not, this is a joint for splitting
|
# for each joint, test if all bits it's connected to are from same number of sources. If not, this is a joint for splitting
|
||||||
# FIXME: this is slow, and maybe can be optimized
|
# FIXME: this is slow, and maybe can be optimized
|
||||||
splits = []
|
splits = []
|
||||||
|
@ -331,7 +331,7 @@ def myCustomFusionRoutine(list_of_shapes):
|
||||||
return [shape]
|
return [shape]
|
||||||
|
|
||||||
from . import ShapeMerge
|
from . import ShapeMerge
|
||||||
|
|
||||||
new_pieces = ShapeMerge.mergeShapes(bit_extractor(shape), split_connections= splits, bool_compsolid= True).childShapes()
|
new_pieces = ShapeMerge.mergeShapes(bit_extractor(shape), split_connections= splits, bool_compsolid= True).childShapes()
|
||||||
if len(new_pieces) == 1:
|
if len(new_pieces) == 1:
|
||||||
#shape was not split (split points found, but the shape remained in one piece).
|
#shape was not split (split points found, but the shape remained in one piece).
|
||||||
|
@ -339,10 +339,10 @@ def myCustomFusionRoutine(list_of_shapes):
|
||||||
return new_pieces
|
return new_pieces
|
||||||
|
|
||||||
def explodeCompounds(self):
|
def explodeCompounds(self):
|
||||||
"""explodeCompounds(): if any of self.pieces is a compound, the compound is exploded.
|
"""explodeCompounds(): if any of self.pieces is a compound, the compound is exploded.
|
||||||
After running this, 'self' is filled with new data, where pieces are updated to
|
After running this, 'self' is filled with new data, where pieces are updated to
|
||||||
contain the stuff extracted from compounds."""
|
contain the stuff extracted from compounds."""
|
||||||
|
|
||||||
has_compounds = False
|
has_compounds = False
|
||||||
for piece in self.pieces:
|
for piece in self.pieces:
|
||||||
if piece.ShapeType == "Compound":
|
if piece.ShapeType == "Compound":
|
||||||
|
@ -351,10 +351,10 @@ def myCustomFusionRoutine(list_of_shapes):
|
||||||
return
|
return
|
||||||
|
|
||||||
from .Utils import compoundLeaves
|
from .Utils import compoundLeaves
|
||||||
|
|
||||||
new_data = GeneralFuseReturnBuilder(self.source_shapes)
|
new_data = GeneralFuseReturnBuilder(self.source_shapes)
|
||||||
new_data.hasher_class = HashableShape #deep hashing not needed here.
|
new_data.hasher_class = HashableShape #deep hashing not needed here.
|
||||||
|
|
||||||
for iPiece in range(len(self.pieces)):
|
for iPiece in range(len(self.pieces)):
|
||||||
piece = self.pieces[iPiece]
|
piece = self.pieces[iPiece]
|
||||||
if piece.ShapeType == "Compound":
|
if piece.ShapeType == "Compound":
|
||||||
|
@ -372,30 +372,30 @@ class GeneralFuseReturnBuilder(FrozenClass):
|
||||||
def __define_attributes(self):
|
def __define_attributes(self):
|
||||||
self.pieces = []
|
self.pieces = []
|
||||||
self._piece_to_index = {} # key = hasher_class(shape). Value = (index_into_self_dot_pieces, shape). Note that GeneralFuseResult uses this item directly.
|
self._piece_to_index = {} # key = hasher_class(shape). Value = (index_into_self_dot_pieces, shape). Note that GeneralFuseResult uses this item directly.
|
||||||
|
|
||||||
self._pieces_from_source = [] #list of list of ints
|
self._pieces_from_source = [] #list of list of ints
|
||||||
self.source_shapes = []
|
self.source_shapes = []
|
||||||
|
|
||||||
self.hasher_class = HashableShape_Deep
|
self.hasher_class = HashableShape_Deep
|
||||||
|
|
||||||
self._freeze()
|
self._freeze()
|
||||||
|
|
||||||
def __init__(self, source_shapes):
|
def __init__(self, source_shapes):
|
||||||
self.__define_attributes()
|
self.__define_attributes()
|
||||||
self.source_shapes = source_shapes
|
self.source_shapes = source_shapes
|
||||||
self._pieces_from_source = [[] for i in range(len(source_shapes))]
|
self._pieces_from_source = [[] for i in range(len(source_shapes))]
|
||||||
|
|
||||||
def addPiece(self, piece_shape, source_shape_index_list):
|
def addPiece(self, piece_shape, source_shape_index_list):
|
||||||
"""addPiece(piece_shape, source_shape_index_list): adds a piece. If the piece
|
"""addPiece(piece_shape, source_shape_index_list): adds a piece. If the piece
|
||||||
already exists, returns False, and only updates source<->piece map."""
|
already exists, returns False, and only updates source<->piece map."""
|
||||||
|
|
||||||
ret = False
|
ret = False
|
||||||
i_piece_existing = None
|
i_piece_existing = None
|
||||||
hash = None
|
hash = None
|
||||||
if piece_shape.ShapeType != "Compound": # do not catch duplicate compounds
|
if piece_shape.ShapeType != "Compound": # do not catch duplicate compounds
|
||||||
hash = self.hasher_class(piece_shape)
|
hash = self.hasher_class(piece_shape)
|
||||||
i_piece_existing, dummy = self._piece_to_index.get(hash, (None, None))
|
i_piece_existing, dummy = self._piece_to_index.get(hash, (None, None))
|
||||||
|
|
||||||
if i_piece_existing is None:
|
if i_piece_existing is None:
|
||||||
#adding
|
#adding
|
||||||
self.pieces.append(piece_shape)
|
self.pieces.append(piece_shape)
|
||||||
|
@ -410,11 +410,11 @@ class GeneralFuseReturnBuilder(FrozenClass):
|
||||||
if not i_piece_existing in self._pieces_from_source[iSource]:
|
if not i_piece_existing in self._pieces_from_source[iSource]:
|
||||||
self._pieces_from_source[iSource].append(i_piece_existing)
|
self._pieces_from_source[iSource].append(i_piece_existing)
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def replacePiece(self, piece_index, new_shape):
|
def replacePiece(self, piece_index, new_shape):
|
||||||
assert(self.pieces[piece_index].ShapeType == "Compound")
|
assert(self.pieces[piece_index].ShapeType == "Compound")
|
||||||
assert(new_shape.ShapeType == "Compound")
|
assert(new_shape.ShapeType == "Compound")
|
||||||
self.pieces[piece_index] = new_shape
|
self.pieces[piece_index] = new_shape
|
||||||
|
|
||||||
def getGFReturn(self):
|
def getGFReturn(self):
|
||||||
return (Part.Compound(self.pieces), [[self.pieces[iPiece] for iPiece in ilist] for ilist in self._pieces_from_source])
|
return (Part.Compound(self.pieces), [[self.pieces[iPiece] for iPiece in ilist] for ilist in self._pieces_from_source])
|
||||||
|
|
|
@ -36,9 +36,9 @@ 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."""
|
"""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
|
#first, check if shapes can be compared by size
|
||||||
ShapeMerge.dimensionOfShapes(list_of_shapes)
|
ShapeMerge.dimensionOfShapes(list_of_shapes)
|
||||||
|
|
||||||
rel_precision = 1e-8
|
rel_precision = 1e-8
|
||||||
|
|
||||||
#find it!
|
#find it!
|
||||||
max_size = -1e100 # max size encountered so far
|
max_size = -1e100 # max size encountered so far
|
||||||
count_max = 0 # number of shapes with size equal to max_size
|
count_max = 0 # number of shapes with size equal to max_size
|
||||||
|
@ -56,40 +56,40 @@ def shapeOfMaxSize(list_of_shapes):
|
||||||
return shape_max
|
return shape_max
|
||||||
|
|
||||||
def connect(list_of_shapes, tolerance = 0.0):
|
def connect(list_of_shapes, tolerance = 0.0):
|
||||||
"""connect(list_of_shapes, tolerance = 0.0): connects solids (walled objects), shells and
|
"""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.
|
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
|
Compounds in list_of_shapes are automatically exploded, so self-intersecting compounds
|
||||||
are valid for connect."""
|
are valid for connect."""
|
||||||
|
|
||||||
# explode all compounds before GFA.
|
# explode all compounds before GFA.
|
||||||
new_list_of_shapes = []
|
new_list_of_shapes = []
|
||||||
for sh in list_of_shapes:
|
for sh in list_of_shapes:
|
||||||
new_list_of_shapes.extend( compoundLeaves(sh) )
|
new_list_of_shapes.extend( compoundLeaves(sh) )
|
||||||
list_of_shapes = new_list_of_shapes
|
list_of_shapes = new_list_of_shapes
|
||||||
|
|
||||||
#test if shapes are compatible for connecting
|
#test if shapes are compatible for connecting
|
||||||
dim = ShapeMerge.dimensionOfShapes(list_of_shapes)
|
dim = ShapeMerge.dimensionOfShapes(list_of_shapes)
|
||||||
if dim == 0:
|
if dim == 0:
|
||||||
raise TypeError("Cannot connect vertices!")
|
raise TypeError("Cannot connect vertices!")
|
||||||
|
|
||||||
if len(list_of_shapes) < 2:
|
if len(list_of_shapes) < 2:
|
||||||
return Part.makeCompound(list_of_shapes)
|
return Part.makeCompound(list_of_shapes)
|
||||||
|
|
||||||
if not generalFuseIsAvailable(): #fallback to legacy
|
if not generalFuseIsAvailable(): #fallback to legacy
|
||||||
result = list_of_shapes[0]
|
result = list_of_shapes[0]
|
||||||
for i in range(1, len(list_of_shapes)):
|
for i in range(1, len(list_of_shapes)):
|
||||||
result = connect_legacy(result, list_of_shapes[i], tolerance)
|
result = connect_legacy(result, list_of_shapes[i], tolerance)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
pieces, map = list_of_shapes[0].generalFuse(list_of_shapes[1:], tolerance)
|
pieces, map = list_of_shapes[0].generalFuse(list_of_shapes[1:], tolerance)
|
||||||
ao = GeneralFuseResult(list_of_shapes, (pieces, map))
|
ao = GeneralFuseResult(list_of_shapes, (pieces, map))
|
||||||
ao.splitAggregates()
|
ao.splitAggregates()
|
||||||
#print len(ao.pieces)," pieces total"
|
#print len(ao.pieces)," pieces total"
|
||||||
|
|
||||||
keepers = []
|
keepers = []
|
||||||
all_danglers = [] # debug
|
all_danglers = [] # debug
|
||||||
|
|
||||||
#add all biggest dangling pieces
|
#add all biggest dangling pieces
|
||||||
for src in ao.source_shapes:
|
for src in ao.source_shapes:
|
||||||
danglers = [piece for piece in ao.piecesFromSource(src) if len(ao.sourcesOfPiece(piece)) == 1]
|
danglers = [piece for piece in ao.piecesFromSource(src) if len(ao.sourcesOfPiece(piece)) == 1]
|
||||||
|
@ -103,24 +103,24 @@ def connect(list_of_shapes, tolerance = 0.0):
|
||||||
for ii in range(2, ao.largestOverlapCount()+1):
|
for ii in range(2, ao.largestOverlapCount()+1):
|
||||||
list_ii_pieces = [piece for piece in ao.pieces if len(ao.sourcesOfPiece(piece)) == ii]
|
list_ii_pieces = [piece for piece in ao.pieces if len(ao.sourcesOfPiece(piece)) == ii]
|
||||||
keepers_2_add = []
|
keepers_2_add = []
|
||||||
for piece in list_ii_pieces:
|
for piece in list_ii_pieces:
|
||||||
if ShapeMerge.isConnected(piece, touch_test_list):
|
if ShapeMerge.isConnected(piece, touch_test_list):
|
||||||
keepers_2_add.append(piece)
|
keepers_2_add.append(piece)
|
||||||
if len(keepers_2_add) == 0:
|
if len(keepers_2_add) == 0:
|
||||||
break
|
break
|
||||||
keepers.extend(keepers_2_add)
|
keepers.extend(keepers_2_add)
|
||||||
touch_test_list = Part.Compound(keepers_2_add)
|
touch_test_list = Part.Compound(keepers_2_add)
|
||||||
|
|
||||||
|
|
||||||
#merge, and we are done!
|
#merge, and we are done!
|
||||||
#print len(keepers)," pieces to keep"
|
#print len(keepers)," pieces to keep"
|
||||||
return ShapeMerge.mergeShapes(keepers)
|
return ShapeMerge.mergeShapes(keepers)
|
||||||
|
|
||||||
def connect_legacy(shape1, shape2, tolerance = 0.0):
|
def connect_legacy(shape1, shape2, tolerance = 0.0):
|
||||||
"""connect_legacy(shape1, shape2, tolerance = 0.0): alternative implementation of
|
"""connect_legacy(shape1, shape2, tolerance = 0.0): alternative implementation of
|
||||||
connect, without use of generalFuse. Slow. Provided for backwards compatibility, and
|
connect, without use of generalFuse. Slow. Provided for backwards compatibility, and
|
||||||
for older OCC."""
|
for older OCC."""
|
||||||
|
|
||||||
if tolerance>0.0:
|
if tolerance>0.0:
|
||||||
import FreeCAD as App
|
import FreeCAD as App
|
||||||
App.Console.PrintWarning("connect_legacy does not support tolerance (yet).\n")
|
App.Console.PrintWarning("connect_legacy does not support tolerance (yet).\n")
|
||||||
|
@ -134,8 +134,8 @@ def connect_legacy(shape1, shape2, tolerance = 0.0):
|
||||||
# (TODO)
|
# (TODO)
|
||||||
|
|
||||||
def embed_legacy(shape_base, shape_tool, tolerance = 0.0):
|
def embed_legacy(shape_base, shape_tool, tolerance = 0.0):
|
||||||
"""embed_legacy(shape_base, shape_tool, tolerance = 0.0): alternative implementation of
|
"""embed_legacy(shape_base, shape_tool, tolerance = 0.0): alternative implementation of
|
||||||
embed, without use of generalFuse. Slow. Provided for backwards compatibility, and
|
embed, without use of generalFuse. Slow. Provided for backwards compatibility, and
|
||||||
for older OCC."""
|
for older OCC."""
|
||||||
if tolerance>0.0:
|
if tolerance>0.0:
|
||||||
import FreeCAD as App
|
import FreeCAD as App
|
||||||
|
@ -152,12 +152,12 @@ def embed_legacy(shape_base, shape_tool, tolerance = 0.0):
|
||||||
elif dim == 1:
|
elif dim == 1:
|
||||||
result = ShapeMerge.mergeShapes(result.Edges)
|
result = ShapeMerge.mergeShapes(result.Edges)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def cutout_legacy(shape_base, shape_tool, tolerance = 0.0):
|
def cutout_legacy(shape_base, shape_tool, tolerance = 0.0):
|
||||||
"""cutout_legacy(shape_base, shape_tool, tolerance = 0.0): alternative implementation of
|
"""cutout_legacy(shape_base, shape_tool, tolerance = 0.0): alternative implementation of
|
||||||
cutout, without use of generalFuse. Slow. Provided for backwards compatibility, and
|
cutout, without use of generalFuse. Slow. Provided for backwards compatibility, and
|
||||||
for older OCC."""
|
for older OCC."""
|
||||||
|
|
||||||
if tolerance>0.0:
|
if tolerance>0.0:
|
||||||
import FreeCAD as App
|
import FreeCAD as App
|
||||||
App.Console.PrintWarning("cutout_legacy does not support tolerance (yet).\n")
|
App.Console.PrintWarning("cutout_legacy does not support tolerance (yet).\n")
|
||||||
|
@ -168,8 +168,7 @@ def cutout_legacy(shape_base, shape_tool, tolerance = 0.0):
|
||||||
for sh in shapes_base:
|
for sh in shapes_base:
|
||||||
result.append(cutout(sh, shape_tool))
|
result.append(cutout(sh, shape_tool))
|
||||||
return Part.Compound(result)
|
return Part.Compound(result)
|
||||||
|
|
||||||
shape_base = shapes_base[0]
|
shape_base = shapes_base[0]
|
||||||
pieces = compoundLeaves(shape_base.cut(shape_tool))
|
pieces = compoundLeaves(shape_base.cut(shape_tool))
|
||||||
return shapeOfMaxSize(pieces)
|
return shapeOfMaxSize(pieces)
|
||||||
|
|
||||||
|
|
|
@ -32,12 +32,12 @@ from .Utils import HashableShape
|
||||||
def findSharedElements(shape_list, element_extractor):
|
def findSharedElements(shape_list, element_extractor):
|
||||||
if len(shape_list) < 2:
|
if len(shape_list) < 2:
|
||||||
raise ValueError("findSharedElements: at least two shapes must be provided (have {num})".format(num= len(shape_list)))
|
raise ValueError("findSharedElements: at least two shapes must be provided (have {num})".format(num= len(shape_list)))
|
||||||
|
|
||||||
all_elements = [] #list of sets of HashableShapes
|
all_elements = [] #list of sets of HashableShapes
|
||||||
for shape in shape_list:
|
for shape in shape_list:
|
||||||
all_elements.append(set(
|
all_elements.append(set(
|
||||||
[HashableShape(sh) for sh in element_extractor(shape)]
|
[HashableShape(sh) for sh in element_extractor(shape)]
|
||||||
))
|
))
|
||||||
shared_elements = None
|
shared_elements = None
|
||||||
for elements in all_elements:
|
for elements in all_elements:
|
||||||
if shared_elements is None:
|
if shared_elements is None:
|
||||||
|
@ -56,22 +56,22 @@ def isConnected(shape1, shape2, shape_dim = -1):
|
||||||
return len(findSharedElements([shape1, shape2], extractor))>0
|
return len(findSharedElements([shape1, shape2], extractor))>0
|
||||||
|
|
||||||
def splitIntoGroupsBySharing(list_of_shapes, element_extractor, split_connections = []):
|
def splitIntoGroupsBySharing(list_of_shapes, element_extractor, split_connections = []):
|
||||||
"""splitIntoGroupsBySharing(list_of_shapes, element_type, split_connections = []): find,
|
"""splitIntoGroupsBySharing(list_of_shapes, element_type, split_connections = []): find,
|
||||||
which shapes in list_of_shapes are connected into groups by sharing elements.
|
which shapes in list_of_shapes are connected into groups by sharing elements.
|
||||||
|
|
||||||
element_extractor: function that takes shape as input, and returns list of shapes.
|
element_extractor: function that takes shape as input, and returns list of shapes.
|
||||||
|
|
||||||
split_connections: list of shapes to exclude when testing for connections. Use to
|
split_connections: list of shapes to exclude when testing for connections. Use to
|
||||||
split groups on purpose.
|
split groups on purpose.
|
||||||
|
|
||||||
return: list of lists of shapes. Top-level list is list of groups; bottom level lists
|
return: list of lists of shapes. Top-level list is list of groups; bottom level lists
|
||||||
enumerate shapes of a group."""
|
enumerate shapes of a group."""
|
||||||
|
|
||||||
split_connections = set([HashableShape(element) for element in split_connections])
|
split_connections = set([HashableShape(element) for element in split_connections])
|
||||||
|
|
||||||
groups = [] #list of tuples (shapes,elements). Shapes is a list of plain shapes. Elements is a set of HashableShapes - all elements of shapes in the group, excluding split_connections.
|
groups = [] #list of tuples (shapes,elements). Shapes is a list of plain shapes. Elements is a set of HashableShapes - all elements of shapes in the group, excluding split_connections.
|
||||||
|
|
||||||
# add shapes to the list of groups, one by one. If not connected to existing groups,
|
# add shapes to the list of groups, one by one. If not connected to existing groups,
|
||||||
# new group is created. If connected, shape is added to groups, and the groups are joined.
|
# new group is created. If connected, shape is added to groups, and the groups are joined.
|
||||||
for shape in list_of_shapes:
|
for shape in list_of_shapes:
|
||||||
shape_elements = set([HashableShape(element) for element in element_extractor(shape)])
|
shape_elements = set([HashableShape(element) for element in element_extractor(shape)])
|
||||||
|
@ -90,19 +90,19 @@ def splitIntoGroupsBySharing(list_of_shapes, element_extractor, split_connection
|
||||||
#shape bridges a gap between some groups. Join them into one.
|
#shape bridges a gap between some groups. Join them into one.
|
||||||
#rebuilding list of groups. First, add the new "supergroup", then add the rest
|
#rebuilding list of groups. First, add the new "supergroup", then add the rest
|
||||||
groups_new = []
|
groups_new = []
|
||||||
|
|
||||||
supergroup = (list(),set())
|
supergroup = (list(),set())
|
||||||
for iGroup in connected_to:
|
for iGroup in connected_to:
|
||||||
supergroup[0].extend( groups[iGroup][0] )# merge lists of shapes
|
supergroup[0].extend( groups[iGroup][0] )# merge lists of shapes
|
||||||
supergroup[1].update( groups[iGroup][1] )# merge lists of elements
|
supergroup[1].update( groups[iGroup][1] )# merge lists of elements
|
||||||
groups_new.append(supergroup)
|
groups_new.append(supergroup)
|
||||||
|
|
||||||
for iGroup in range(len(groups)):
|
for iGroup in range(len(groups)):
|
||||||
if not iGroup in connected_to: #fixme: inefficient!
|
if not iGroup in connected_to: #fixme: inefficient!
|
||||||
groups_new.append(groups[iGroup])
|
groups_new.append(groups[iGroup])
|
||||||
groups = groups_new
|
groups = groups_new
|
||||||
connected_to = [0]
|
connected_to = [0]
|
||||||
|
|
||||||
# add shape to the group it is connected to (if to many, the groups should have been unified by the above code snippet)
|
# add shape to the group it is connected to (if to many, the groups should have been unified by the above code snippet)
|
||||||
if len(connected_to) > 0:
|
if len(connected_to) > 0:
|
||||||
iGroup = connected_to[0]
|
iGroup = connected_to[0]
|
||||||
|
@ -111,16 +111,16 @@ def splitIntoGroupsBySharing(list_of_shapes, element_extractor, split_connection
|
||||||
else:
|
else:
|
||||||
newgroup = ([shape], shape_elements)
|
newgroup = ([shape], shape_elements)
|
||||||
groups.append(newgroup)
|
groups.append(newgroup)
|
||||||
|
|
||||||
# done. Discard unnecessary data and return result.
|
# done. Discard unnecessary data and return result.
|
||||||
return [shapes for shapes,elements in groups]
|
return [shapes for shapes,elements in groups]
|
||||||
|
|
||||||
def mergeSolids(list_of_solids_compsolids, flag_single = False, split_connections = [], bool_compsolid = False):
|
def mergeSolids(list_of_solids_compsolids, flag_single = False, split_connections = [], bool_compsolid = False):
|
||||||
"""mergeSolids(list_of_solids, flag_single = False): merges touching solids that share
|
"""mergeSolids(list_of_solids, flag_single = False): merges touching solids that share
|
||||||
faces. If flag_single is True, it is assumed that all solids touch, and output is a
|
faces. If flag_single is True, it is assumed that all solids touch, and output is a
|
||||||
single solid. If flag_single is False, the output is a compound containing all
|
single solid. If flag_single is False, the output is a compound containing all
|
||||||
resulting solids.
|
resulting solids.
|
||||||
|
|
||||||
Note. CompSolids are treated as lists of solids - i.e., merged into solids."""
|
Note. CompSolids are treated as lists of solids - i.e., merged into solids."""
|
||||||
|
|
||||||
solids = []
|
solids = []
|
||||||
|
@ -128,7 +128,7 @@ def mergeSolids(list_of_solids_compsolids, flag_single = False, split_connection
|
||||||
solids.extend(sh.Solids)
|
solids.extend(sh.Solids)
|
||||||
if flag_single:
|
if flag_single:
|
||||||
cs = Part.CompSolid(solids)
|
cs = Part.CompSolid(solids)
|
||||||
return cs if bool_compsolid else Part.makeSolid(cs)
|
return cs if bool_compsolid else Part.makeSolid(cs)
|
||||||
else:
|
else:
|
||||||
if len(solids)==0:
|
if len(solids)==0:
|
||||||
return Part.Compound([])
|
return Part.Compound([])
|
||||||
|
@ -148,7 +148,7 @@ def mergeShells(list_of_faces_shells, flag_single = False, split_connections = [
|
||||||
else:
|
else:
|
||||||
groups = splitIntoGroupsBySharing(faces, lambda(sh): sh.Edges, split_connections)
|
groups = splitIntoGroupsBySharing(faces, lambda(sh): sh.Edges, split_connections)
|
||||||
return Part.makeCompound([Part.Shell(group) for group in groups])
|
return Part.makeCompound([Part.Shell(group) for group in groups])
|
||||||
|
|
||||||
def mergeWires(list_of_edges_wires, flag_single = False, split_connections = []):
|
def mergeWires(list_of_edges_wires, flag_single = False, split_connections = []):
|
||||||
edges = []
|
edges = []
|
||||||
for sh in list_of_edges_wires:
|
for sh in list_of_edges_wires:
|
||||||
|
@ -158,30 +158,30 @@ def mergeWires(list_of_edges_wires, flag_single = False, split_connections = [])
|
||||||
else:
|
else:
|
||||||
groups = splitIntoGroupsBySharing(edges, lambda(sh): sh.Vertexes, split_connections)
|
groups = splitIntoGroupsBySharing(edges, lambda(sh): sh.Vertexes, split_connections)
|
||||||
return Part.makeCompound([Part.Wire(Part.getSortedClusters(group)[0]) for group in groups])
|
return Part.makeCompound([Part.Wire(Part.getSortedClusters(group)[0]) for group in groups])
|
||||||
|
|
||||||
def mergeVertices(list_of_vertices, flag_single = False, split_connections = []):
|
def mergeVertices(list_of_vertices, flag_single = False, split_connections = []):
|
||||||
# no comprehensive support, just following the footprint of other mergeXXX()
|
# no comprehensive support, just following the footprint of other mergeXXX()
|
||||||
return Part.makeCompound(removeDuplicates(list_of_vertices))
|
return Part.makeCompound(removeDuplicates(list_of_vertices))
|
||||||
|
|
||||||
def mergeShapes(list_of_shapes, flag_single = False, split_connections = [], bool_compsolid = False):
|
def mergeShapes(list_of_shapes, flag_single = False, split_connections = [], bool_compsolid = False):
|
||||||
"""mergeShapes(list_of_shapes, flag_single = False, split_connections = [], bool_compsolid = False):
|
"""mergeShapes(list_of_shapes, flag_single = False, split_connections = [], bool_compsolid = False):
|
||||||
merges list of edges/wires into wires, faces/shells into shells, solids/compsolids
|
merges list of edges/wires into wires, faces/shells into shells, solids/compsolids
|
||||||
into solids or compsolids.
|
into solids or compsolids.
|
||||||
|
|
||||||
list_of_shapes: shapes to merge. Shapes must share elements in order to be merged.
|
list_of_shapes: shapes to merge. Shapes must share elements in order to be merged.
|
||||||
|
|
||||||
flag_single: assume all shapes in list are connected. If False, return is a compound.
|
flag_single: assume all shapes in list are connected. If False, return is a compound.
|
||||||
If True, return is the single piece (e.g. a shell).
|
If True, return is the single piece (e.g. a shell).
|
||||||
|
|
||||||
split_connections: list of shapes that are excluded when searching for connections.
|
split_connections: list of shapes that are excluded when searching for connections.
|
||||||
This can be used for example to split a wire in two by supplying vertices where to
|
This can be used for example to split a wire in two by supplying vertices where to
|
||||||
split. If flag_single is True, this argument is ignored.
|
split. If flag_single is True, this argument is ignored.
|
||||||
|
|
||||||
bool_compsolid: determines behavior when dealing with solids/compsolids. If True,
|
bool_compsolid: determines behavior when dealing with solids/compsolids. If True,
|
||||||
result is compsolid/compound of compsolids. If False, all touching solids and
|
result is compsolid/compound of compsolids. If False, all touching solids and
|
||||||
compsolids are unified into single solids. If not merging solids/compsolids, this
|
compsolids are unified into single solids. If not merging solids/compsolids, this
|
||||||
argument is ignored."""
|
argument is ignored."""
|
||||||
|
|
||||||
if len(list_of_shapes)==0:
|
if len(list_of_shapes)==0:
|
||||||
return Part.Compound([])
|
return Part.Compound([])
|
||||||
args = [list_of_shapes, flag_single, split_connections]
|
args = [list_of_shapes, flag_single, split_connections]
|
||||||
|
@ -209,11 +209,11 @@ def removeDuplicates(list_of_shapes):
|
||||||
new_list.append(sh)
|
new_list.append(sh)
|
||||||
hashes.add(hash)
|
hashes.add(hash)
|
||||||
return new_list
|
return new_list
|
||||||
|
|
||||||
def dimensionOfShapes(list_of_shapes):
|
def dimensionOfShapes(list_of_shapes):
|
||||||
"""dimensionOfShapes(list_of_shapes): returns dimension (0D, 1D, 2D, or 3D) of shapes
|
"""dimensionOfShapes(list_of_shapes): returns dimension (0D, 1D, 2D, or 3D) of shapes
|
||||||
in the list. If dimension of shapes varies, TypeError is raised."""
|
in the list. If dimension of shapes varies, TypeError is raised."""
|
||||||
|
|
||||||
dimensions = [["Vertex"], ["Edge","Wire"], ["Face","Shell"], ["Solid","CompSolid"]]
|
dimensions = [["Vertex"], ["Edge","Wire"], ["Face","Shell"], ["Solid","CompSolid"]]
|
||||||
dim = -1
|
dim = -1
|
||||||
for sh in list_of_shapes:
|
for sh in list_of_shapes:
|
||||||
|
@ -224,4 +224,4 @@ def dimensionOfShapes(list_of_shapes):
|
||||||
dim = iDim
|
dim = iDim
|
||||||
if iDim != dim:
|
if iDim != dim:
|
||||||
raise TypeError("Shapes are of different dimensions ({t1} and {t2}), and cannot be merged or compared.".format(t1= list_of_shapes[0].ShapeType, t2= sht))
|
raise TypeError("Shapes are of different dimensions ({t1} and {t2}), and cannot be merged or compared.".format(t1= list_of_shapes[0].ShapeType, t2= sht))
|
||||||
return dim
|
return dim
|
||||||
|
|
|
@ -33,16 +33,16 @@ from . import Utils
|
||||||
import FreeCAD
|
import FreeCAD
|
||||||
|
|
||||||
def booleanFragments(list_of_shapes, mode, tolerance = 0.0):
|
def booleanFragments(list_of_shapes, mode, tolerance = 0.0):
|
||||||
"""booleanFragments(list_of_shapes, mode, tolerance = 0.0): functional part of
|
"""booleanFragments(list_of_shapes, mode, tolerance = 0.0): functional part of
|
||||||
BooleanFragments feature. It's just result of generalFuse plus a bit of
|
BooleanFragments feature. It's just result of generalFuse plus a bit of
|
||||||
post-processing.
|
post-processing.
|
||||||
|
|
||||||
mode is a string. It can be "Standard", "Split" or "CompSolid".
|
mode is a string. It can be "Standard", "Split" or "CompSolid".
|
||||||
"Standard" - return generalFuse as is.
|
"Standard" - return generalFuse as is.
|
||||||
"Split" - wires and shells will be split at intersections.
|
"Split" - wires and shells will be split at intersections.
|
||||||
"CompSolid" - solids will be extracted from result of generelFuse, and compsolids will
|
"CompSolid" - solids will be extracted from result of generelFuse, and compsolids will
|
||||||
be made from them; all other stuff is discarded."""
|
be made from them; all other stuff is discarded."""
|
||||||
|
|
||||||
pieces, map = list_of_shapes[0].generalFuse(list_of_shapes[1:], tolerance)
|
pieces, map = list_of_shapes[0].generalFuse(list_of_shapes[1:], tolerance)
|
||||||
if mode == "Standard":
|
if mode == "Standard":
|
||||||
return pieces
|
return pieces
|
||||||
|
@ -61,15 +61,15 @@ def booleanFragments(list_of_shapes, mode, tolerance = 0.0):
|
||||||
raise ValueError("Unknown mode: {mode}".format(mode= mode))
|
raise ValueError("Unknown mode: {mode}".format(mode= mode))
|
||||||
|
|
||||||
def slice(base_shape, tool_shapes, mode, tolerance = 0.0):
|
def slice(base_shape, tool_shapes, mode, tolerance = 0.0):
|
||||||
"""slice(base_shape, tool_shapes, mode, tolerance = 0.0): functional part of
|
"""slice(base_shape, tool_shapes, mode, tolerance = 0.0): functional part of
|
||||||
Slice feature. Splits base_shape into pieces based on intersections with tool_shapes.
|
Slice feature. Splits base_shape into pieces based on intersections with tool_shapes.
|
||||||
|
|
||||||
mode is a string. It can be "Standard", "Split" or "CompSolid".
|
mode is a string. It can be "Standard", "Split" or "CompSolid".
|
||||||
"Standard" - return like generalFuse: edges, faces and solids are split, but wires,
|
"Standard" - return like generalFuse: edges, faces and solids are split, but wires,
|
||||||
shells, compsolids get extra segments but remain in one piece.
|
shells, compsolids get extra segments but remain in one piece.
|
||||||
"Split" - wires and shells will be split at intersections, too.
|
"Split" - wires and shells will be split at intersections, too.
|
||||||
"CompSolid" - slice a solid and glue it back together to make a compsolid"""
|
"CompSolid" - slice a solid and glue it back together to make a compsolid"""
|
||||||
|
|
||||||
shapes = [base_shape] + [Part.Compound([tool_shape]) for tool_shape in tool_shapes] # hack: putting tools into compounds will prevent contamination of result with pieces of tools
|
shapes = [base_shape] + [Part.Compound([tool_shape]) for tool_shape in tool_shapes] # hack: putting tools into compounds will prevent contamination of result with pieces of tools
|
||||||
if len(shapes) < 2:
|
if len(shapes) < 2:
|
||||||
raise ValueError("No slicing objects supplied!")
|
raise ValueError("No slicing objects supplied!")
|
||||||
|
|
|
@ -123,7 +123,7 @@ class ViewProviderBooleanFragments:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def cmdCreateBooleanFragmentsFeature(name, mode):
|
def cmdCreateBooleanFragmentsFeature(name, mode):
|
||||||
"""cmdCreateBooleanFragmentsFeature(name, mode): implementation of GUI command to create
|
"""cmdCreateBooleanFragmentsFeature(name, mode): implementation of GUI command to create
|
||||||
BooleanFragments feature (GFA). Mode can be "Standard", "Split", or "CompSolid"."""
|
BooleanFragments feature (GFA). Mode can be "Standard", "Split", or "CompSolid"."""
|
||||||
sel = FreeCADGui.Selection.getSelectionEx()
|
sel = FreeCADGui.Selection.getSelectionEx()
|
||||||
FreeCAD.ActiveDocument.openTransaction("Create Boolean Fragments")
|
FreeCAD.ActiveDocument.openTransaction("Create Boolean Fragments")
|
||||||
|
@ -250,7 +250,7 @@ class ViewProviderSlice:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def cmdCreateSliceFeature(name, mode):
|
def cmdCreateSliceFeature(name, mode):
|
||||||
"""cmdCreateSliceFeature(name, mode): implementation of GUI command to create
|
"""cmdCreateSliceFeature(name, mode): implementation of GUI command to create
|
||||||
Slice feature. Mode can be "Standard", "Split", or "CompSolid"."""
|
Slice feature. Mode can be "Standard", "Split", or "CompSolid"."""
|
||||||
sel = FreeCADGui.Selection.getSelectionEx()
|
sel = FreeCADGui.Selection.getSelectionEx()
|
||||||
FreeCAD.ActiveDocument.openTransaction("Create Slice")
|
FreeCAD.ActiveDocument.openTransaction("Create Slice")
|
||||||
|
@ -378,7 +378,7 @@ class ViewProviderXOR:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def cmdCreateXORFeature(name):
|
def cmdCreateXORFeature(name):
|
||||||
"""cmdCreateXORFeature(name): implementation of GUI command to create
|
"""cmdCreateXORFeature(name): implementation of GUI command to create
|
||||||
XOR feature (GFA). Mode can be "Standard", "Split", or "CompSolid"."""
|
XOR feature (GFA). Mode can be "Standard", "Split", or "CompSolid"."""
|
||||||
sel = FreeCADGui.Selection.getSelectionEx()
|
sel = FreeCADGui.Selection.getSelectionEx()
|
||||||
FreeCAD.ActiveDocument.openTransaction("Create Boolean XOR")
|
FreeCAD.ActiveDocument.openTransaction("Create Boolean XOR")
|
||||||
|
|
|
@ -40,7 +40,7 @@ class HashableShape(object):
|
||||||
|
|
||||||
class HashableShape_Deep(object):
|
class HashableShape_Deep(object):
|
||||||
"""Similar to HashableShape, except that the things the shape is composed of are compared.
|
"""Similar to HashableShape, except that the things the shape is composed of are compared.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
>>> wire2 = Part.Wire(wire1.childShapes())
|
>>> wire2 = Part.Wire(wire1.childShapes())
|
||||||
>>> wire2.isSame(wire1)
|
>>> wire2.isSame(wire1)
|
||||||
|
@ -48,29 +48,29 @@ Example:
|
||||||
>>> HashableShape_Deep(wire2) == HashableShape_Deep(wire1)
|
>>> HashableShape_Deep(wire2) == HashableShape_Deep(wire1)
|
||||||
True # <--- made of same set of elements
|
True # <--- made of same set of elements
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, shape):
|
def __init__(self, shape):
|
||||||
self.Shape = shape
|
self.Shape = shape
|
||||||
self.hash = 0
|
self.hash = 0
|
||||||
for el in shape.childShapes():
|
for el in shape.childShapes():
|
||||||
self.hash = self.hash ^ el.hashCode()
|
self.hash = self.hash ^ el.hashCode()
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
# avoiding extensive comparison for now. Just doing a few extra tests should reduce the already-low chances of false-positives
|
# avoiding extensive comparison for now. Just doing a few extra tests should reduce the already-low chances of false-positives
|
||||||
if self.hash == other.hash:
|
if self.hash == other.hash:
|
||||||
if len(self.Shape.childShapes()) == len(other.Shape.childShapes()):
|
if len(self.Shape.childShapes()) == len(other.Shape.childShapes()):
|
||||||
if self.Shape.ShapeType == other.Shape.ShapeType:
|
if self.Shape.ShapeType == other.Shape.ShapeType:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def __hash__(self):
|
def __hash__(self):
|
||||||
return self.hash
|
return self.hash
|
||||||
|
|
||||||
def compoundLeaves(shape_or_compound):
|
def compoundLeaves(shape_or_compound):
|
||||||
"""compoundLeaves(shape_or_compound): extracts all non-compound shapes from a nested compound.
|
"""compoundLeaves(shape_or_compound): extracts all non-compound shapes from a nested compound.
|
||||||
Note: shape_or_compound may be a non-compound; then, it is the only thing in the
|
Note: shape_or_compound may be a non-compound; then, it is the only thing in the
|
||||||
returned list."""
|
returned list."""
|
||||||
|
|
||||||
if shape_or_compound.ShapeType == "Compound":
|
if shape_or_compound.ShapeType == "Compound":
|
||||||
leaves = []
|
leaves = []
|
||||||
for child in shape_or_compound.childShapes():
|
for child in shape_or_compound.childShapes():
|
||||||
|
@ -78,23 +78,23 @@ def compoundLeaves(shape_or_compound):
|
||||||
return leaves
|
return leaves
|
||||||
else:
|
else:
|
||||||
return [shape_or_compound]
|
return [shape_or_compound]
|
||||||
|
|
||||||
def upgradeToAggregateIfNeeded(list_of_shapes, types = None):
|
def upgradeToAggregateIfNeeded(list_of_shapes, types = None):
|
||||||
"""upgradeToAggregateIfNeeded(list_of_shapes, types = None): upgrades non-aggregate type
|
"""upgradeToAggregateIfNeeded(list_of_shapes, types = None): upgrades non-aggregate type
|
||||||
shapes to aggregate-type shapes if the list has a mix of aggregate and non-aggregate
|
shapes to aggregate-type shapes if the list has a mix of aggregate and non-aggregate
|
||||||
type shapes. Returns the new list. Recursively traverses into compounds.
|
type shapes. Returns the new list. Recursively traverses into compounds.
|
||||||
|
|
||||||
aggregate shape types are Wire, Shell, CompSolid
|
aggregate shape types are Wire, Shell, CompSolid
|
||||||
non-aggregate shape types are Vertex, Edge, Face, Solid
|
non-aggregate shape types are Vertex, Edge, Face, Solid
|
||||||
Compounds are something special: they are recursively traversed to upgrade the
|
Compounds are something special: they are recursively traversed to upgrade the
|
||||||
contained shapes.
|
contained shapes.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
list_of_shapes contains only faces -> nothing happens
|
list_of_shapes contains only faces -> nothing happens
|
||||||
list_of_shapes contains faces and shells -> faces are converted to shells
|
list_of_shapes contains faces and shells -> faces are converted to shells
|
||||||
|
|
||||||
'types' argument is needed for recursive traversal. Do not supply."""
|
'types' argument is needed for recursive traversal. Do not supply."""
|
||||||
|
|
||||||
import Part
|
import Part
|
||||||
if types is None:
|
if types is None:
|
||||||
types = set()
|
types = set()
|
||||||
|
@ -127,4 +127,3 @@ class FrozenClass(object):
|
||||||
|
|
||||||
def _unfreeze(self):
|
def _unfreeze(self):
|
||||||
self.__isfrozen = False
|
self.__isfrozen = False
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
__title__ = "BOPTools package"
|
__title__ = "BOPTools package"
|
||||||
__url__ = "http://www.freecadweb.org"
|
__url__ = "http://www.freecadweb.org"
|
||||||
__doc__ = """BOPTools Package (part of FreeCAD). Routines that power Connect, Embed, Cutout,
|
__doc__ = """BOPTools Package (part of FreeCAD). Routines that power Connect, Embed, Cutout,
|
||||||
BooleanFragments, Slice and XOR features of Part Workbench. Useful for other custom
|
BooleanFragments, Slice and XOR features of Part Workbench. Useful for other custom
|
||||||
BOP-like operations"""
|
BOP-like operations"""
|
||||||
|
|
||||||
## @package BOPTools
|
## @package BOPTools
|
||||||
|
@ -57,14 +57,14 @@ def reloadAll():
|
||||||
import FreeCAD
|
import FreeCAD
|
||||||
if FreeCAD.GuiUp:
|
if FreeCAD.GuiUp:
|
||||||
addCommands()
|
addCommands()
|
||||||
|
|
||||||
def addCommands():
|
def addCommands():
|
||||||
"addCommands(): add all GUI commands of BOPTools package to FreeCAD command manager."
|
"addCommands(): add all GUI commands of BOPTools package to FreeCAD command manager."
|
||||||
JoinFeatures.addCommands()
|
JoinFeatures.addCommands()
|
||||||
SplitFeatures.addCommands()
|
SplitFeatures.addCommands()
|
||||||
|
|
||||||
def generalFuseIsAvailable():
|
def generalFuseIsAvailable():
|
||||||
"""generalFuseIsAvailable(): returns True if FreeCAD's Part.Shape.generalFuse is functional.
|
"""generalFuseIsAvailable(): returns True if FreeCAD's Part.Shape.generalFuse is functional.
|
||||||
True if Part.OCC_VERSION >= 6.9.0."""
|
True if Part.OCC_VERSION >= 6.9.0."""
|
||||||
import Part
|
import Part
|
||||||
if not hasattr(Part, "OCC_VERSION"):
|
if not hasattr(Part, "OCC_VERSION"):
|
||||||
|
|
Loading…
Reference in New Issue
Block a user