import os import sys import asyncio import math sys.path.append(os.path.join(os.path.dirname(__file__), 'utils')) import vspt_freecad import vspt_coroutine verbose = False project_folder = os.getcwd() def apply_styles(page): modified = False dim_font_size = 3.0 for view in page.Views: if view.TypeId == 'TechDraw::DrawViewDimension': if view.ViewObject.Fontsize != dim_font_size: view.ViewObject.Fontsize = dim_font_size modified = True return modified async def generate_2d_drawing(file_name): doc = App.open(project_folder + '/' + file_name) modified = False page_name = doc.Name + '_Drawing' page = doc.getObject(page_name) if page is not None: print('2D drawing already exists - skipped') else: modified = True template_file_name = project_folder + '/lib/A4_Landscape_VSPT.svg' root_objects = [] main_object = None for obj in doc.Objects: if len(obj.Parents) == 0: root_objects.append(obj) if obj.Label == doc.Name: main_object = obj if main_object is None and len(root_objects) == 1: main_object = root_objects[0] if main_object is None: raise Exception("Can't find main object in file " + doc.FileName + " (found " + str(len(root_objects)) + " root object(s), none named like the document " + doc.Name + ")") code_obj = doc.getObjectsByLabel('Code_Tube_Draft') if len(code_obj) == 1: code_obj = code_obj[0] else: code_obj = None sources = [main_object] bound_box = main_object.Shape.BoundBox proj_size = [0, 0, 0] # size of the original part front view after projection at scale 1:1 if bound_box.XLength > bound_box.YLength: if bound_box.XLength > bound_box.ZLength: main_axis = 0 proj_size[0] = bound_box.XLength proj_size[1] = bound_box.ZLength proj_size[2] = bound_box.YLength else: main_axis = 2 proj_size[0] = bound_box.ZLength proj_size[1] = bound_box.XLength proj_size[2] = bound_box.YLength else: if bound_box.YLength > bound_box.ZLength: main_axis = 1 proj_size[0] = bound_box.YLength proj_size[1] = bound_box.ZLength proj_size[2] = bound_box.XLength else: main_axis = 2 proj_size[0] = bound_box.ZLength proj_size[1] = bound_box.XLength proj_size[2] = bound_box.YLength if verbose: print("Adding drawing page..."); page = doc.addObject('TechDraw::DrawPage', page_name) template = doc.addObject('TechDraw::DrawSVGTemplate', 'Template') template.Template = template_file_name page.Template = template if verbose: print("Computing best scale..."); scale_denominators = [4.0, 5.0, 6.0, 8.0, 10.0] scale_numerator = 1.0 scale_denominator = scale_denominators[0] proj_total_size = [proj_size[0] + proj_size[2], proj_size[1] + proj_size[2]] # projected size of all views (without spacing) at scale 1:1 spacingX = 20.0 spacingY = 50.0 maxSizeX = 280.0 maxSizeY = 160.0 for denom in scale_denominators: scale_denominator = denom if proj_total_size[0]*scale_numerator/denom + spacingX <= maxSizeX and proj_total_size[1]*scale_numerator/denom + spacingY <= maxSizeY: break if verbose: print("Adding projection group..."); projGroup = doc.addObject('TechDraw::DrawProjGroup', doc.Name + '_ProjGroup') page.addView(projGroup) projGroup.ScaleType = 'Custom' projGroup.Scale = scale_numerator/scale_denominator projGroup.spacingX = 20.0 projGroup.spacingY = 50.0 projGroup.Source = sources projGroup.addProjection('Front') if main_axis == 0: projGroup.Anchor.Direction = App.Vector(0,1,0) projGroup.Anchor.XDirection = App.Vector(-1,0,0) projGroup.Anchor.RotationVector = App.Vector(-1,0,0) elif main_axis == 1: projGroup.Anchor.Direction = App.Vector(1,0,0) projGroup.Anchor.XDirection = App.Vector(0,1,0) projGroup.Anchor.RotationVector = App.Vector(0,1,0) elif main_axis == 2: projGroup.Anchor.Direction = App.Vector(0,1,0) projGroup.Anchor.XDirection = App.Vector(0,0,1) projGroup.Anchor.RotationVector = App.Vector(0,0,1) projGroup.addProjection('Top') projGroup.addProjection('Left') projGroup.X = 130.0 projGroup.Y = 150.0 texts = page.Template.EditableTexts texts['SCALE'] = str(int(scale_numerator+0.5))+':'+str(int(scale_denominator+0.5)) try: texts['PM'] = main_object.Assembly_handbook_Material except: pass texts['PN'] = doc.Name texts['TITLELINE-1'] = doc.Name page.Template.EditableTexts = texts async def addDimensions(): for view in projGroup.Views: if verbose: print("View: " + view.Label + "...") edges = [] visibleEdges = view.getVisibleEdges() edgeIdx = 0 lowestEdgeName = '' lowestEdgePos = 1000000 while True: try: edge = view.getEdgeByIndex(edgeIdx) except: break edges.append(edge) if edge.BoundBox.YLength < 0.01 and edge.BoundBox.Center.y < lowestEdgePos: lowestEdgePos = edge.BoundBox.Center.y lowestEdgeName = 'Edge' + str(edgeIdx) edgeIdx = edgeIdx + 1 vertices = [] vertIdx = 0 while True: try: vert = view.getVertexByIndex(vertIdx) except: break vertices.append(vert) vertIdx = vertIdx + 1 def getFeatureName(edge): if edge.Curve.TypeId == 'Part::GeomCircle': vertIdx = 0 c = edge.BoundBox.Center closestDist = 100000000 closestVert = None for vert in vertices: dx = vert.X - c.x dy = vert.Y - c.y dist = math.sqrt(dx*dx + dy*dy) if dist < closestDist: closestDist = dist closestVert = vert vertIdx = vertIdx + 1 if closestVert is not None: return 'Vertex' + str(vertices.index(closestVert)) else: return '' else: return 'Edge'+str(edges.index(edge)) if verbose: print("Listing features...") features = [] for edge in edges: if (edge.Curve.TypeId == 'Part::GeomLine' and edge.BoundBox.XLength <= 0.01) or (edge.Curve.TypeId == 'Part::GeomCircle' and abs(edge.Curve.Radius * 2.0 - edge.BoundBox.XLength) < 0.001 and abs(edge.Curve.Radius * 2.0 - edge.BoundBox.YLength) < 0.001): featureName = getFeatureName(edge) if featureName == '': continue pos = edge.BoundBox.Center.x duplicate = False for otherFeature in features: if abs(otherFeature[0] - pos) < 0.1: duplicate = True break if not duplicate: features.append((pos, edge, featureName)) features.sort(key=lambda e: e[0]) def addDimension(edgeA, edgeB, posY): dim = doc.addObject('TechDraw::DrawViewDimension','Dimension') dim.Type = 'DistanceX' dim.FormatSpec = '%.1f' dim.References2D = [(view, (getFeatureName(edgeA), getFeatureName(edgeB)))] visibleEdgeA = visibleEdges[edges.index(edgeA)] visibleEdgeB = visibleEdges[edges.index(edgeB)] dim.X = (visibleEdgeA.BoundBox.Center.x + visibleEdgeB.BoundBox.Center.x) * 0.5 dim.Y = posY page.addView(dim) if edgeB.Curve.TypeId == 'Part::GeomCircle': if abs(edgeB.BoundBox.XLength - 6.5) > 0.01: dim = doc.addObject('TechDraw::DrawViewDimension','Dimension') dim.Type = 'Diameter' dim.FormatSpec = '%.1f' dim.References2D = [(view, ('Edge'+str(edges.index(edgeB)),))] dim.X = visibleEdgeB.BoundBox.Center.x + 6.0 dim.Y = -6.0 page.addView(dim) if abs(edgeB.BoundBox.Center.y) > 0.01 and lowestEdgeName != '': dim = doc.addObject('TechDraw::DrawViewDimension','Dimension') dim.Type = 'DistanceY' dim.FormatSpec = '%.1f' dim.References2D = [(view, (getFeatureName(edgeB),lowestEdgeName))] dim.X = visibleEdgeB.BoundBox.Center.x + 2.0 dim.Y = -6.0 page.addView(dim) if verbose: print("Adding dimensions...") if len(features) >= 2: if projGroup.Views.index(view) != 0: addDimension(features[0][1], features[len(features)-1][1], -25.0) if len(features) > 2: for featureIdx in range(0, len(features) - 1): if featureIdx == 0 or features[featureIdx][1].Curve.TypeId != 'Part::GeomLine': addDimension(features[featureIdx][1], features[featureIdx + 1][1], 15.0) if verbose: print("Adding secondary objects...") if code_obj is not None: projGroup.Source = projGroup.Source + [code_obj] page.recompute(True) await vspt_coroutine.get_main_loop().wait(1) await addDimensions() if apply_styles(page): modified = True if modified: if verbose: print("Saving...") page.recompute(True) page.ViewObject.Visibility = False # don't save the document with the page open or it will automatically reopen on load await vspt_coroutine.get_main_loop().wait(1) doc.save() if verbose: print("Closing...") vspt_freecad.close_all_docs() async def run(): try: folders = [ 'tubes' ] for folder in folders: files = os.listdir(project_folder + '/' + folder) for source_file in files: if not source_file.endswith('.FCStd'): continue source_path = folder + '/' + source_file print(source_path) await generate_2d_drawing(source_path) # exit FreeCAD vspt_freecad.close_all_docs() FreeCADGui.getMainWindow().close() except Exception as e: print(e) vspt_coroutine.get_main_loop().create_task(run())