Commit e39a708f authored by Andrey Filippov's avatar Andrey Filippov

added top level boundin box generation to x3d models

parent aa4f4508
...@@ -145,6 +145,45 @@ def verticesToCheck(solid): ...@@ -145,6 +145,45 @@ def verticesToCheck(solid):
lv.append((v.X,v.Y,v.Z)) lv.append((v.X,v.Y,v.Z))
return lv return lv
def getBoundBox(freecadObjects):
"""
Calculate BoundBox for all shapes in the document
@param freecadObjects - list of FreeCAD objects or all solids/shells
@return FreeCAD.BoundBox object for the whole document
"""
shells =[]
for o in freecadObjects:
if hasattr(o, "Shape"):
shape=o.Shape
for shell in shape.Shells: # solids and open shells
shells.append(shell)
elif hasattr(o, "BoundBox"):
shells.append(o)
bBox=None
for shell in shells:
thisBBox = shell.BoundBox
if not bBox:
bBox = FreeCAD.BoundBox(thisBBox.XMin,thisBBox.YMin,thisBBox.ZMin,
thisBBox.XMax,thisBBox.YMax,thisBBox.ZMax)
else:
bBox = FreeCAD.BoundBox(min(thisBBox.XMin,bBox.XMin),min(thisBBox.YMin,bBox.YMin),min(thisBBox.ZMin,bBox.ZMin),
max(thisBBox.XMax,bBox.XMax),max(thisBBox.YMax,bBox.YMax),max(thisBBox.ZMax,bBox.ZMax))
return bBox
def bBoxToX3d(bBox):
"""
Convert FreeCAD BoundBox to X3D representation (center, size)
@param bBox - FreeCAD BoundBox object
@return dictionary of {'center':(xc,yc,zc), size:(xs,ys,zs)}
"""
return {'center':((bBox.XMax + bBox.XMin)/2,(bBox.YMax + bBox.YMin)/2,(bBox.ZMax + bBox.ZMin)/2),
'size': ( bBox.XMax - bBox.XMin, bBox.YMax - bBox.YMin, bBox.ZMax - bBox.ZMin)}
#FreeCAD.BoundBox(0,0,0,0,0,0)
def create_file_info_nogui(shape, fname=""): def create_file_info_nogui(shape, fname=""):
""" """
A no-Gui version of the create_file_info, rather useless now as the color A no-Gui version of the create_file_info, rather useless now as the color
...@@ -191,7 +230,7 @@ def create_file_info(freecadObjects, fname=""): ...@@ -191,7 +230,7 @@ def create_file_info(freecadObjects, fname=""):
""" """
Collect information about each part/solid to be used for comparison between Collect information about each part/solid to be used for comparison between
assembly objects and parts assembly objects and parts
@param shape - FreeCAD Shape, containing one or more solids @param freecadObjects - list of FreeCAD objects
@param fname - source file path @param fname - source file path
@return a pair of a list of info for each solid (as a dictionary) and a @return a pair of a list of info for each solid (as a dictionary) and a
list of solids in the shape list of solids in the shape
...@@ -395,7 +434,7 @@ def get_info_files(dir_list = None): ...@@ -395,7 +434,7 @@ def get_info_files(dir_list = None):
info_dict[name] = pickle.load(open(info_path, "rb")) info_dict[name] = pickle.load(open(info_path, "rb"))
progress_bar.next() progress_bar.next()
progress_bar.stop() progress_bar.stop()
FreeCAD.Console.PrintMessage("get_info_files() - loaded"); # FreeCAD.Console.PrintMessage("get_info_files() - loaded"); #Rare FreeCAD crash?
#Put largest element of multi-solid parts as the [0] index. #Put largest element of multi-solid parts as the [0] index.
for k in info_dict: for k in info_dict:
...@@ -409,7 +448,7 @@ def get_info_files(dir_list = None): ...@@ -409,7 +448,7 @@ def get_info_files(dir_list = None):
print ("Largest solid is number %d"%(mi)) print ("Largest solid is number %d"%(mi))
if mi>0: if mi>0:
o.insert(0,o.pop(mi)) o.insert(0,o.pop(mi))
FreeCAD.Console.PrintMessage("get_info_files() - largest made first"); # FreeCAD.Console.PrintMessage("get_info_files() - largest made first"); # rare FreeCAD crash?
return info_dict return info_dict
...@@ -849,7 +888,7 @@ def getShapeNode(vertices, faces, diffuseColor = None, main_color_index = 0, col ...@@ -849,7 +888,7 @@ def getShapeNode(vertices, faces, diffuseColor = None, main_color_index = 0, col
materialNode.set('diffuseColor', "%f %f %f" % tuple(diffuseColor[main_color_index * 3: main_color_index * 3 + 3])) materialNode.set('diffuseColor', "%f %f %f" % tuple(diffuseColor[main_color_index * 3: main_color_index * 3 + 3]))
return shapeNode return shapeNode
def exportX3D(objects, filepath, id="part", colorPerVertex=False): def exportX3D(objects, filepath, partName="", bbox = None, colorPerVertex=False):
""" """
Export given list of objects to a X3D file. Export given list of objects to a X3D file.
@param objects - a list of dictionaries in the following format: @param objects - a list of dictionaries in the following format:
...@@ -860,8 +899,11 @@ def exportX3D(objects, filepath, id="part", colorPerVertex=False): ...@@ -860,8 +899,11 @@ def exportX3D(objects, filepath, id="part", colorPerVertex=False):
} }
@param filepath - os path of the file to save X3D data @param filepath - os path of the file to save X3D data
@param id - id set for the X3D Group node wrapping all the objects in the file @param id - id set for the X3D Group node wrapping all the objects in the file
@param bbox - optional bound box as a dictionary {'center':(xc,yc,zc), size:(xs,ys,zs)}
@param colorPerVertex - True: specify color per erach vertex, False - for each face (reduces file size) @param colorPerVertex - True: specify color per erach vertex, False - for each face (reduces file size)
""" """
progress_bar = Base.ProgressIndicator() progress_bar = Base.ProgressIndicator()
progress_bar.start("Saving objects to X3D file %s ..."%(filepath), len(objects)) progress_bar.start("Saving objects to X3D file %s ..."%(filepath), len(objects))
...@@ -869,8 +911,21 @@ def exportX3D(objects, filepath, id="part", colorPerVertex=False): ...@@ -869,8 +911,21 @@ def exportX3D(objects, filepath, id="part", colorPerVertex=False):
x3dNode.set('profile', 'Interchange') x3dNode.set('profile', 'Interchange')
x3dNode.set('version', '3.3') x3dNode.set('version', '3.3')
sceneNode = et.SubElement(x3dNode, 'Scene') sceneNode = et.SubElement(x3dNode, 'Scene')
groupNode = et.SubElement(sceneNode, 'Group') transformNode =et.SubElement(sceneNode, 'Transform') # Empty transform to adjust center for offset models (use bboxCenter)
groupNode.set('id', id) transformNode.set('id', 'transformTop_'+partName)
transformNode.set('class', 'transformTop_'+partName)
# if bbox:
# transformNode.set('translation','%f %f %f'%(-bbox['center'][0],-bbox['center'][1],-bbox['center'][2]))
# else:
transformNode.set('translation','%f %f %f'%(0,0,0))
transformNode.set('rotation','%f %f %f %f'%(0,0,0,0))
groupNode = et.SubElement(transformNode, 'Group')
groupNode.set('id', 'groupTop_'+partName)
groupNode.set('class', 'groupTop_'+partName)
if bbox:
groupNode.set('bboxSize','%f %f %f'%bbox['size'])
groupNode.set('bboxCenter','%f %f %f'%bbox['center'])
for o in objects: for o in objects:
shapeNode = getShapeNode(o["points"], o["faces"], o["color"], o["main_color_index"], colorPerVertex) shapeNode = getShapeNode(o["points"], o["faces"], o["color"], o["main_color_index"], colorPerVertex)
...@@ -903,7 +958,8 @@ def prepareX3dExport(freecadObjects, fname=""): ...@@ -903,7 +958,8 @@ def prepareX3dExport(freecadObjects, fname=""):
if fname: if fname:
txt += " in "+fname txt += " in "+fname
progress_bar.start("Generating objects%s to export to X3D ..."%(txt), len(freecadObjects)) progress_bar.start("Generating objects%s to export to X3D ..."%(txt), len(freecadObjects))
xyzMin=None
xyzMax=None
for o in freecadObjects: for o in freecadObjects:
progress_bar.next() # True) # have to do it here as 'for' uses 'continue', True - enable ESC to abort progress_bar.next() # True) # have to do it here as 'for' uses 'continue', True - enable ESC to abort
if (not o.ViewObject) or (o.ViewObject.Visibility): if (not o.ViewObject) or (o.ViewObject.Visibility):
...@@ -983,8 +1039,10 @@ def generatePartsX3d(dir_list = [STEP_PARTS], colorPerVertex = COLOR_PER_VERTEX) ...@@ -983,8 +1039,10 @@ def generatePartsX3d(dir_list = [STEP_PARTS], colorPerVertex = COLOR_PER_VERTEX)
FreeCAD.loadFile(step_file) # os.path.join(ROOT_DIR,step_file)) FreeCAD.loadFile(step_file) # os.path.join(ROOT_DIR,step_file))
doc = FreeCAD.activeDocument() doc = FreeCAD.activeDocument()
doc.Label = partName doc.Label = partName
x3d_objects = prepareX3dExport(doc.Objects, step_file) # step_file needed just for progress bar x3d_objects = prepareX3dExport(doc.Objects, step_file) # step_file needed just for progress bar
exportX3D(x3d_objects, x3dFile, id="part_"+partName, colorPerVertex=colorPerVertex) bboxX3d= bBoxToX3d(getBoundBox(doc.Objects))
exportX3D(x3d_objects, x3dFile, partName = partName, bbox = bboxX3d, colorPerVertex=colorPerVertex)
FreeCAD.closeDocument(doc.Name) FreeCAD.closeDocument(doc.Name)
FreeCADGui.updateGui() FreeCADGui.updateGui()
numExported += 1 numExported += 1
...@@ -1075,7 +1133,7 @@ def generateAssemblyX3d(assembly_path, ...@@ -1075,7 +1133,7 @@ def generateAssemblyX3d(assembly_path,
start_time=time.time() start_time=time.time()
info_dict = get_info_files(dir_list) # Will (re-) build info files if missing info_dict = get_info_files(dir_list) # Will (re-) build info files if missing
FreeCAD.Console.PrintMessage("generateAssemblyX3d()"); # FreeCAD.Console.PrintMessage("generateAssemblyX3d()");
generatePartsX3d(dir_list = [STEP_PARTS], colorPerVertex = COLOR_PER_VERTEX) # Will only run if files are not there yet generatePartsX3d(dir_list = [STEP_PARTS], colorPerVertex = COLOR_PER_VERTEX) # Will only run if files are not there yet
FreeCAD.Console.PrintMessage("generatePartsX3d() Done"); FreeCAD.Console.PrintMessage("generatePartsX3d() Done");
if not components: if not components:
...@@ -1105,11 +1163,24 @@ def generateAssemblyX3d(assembly_path, ...@@ -1105,11 +1163,24 @@ def generateAssemblyX3d(assembly_path,
inlineNode.set('id', ass_with_suffix + '_config') inlineNode.set('id', ass_with_suffix + '_config')
inlineNode.set('url',ass_with_suffix + '_config'+ X3D_EXT) inlineNode.set('url',ass_with_suffix + '_config'+ X3D_EXT)
inlineNode.set('nameSpaceName', ass_with_suffix ) inlineNode.set('nameSpaceName', ass_with_suffix )
#empty Transform node to move/rotate the whole assembly now translation to -(bounding box center)
modelNode = et.SubElement(sceneNode, 'Transform') bboxX3d= bBoxToX3d(getBoundBox(components['solids']))
modelNode.set('id','transform_'+ass_with_suffix) transformNode = et.SubElement(sceneNode, 'Transform')
modelNode.set('translation','%f %f %f'%(0,0,0)) transformNode.set('id','transformTop_'+ass_with_suffix)
modelNode.set('rotation','%f %f %f %f'%(0,0,0,0)) transformNode.set('class','transformTop_'+ass_with_suffix)
# if bboxX3d:
# transformNode.set('translation','%f %f %f'%(-bboxX3d['center'][0],-bboxX3d['center'][1],-bboxX3d['center'][2]))
# else:
transformNode.set('translation','%f %f %f'%(0,0,0))
transformNode.set('rotation','%f %f %f %f'%(0,0,0,0))
#Group node to provide a bbox center/size of the whole assembly (and center the view)
modelNode = et.SubElement(transformNode, 'Group')
modelNode.set('id','groupTop_'+ass_with_suffix)
modelNode.set('class','groupTop_'+ass_with_suffix)
if bboxX3d:
modelNode.set('bboxSize','%f %f %f'%bboxX3d['size'])
modelNode.set('bboxCenter','%f %f %f'%bboxX3d['center'])
defined_parts = {} # for each defined part holds index (for ID generation) defined_parts = {} # for each defined part holds index (for ID generation)
#TODO: Reorder parts - smallest (by a product og gyration radii) first #TODO: Reorder parts - smallest (by a product og gyration radii) first
...@@ -1132,9 +1203,7 @@ def generateAssemblyX3d(assembly_path, ...@@ -1132,9 +1203,7 @@ def generateAssemblyX3d(assembly_path,
part_name = part + ASSEMBLY_SUFFIX part_name = part + ASSEMBLY_SUFFIX
transformation = transformations[part] transformation = transformations[part]
bbox=components['solids'][i].BoundBox bbox= bBoxToX3d(components['solids'][i].BoundBox)
bboxCenter=((bbox.XMax + bbox.XMin)/2,(bbox.YMax + bbox.YMin)/2,(bbox.ZMax + bbox.ZMin)/2)
bboxSize= ( bbox.XMax - bbox.XMin, bbox.YMax - bbox.YMin, bbox.ZMax - bbox.ZMin)
transform = matrix4ToX3D(transformation) transform = matrix4ToX3D(transformation)
rot=transform['rotation'] rot=transform['rotation']
...@@ -1155,8 +1224,9 @@ def generateAssemblyX3d(assembly_path, ...@@ -1155,8 +1224,9 @@ def generateAssemblyX3d(assembly_path,
groupNode = et.SubElement(transformNode, 'Group') groupNode = et.SubElement(transformNode, 'Group')
groupNode.set('id','group_'+part_name+":"+str(defined_parts[part])) groupNode.set('id','group_'+part_name+":"+str(defined_parts[part]))
groupNode.set('class','group_'+part_name) groupNode.set('class','group_'+part_name)
groupNode.set('bboxSize','%f %f %f'%bboxSize) groupNode.set('bboxSize','%f %f %f'%bbox['size'])
groupNode.set('bboxCenter','%f %f %f'%bboxCenter) groupNode.set('bboxCenter','%f %f %f'%bbox['center'])
if defined_parts[part]: if defined_parts[part]:
groupNode.set('USE', part_name) groupNode.set('USE', part_name)
...@@ -1742,7 +1812,7 @@ class X3dStepAssyDialog(QtGui.QWidget): ...@@ -1742,7 +1812,7 @@ class X3dStepAssyDialog(QtGui.QWidget):
def executeMacro(self): def executeMacro(self):
global COMPONENTS global COMPONENTS
COMPONENTS = None # Start with new ones COMPONENTS = None # Start with new ones
FreeCAD.Console.PrintMessage("Starting conversion...") # FreeCAD.Console.PrintMessage("Starting conversion...")
self.preRun() self.preRun()
if self.log_file: if self.log_file:
sys.stdout = open(self.log_file,"w") sys.stdout = open(self.log_file,"w")
...@@ -1839,6 +1909,7 @@ import x3d_step_assy ...@@ -1839,6 +1909,7 @@ import x3d_step_assy
reload (x3d_step_assy) reload (x3d_step_assy)
form = x3d_step_assy.X3dStepAssyDialog() form = x3d_step_assy.X3dStepAssyDialog()
form.show() form.show()
x3d_step_assy.generateAssemblyX3d("") #or with path
or just run conversion from the command line in Python console or just run conversion from the command line in Python console
x3d_step_assy.generateAssemblyX3d("") #or with path x3d_step_assy.generateAssemblyX3d("") #or with path
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment