# -*- coding: utf-8 -*- """SV2A geometry transfer. Run: abaqus cae noGUI=sv2a_abaqus.py Or use File > Run Script in Abaqus/CAE (Python 3 releases). Creates a unique output folder containing model.cae and import_report.json. Native solids/shells/wires where supported; otherwise orphan surface meshes. Surface meshes are not solid volume meshes or validated analysis meshes. Complete sections, materials, mesh, interactions, steps, BCs and loads in CAE. No solver job is submitted. Existing models are not cleared or overwritten. """ from abaqus import mdb, session from abaqusConstants import THREE_D, DEFORMABLE_BODY, ON, OFF, COUNTERCLOCKWISE, IMPRINT, TRI3 import part import sketch import assembly import mesh import json import math import os import tempfile DATA = json.loads('{"shapes": [{"id": "/ShapeGallery/assembly/housing", "type": "RectangularCuboid", "parameters": {"length": 120.0, "width": 60.0, "height": 35.0}, "transform": [[0.8660254037844387, -0.49999999999999994, 0.0, 200.0], [0.49999999999999994, 0.8660254037844387, 0.0, 100.0], [0.0, 0.0, 1.0, 0.0], [0.0, 0.0, 0.0, 1.0]], "localTransform": [[1.0, 0.0, 0.0, 0.0], [0.0, 1.0, 0.0, 0.0], [0.0, 0.0, 1.0, 0.0], [0.0, 0.0, 0.0, 1.0]], "placementSource": "standard TranslationRotationSequence", "file": "shapes.sysml", "line": 29, "clipped": false}, {"id": "/ShapeGallery/assembly/shaft", "type": "RightCircularCylinder", "parameters": {"radius": 15.0, "height": 90.0}, "transform": [[0.8660254037844387, -0.49999999999999994, 0.0, 236.96152422706632], [0.49999999999999994, 0.8660254037844387, 0.0, 155.98076211353316], [0.0, 0.0, 1.0, 35.0], [0.0, 0.0, 0.0, 1.0]], "localTransform": [[1.0, 0.0, 0.0, 60.0], [0.0, 1.0, 0.0, 30.0], [0.0, 0.0, 1.0, 35.0], [0.0, 0.0, 0.0, 1.0]], "placementSource": "standard TranslationRotationSequence", "file": "shapes.sysml", "line": 44, "clipped": false}, {"id": "/ShapeGallery/assembly/ball", "type": "Sphere", "parameters": {"radius": 25.0}, "transform": [[0.8660254037844387, -0.49999999999999994, 0.0, 236.96152422706632], [0.49999999999999994, 0.8660254037844387, 0.0, 155.98076211353316], [0.0, 0.0, 1.0, 155.0], [0.0, 0.0, 0.0, 1.0]], "localTransform": [[1.0, 0.0, 0.0, 60.0], [0.0, 1.0, 0.0, 30.0], [0.0, 0.0, 1.0, 155.0], [0.0, 0.0, 0.0, 1.0]], "placementSource": "standard TranslationRotationSequence", "file": "shapes.sysml", "line": 59, "clipped": false}, {"id": "/ShapeGallery/assembly/seal", "type": "Torus", "parameters": {"majorRadius": 35.0, "minorRadius": 5.0}, "transform": [[0.8660254037844387, -0.49999999999999994, 0.0, 236.96152422706632], [0.49999999999999994, 0.8660254037844387, 0.0, 155.98076211353316], [0.0, 0.0, 1.0, 110.0], [0.0, 0.0, 0.0, 1.0]], "localTransform": [[1.0, 0.0, 0.0, 60.0], [0.0, 1.0, 0.0, 30.0], [0.0, 0.0, 1.0, 110.0], [0.0, 0.0, 0.0, 1.0]], "placementSource": "standard TranslationRotationSequence", "file": "shapes.sysml", "line": 75, "clipped": false}, {"id": "/ShapeGallery/assembly/nose", "type": "RightCircularCone", "parameters": {"radius": 25.0, "height": 60.0}, "transform": [[0.8660254037844387, -0.49999999999999994, 0.0, 236.96152422706632], [0.49999999999999994, 0.8660254037844387, 0.0, 155.98076211353316], [0.0, 0.0, 1.0, 190.0], [0.0, 0.0, 0.0, 1.0]], "localTransform": [[1.0, 0.0, 0.0, 60.0], [0.0, 1.0, 0.0, 30.0], [0.0, 0.0, 1.0, 190.0], [0.0, 0.0, 0.0, 1.0]], "placementSource": "standard TranslationRotationSequence", "file": "shapes.sysml", "line": 92, "clipped": false}, {"id": "/ShapeGallery/assembly/wedge", "type": "RightTriangularPrism", "parameters": {"length": 50.0, "width": 35.0, "height": 60.0}, "transform": [[-0.4999999999999999, -0.8660254037844387, 0.0, 338.5640646055102], [0.8660254037844387, -0.4999999999999999, 0.0, 180.0], [0.0, 0.0, 1.0, 0.0], [0.0, 0.0, 0.0, 1.0]], "localTransform": [[6.123233995736766e-17, -1.0, 0.0, 160.0], [1.0, 6.123233995736766e-17, 0.0, 0.0], [0.0, 0.0, 1.0, 0.0], [0.0, 0.0, 0.0, 1.0]], "placementSource": "standard TranslationRotationSequence", "file": "shapes.sysml", "line": 109, "clipped": false}], "manifest": [{"id": "/ShapeGallery/assembly/housing", "type": "RectangularCuboid", "representation": "native geometry", "clipped": false, "worldTransform": [[0.8660254037844387, -0.49999999999999994, 0.0, 200.0], [0.49999999999999994, 0.8660254037844387, 0.0, 100.0], [0.0, 0.0, 1.0, 0.0], [0.0, 0.0, 0.0, 1.0]], "localTransform": [[1.0, 0.0, 0.0, 0.0], [0.0, 1.0, 0.0, 0.0], [0.0, 0.0, 1.0, 0.0], [0.0, 0.0, 0.0, 1.0]], "placementSource": "standard TranslationRotationSequence"}, {"id": "/ShapeGallery/assembly/shaft", "type": "RightCircularCylinder", "representation": "native geometry", "clipped": false, "worldTransform": [[0.8660254037844387, -0.49999999999999994, 0.0, 236.96152422706632], [0.49999999999999994, 0.8660254037844387, 0.0, 155.98076211353316], [0.0, 0.0, 1.0, 35.0], [0.0, 0.0, 0.0, 1.0]], "localTransform": [[1.0, 0.0, 0.0, 60.0], [0.0, 1.0, 0.0, 30.0], [0.0, 0.0, 1.0, 35.0], [0.0, 0.0, 0.0, 1.0]], "placementSource": "standard TranslationRotationSequence"}, {"id": "/ShapeGallery/assembly/ball", "type": "Sphere", "representation": "native geometry", "clipped": false, "worldTransform": [[0.8660254037844387, -0.49999999999999994, 0.0, 236.96152422706632], [0.49999999999999994, 0.8660254037844387, 0.0, 155.98076211353316], [0.0, 0.0, 1.0, 155.0], [0.0, 0.0, 0.0, 1.0]], "localTransform": [[1.0, 0.0, 0.0, 60.0], [0.0, 1.0, 0.0, 30.0], [0.0, 0.0, 1.0, 155.0], [0.0, 0.0, 0.0, 1.0]], "placementSource": "standard TranslationRotationSequence"}, {"id": "/ShapeGallery/assembly/seal", "type": "Torus", "representation": "native geometry", "clipped": false, "worldTransform": [[0.8660254037844387, -0.49999999999999994, 0.0, 236.96152422706632], [0.49999999999999994, 0.8660254037844387, 0.0, 155.98076211353316], [0.0, 0.0, 1.0, 110.0], [0.0, 0.0, 0.0, 1.0]], "localTransform": [[1.0, 0.0, 0.0, 60.0], [0.0, 1.0, 0.0, 30.0], [0.0, 0.0, 1.0, 110.0], [0.0, 0.0, 0.0, 1.0]], "placementSource": "standard TranslationRotationSequence"}, {"id": "/ShapeGallery/assembly/nose", "type": "RightCircularCone", "representation": "native geometry", "clipped": false, "worldTransform": [[0.8660254037844387, -0.49999999999999994, 0.0, 236.96152422706632], [0.49999999999999994, 0.8660254037844387, 0.0, 155.98076211353316], [0.0, 0.0, 1.0, 190.0], [0.0, 0.0, 0.0, 1.0]], "localTransform": [[1.0, 0.0, 0.0, 60.0], [0.0, 1.0, 0.0, 30.0], [0.0, 0.0, 1.0, 190.0], [0.0, 0.0, 0.0, 1.0]], "placementSource": "standard TranslationRotationSequence"}, {"id": "/ShapeGallery/assembly/wedge", "type": "RightTriangularPrism", "representation": "native geometry", "clipped": false, "worldTransform": [[-0.4999999999999999, -0.8660254037844387, 0.0, 338.5640646055102], [0.8660254037844387, -0.4999999999999999, 0.0, 180.0], [0.0, 0.0, 1.0, 0.0], [0.0, 0.0, 0.0, 1.0]], "localTransform": [[6.123233995736766e-17, -1.0, 0.0, 160.0], [1.0, 6.123233995736766e-17, 0.0, 0.0], [0.0, 0.0, 1.0, 0.0], [0.0, 0.0, 0.0, 1.0]], "placementSource": "standard TranslationRotationSequence"}], "selectedView": "ShapeGallery::assemblyView"}') def polyline(sketch_object, points): for a,b in zip(points, points[1:]+points[:1]): sketch_object.Line(point1=tuple(a), point2=tuple(b)) def native_part(model, name, shape): p=shape['parameters'];kind=shape['type'] part_object=model.Part(name=name, dimensionality=THREE_D, type=DEFORMABLE_BODY) if kind=='Line': part_object.WirePolyLine(points=(((0.,0.,0.),(p['length'],0.,0.)),),mergeType=IMPRINT,meshable=ON) return part_object,0 size=max([abs(float(v)) for v in p.values()]+[1.])*6 s=model.ConstrainedSketch(name=name+'_sketch',sheetSize=size) correction=0 if kind in ('RectangularCuboid','Rectangle'): s.rectangle(point1=(0.,0.),point2=(p['length'],p['width'])) elif kind in ('Triangle','RightTriangle','RightTriangularPrism'): if kind=='RightTriangularPrism':pts=[(0.,0.),(p['length'],0.),(p['length'],p['width'])] else:pts=[(-p['length']/2,0.),(p['length']/2,0.),(p['length']/2 if kind=='RightTriangle' else p['xoffset'],p['width'])] polyline(s,pts) elif kind in ('Circle','CircularDisc','RightCircularCylinder'): s.CircleByCenterPerimeter(center=(0.,0.),point1=(p['radius'],0.)) elif kind in ('Ellipse','Disc'): if abs(p['semiMajorAxis']-p['semiMinorAxis'])<1e-12: s.CircleByCenterPerimeter(center=(0.,0.),point1=(p['semiMajorAxis'],0.)) else:s.EllipseByCenterPerimeter(center=(0.,0.),axisPoint1=(p['semiMajorAxis'],0.),axisPoint2=(0.,p['semiMinorAxis'])) elif kind in ('RightCircularCone','Sphere','Torus','RectangularToroid'): s.ConstructionLine(point1=(0.,-size),point2=(0.,size)) if kind=='RightCircularCone':polyline(s,[(0.,0.),(p['radius'],0.),(0.,p['height'])]) elif kind=='Sphere': r=p['radius'];s.ArcByCenterEnds(center=(0.,0.),point1=(0.,-r),point2=(0.,r),direction=COUNTERCLOCKWISE) s.Line(point1=(0.,r),point2=(0.,-r)) elif kind=='Torus':s.CircleByCenterPerimeter(center=(p['majorRadius'],0.),point1=(p['majorRadius']+p['minorRadius'],0.)) else: r=p['revolutionRadius'];a=p['rectangleLength']/2;b=p['rectangleWidth']/2 s.rectangle(point1=(r-a,-b),point2=(r+a,b)) part_object.BaseSolidRevolve(sketch=s,angle=360.,flipRevolveDirection=OFF) correction=90 # Sketch revolve axis Y becomes the source local Z axis. del model.sketches[name+'_sketch'] return part_object,correction else:raise ValueError('Native construction not defined: '+kind) if kind in ('RectangularCuboid','RightTriangularPrism','RightCircularCylinder'): part_object.BaseSolidExtrude(sketch=s,depth=p['height']) elif kind in ('Circle','Ellipse'):part_object.BaseWire(sketch=s) else:part_object.BaseShell(sketch=s) del model.sketches[name+'_sketch'] return part_object,correction def surface_part(model,name,shape): data=shape['mesh'];obj=model.Part(name=name,dimensionality=THREE_D,type=DEFORMABLE_BODY) if not data['faces']: for a,b in data['edges']:obj.WirePolyLine(points=((tuple(data['vertices'][a]),tuple(data['vertices'][b])),),mergeType=IMPRINT,meshable=ON) else: nodes=[obj.Node(coordinates=tuple(point),label=i+1) for i,point in enumerate(data['vertices'])] for i,face in enumerate(data['faces']):obj.Element(nodes=tuple(nodes[k] for k in face),elemShape=TRI3,label=i+1) return obj,0 def rotate_matrix(assembly_object,name,matrix): # Robust matrix -> quaternion -> axis/angle, including rotations close to pi. r=matrix;trace=r[0][0]+r[1][1]+r[2][2] if trace>0: s=math.sqrt(trace+1.)*2;qw=.25*s;qx=(r[2][1]-r[1][2])/s;qy=(r[0][2]-r[2][0])/s;qz=(r[1][0]-r[0][1])/s else: i=max(range(3),key=lambda k:r[k][k]);j=(i+1)%3;k=(i+2)%3 s=math.sqrt(max(0.,1.+r[i][i]-r[j][j]-r[k][k]))*2 q=[0.,0.,0.];q[i]=.25*s;q[j]=(r[j][i]+r[i][j])/s;q[k]=(r[k][i]+r[i][k])/s qw=(r[k][j]-r[j][k])/s;qx,qy,qz=q length=math.sqrt(qx*qx+qy*qy+qz*qz) if length>1e-12: angle=math.degrees(2*math.atan2(length,qw)) assembly_object.rotate(instanceList=(name,),axisPoint=(0.,0.,0.),axisDirection=(qx/length,qy/length,qz/length),angle=angle) assembly_object.translate(instanceList=(name,),vector=tuple(r[i][3] for i in range(3))) def main(): output=tempfile.mkdtemp(prefix='sv2a_',dir=os.getcwd()) model_name=os.path.basename(output) while model_name in mdb.models:model_name+='x' model=mdb.Model(name=model_name);root=model.rootAssembly report={'model':model_name,'selected_view':DATA.get('selectedView'),'units':'mm','analysis_ready':False,'parts':DATA['manifest'],'failures':[], 'next_steps':['Review native versus orphan surface representations','Assign materials and sections; surface meshes need shell thickness','Mesh native parts; rebuild solid volume meshes where required','Define connections/contact, steps, boundary conditions, loads and outputs','Run datacheck before solving'], 'input_export':'After completing analysis setup: mdb.Job(name=job_name,model=model_name).writeInput(consistencyChecking=ON)'} for i,shape in enumerate(DATA['shapes']): name='SV2A_P%03d'%(i+1) try: if 'mesh' in shape:obj,correction=surface_part(model,name,shape) else:obj,correction=native_part(model,name,shape) instance_name=name+'-1';root.Instance(name=instance_name,part=obj,dependent=ON) if correction:root.rotate(instanceList=(instance_name,),axisPoint=(0.,0.,0.),axisDirection=(1.,0.,0.),angle=correction) rotate_matrix(root,instance_name,shape['transform']) report['parts'][i]['abaqus_part']=name except Exception as error:report['failures'].append({'id':shape['id'],'error':str(error)}) report_path=os.path.join(output,'import_report.json') with open(report_path,'w') as stream:json.dump(report,stream,indent=2) if report['failures']:raise RuntimeError('Import incomplete; no CAE file saved. Review '+report_path) root.regenerate() mdb.saveAs(pathName=os.path.join(output,'model.cae')) if 'Viewport: 1' in session.viewports: viewport=session.viewports['Viewport: 1'];viewport.setValues(displayedObject=root);viewport.view.fitView() print('SV2A model saved in '+output) print('Geometry transfer only. Complete analysis setup before writing an analysis INP.') if __name__=='__main__':main()