├── 1. Raw font profiles ├── fontImages │ ├── SimSun-ExtB_I.png │ ├── SimSun_I.png │ ├── Sitka_I.png │ ├── Snap ITC_I.png │ ├── Source Code Pro_I.png │ ├── Stencil_I.png │ ├── Swis721 BT_I.png │ ├── Swis721 BdOul BT_I.png │ ├── Swis721 LtCn BT_I.png │ ├── Sylfaen_I.png │ ├── Tahoma_I.png │ ├── Tempus Sans ITC_I.png │ ├── Times New Roman_I.png │ ├── Trebuchet MS_I.png │ ├── Tw Cen MT Condensed Extra Bold_I.png │ ├── Tw Cen MT Condensed_I.png │ ├── Tw Cen MT_I.png │ ├── Verdana_I.png │ ├── Viner Hand ITC_I.png │ ├── Vivaldi_I.png │ ├── Vladimir Script_I.png │ ├── Wide Latin_I.png │ ├── Yu Gothic_I.png │ └── Zapfino_I.png └── generateFonts.py ├── 2. Vectorised graphics ├── generateVectors.py └── vectorisedProfiles.zip ├── 3. Abaqus simulation ├── abaqusBending.py ├── abaqusBuckling.py ├── abaqusTorsion.py └── fontsToTest.txt ├── 4. Results and graphs ├── allProfilesSmall.zip ├── normalisedResults.txt └── showGraphs.py ├── LICENSE └── README.md /1. Raw font profiles/fontImages/SimSun-ExtB_I.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AtomicFrontierCode/typefaces/ef4c5a3713b993732bdab333b42eac4ea08fb200/1. Raw font profiles/fontImages/SimSun-ExtB_I.png -------------------------------------------------------------------------------- /1. Raw font profiles/fontImages/SimSun_I.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AtomicFrontierCode/typefaces/ef4c5a3713b993732bdab333b42eac4ea08fb200/1. Raw font profiles/fontImages/SimSun_I.png -------------------------------------------------------------------------------- /1. Raw font profiles/fontImages/Sitka_I.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AtomicFrontierCode/typefaces/ef4c5a3713b993732bdab333b42eac4ea08fb200/1. Raw font profiles/fontImages/Sitka_I.png -------------------------------------------------------------------------------- /1. Raw font profiles/fontImages/Snap ITC_I.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AtomicFrontierCode/typefaces/ef4c5a3713b993732bdab333b42eac4ea08fb200/1. Raw font profiles/fontImages/Snap ITC_I.png -------------------------------------------------------------------------------- /1. Raw font profiles/fontImages/Source Code Pro_I.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AtomicFrontierCode/typefaces/ef4c5a3713b993732bdab333b42eac4ea08fb200/1. Raw font profiles/fontImages/Source Code Pro_I.png -------------------------------------------------------------------------------- /1. Raw font profiles/fontImages/Stencil_I.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AtomicFrontierCode/typefaces/ef4c5a3713b993732bdab333b42eac4ea08fb200/1. Raw font profiles/fontImages/Stencil_I.png -------------------------------------------------------------------------------- /1. Raw font profiles/fontImages/Swis721 BT_I.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AtomicFrontierCode/typefaces/ef4c5a3713b993732bdab333b42eac4ea08fb200/1. Raw font profiles/fontImages/Swis721 BT_I.png -------------------------------------------------------------------------------- /1. Raw font profiles/fontImages/Swis721 BdOul BT_I.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AtomicFrontierCode/typefaces/ef4c5a3713b993732bdab333b42eac4ea08fb200/1. Raw font profiles/fontImages/Swis721 BdOul BT_I.png -------------------------------------------------------------------------------- /1. Raw font profiles/fontImages/Swis721 LtCn BT_I.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AtomicFrontierCode/typefaces/ef4c5a3713b993732bdab333b42eac4ea08fb200/1. Raw font profiles/fontImages/Swis721 LtCn BT_I.png -------------------------------------------------------------------------------- /1. Raw font profiles/fontImages/Sylfaen_I.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AtomicFrontierCode/typefaces/ef4c5a3713b993732bdab333b42eac4ea08fb200/1. Raw font profiles/fontImages/Sylfaen_I.png -------------------------------------------------------------------------------- /1. Raw font profiles/fontImages/Tahoma_I.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AtomicFrontierCode/typefaces/ef4c5a3713b993732bdab333b42eac4ea08fb200/1. Raw font profiles/fontImages/Tahoma_I.png -------------------------------------------------------------------------------- /1. Raw font profiles/fontImages/Tempus Sans ITC_I.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AtomicFrontierCode/typefaces/ef4c5a3713b993732bdab333b42eac4ea08fb200/1. Raw font profiles/fontImages/Tempus Sans ITC_I.png -------------------------------------------------------------------------------- /1. Raw font profiles/fontImages/Times New Roman_I.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AtomicFrontierCode/typefaces/ef4c5a3713b993732bdab333b42eac4ea08fb200/1. Raw font profiles/fontImages/Times New Roman_I.png -------------------------------------------------------------------------------- /1. Raw font profiles/fontImages/Trebuchet MS_I.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AtomicFrontierCode/typefaces/ef4c5a3713b993732bdab333b42eac4ea08fb200/1. Raw font profiles/fontImages/Trebuchet MS_I.png -------------------------------------------------------------------------------- /1. Raw font profiles/fontImages/Tw Cen MT Condensed Extra Bold_I.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AtomicFrontierCode/typefaces/ef4c5a3713b993732bdab333b42eac4ea08fb200/1. Raw font profiles/fontImages/Tw Cen MT Condensed Extra Bold_I.png -------------------------------------------------------------------------------- /1. Raw font profiles/fontImages/Tw Cen MT Condensed_I.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AtomicFrontierCode/typefaces/ef4c5a3713b993732bdab333b42eac4ea08fb200/1. Raw font profiles/fontImages/Tw Cen MT Condensed_I.png -------------------------------------------------------------------------------- /1. Raw font profiles/fontImages/Tw Cen MT_I.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AtomicFrontierCode/typefaces/ef4c5a3713b993732bdab333b42eac4ea08fb200/1. Raw font profiles/fontImages/Tw Cen MT_I.png -------------------------------------------------------------------------------- /1. Raw font profiles/fontImages/Verdana_I.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AtomicFrontierCode/typefaces/ef4c5a3713b993732bdab333b42eac4ea08fb200/1. Raw font profiles/fontImages/Verdana_I.png -------------------------------------------------------------------------------- /1. Raw font profiles/fontImages/Viner Hand ITC_I.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AtomicFrontierCode/typefaces/ef4c5a3713b993732bdab333b42eac4ea08fb200/1. Raw font profiles/fontImages/Viner Hand ITC_I.png -------------------------------------------------------------------------------- /1. Raw font profiles/fontImages/Vivaldi_I.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AtomicFrontierCode/typefaces/ef4c5a3713b993732bdab333b42eac4ea08fb200/1. Raw font profiles/fontImages/Vivaldi_I.png -------------------------------------------------------------------------------- /1. Raw font profiles/fontImages/Vladimir Script_I.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AtomicFrontierCode/typefaces/ef4c5a3713b993732bdab333b42eac4ea08fb200/1. Raw font profiles/fontImages/Vladimir Script_I.png -------------------------------------------------------------------------------- /1. Raw font profiles/fontImages/Wide Latin_I.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AtomicFrontierCode/typefaces/ef4c5a3713b993732bdab333b42eac4ea08fb200/1. Raw font profiles/fontImages/Wide Latin_I.png -------------------------------------------------------------------------------- /1. Raw font profiles/fontImages/Yu Gothic_I.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AtomicFrontierCode/typefaces/ef4c5a3713b993732bdab333b42eac4ea08fb200/1. Raw font profiles/fontImages/Yu Gothic_I.png -------------------------------------------------------------------------------- /1. Raw font profiles/fontImages/Zapfino_I.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AtomicFrontierCode/typefaces/ef4c5a3713b993732bdab333b42eac4ea08fb200/1. Raw font profiles/fontImages/Zapfino_I.png -------------------------------------------------------------------------------- /1. Raw font profiles/generateFonts.py: -------------------------------------------------------------------------------- 1 | import matplotlib.font_manager as fm 2 | from PIL import Image, ImageDraw, ImageFont 3 | import os 4 | 5 | beginSaving = 0 6 | 7 | # List of fonts to process 8 | # Get a list of all font paths 9 | font_list = fm.findSystemFonts(fontpaths=None, fontext='ttf') 10 | 11 | # Use a dictionary to store font paths and names to avoid duplicates 12 | font_dict = {} 13 | for font in font_list: 14 | try: 15 | font_name = fm.FontProperties(fname=font).get_name() 16 | font_properties = fm.FontProperties(fname=font) 17 | if 'italic' not in font_properties.get_style(): 18 | font_dict[font] = font_name 19 | except RuntimeError: 20 | continue 21 | 22 | # Create a directory to save the images if it doesn't exist 23 | os.makedirs('fontImages', exist_ok=True) 24 | 25 | # Print the list of fonts and create an image for each font 26 | i = 1 27 | old_font = "" 28 | letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 29 | 30 | for font_path, font_name in sorted(font_dict.items(), key=lambda item: item[1]): 31 | if font_name != old_font: 32 | old_font = font_name 33 | try: 34 | if i > beginSaving: 35 | for letter in letters: 36 | print(f"{font_name}\t{letter}") 37 | 38 | # Set text size based on font name 39 | text_size = 2500 40 | if font_name == "Amiri Quran": 41 | text_size = 1000 42 | if font_name == "Meddon": 43 | text_size = 1000 44 | if font_name == "Miama": 45 | text_size = 1000 46 | 47 | # Create a new image with a white background 48 | img = Image.new('RGB', (5000, 5000), color='white') 49 | d = ImageDraw.Draw(img) 50 | 51 | # Load the font and draw the letter 52 | font = ImageFont.truetype(font_path, text_size) 53 | text = letter 54 | 55 | # Calculate the position to center the text using textbbox 56 | text_bbox = d.textbbox((0, 0), text, font=font) 57 | text_width = text_bbox[2] - text_bbox[0] 58 | text_height = text_bbox[3] - text_bbox[1] 59 | position = ((5000 - text_width) // 2, (5000 - text_height) // 2) 60 | 61 | # Draw the text on the image 62 | d.text(position, text, font=font, fill=(0, 0, 0)) 63 | 64 | # Crop the image 65 | pixels = img.load() 66 | 67 | # Get image dimensions 68 | width, height = img.size 69 | 70 | # Initialize crop boundaries 71 | left = width 72 | right = 0 73 | top = height 74 | bottom = 0 75 | 76 | # Find the boundaries of the black pixels 77 | for y in range(height): 78 | for x in range(width): 79 | if pixels[x, y] == (0, 0, 0): # Assuming black pixels are (0, 0, 0) 80 | if x < left: 81 | left = x 82 | if x > right: 83 | right = x 84 | if y < top: 85 | top = y 86 | if y > bottom: 87 | bottom = y 88 | 89 | # Check if valid black pixels were found 90 | if left < right and top < bottom: 91 | # Determine the side length of the square crop 92 | crop_width = right - left + 1 93 | crop_height = bottom - top + 1 94 | side_length = max(crop_width, crop_height) 95 | 96 | # Calculate new boundaries for the square crop 97 | center_x = (left + right) // 2 98 | center_y = (top + bottom) // 2 99 | 100 | new_left = max(0, center_x - side_length // 2) 101 | new_right = min(width, center_x + side_length // 2 + (side_length % 2)) 102 | new_top = max(0, center_y - side_length // 2) 103 | new_bottom = min(height, center_y + side_length // 2 + (side_length % 2)) 104 | 105 | # Crop the image 106 | cropped_img = img.crop((new_left, new_top, new_right, new_bottom)) 107 | bwImage = cropped_img.convert('1') 108 | 109 | # Resize the cropped image to 1000x1000 pixels 110 | resized_img = bwImage.resize((1000, 1000), Image.BICUBIC) 111 | 112 | # Save the image 113 | resized_img.save(f'fontImages/{font_name}_{letter}.png') 114 | 115 | i += 1 116 | except Exception as e: 117 | print(f"Could not process font: {font_path}, Error: {e}") 118 | continue 119 | -------------------------------------------------------------------------------- /2. Vectorised graphics/generateVectors.py: -------------------------------------------------------------------------------- 1 | import cv2 2 | import numpy as np 3 | from PIL import Image 4 | import os 5 | import matplotlib.pyplot as plt 6 | 7 | def count_black_pixels(image_path): 8 | image = Image.open(image_path).convert('RGB') 9 | pixels = image.load() 10 | width, height = image.size 11 | black_pixel_count = 0 12 | for x in range(width): 13 | for y in range(height): 14 | if pixels[x, y] == (0, 0, 0): 15 | black_pixel_count += 1 16 | return black_pixel_count 17 | 18 | def find_optimal_point(binary_image): 19 | height, width = binary_image.shape 20 | max_black_squares = 5 21 | for y in range(max_black_squares, height - max_black_squares): 22 | for x in range(max_black_squares, width - max_black_squares): 23 | if all(binary_image[y+dy, x+dx] == 255 for dy in range(-max_black_squares, max_black_squares + 1) for dx in range(-max_black_squares, max_black_squares + 1)): 24 | return (x, y) 25 | return None 26 | 27 | def contour_to_lines_and_draw(contour, draw_image): 28 | instructions = [] 29 | for i in range(len(contour)): 30 | point1 = ((contour[i][0][0] * 0.1) - 51.0, (contour[i][0][1] * 0.1) - 51.0) 31 | point2 = ((contour[(i + 1) % len(contour)][0][0] * 0.1) - 51.0, (contour[(i + 1) % len(contour)][0][1] * 0.1) - 51.0) 32 | point1 = (round(point1[0], 2), round(point1[1], 2)) 33 | point2 = (round(point2[0], 2), round(point2[1], 2)) 34 | instructions.append(f"s.Line(point1={point1}, point2={point2})") 35 | draw_point1 = (int((point1[0] + 51.0) / 0.1), int((point1[1] + 51.0) / 0.1)) 36 | draw_point2 = (int((point2[0] + 51.0) / 0.1), int((point2[1] + 51.0) / 0.1)) 37 | cv2.line(draw_image, draw_point1, draw_point2, (0, 255, 0), 2) 38 | return instructions 39 | 40 | folderA = 'fontImages' 41 | folderB = 'vectorisedProfiles' 42 | 43 | plotting = True 44 | 45 | for filename in os.listdir(folderA): 46 | pathA = os.path.join(folderA, filename) 47 | pathB = os.path.join(folderB, filename) 48 | mass = count_black_pixels(pathA) 49 | density = 1E9 / mass 50 | image = cv2.imread(pathA, cv2.IMREAD_GRAYSCALE) 51 | _, binary_image = cv2.threshold(image, 127, 255, cv2.THRESH_BINARY_INV) 52 | contours, _ = cv2.findContours(binary_image, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) 53 | draw_image = np.zeros((image.shape[0], image.shape[1], 3), dtype=np.uint8) 54 | all_instructions = [] 55 | for contour in contours: 56 | # Approximate contour to reduce the number of points 57 | epsilon = 0.001 * cv2.arcLength(contour, True) 58 | approx = cv2.approxPolyDP(contour, epsilon, True) 59 | # Limit the number of points to 20 60 | instructions = contour_to_lines_and_draw(approx, draw_image) 61 | all_instructions.extend(instructions) 62 | optimal_point = find_optimal_point(binary_image) 63 | point_coords = "" 64 | if optimal_point: 65 | x, y = optimal_point 66 | point_coords = (round((x * 0.1) - 51.0, 2), round((y * 0.1) - 51.0, 2)) 67 | cv2.circle(draw_image, (x, y), 5, (255, 0, 0), -1) 68 | output_text_path = pathB.replace('.png', '.txt') 69 | with open(output_text_path, 'w') as f: 70 | f.write(f"filename = {os.path.splitext(filename)[0]}, density = {density:.2f}, point = {point_coords}\n") 71 | for instruction in all_instructions: 72 | f.write(instruction + '\n') 73 | if plotting: 74 | output_image_path = pathB.replace('.png', '_sketch.png') 75 | plt.figure(figsize=(8, 8)) 76 | plt.imshow(cv2.cvtColor(draw_image, cv2.COLOR_BGR2RGB)) 77 | plt.title(f'Final Sketch: {filename}') 78 | plt.axis('off') 79 | plt.savefig(output_image_path) 80 | plt.close() 81 | 82 | def list_txt_files(folder_path): 83 | files = os.listdir(folder_path) 84 | txt_files = [file for file in files if file.endswith('.txt')] 85 | output_file_path = os.path.join(folder_path, 'fontsToTest.txt') 86 | with open(output_file_path, 'w') as output_file: 87 | for txt_file in txt_files: 88 | output_file.write(txt_file + '\n') 89 | print(f"List of text files has been written to {output_file_path}") 90 | 91 | folder_path = '/' 92 | list_txt_files(folder_path) 93 | 94 | 95 | -------------------------------------------------------------------------------- /2. Vectorised graphics/vectorisedProfiles.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AtomicFrontierCode/typefaces/ef4c5a3713b993732bdab333b42eac4ea08fb200/2. Vectorised graphics/vectorisedProfiles.zip -------------------------------------------------------------------------------- /3. Abaqus simulation/abaqusBending.py: -------------------------------------------------------------------------------- 1 | # -*- coding: mbcs -*- 2 | # Do not delete the following import lines 3 | from abaqus import * 4 | from abaqusConstants import * 5 | import __main__ 6 | from odbAccess import * 7 | import numpy as np 8 | import os 9 | 10 | def Beam(fileName): 11 | try: 12 | # Read the input file 13 | with open(fileName, 'r') as file: 14 | lines = file.readlines() 15 | 16 | # Extract parameters from the input file 17 | params = lines[0].strip().split(', ') 18 | filename = params[0].split('= ')[1] 19 | density = float(params[1].split('= ')[1]) 20 | point = float(params[2][9:]), float(params[3][:-1]) 21 | 22 | # Extract sketch lines 23 | sketch_lines = lines[1:] 24 | 25 | import section 26 | import regionToolset 27 | import displayGroupMdbToolset as dgm 28 | import part 29 | import material 30 | import assembly 31 | import step 32 | import interaction 33 | import load 34 | import mesh 35 | import optimization 36 | import job 37 | import sketch 38 | import visualization 39 | import xyPlot 40 | import displayGroupOdbToolset as dgo 41 | import connectorBehavior 42 | 43 | s = mdb.models['Model-1'].ConstrainedSketch(name='__profile__', 44 | sheetSize=200.0) 45 | g, v, d, c = s.geometry, s.vertices, s.dimensions, s.constraints 46 | s.setPrimaryObject(option=STANDALONE) 47 | session.viewports['Viewport: 1'].view.setValues(nearPlane=155.166, 48 | farPlane=221.957, width=497.972, height=228.673, cameraPosition=( 49 | 34.5536, -0.0754834, 188.562), cameraTarget=(34.5536, -0.0754834, 0)) 50 | 51 | # Add lines to the sketch 52 | for line in sketch_lines: 53 | exec(line.strip()) 54 | 55 | # Make 3D part 56 | p = mdb.models['Model-1'].Part(name='Beam1', dimensionality=THREE_D, 57 | type=DEFORMABLE_BODY) 58 | p = mdb.models['Model-1'].parts['Beam1'] 59 | p.BaseSolidExtrude(sketch=s, depth=1000.0) 60 | s.unsetPrimaryObject() 61 | p = mdb.models['Model-1'].parts['Beam1'] 62 | session.viewports['Viewport: 1'].setValues(displayedObject=p) 63 | del mdb.models['Model-1'].sketches['__profile__'] 64 | session.viewports['Viewport: 1'].partDisplay.setValues(sectionAssignments=ON, 65 | engineeringFeatures=ON) 66 | session.viewports['Viewport: 1'].partDisplay.geometryOptions.setValues( 67 | referenceRepresentation=OFF) 68 | mdb.models['Model-1'].Material(name='MyMaterial') 69 | 70 | # Set material properties 71 | mdb.models['Model-1'].materials['MyMaterial'].Density(table=((density, ), )) 72 | mdb.models['Model-1'].materials['MyMaterial'].Elastic(table=((209000.0, 0.3), )) 73 | 74 | # Sections 75 | mdb.models['Model-1'].HomogeneousSolidSection(name='Section-1', 76 | material='MyMaterial', thickness=None) 77 | 78 | # Assign sections 79 | session.viewports['Viewport: 1'].view.setValues(nearPlane=1735.75, 80 | farPlane=2690.12, width=1941.83, height=867.947, viewOffsetX=-102.925, 81 | viewOffsetY=36.8656) 82 | p = mdb.models['Model-1'].parts['Beam1'] 83 | c = p.cells 84 | cells = c.getSequenceFromMask(mask=('[#1 ]', ), ) 85 | region = p.Set(cells=cells, name='Set-1') 86 | p = mdb.models['Model-1'].parts['Beam1'] 87 | p.SectionAssignment(region=region, sectionName='Section-1', offset=0.0, 88 | offsetType=MIDDLE_SURFACE, offsetField='', 89 | thicknessAssignment=FROM_SECTION) 90 | session.viewports['Viewport: 1'].view.setValues(nearPlane=1705.52, 91 | farPlane=2720.35, width=2132.95, height=953.373, viewOffsetX=-22.1751, 92 | viewOffsetY=-26.3117) 93 | 94 | # Create assembly and instance 95 | a = mdb.models['Model-1'].rootAssembly 96 | session.viewports['Viewport: 1'].setValues(displayedObject=a) 97 | a1 = mdb.models['Model-1'].rootAssembly 98 | a1.DatumCsysByDefault(CARTESIAN) 99 | p = mdb.models['Model-1'].parts['Beam1'] 100 | a1.Instance(name='Beam1-1', part=p, dependent=ON) 101 | session.viewports['Viewport: 1'].assemblyDisplay.setValues( 102 | adaptiveMeshConstraints=ON) 103 | 104 | # Create step 105 | mdb.models['Model-1'].StaticStep(name='Step-1', previous='Initial') 106 | session.viewports['Viewport: 1'].assemblyDisplay.setValues(step='Step-1') 107 | session.viewports['Viewport: 1'].assemblyDisplay.setValues(interactions=ON, 108 | constraints=ON, connectors=ON, engineeringFeatures=ON, 109 | adaptiveMeshConstraints=OFF) 110 | session.viewports['Viewport: 1'].assemblyDisplay.setValues(loads=ON, bcs=ON, 111 | predefinedFields=ON, interactions=OFF, constraints=OFF, 112 | engineeringFeatures=OFF) 113 | 114 | # Apply BCs 115 | a = mdb.models['Model-1'].rootAssembly 116 | f1 = a.instances['Beam1-1'].faces 117 | faces1 = f1.findAt(((point[0], point[1], 0),)) 118 | region = a.Set(faces=faces1, name='Set-1') 119 | mdb.models['Model-1'].DisplacementBC(name='BC-1', createStepName='Step-1', 120 | region=region, u1=0.0, u2=0.0, u3=UNSET, ur1=UNSET, ur2=0.0, ur3=0.0, 121 | amplitude=UNSET, fixed=OFF, distributionType=UNIFORM, fieldName='', 122 | localCsys=None) 123 | session.viewports['Viewport: 1'].view.setValues(nearPlane=1798.16, 124 | farPlane=2533.32, width=1014.35, height=453.387, cameraPosition=( 125 | 1471.81, 1251.45, -479.166), cameraUpVector=(-0.673042, 0.57735, 126 | 0.462256), cameraTarget=(-17.5891, -26.1896, 543.779)) 127 | 128 | a = mdb.models['Model-1'].rootAssembly 129 | f1 = a.instances['Beam1-1'].faces 130 | faces1 = f1.findAt(((point[0], point[1], 1000.0),)) 131 | region = a.Set(faces=faces1, name='Set-2') 132 | mdb.models['Model-1'].DisplacementBC(name='BC-2', createStepName='Step-1', 133 | region=region, u1=0.0, u2=0.0, u3=UNSET, ur1=UNSET, ur2=0.0, ur3=0.0, 134 | amplitude=UNSET, fixed=OFF, distributionType=UNIFORM, fieldName='', 135 | localCsys=None) 136 | mdb.models['Model-1'].Gravity(name='Load-1', createStepName='Step-1', 137 | comp2=-10.0, distributionType=UNIFORM, field='') 138 | session.viewports['Viewport: 1'].assemblyDisplay.setValues(mesh=ON, loads=OFF, 139 | bcs=OFF, predefinedFields=OFF, connectors=OFF) 140 | session.viewports['Viewport: 1'].assemblyDisplay.meshOptions.setValues( 141 | meshTechnique=ON) 142 | 143 | # Generate mesh 144 | p = mdb.models['Model-1'].parts['Beam1'] 145 | session.viewports['Viewport: 1'].setValues(displayedObject=p) 146 | session.viewports['Viewport: 1'].partDisplay.setValues(sectionAssignments=OFF, 147 | engineeringFeatures=OFF, mesh=ON) 148 | session.viewports['Viewport: 1'].partDisplay.meshOptions.setValues( 149 | meshTechnique=ON) 150 | p = mdb.models['Model-1'].parts['Beam1'] 151 | p.seedPart(size=50.0, deviationFactor=0.1, minSizeFactor=0.01) 152 | p = mdb.models['Model-1'].parts['Beam1'] 153 | p.seedPart(size=50.0, deviationFactor=0.1, minSizeFactor=0.001) 154 | p = mdb.models['Model-1'].parts['Beam1'] 155 | p.seedPart(size=10.0, deviationFactor=0.1, minSizeFactor=0.001) 156 | session.viewports['Viewport: 1'].view.setValues(nearPlane=1846.38, 157 | farPlane=2579.49, width=299.684, height=134.185, viewOffsetX=-272.012, 158 | viewOffsetY=-129.67) 159 | p = mdb.models['Model-1'].parts['Beam1'] 160 | p.seedPart(size=5.0, deviationFactor=0.1, minSizeFactor=0.001) 161 | session.viewports['Viewport: 1'].view.setValues(nearPlane=1750.29, 162 | farPlane=2675.59, width=1534.94, height=687.279, viewOffsetX=-20.3906, 163 | viewOffsetY=-54.5182) 164 | p = mdb.models['Model-1'].parts['Beam1'] 165 | p.generateMesh() 166 | session.viewports['Viewport: 1'].view.setValues(nearPlane=1798, 167 | farPlane=2627.88, width=903.485, height=404.541, viewOffsetX=-128.406, 168 | viewOffsetY=-86.2053) 169 | a1 = mdb.models['Model-1'].rootAssembly 170 | a1.regenerate() 171 | a = mdb.models['Model-1'].rootAssembly 172 | session.viewports['Viewport: 1'].setValues(displayedObject=a) 173 | session.viewports['Viewport: 1'].assemblyDisplay.setValues(mesh=OFF, 174 | optimizationTasks=ON, geometricRestrictions=ON, stopConditions=ON) 175 | session.viewports['Viewport: 1'].assemblyDisplay.meshOptions.setValues( 176 | meshTechnique=OFF) 177 | session.viewports['Viewport: 1'].assemblyDisplay.setValues( 178 | optimizationTasks=OFF, geometricRestrictions=OFF, stopConditions=OFF) 179 | 180 | # Create and submit job 181 | mdb.Job(name='Job-1', model='Model-1', description='', type=ANALYSIS, 182 | atTime=None, waitMinutes=0, waitHours=0, queue=None, memory=90, 183 | memoryUnits=PERCENTAGE, getMemoryFromAnalysis=True, 184 | explicitPrecision=SINGLE, nodalOutputPrecision=SINGLE, echoPrint=OFF, 185 | modelPrint=OFF, contactPrint=OFF, historyPrint=OFF, userSubroutine='', 186 | scratch='', resultsFormat=ODB, numThreadsPerMpiProcess=1, 187 | multiprocessingMode=DEFAULT, numCpus=1, numGPUs=1) 188 | mdb.jobs['Job-1'].submit(consistencyChecking=OFF) 189 | 190 | # Wait for the job to complete 191 | mdb.jobs['Job-1'].waitForCompletion() 192 | 193 | # Open the output database and set up the display 194 | session.mdbData.summary() 195 | o3 = session.openOdb(name='C:/temp/Job-1.odb') 196 | session.viewports['Viewport: 1'].setValues(displayedObject=o3) 197 | session.viewports['Viewport: 1'].makeCurrent() 198 | session.viewports['Viewport: 1'].odbDisplay.display.setValues(plotState=( 199 | CONTOURS_ON_DEF, )) 200 | 201 | # Customize display settings 202 | session.viewports['Viewport: 1'].odbDisplay.commonOptions.setValues( 203 | visibleEdges=FEATURE) 204 | session.viewports['Viewport: 1'].viewportAnnotationOptions.setValues( 205 | triad=OFF, title=OFF, state=OFF, annotations=OFF, compass=OFF) 206 | session.viewports['Viewport: 1'].odbDisplay.setPrimaryVariable( 207 | variableLabel='S', outputPosition=INTEGRATION_POINT, 208 | refinement=(INVARIANT, 'Mises'), ) 209 | session.viewports['Viewport: 1'].odbDisplay.commonOptions.setValues( 210 | deformationScaling=UNIFORM, uniformScaleFactor=1e-6) 211 | 212 | session.viewports['Viewport: 1'].odbDisplay.contourOptions.setValues(maxAutoCompute=OFF, minAutoCompute=OFF) 213 | 214 | session.viewports['Viewport: 1'].odbDisplay.contourOptions.setValues(maxValue=1e9, minValue=1e7) 215 | 216 | 217 | # Ensure the contour plot uses the defined spectrum range 218 | #session.spectrum('Blue to Red').setValues(maxValue=5e11, minValue=1e8) 219 | 220 | # Save the viewport image 221 | image_filename = '/{}.png'.format(filename) 222 | session.printToFile(fileName=image_filename, format=PNG, canvasObjects=(session.viewports['Viewport: 1'],)) 223 | 224 | 225 | # Open the .odb file 226 | odb = openOdb(path='C:/temp/Job-1.odb') 227 | 228 | # Access the last frame of the last step 229 | lastFrame = odb.steps['Step-1'].frames[-1] 230 | 231 | # Access the stress field output 232 | stressField = lastFrame.fieldOutputs['S'] 233 | 234 | # Initialize lists to store the von Mises, tensile, and compressive stresses 235 | vonMisesList = [] 236 | tensileList = [] 237 | compressiveList = [] 238 | 239 | # Loop through each value in the stress field output 240 | for stressValue in stressField.values: 241 | # Update von Mises stress list 242 | vonMisesList.append(stressValue.mises) 243 | 244 | # Get the components of the stress tensor 245 | tensor = stressValue.data 246 | 247 | # Calculate principal stresses 248 | stressTensor = np.array([[tensor[0], tensor[3], tensor[5]], [tensor[3], tensor[1], tensor[4]], [tensor[5], tensor[4], tensor[2]]]) 249 | 250 | principalStresses = np.linalg.eigvalsh(stressTensor) 251 | 252 | # Update tensile and compressive stress lists 253 | tensileList.append(principalStresses.max()) 254 | compressiveList.append(principalStresses.min()) 255 | 256 | # Sort the stress lists 257 | vonMisesList.sort(reverse=True) 258 | tensileList.sort(reverse=True) 259 | compressiveList.sort() 260 | 261 | # Compute the average of the top 10 highest values, ignoring the top no. 1 highest value 262 | averageVonMises = np.mean(vonMisesList[1:11]) 263 | averageTensile = np.mean(tensileList[1:11]) 264 | averageCompressive = np.mean(compressiveList[1:11]) 265 | 266 | # Append the results to the beam_results.txt file 267 | results_file_path = '/bendingResults.txt' 268 | with open(results_file_path, 'a') as f: 269 | f.write("{}, VM = {}, Tensile = {}, Compressive = {}\n".format(filename, averageVonMises, averageTensile, averageCompressive)) 270 | 271 | # Close the .odb file 272 | odb.close() 273 | 274 | except Exception as e: 275 | # Log the error and continue with the next file 276 | with open('/error_log.txt', 'a') as error_file: 277 | error_file.write("Error processing file {}: {}\n".format(fileName, str(e))) 278 | print("Error processing file {}: {}\n".format(fileName, str(e))) 279 | 280 | # Read the list of text files 281 | instructions_path = '/' 282 | with open(os.path.join(instructions_path, 'fontsToTest.txt'), 'r') as file_list: 283 | txt_files = file_list.readlines() 284 | 285 | # Ensure the results file exists 286 | results_file_path = '/bendingResults.txt' 287 | if not os.path.exists(results_file_path): 288 | with open(results_file_path, 'w') as f: 289 | f.write("") 290 | 291 | # Iterate through each text file and run the Beam function 292 | for txt_file in txt_files: 293 | txt_file_path = os.path.join(instructions_path, txt_file.strip()) 294 | Beam(txt_file_path) 295 | 296 | 297 | -------------------------------------------------------------------------------- /3. Abaqus simulation/abaqusBuckling.py: -------------------------------------------------------------------------------- 1 | # -*- coding: mbcs -*- 2 | from abaqus import * 3 | from abaqusConstants import * 4 | import __main__ 5 | from odbAccess import * 6 | import numpy as np 7 | import os 8 | 9 | def Beam(fileName): 10 | try: 11 | # Read the input file 12 | with open(fileName, 'r') as file: 13 | lines = file.readlines() 14 | 15 | # Extract parameters from the input file 16 | params = lines[0].strip().split(', ') 17 | filename = params[0].split('= ')[1] 18 | density = float(params[1].split('= ')[1]) 19 | point = float(params[2][9:]), float(params[3][:-1]) 20 | 21 | # Extract sketch lines 22 | sketch_lines = lines[1:] 23 | 24 | import section 25 | import regionToolset 26 | import displayGroupMdbToolset as dgm 27 | import part 28 | import material 29 | import assembly 30 | import step 31 | import interaction 32 | import load 33 | import mesh 34 | import optimization 35 | import job 36 | import sketch 37 | import visualization 38 | import xyPlot 39 | import displayGroupOdbToolset as dgo 40 | import connectorBehavior 41 | 42 | s = mdb.models['Model-1'].ConstrainedSketch(name='__profile__', 43 | sheetSize=200.0) 44 | g, v, d, c = s.geometry, s.vertices, s.dimensions, s.constraints 45 | s.setPrimaryObject(option=STANDALONE) 46 | session.viewports['Viewport: 1'].view.setValues(nearPlane=155.166, 47 | farPlane=221.957, width=497.972, height=228.673, cameraPosition=( 48 | 34.5536, -0.0754834, 188.562), cameraTarget=(34.5536, -0.0754834, 0)) 49 | 50 | # Add lines to the sketch 51 | for line in sketch_lines: 52 | exec(line.strip()) 53 | 54 | # Make 3D part 55 | p = mdb.models['Model-1'].Part(name='Beam1', dimensionality=THREE_D, 56 | type=DEFORMABLE_BODY) 57 | p = mdb.models['Model-1'].parts['Beam1'] 58 | p.BaseSolidExtrude(sketch=s, depth=1000.0) 59 | s.unsetPrimaryObject() 60 | p = mdb.models['Model-1'].parts['Beam1'] 61 | session.viewports['Viewport: 1'].setValues(displayedObject=p) 62 | del mdb.models['Model-1'].sketches['__profile__'] 63 | session.viewports['Viewport: 1'].partDisplay.setValues(sectionAssignments=ON, 64 | engineeringFeatures=ON) 65 | session.viewports['Viewport: 1'].partDisplay.geometryOptions.setValues( 66 | referenceRepresentation=OFF) 67 | mdb.models['Model-1'].Material(name='MyMaterial') 68 | 69 | # Set material properties 70 | mdb.models['Model-1'].materials['MyMaterial'].Density(table=((density, ), )) 71 | mdb.models['Model-1'].materials['MyMaterial'].Elastic(table=((209000.0, 0.3), )) 72 | 73 | # Sections 74 | mdb.models['Model-1'].HomogeneousSolidSection(name='Section-1', 75 | material='MyMaterial', thickness=None) 76 | 77 | # Assign sections 78 | session.viewports['Viewport: 1'].view.setValues(nearPlane=1735.75, 79 | farPlane=2690.12, width=1941.83, height=867.947, viewOffsetX=-102.925, 80 | viewOffsetY=36.8656) 81 | p = mdb.models['Model-1'].parts['Beam1'] 82 | c = p.cells 83 | cells = c.getSequenceFromMask(mask=('[#1 ]', ), ) 84 | region = p.Set(cells=cells, name='Set-1') 85 | p = mdb.models['Model-1'].parts['Beam1'] 86 | p.SectionAssignment(region=region, sectionName='Section-1', offset=0.0, 87 | offsetType=MIDDLE_SURFACE, offsetField='', 88 | thicknessAssignment=FROM_SECTION) 89 | session.viewports['Viewport: 1'].view.setValues(nearPlane=1705.52, 90 | farPlane=2720.35, width=2132.95, height=953.373, viewOffsetX=-22.1751, 91 | viewOffsetY=-26.3117) 92 | 93 | # Create assembly and instance 94 | a = mdb.models['Model-1'].rootAssembly 95 | session.viewports['Viewport: 1'].setValues(displayedObject=a) 96 | a1 = mdb.models['Model-1'].rootAssembly 97 | a1.DatumCsysByDefault(CARTESIAN) 98 | p = mdb.models['Model-1'].parts['Beam1'] 99 | a1.Instance(name='Beam1-1', part=p, dependent=ON) 100 | session.viewports['Viewport: 1'].assemblyDisplay.setValues( 101 | adaptiveMeshConstraints=ON) 102 | 103 | # Create buckling step with numEigen=2 104 | mdb.models['Model-1'].BuckleStep(name='Step-1', previous='Initial', numEigen=2, 105 | vectors=2, maxIterations=200) 106 | session.viewports['Viewport: 1'].assemblyDisplay.setValues(step='Step-1') 107 | session.viewports['Viewport: 1'].assemblyDisplay.setValues(interactions=ON, 108 | constraints=ON, connectors=ON, engineeringFeatures=ON, 109 | adaptiveMeshConstraints=OFF) 110 | session.viewports['Viewport: 1'].assemblyDisplay.setValues(loads=ON, bcs=ON, 111 | predefinedFields=ON, interactions=OFF, constraints=OFF, 112 | engineeringFeatures=OFF) 113 | 114 | # Apply EncastreBC at bottom face (Z=0.0) 115 | a = mdb.models['Model-1'].rootAssembly 116 | f1 = a.instances['Beam1-1'].faces 117 | faces_bottom = f1.getByBoundingBox(zMin=-1e-6, zMax=1e-6) 118 | region = a.Set(faces=faces_bottom, name='Set-BC') 119 | mdb.models['Model-1'].EncastreBC(name='BC-1', createStepName='Step-1', 120 | region=region, localCsys=None, buckleCase=PERTURBATION_AND_BUCKLING) 121 | 122 | # Apply pressure load on top face (Z=1000.0) 123 | faces_top = f1.getByBoundingBox(zMin=1000.0 - 1e-6, zMax=1000.0 + 1e-6) 124 | region = a.Surface(side1Faces=faces_top, name='Surf-Load') 125 | mdb.models['Model-1'].Pressure(name='Load-1', createStepName='Step-1', 126 | region=region, distributionType=TOTAL_FORCE, field='', magnitude=1.0) 127 | 128 | session.viewports['Viewport: 1'].assemblyDisplay.setValues(mesh=ON, loads=OFF, 129 | bcs=OFF, predefinedFields=OFF, connectors=OFF) 130 | session.viewports['Viewport: 1'].assemblyDisplay.meshOptions.setValues( 131 | meshTechnique=ON) 132 | 133 | # Generate mesh 134 | p = mdb.models['Model-1'].parts['Beam1'] 135 | session.viewports['Viewport: 1'].setValues(displayedObject=p) 136 | session.viewports['Viewport: 1'].partDisplay.setValues(sectionAssignments=OFF, 137 | engineeringFeatures=OFF, mesh=ON) 138 | session.viewports['Viewport: 1'].partDisplay.meshOptions.setValues( 139 | meshTechnique=ON) 140 | p = mdb.models['Model-1'].parts['Beam1'] 141 | p.seedPart(size=5.0, deviationFactor=0.1, minSizeFactor=0.001) 142 | p.generateMesh() 143 | a1 = mdb.models['Model-1'].rootAssembly 144 | a1.regenerate() 145 | a = mdb.models['Model-1'].rootAssembly 146 | session.viewports['Viewport: 1'].setValues(displayedObject=a) 147 | session.viewports['Viewport: 1'].assemblyDisplay.setValues(mesh=OFF, 148 | optimizationTasks=ON, geometricRestrictions=ON, stopConditions=ON) 149 | session.viewports['Viewport: 1'].assemblyDisplay.meshOptions.setValues( 150 | meshTechnique=OFF) 151 | session.viewports['Viewport: 1'].assemblyDisplay.setValues( 152 | optimizationTasks=OFF, geometricRestrictions=OFF, stopConditions=OFF) 153 | 154 | # Create and submit job 155 | mdb.Job(name='Job-1', model='Model-1', description='', type=ANALYSIS, 156 | atTime=None, waitMinutes=0, waitHours=0, queue=None, memory=90, 157 | memoryUnits=PERCENTAGE, getMemoryFromAnalysis=True, 158 | explicitPrecision=SINGLE, nodalOutputPrecision=SINGLE, echoPrint=OFF, 159 | modelPrint=OFF, contactPrint=OFF, historyPrint=OFF, userSubroutine='', 160 | scratch='', resultsFormat=ODB, numThreadsPerMpiProcess=1, 161 | multiprocessingMode=DEFAULT, numCpus=1, numGPUs=1) 162 | mdb.jobs['Job-1'].submit(consistencyChecking=OFF) 163 | 164 | # Wait for the job to complete 165 | mdb.jobs['Job-1'].waitForCompletion() 166 | 167 | # Open the output database and set up the display 168 | session.mdbData.summary() 169 | o3 = session.openOdb(name='C:/temp/Job-1.odb') 170 | session.viewports['Viewport: 1'].setValues(displayedObject=o3) 171 | session.viewports['Viewport: 1'].makeCurrent() 172 | 173 | # Set the frame to the second eigenmode (frame 1) 174 | session.viewports['Viewport: 1'].odbDisplay.setFrame(step=0, frame=1) 175 | 176 | # Set deformation scaling 177 | session.viewports['Viewport: 1'].odbDisplay.commonOptions.setValues( 178 | deformationScaling=AUTO) 179 | 180 | # Set plot state 181 | session.viewports['Viewport: 1'].odbDisplay.display.setValues(plotState=( 182 | CONTOURS_ON_DEF, )) 183 | 184 | # Customize display settings 185 | session.viewports['Viewport: 1'].odbDisplay.commonOptions.setValues( 186 | visibleEdges=FEATURE) 187 | session.viewports['Viewport: 1'].viewportAnnotationOptions.setValues( 188 | triad=OFF, title=OFF, state=OFF, annotations=OFF, compass=OFF) 189 | 190 | # Set primary variable with invariant 191 | session.viewports['Viewport: 1'].odbDisplay.setPrimaryVariable( 192 | variableLabel='U', outputPosition=NODAL, refinement=(INVARIANT, 'Magnitude')) 193 | 194 | session.viewports['Viewport: 1'].odbDisplay.commonOptions.setValues(deformationScaling=UNIFORM, uniformScaleFactor=1e-6) 195 | 196 | session.viewports['Viewport: 1'].odbDisplay.contourOptions.setValues(maxAutoCompute=OFF, minAutoCompute=OFF) 197 | 198 | session.viewports['Viewport: 1'].odbDisplay.contourOptions.setValues(maxValue=1, minValue=0) 199 | 200 | # Save the viewport image 201 | image_filename = '/{}.png'.format(filename) 202 | session.printToFile(fileName=image_filename, format=PNG, canvasObjects=(session.viewports['Viewport: 1'],)) 203 | 204 | # Open the .odb file 205 | odb = openOdb(path='C:/temp/Job-1.odb') 206 | 207 | # Access the second eigenvalue 208 | frame = odb.steps['Step-1'].frames[1] # frame 1 corresponds to Mode 2 209 | description = frame.description # 'Mode 2: EigenValue = ...' 210 | 211 | # Extract eigenvalue from description 212 | import re 213 | match = re.search(r"Eigen[Vv]alue\s*=\s*([-\d\.E+]+)", description) 214 | if match: 215 | eigenvalue = float(match.group(1)) 216 | else: 217 | eigenvalue = None 218 | 219 | # Append the results to the beam_results.txt file 220 | results_file_path = '/bucklingResults.txt' 221 | with open(results_file_path, 'a') as f: 222 | f.write("{}, EV = {}\n".format(filename, eigenvalue)) 223 | 224 | # Close the .odb file 225 | odb.close() 226 | 227 | except Exception as e: 228 | # Log the error and continue with the next file 229 | with open('/error_log.txt', 'a') as error_file: 230 | error_file.write("Error processing file {}: {}\n".format(fileName, str(e))) 231 | print("Error processing file {}: {}\n".format(fileName, str(e))) 232 | 233 | # Read the list of text files 234 | instructions_path = '/' 235 | with open(os.path.join(instructions_path, 'fontsToTest.txt'), 'r') as file_list: 236 | txt_files = file_list.readlines() 237 | 238 | # Ensure the results file exists 239 | results_file_path = 'C/bucklingResults.txt' 240 | if not os.path.exists(results_file_path): 241 | with open(results_file_path, 'w') as f: 242 | f.write("") 243 | 244 | # Iterate through each text file and run the Beam function 245 | for txt_file in txt_files: 246 | txt_file_path = os.path.join(instructions_path, txt_file.strip()) 247 | Beam(txt_file_path) 248 | 249 | -------------------------------------------------------------------------------- /3. Abaqus simulation/abaqusTorsion.py: -------------------------------------------------------------------------------- 1 | # -*- coding: mbcs -*- 2 | # Do not delete the following import lines 3 | from abaqus import * 4 | from abaqusConstants import * 5 | import __main__ 6 | from odbAccess import * 7 | import numpy as np 8 | import os 9 | 10 | def Beam(fileName): 11 | try: 12 | # Read the input file 13 | with open(fileName, 'r') as file: 14 | lines = file.readlines() 15 | 16 | # Extract parameters from the input file 17 | params = lines[0].strip().split(', ') 18 | filename = params[0].split('= ')[1] 19 | density = float(params[1].split('= ')[1]) 20 | point = float(params[2][9:]), float(params[3][:-1]) 21 | 22 | # Extract sketch lines 23 | sketch_lines = lines[1:] 24 | 25 | import section 26 | import regionToolset 27 | import displayGroupMdbToolset as dgm 28 | import part 29 | import material 30 | import assembly 31 | import step 32 | import interaction 33 | import load 34 | import mesh 35 | import optimization 36 | import job 37 | import sketch 38 | import visualization 39 | import xyPlot 40 | import displayGroupOdbToolset as dgo 41 | import connectorBehavior 42 | 43 | s = mdb.models['Model-1'].ConstrainedSketch(name='__profile__', 44 | sheetSize=200.0) 45 | g, v, d, c = s.geometry, s.vertices, s.dimensions, s.constraints 46 | s.setPrimaryObject(option=STANDALONE) 47 | session.viewports['Viewport: 1'].view.setValues(nearPlane=155.166, 48 | farPlane=221.957, width=497.972, height=228.673, cameraPosition=( 49 | 34.5536, -0.0754834, 188.562), cameraTarget=(34.5536, -0.0754834, 0)) 50 | 51 | # Add lines to the sketch 52 | for line in sketch_lines: 53 | exec(line.strip()) 54 | 55 | # Make 3D part 56 | p = mdb.models['Model-1'].Part(name='Beam1', dimensionality=THREE_D, 57 | type=DEFORMABLE_BODY) 58 | p = mdb.models['Model-1'].parts['Beam1'] 59 | p.BaseSolidExtrude(sketch=s, depth=1000.0) 60 | s.unsetPrimaryObject() 61 | p = mdb.models['Model-1'].parts['Beam1'] 62 | session.viewports['Viewport: 1'].setValues(displayedObject=p) 63 | del mdb.models['Model-1'].sketches['__profile__'] 64 | session.viewports['Viewport: 1'].partDisplay.setValues(sectionAssignments=ON, 65 | engineeringFeatures=ON) 66 | session.viewports['Viewport: 1'].partDisplay.geometryOptions.setValues( 67 | referenceRepresentation=OFF) 68 | mdb.models['Model-1'].Material(name='MyMaterial') 69 | 70 | # Set material properties 71 | mdb.models['Model-1'].materials['MyMaterial'].Density(table=((density, ), )) 72 | mdb.models['Model-1'].materials['MyMaterial'].Elastic(table=((209000.0, 0.3), )) 73 | 74 | # Sections 75 | mdb.models['Model-1'].HomogeneousSolidSection(name='Section-1', 76 | material='MyMaterial', thickness=None) 77 | 78 | # Assign sections 79 | p = mdb.models['Model-1'].parts['Beam1'] 80 | c = p.cells 81 | cells = c.getSequenceFromMask(mask=('[#1 ]', ), ) 82 | region = p.Set(cells=cells, name='Set-1') 83 | p.SectionAssignment(region=region, sectionName='Section-1', offset=0.0, 84 | offsetType=MIDDLE_SURFACE, offsetField='', 85 | thicknessAssignment=FROM_SECTION) 86 | 87 | # Create assembly and instance 88 | a = mdb.models['Model-1'].rootAssembly 89 | a.DatumCsysByDefault(CARTESIAN) 90 | p = mdb.models['Model-1'].parts['Beam1'] 91 | a.Instance(name='Beam1-1', part=p, dependent=ON) 92 | 93 | # Create step 94 | mdb.models['Model-1'].StaticStep(name='Step-1', previous='Initial') 95 | 96 | # Apply BCs 97 | a = mdb.models['Model-1'].rootAssembly 98 | f1 = a.instances['Beam1-1'].faces 99 | 100 | # Bottom face BC (Fully or partially fixed) 101 | faces1 = f1.findAt(((point[0], point[1], 0),)) 102 | region = a.Set(faces=faces1, name='Set-1') 103 | # Keep the bottom face constraints as before 104 | mdb.models['Model-1'].DisplacementBC(name='BC-1', createStepName='Step-1', 105 | region=region, u1=0.0, u2=0.0, u3=UNSET, ur1=UNSET, ur2=0.0, ur3=0.0, 106 | amplitude=UNSET, fixed=OFF, distributionType=UNIFORM, fieldName='', 107 | localCsys=None) 108 | 109 | # Top face: Instead of a simple displacement BC, we apply a rotation 110 | faces_top = f1.findAt(((point[0], point[1], 1000.0),)) 111 | top_face_region = a.Set(faces=faces_top, name='Set-2') 112 | 113 | # Create a reference point and couple it to the top face 114 | rp = a.ReferencePoint(point=(point[0], point[1], 1000.0)) 115 | rpRegion = a.Set(referencePoints=(a.referencePoints[rp.id],), name='RP') 116 | 117 | # Coupling: Kinematic coupling between RP and top face 118 | mdb.models['Model-1'].Coupling(name='Couple-RP-TopFace', 119 | controlPoint=rpRegion, surface=top_face_region, influenceRadius=WHOLE_SURFACE, 120 | couplingType=KINEMATIC, u1=ON, u2=ON, u3=ON, ur1=ON, ur2=ON, ur3=ON) 121 | 122 | # Apply a rotational displacement at the reference point 123 | # For example, rotate about the 3rd axis (ur3) by 0.2 radians 124 | mdb.models['Model-1'].DisplacementBC(name='BC-Rotation', createStepName='Step-1', 125 | region=rpRegion, u1=UNSET, u2=UNSET, u3=UNSET, ur1=UNSET, ur2=UNSET, ur3=0.2) 126 | 127 | # Generate mesh 128 | p = mdb.models['Model-1'].parts['Beam1'] 129 | session.viewports['Viewport: 1'].setValues(displayedObject=p) 130 | session.viewports['Viewport: 1'].partDisplay.setValues(sectionAssignments=OFF, 131 | engineeringFeatures=OFF, mesh=ON) 132 | session.viewports['Viewport: 1'].partDisplay.meshOptions.setValues( 133 | meshTechnique=ON) 134 | p = mdb.models['Model-1'].parts['Beam1'] 135 | p.seedPart(size=50.0, deviationFactor=0.1, minSizeFactor=0.01) 136 | p = mdb.models['Model-1'].parts['Beam1'] 137 | p.seedPart(size=50.0, deviationFactor=0.1, minSizeFactor=0.001) 138 | p = mdb.models['Model-1'].parts['Beam1'] 139 | p.seedPart(size=10.0, deviationFactor=0.1, minSizeFactor=0.001) 140 | session.viewports['Viewport: 1'].view.setValues(nearPlane=1846.38, 141 | farPlane=2579.49, width=299.684, height=134.185, viewOffsetX=-272.012, 142 | viewOffsetY=-129.67) 143 | p = mdb.models['Model-1'].parts['Beam1'] 144 | p.seedPart(size=5.0, deviationFactor=0.1, minSizeFactor=0.001) 145 | session.viewports['Viewport: 1'].view.setValues(nearPlane=1750.29, 146 | farPlane=2675.59, width=1534.94, height=687.279, viewOffsetX=-20.3906, 147 | viewOffsetY=-54.5182) 148 | p = mdb.models['Model-1'].parts['Beam1'] 149 | p.generateMesh() 150 | session.viewports['Viewport: 1'].view.setValues(nearPlane=1798, 151 | farPlane=2627.88, width=903.485, height=404.541, viewOffsetX=-128.406, 152 | viewOffsetY=-86.2053) 153 | a1 = mdb.models['Model-1'].rootAssembly 154 | a1.regenerate() 155 | a = mdb.models['Model-1'].rootAssembly 156 | session.viewports['Viewport: 1'].setValues(displayedObject=a) 157 | session.viewports['Viewport: 1'].assemblyDisplay.setValues(mesh=OFF, 158 | optimizationTasks=ON, geometricRestrictions=ON, stopConditions=ON) 159 | session.viewports['Viewport: 1'].assemblyDisplay.meshOptions.setValues( 160 | meshTechnique=OFF) 161 | session.viewports['Viewport: 1'].assemblyDisplay.setValues( 162 | optimizationTasks=OFF, geometricRestrictions=OFF, stopConditions=OFF) 163 | 164 | # Create and submit job 165 | mdb.Job(name='Job-1', model='Model-1', description='', type=ANALYSIS, 166 | atTime=None, waitMinutes=0, waitHours=0, queue=None, memory=90, 167 | memoryUnits=PERCENTAGE, getMemoryFromAnalysis=True, 168 | explicitPrecision=SINGLE, nodalOutputPrecision=SINGLE, echoPrint=OFF, 169 | modelPrint=OFF, contactPrint=OFF, historyPrint=OFF, userSubroutine='', 170 | scratch='', resultsFormat=ODB, numThreadsPerMpiProcess=1, 171 | multiprocessingMode=DEFAULT, numCpus=1, numGPUs=1) 172 | mdb.jobs['Job-1'].submit(consistencyChecking=OFF) 173 | 174 | # Wait for the job to complete 175 | mdb.jobs['Job-1'].waitForCompletion() 176 | 177 | # Open the output database and set up the display 178 | session.mdbData.summary() 179 | o3 = session.openOdb(name='C:/temp/Job-1.odb') 180 | session.viewports['Viewport: 1'].setValues(displayedObject=o3) 181 | session.viewports['Viewport: 1'].makeCurrent() 182 | session.viewports['Viewport: 1'].odbDisplay.display.setValues(plotState=( 183 | CONTOURS_ON_DEF, )) 184 | 185 | # Customize display settings 186 | session.viewports['Viewport: 1'].odbDisplay.commonOptions.setValues( 187 | visibleEdges=FEATURE) 188 | session.viewports['Viewport: 1'].viewportAnnotationOptions.setValues( 189 | triad=OFF, title=OFF, state=OFF, annotations=OFF, compass=OFF) 190 | session.viewports['Viewport: 1'].odbDisplay.setPrimaryVariable( 191 | variableLabel='S', outputPosition=INTEGRATION_POINT, 192 | refinement=(INVARIANT, 'Mises'), ) 193 | session.viewports['Viewport: 1'].odbDisplay.commonOptions.setValues( 194 | deformationScaling=UNIFORM, uniformScaleFactor=1) 195 | 196 | session.viewports['Viewport: 1'].odbDisplay.contourOptions.setValues(maxAutoCompute=OFF, minAutoCompute=OFF) 197 | 198 | session.viewports['Viewport: 1'].odbDisplay.contourOptions.setValues(maxValue=1e3, minValue=0) 199 | 200 | 201 | # Ensure the contour plot uses the defined spectrum range 202 | #session.spectrum('Blue to Red').setValues(maxValue=5e11, minValue=1e8) 203 | 204 | # Save the viewport image 205 | image_filename = '/{}.png'.format(filename) 206 | session.printToFile(fileName=image_filename, format=PNG, canvasObjects=(session.viewports['Viewport: 1'],)) 207 | 208 | 209 | # Open the .odb file 210 | odb = openOdb(path='C:/temp/Job-1.odb') 211 | 212 | # Access the last frame of the last step 213 | lastFrame = odb.steps['Step-1'].frames[-1] 214 | 215 | # Access the stress field output 216 | stressField = lastFrame.fieldOutputs['S'] 217 | 218 | # Initialize lists to store the von Mises, tensile, and compressive stresses 219 | vonMisesList = [] 220 | tensileList = [] 221 | compressiveList = [] 222 | 223 | # Loop through each value in the stress field output 224 | for stressValue in stressField.values: 225 | # Update von Mises stress list 226 | vonMisesList.append(stressValue.mises) 227 | 228 | # Get the components of the stress tensor 229 | tensor = stressValue.data 230 | 231 | # Calculate principal stresses 232 | stressTensor = np.array([[tensor[0], tensor[3], tensor[5]], [tensor[3], tensor[1], tensor[4]], [tensor[5], tensor[4], tensor[2]]]) 233 | 234 | principalStresses = np.linalg.eigvalsh(stressTensor) 235 | 236 | # Update tensile and compressive stress lists 237 | tensileList.append(principalStresses.max()) 238 | compressiveList.append(principalStresses.min()) 239 | 240 | # Sort the stress lists 241 | vonMisesList.sort(reverse=True) 242 | tensileList.sort(reverse=True) 243 | compressiveList.sort() 244 | 245 | # Compute the average of the top 10 highest values, ignoring the top no. 1 highest value 246 | averageVonMises = np.mean(vonMisesList[1:11]) 247 | averageTensile = np.mean(tensileList[1:11]) 248 | averageCompressive = np.mean(compressiveList[1:11]) 249 | 250 | # Append the results to the beam_results.txt file 251 | results_file_path = 'torsionResults.txt' 252 | with open(results_file_path, 'a') as f: 253 | f.write("{}, VM = {}, Tensile = {}, Compressive = {}\n".format(filename, averageVonMises, averageTensile, averageCompressive)) 254 | 255 | # Close the .odb file 256 | odb.close() 257 | 258 | except Exception as e: 259 | # Log the error and continue with the next file 260 | with open('/error_log.txt', 'a') as error_file: 261 | error_file.write("Error processing file {}: {}\n".format(fileName, str(e))) 262 | print("Error processing file {}: {}\n".format(fileName, str(e))) 263 | 264 | # Read the list of text files 265 | instructions_path = '/' 266 | with open(os.path.join(instructions_path, 'fontsToTest.txt'), 'r') as file_list: 267 | txt_files = file_list.readlines() 268 | 269 | # Ensure the results file exists 270 | results_file_path = 'torsionResults.txt' 271 | if not os.path.exists(results_file_path): 272 | with open(results_file_path, 'w') as f: 273 | f.write("") 274 | 275 | # Iterate through each text file and run the Beam function 276 | for txt_file in txt_files: 277 | txt_file_path = os.path.join(instructions_path, txt_file.strip()) 278 | Beam(txt_file_path) 279 | 280 | -------------------------------------------------------------------------------- /4. Results and graphs/allProfilesSmall.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AtomicFrontierCode/typefaces/ef4c5a3713b993732bdab333b42eac4ea08fb200/4. Results and graphs/allProfilesSmall.zip -------------------------------------------------------------------------------- /4. Results and graphs/showGraphs.py: -------------------------------------------------------------------------------- 1 | import os 2 | import csv 3 | import difflib 4 | import numpy as np 5 | from PIL import Image, ImageTk 6 | import matplotlib.pyplot as plt 7 | from matplotlib.offsetbox import OffsetImage, AnnotationBbox 8 | import matplotlib.gridspec as gridspec 9 | import tkinter as tk 10 | 11 | # === USER SETTINGS === 12 | loading = "Bending" 13 | show = "A" 14 | dataSource = r"normalisedResults.txt" 15 | imageFolder = r"allProfilesSmall" 16 | 17 | # === CONSTANTS === 18 | VALID_LOADS = ["bending", "buckling", "torsion"] 19 | ALPHABET_ORDER = [ 20 | "A", "B", "C", "D", "E", "F", 21 | "G", "H", "H_rot", "I", "J", "K", 22 | "L", "M", "N", "O", "P", "Q", 23 | "R", "S", "T", "U", "V", "W" 24 | ] 25 | 26 | # === DATA LOADING === 27 | def load_data(file_path): 28 | data = [] 29 | with open(file_path, newline='', encoding='utf-8') as f: 30 | reader = csv.DictReader(f) 31 | for row in reader: 32 | row['mass'] = float(row['mass']) 33 | for load in VALID_LOADS: 34 | row[load] = float(row[load]) 35 | data.append(row) 36 | return data 37 | 38 | def normalize_loading(value): 39 | value = value.strip().lower() 40 | return difflib.get_close_matches(value, VALID_LOADS, n=1, cutoff=0.6)[0] 41 | 42 | def normalize_show(value): 43 | value = value.strip().upper() 44 | if value == "H_ROT": 45 | return "H_rot" 46 | return value 47 | 48 | # === IMAGE WINDOW === 49 | class ImageWindow(tk.Tk): 50 | def __init__(self): 51 | super().__init__() 52 | self.title("Beam Profile") 53 | self.label = tk.Label(self) 54 | self.label.pack() 55 | self.tk_image = None 56 | 57 | def show(self, path, rotate=False): 58 | if os.path.exists(path): 59 | img = Image.open(path) 60 | if rotate: 61 | img = img.rotate(90, expand=True) 62 | self.tk_image = ImageTk.PhotoImage(img) 63 | self.label.configure(image=self.tk_image) 64 | 65 | # === PLOT HELPERS === 66 | def get_image_path(font, char): 67 | return os.path.join(imageFolder, f"{font}_{char}.png") 68 | 69 | def setup_hover(fig, ax, points, labels, paths, image_win, rotate_flags): 70 | tooltip = ax.annotate("", xy=(0, 0), xytext=(10, 10), textcoords="offset points", 71 | bbox=dict(boxstyle="round", fc="white"), 72 | arrowprops=dict(arrowstyle="->")) 73 | tooltip.set_visible(False) 74 | 75 | def on_move(event): 76 | vis = False 77 | for i, pt in enumerate(points): 78 | cont, _ = pt.contains(event) 79 | if cont: 80 | x, y = pt.get_xdata()[0], pt.get_ydata()[0] 81 | tooltip.xy = (x, y) 82 | tooltip.set_text(labels[i]) 83 | tooltip.set_visible(True) 84 | image_win.show(paths[i], rotate=rotate_flags[i]) 85 | vis = True 86 | break 87 | if not vis: 88 | tooltip.set_visible(False) 89 | fig.canvas.draw_idle() 90 | 91 | fig.canvas.mpl_connect("motion_notify_event", on_move) 92 | 93 | def plot_single_letter(data, letter, load, image_win): 94 | fig, ax = plt.subplots() 95 | ax.set_xlim(0, 1) 96 | ax.set_ylim(0, 1) 97 | ax.set_title(f"Glyph: {letter}") 98 | ax.set_xlabel("Mass (Normalized)") 99 | ax.set_ylabel(f"{load.capitalize()} Load (Normalized)") 100 | ax.grid(True) 101 | 102 | filled = next((d for d in data if d["fontName"] == "filled"), None) 103 | control = next((d for d in data if d["fontName"].lower().startswith("control")), None) 104 | 105 | if filled: 106 | ax.plot([0, filled['mass']], [0, filled[load]], color='black', lw=2) 107 | if load == 'bending' and control: 108 | slope = control[load] / control['mass'] 109 | ax.plot([0, 1], [0, slope], color='orange', lw=2) 110 | 111 | points, labels, paths, rotates = [], [], [], [] 112 | 113 | for row in data: 114 | match = (row['letter'].upper() == letter.upper()) or \ 115 | (letter == "H_rot" and row['letter'].upper() == "H") 116 | if match or row['fontName'] in ["filled", "ControlIPE100"]: 117 | x, y = row['mass'], row[load] 118 | 119 | if row['fontName'] == "filled": 120 | pt, = ax.plot(x, y, 'o', color='black', alpha=1.0, markersize=7) 121 | elif row['fontName'].lower().startswith("control"): 122 | pt, = ax.plot(x, y, 'o', color='orange', alpha=0.8, markersize=7) 123 | else: 124 | pt, = ax.plot(x, y, 'o', color='black', alpha=0.5, markersize=6) 125 | 126 | points.append(pt) 127 | labels.append(row['fontName']) 128 | paths.append(get_image_path(row['fontName'], row['letter'])) 129 | rotates.append(letter == "H_rot") 130 | 131 | setup_hover(fig, ax, points, labels, paths, image_win, rotates) 132 | plt.show() 133 | 134 | 135 | # === RUN === 136 | data = load_data(dataSource) 137 | loading = normalize_loading(loading) 138 | show = normalize_show(show) 139 | 140 | # Create the GUI window and call plotting inside it 141 | app = ImageWindow() 142 | 143 | def start_plot(): 144 | plot_single_letter(data, show, loading, app) 145 | 146 | app.after(100, start_plot) 147 | app.mainloop() 148 | 149 | 150 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 AtomicFrontierCode 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | I-beam analysis code for the "Which typeface's capital "I" makes the best I-beam?" video. 2 | Video link: https://youtu.be/AQJDKs8jsjk 3 | 4 | Hacked together in Python because I'm lazy and it works. 5 | 6 | == To play around with data == 7 | 8 | Download the <<4.Results and Graphs>> folder. The 'showGraphs.py' file will plot the data storred in 'normalisedData.txt'. 9 | If you also download font images (really large set of files, sorry) then it should also plot a preview. Make sure to unzip the file and make sure the names/ file locations are set correctly. I've restricted it to just a single letter since it tends to be unstable if you have the full alphabet. 10 | 11 | == To test your own fonts == 12 | 13 | This is a little harder as you'll need an Abaqus license. There might be information about getting a trial / university copy at https://www.3ds.com/products/simulia/abaqus/cae. I don't know, I just used my university lab computer (sorry people who actually have important research). 14 | 15 | You'll then want to run through the following folders in order... 16 | 17 | <1. Raw font profiles> 18 | This is a collection of the origional beam profiles. The included 'generateFonts.py' should generate a bunch based on what fonts you have installed. It's a little broken though so you can also just insert your own 1000px x 1000px jpg. 19 | 20 | <2. Vectorised graphics> 21 | This is a collection of the beam profiles simplified as vectors. The 'generateVectors.py' should be able to take the raw fonts from the previous stage and convert them into vectors. 22 | 23 | <3. Abaqus simulation> 24 | This is the step that needs Abaqus. If somone wants to substitute it for an open source python version that would be awesome... I just found Abaqus' record feature really useful and was able to hack together a vector-to-beam conversion. 25 | The 'fontsToTest.txt' file contains the list of fonts you want to test. Modify as needed. 26 | Run the 'abaqusBending.py', 'abaqusBuckling.py', 'abaqusTorsion.py', 'abaqusTension.py' of your choice. 27 | The function saves an output image and the stress data. It doesn't save the full results because that produces far too much data. 28 | The 'cleanData.py' function should combine and clean your raw data outputs. 29 | 30 | <4. Results and graphs> 31 | Finally you should be able to run the 'showGraphs.py' file. 32 | 33 | == Modifying and updating == 34 | 35 | If you have an interrest please feel free to modify an update as you see fit. I'm bad at maintaining code and never want to see an I-beam again. I wish you the best! 36 | - James 37 | --------------------------------------------------------------------------------