Youen
1 year ago
5 changed files with 348 additions and 9 deletions
@ -0,0 +1,279 @@
|
||||
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() |
||||
|
||||
async def generate_2d_drawing(file_name): |
||||
doc = App.open(project_folder + '/' + file_name) |
||||
|
||||
page_name = doc.Name + '_Drawing' |
||||
|
||||
if doc.getObject(page_name) is not None: |
||||
print('2D drawing already exists - skipped') |
||||
return |
||||
|
||||
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.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.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.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 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 |
||||
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()) |
||||
|
@ -0,0 +1,16 @@
|
||||
#!/bin/bash |
||||
|
||||
set -e |
||||
|
||||
# Set the path to your FreeCAD executable here |
||||
FREECAD=~/dev/FreeCAD-asm3-Daily-Conda-Py3.10-20221128-glibc2.12-x86_64.AppImage |
||||
|
||||
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) |
||||
cd $SCRIPT_DIR/.. |
||||
|
||||
# Check script syntax before starting freecad |
||||
python3 -m py_compile tools/generate-2d-drawings.py |
||||
|
||||
# Start freecad to run the script. We must start freecad with GUI. We start it hidden in a virtual framebuffer (xvfb) so that it can run cleanly in the background. |
||||
xvfb-run $FREECAD tools/generate-2d-drawings.py |
||||
#$FREECAD tools/generate-2d-drawings.py |
@ -0,0 +1,38 @@
|
||||
import asyncio |
||||
from PySide.QtCore import QTimer |
||||
|
||||
class EventLoop: |
||||
loop = None |
||||
|
||||
def __init__(self): |
||||
self.loop = asyncio.new_event_loop() |
||||
|
||||
def create_task(self, coro): |
||||
self.loop.create_task(coro) |
||||
self.update() |
||||
|
||||
def update(self): |
||||
self.loop.stop() |
||||
self.loop.run_forever() |
||||
|
||||
async def wait(self, time_milliseconds): |
||||
#print("waiting " + str(time_milliseconds) + "ms...") |
||||
currentLoop = self |
||||
fut = self.loop.create_future() |
||||
def callback(): |
||||
#print("wait callback") |
||||
fut.set_result(True) |
||||
currentLoop.update() |
||||
QTimer.singleShot(time_milliseconds, callback) |
||||
await fut |
||||
#print("end wait") |
||||
|
||||
main_loop = None |
||||
|
||||
def get_main_loop(): |
||||
global main_loop |
||||
if main_loop is None: |
||||
#print("Creating main loop") |
||||
main_loop = EventLoop() |
||||
return main_loop |
||||
|
Loading…
Reference in new issue