Find McMaster Details with AutoHotkey

Using AutoHotkey to open a URL.

AutoHotkey script for re-assigning the F6 function key to look up a selected part number on McMaster-Carr.

As you can see below, we’re able to highlight a McMaster part number with and then hit F6 which opens up the appropriate webpage.

McMaster

Prerequisites:

  • Google Chrome installed
  • AutoHotkey installed
  • AHK Script is running

Save the follow script as a .ahk file and execute it.  If you want your .ahk scripts to run immediately on system startup, create shortcuts to the .ahk files in your systems startup directory.  For me on Windows 7, this is in C:\Users\Craig\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup.


F6:: ; Open McMaster based on selected part number
 Clipboard =
 Send ^c
 ClipWait ;wait for clipboard to have content
 Run, chrome.exe https://www.mcmaster.com/#%clipboard%/

Thanks to Tom Sherman, McMaster now also supports OpenSearch!

Web Scraping for Engineers

Scrape 3D models from McMaster-Carr with Python & Selenium.

Here is a script for fetching 3D models from McMaster-Carr using Selenium.

Make sure Chromedriver is in the same directory as your .py file.  The 3D models will be downloaded to your default Downloads directory.

# -*- coding: utf-8 -*-
"""
Scrape 3D models from McMaster-Carr.

Requirements: Chromedriver.exe is in the same folder as this script.
"""
from selenium import webdriver
import time

test_part_numbers=['98173A200', '7529K105', '93250A440']

def fetch_model(part_numbers, delay=3):
    if type(part_numbers) is str:
        part_numbers=[part_numbers]
    
    #Start browser
    options = webdriver.ChromeOptions()
    driver = webdriver.Chrome(chrome_options=options)
    
    #For each part number
    for part_number in part_numbers:
        driver.get('https://www.mcmaster.com/#' + str(part_number) + '/')
        #Pause for page to load
        time.sleep(delay)    
        #Find and Click submit button
        try:
            try:
                submit_button=driver.find_element_by_class_name("button-save--sidebyside")
            except:
                submit_button=driver.find_element_by_class_name("button-save--stacked")
            finally:
                submit_button.click()
        except:
            print('No button found or other error occured')
        finally:
            time.sleep(delay)
            
    driver.close()
    driver.quit
    
fetch_model(test_part_numbers)

Find and Zip All .py Files

Create a Zip folder including all .py files on your computer.

This is a program for finding all .py files in a directory and zipping them into a single folder.  We can substitute any file extension (.doc, .xlsx, .dxf, etc.) that we’d like to search for, but in our example we’ll use .py

To find all of the files in a given directory, we’ll import and use functionality from the FolderHierarchy script.  Be sure to save this file in the same directory as the script below.

Next, we’ll filter our FolderHierarchy results to include just the .py files.  Then we simply loop through the filtered DataFrame and add each file to a zip folder using the zipfile module.

# -*- coding: utf-8 -*-
"""
Purpose: Copy all files of a given filetype within a folder, zip them,
         and save.

Input: 1. A folderpath to search in.
       2. File type/File Extension to search for.
Output: A zip file containing all of the files with the specified file extension
        that were folder in the specified input folder.
"""
import pandas as pd
import zipfile
import FolderHierarchy

#Define what filetype to search for
extension=".py"

#Execute 'FolderHierarchy' program (in same directory)
FolderHierarchy
results=FolderHierarchy.all_levels

#Filtering for just files with the defined file extension
found_files=pd.concat([results[level][results[level]['Path'].str.contains(extension,
                                  regex=False)] for level in results])

#Copy and zip all of the files found.
new_zip=zipfile.ZipFile('all_'+extension+ '_files'+'.zip',mode='w')

#Writing to zip (https://pymotw.com/2/zipfile/)
for file in found_files['Path']:
    new_zip.write(file,arcname=file.split('\\')[-1])
new_zip.close()

print('Found '+str(len(found_files))+' '+extension+' files.')

Automated Email with Python

Automated email notifications and task tracking system.

This article explains how to use a Python script in conjunction with a simple Action Tracking spreadsheet to create an automated email notifications and task-tracking system for your team.

To begin, let’s setup our “ActionTracker” spreadsheet as shown below:

ActionTracker_Overview

We can use the expression “=IF(ISBLANK(H2)=TRUE,”Active”,”Closed”)” in our Status column to acknowledge when a date has been entered in the “Completion Date” column.  This will help our script later on.

