forked from vhelio/vheliotech-freecad
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