The “Days Open” column can be calculated using “=IF(ISBLANK(H2)=FALSE,H2-F2,TODAY()-F2)”.  As your list grows, be sure to drag down your formulas.

It can be helpful to apply conditional formatting here in order to see which items are “Open” and late, so that we know which items we expect to send notifications about.  This can be accomplished by the expression shown below, but it is not a necessary step.  Again, remember to update your applicable range as your list grows.

ConditionalFormatting

On our “Email” tab, we’ll list our unique assignees by name and add their email addresses (separated by a comma and a space) in column B.

EmailTab

In order to minimize errors, we can apply a Data Validation rule to our “Assignee” column on the “ActionList” tab.  We’ll select all of the unique names on our “Email” tab as the Source validation criteria.  New emails can easily be added to this list, however, we must update our Source range.

DataValidation

Here’s a download link for the ActionTracker template.

Next, we’ll use the following Python script to send automated email notifications to our team for any Open actions that are open for more than three days.  The three day threshold can be easily adjusted in line #50 of the script below.

Note: In order to allow the script to access your gmail account, make sure that your less secure app access settings are currently turned on.

import smtplib
import pandas as pd
import sys
from tkinter import Tk
from tkinter import filedialog
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart

#Define email login information.
from_email="_____@gmail.com" #Replace with your email address.  Must be gmail.
pw="_____" #Replace with gmail password.

#Select file to use.
Tk().withdraw()
filepath=filedialog.askopenfilename(defaultextension='*.xlsx',
                                    filetypes=[('.xlsx files','*.xlsx'),
                                               ('All files','*.*')],
                                    initialdir=r'C:\Users')
if filepath=="." or filepath=="":
    sys.exit(0)

#Import ActionTracker
ActionTracker = pd.DataFrame(pd.read_excel(filepath,sheetname='ActionList',
                                           parse_cols='A:E'))
ActionTracker_maxrow=len(ActionTracker)
status=ActionTracker.iloc[:,0]
LineItem=ActionTracker.iloc[:,1]
action=ActionTracker.iloc[:,2]
person=ActionTracker.iloc[:,3]
DaysOpen=ActionTracker.iloc[:,4]

#Import Email Addresses by name
EmailList=pd.DataFrame(pd.read_excel(filepath,sheetname='Email',index_col=0,
                                     parse_cols='A:B'))

#Establish connection to gmail server, login.
server = smtplib.SMTP('smtp.gmail.com',587)
server.starttls()
server.login(from_email, pw)

msg=MIMEMultipart('alternative')
msg.set_charset('utf8')
msg['FROM']=from_email

#Initialize a list of submittals with invalid email addresses
invalid_addresses=[]

#Send emails to late Action Tracker assignees
for i in range(0,ActionTracker_maxrow):
    if status[i]=='Active' and DaysOpen[i]>3:
        print('Active Line Item #'+str(LineItem[i])+': '+person[i])
        msg=MIMEText("Action Tracker Line Item #" + str(LineItem[i]) + " has been open for " +
                     str(DaysOpen[i]) + " days.\n\n" + str(action[i]) +
                     "\n\nPlease take action.",_charset="UTF-8")
        msg['Subject']="Open Action #" + str(LineItem[i])
        msg['TO']=str(EmailList.iloc[EmailList.index.get_loc(person[i]),0])
        try:
            server.sendmail(from_email, msg['TO'].split(","),
                            msg.as_string())
        except smtplib.SMTPRecipientsRefused:
            invalid_addresses.append(LineItem[i])
            print('Line Item #' + str(LineItem[i]) + 'has an invalid email address.')

if len(invalid_addresses) != 0:
    for i in range(0,len(invalid_addresses)):
        invalid_addresses[i]=invalid_addresses[i].strip('\xa0')
    try:
        if len(invalid_addresses)==1:
            msg=MIMEText(str(invalid_addresses) +
            " has an invalid email address associated with the responsible party.",
            _charset="UTF-8")
        else:
            msg=MIMEText(str(invalid_addresses) +
                             " have invalid email addresses associated with the responsible parties.",
                             _charset="UTF-8")
        msg['Subject']='Invalid Email Addresses'
        msg['TO']=str(from_email)
        server.sendmail(from_email, msg['TO'].split(","),
                        msg.as_string())
    except smtplib.SMTPRecipientsRefused:
        print('Invalid Email Address notification email failed.')

server.quit()

 

And that’s it.  Full automation can be achieved by hard-coding in the file location and using Windows Task Scheduler to execute the Python script.