MayaからExcelを操作しないといけない場面がありそうだったので、
pythonとclassの勉強の為にExcel操作クラスを作ってみました。
## -*- coding: utf-8 -*-
import site
import os
import re
site.addsitedir(os.path.dirname(os.path.abspath(__file__)) + r'\win32maya2015')
import win32com.client as com
# ------------------------------------------------
# 数字をアルファベットに変換 1 -> A 27 -> AA
def num2char(num):
quotient, remainder = divmod(num, 26)
chars = ''
if quotient > 0:
chars = chr(quotient + 64)
if remainder > 0:
chars = chars + chr(remainder + 64)
return chars
# ------------------------------------------------
# アルファベットを数字に変換 A -> 1 AA -> 27
def char2num(chars):
num = 0
for c in chars:
num = num * 26 + (ord(c) - 64)
return num
'''--------------------------------------------------------------------------------'''
class App(object):
XLMAXIMIZED = -4137
def __init__(self, visible=False):
self.__excel_app = com.Dispatch("Excel.Application")
#警告メッセージを表示しない。
self.__excel_app.DisplayAlerts = False
self.__excel_app.Visible = visible
if(visible==True):
self.__excel_app.WindowState = self.XLMAXIMIZED
self.__excel_app.Visible = True
# ------------------------------------------------
# Excelブックを新規作成
def add(self):
book = self.__excel_app.Workbooks.Add()
return Workbook(book)
# ------------------------------------------------
# エクセルファイルを開く
def open(self, path, sheet=None, readonly=False):
book = self.__excel_app.Workbooks.Open(path, 2, readonly)
return Workbook(book)
# ------------------------------------------------
# エクセルプロセスを終了
# (閉めにこれをやらないとプロセスに__excel_appがいっぱい残るので注意)
def quit(self):
self.__excel_app.Application.Quit()
'''--------------------------------------------------------------------------------'''
class Workbook(object):
def __init__(self, book):
self.__book = book
# ------------------------------------------------
# nameをアクセサ付きのプロパティとして登録
sheets = property(doc='sheets property')
@sheets.setter
def sheets(self):
pass
@sheets.getter
def sheets(self):
return self._get_sheet_list()
@sheets.deleter
def sheets(self):
pass
# ------------------------------------------------
# ブックを閉じる
def close(self):
self.__book.Close()
self.__book = None
# ------------------------------------------------
# 上書き保存
def save(self):
self.__book.Save()
# ------------------------------------------------
# 名前をつけて保存
def save_as(self, path):
self.__book.SaveAs(path)
# ------------------------------------------------
# ブック内のシート一覧を取得する
def _get_sheet_list(self):
list = []
for value in self.__book.Worksheets:
list.append(Sheet(value))
return list
# ------------------------------------------------
# 名前でシートを検索
def find_sheet(self, name):
if isinstance(name, str):
name = name.decode('ShiftJIS')
for value in self.sheets:
if value.name == name:
return value
return None
# ------------------------------------------------
# シート削除(シート名、もしくは番号で指定)
def delete_sheet(self, target):
if type(target) is str:
if not self.find_sheet(target):
print u"[error]Designation of the sheet does not exist"
return None
if type(target) is int:
if target < 1 or target > len(self.sheets):
print u"[error]Designation of the sheet does not exist"
return None
self.__book.Worksheets(target).Delete()
# ------------------------------------------------
# シート作成
def add_sheet(self, name):
ws = self.__book.Worksheets.Add()
ws.Name = name
return Sheet(ws)
'''--------------------------------------------------------------------------------'''
class Sheet(object):
XLDOWN = -4121
XLUP = -4162
XLTOLEFT = -4159
XLTORIGHT = -4161
def __init__(self, sheet):
self.__sheet = sheet
self._name = sheet.Name
self._index = sheet.Index
# ------------------------------------------------
# nameをアクセサ付きのプロパティとして登録
name = property(doc='name property')
@name.setter
def name(self, value):
self._name = value
self.__sheet.Name = value
@name.getter
def name(self):
return self._name
@name.deleter
def name(self):
pass
# ------------------------------------------------
# indexをアクセサ付きのプロパティとして登録
index = property(doc='index property')
@index.setter
def index(self):
pass
@index.getter
def index(self):
return self._index
@index.deleter
def index(self):
pass
# ------------------------------------------------
# セルを取得
def get_cell(self, x, y):
return Cell(self.__sheet, x, y)
# ------------------------------------------------
# 指定列(x)の最終行を取得する
def max_row(self, x):
if type(x) is str:
x = char2num(x)
return self.__sheet.Cells(65536, x).End(self.XLUP).Row
# ------------------------------------------------
# 指定行(y)の最終列を取得する
def max_colum(self, y):
return self.__sheet.Cells(y, 256).End(self.XLTOLEFT).Column
# ------------------------------------------------
# 行から指定の値のセルを取得する(横方向に調べる)
def find_cell_from_rows(self, y, search_value, start_x=1):
cells = []
if isinstance(start_x, str):
start_x = char2num(start_x)
max_x = self.max_colum(y)
for x in range(start_x, max_x):
c = self.__comparison_detail(x, y, search_value)
if c is not None:
cells.append(c)
return cells
# ------------------------------------------------
# 列から指定の値のセルを取得する(縦方向に調べる)
def find_cell_from_columns(self, x, search_value, start_y=1):
cells = []
max_y = self.max_row(x)
for y in range(start_y, max_y):
c = self.__comparison_detail(x, y, search_value)
if c is not None:
cells.append(c)
return cells
# ------------------------------------------------
# セルの内容比較
def __comparison_detail(self, x, y, search_value):
#cell_value = self.__sheet.Cells(y, x).Value
cell = Cell(self.__sheet, x, y)
cell_value = cell.text
if isinstance(search_value, str):
search_value = search_value.decode('ShiftJIS')
if cell_value is None:
return None
f = re.search(search_value, cell_value)
if f is None:
return None
return cell
'''--------------------------------------------------------------------------------'''
class Cell(object):
def __init__(self, sheet, x, y):
self.__sheet = sheet
self.__cell = sheet.Cells(y, x)
self.__range = sheet.Range(num2char(self.x) + str(self.y))
# ------------------------------------------------
# xをアクセサ付きのプロパティとして登録
x = property(doc='x property')
@x.setter
def x(self):
pass
@x.getter
def x(self):
return self.__cell.Column
@x.deleter
def x(self):
pass
# ------------------------------------------------
# yをアクセサ付きのプロパティとして登録
y = property(doc='y property')
@y.setter
def y(self):
pass
@y.getter
def y(self):
return self.__cell.Row
@y.deleter
def y(self):
pass
# ------------------------------------------------
# valueをアクセサ付きのプロパティとして登録
value = property(doc='value property')
@value.setter
def value(self, value):
self.__cell.Value = value
@value.getter
def value(self):
return self.__cell.Value
@value.deleter
def value(self):
pass
# ------------------------------------------------
# textをアクセサ付きのプロパティとして登録
text = property(doc='text property')
@text.setter
def text(self):
pass
#表示そのままの文字列を取得するため、RangeオブジェクトにあるTextプロパティを戻す
@text.getter
def text(self):
return self.__range.Text
@text.deleter
def text(self):
pass
# ------------------------------------------------
# heightをアクセサ付きのプロパティとして登録
height = property(doc='height property')
@height.setter
def height(self, value):
self.__cell.RowHeight = value
@height.getter
def height(self):
return self.__cell.RowHeight
@height.deleter
def height(self):
pass
# ------------------------------------------------
# widthをアクセサ付きのプロパティとして登録
width = property(doc='width property')
@width.setter
def width(self, value):
self.__cell.ColumnWidth = value
@width.getter
def width(self):
return self.__cell.ColumnWidth
@width.deleter
def width(self):
pass
# ------------------------------------------------
# colorをアクセサ付きのプロパティとして登録
bg_color = property(doc='bg_color property')
@bg_color.setter
def bg_color(self, value):
#とりあえずcolorインデックスでの指定のみ対応
self.__cell.Interior.ColorIndex = value
@bg_color.getter
def bg_color(self):
pass
@bg_color.deleter
def bg_color(self):
pass
使い方としては以下のような感じですね。
## -*- coding: utf-8 -*-
#まずはインポート
import Excel
#------------------------------------------------------------------------------
#◆Appクラス
#これでエクセルを立ち上げる
excel = Excel.App()
#既存のエクセルファイルを開く→Workbookクラスが戻ってくる
book = excel.open(r"エクセルファイルのパス")
#新しいブックを開く場合はこう→Workbookクラスが戻ってくる
book = excel.add()
#すべてが終わったらエクセルを終了しましょう
excel.quit()
#------------------------------------------------------------------------------
#◆Workbookクラス
#これでシートのリストが取得できる
book.sheets
#ワークブック自身を閉じる
book.close()
#上書き保存
book.save()
#名前を付けて保存
book.save_as(r"セーブするパス")
#シートを名前で検索→存在していた場合シートクラスのインスタンスが戻される
sheet = book.find_sheet("Sheet1")
#シートを削除
book.delete_sheet(名前もしくは番号を指定)
#シートを作成→シートクラスのインスタンスが戻される
sheet = book.add_sheet()
#------------------------------------------------------------------------------
#◆Sheetクラス
#シート名
sheet.name
#シート名変更
sheet.name = "ほげほげ"
#シートのインデックス(番号)
sheet.index
#セルの取得→Cellクラスが戻る
#x : 列(横方向) A〜Zもしくは数字を指定
#y : 行(縦方向) 数字を指定
sheet.get_cell(x ,y)
#指定列(x)の最終行を取得する
sheet.max_row(x)
#指定行(y)の最終列を取得する
sheet.max_colum(y)
#行から指定の値のセルを取得する(横方向に調べる)→Cellクラスが戻る
#y : 縦方向 数字を指定
#search_value : 対象の値(調べるのは表示されている文字列。内部の式などは見ない)
#start_x : 何列目からサーチを開始するか。でふぉは1から
#現時点ではsearch_valueが含まれていれば検出対象となる。完全一致などのしては出来ない
sheet.find_cell_from_rows(y, search_value, start_x)
#列から指定の値のセルを取得する(縦方向に調べる)
#上記とほとんど一緒
sheet.find_cell_from_columns(x, search_value, start_y)
#------------------------------------------------------------------------------
#◆Cellクラス
#メソッドはなし。
#セルの列番号(横方向)
#読み取り専用
cell.x
#セルの行番号(縦方向)
#読み取り専用
cell.y
#セルの内容
#セルの内容によって戻ってくる型が違う。場合によってはちょっと扱いづらいかも?
#1と表示されているセルでも1.0として戻ってきたりする
cell.value
#セルのテキスト
#表示されている値を文字列として取得
#1と表示されているセルは1が戻ってくる。
#読み取り専用
cell.text
#セルの縦幅
cell.height
#セルの横幅
cell.width
#セルの背景色
#設定する場合は番号を指定。そのうち色直指定できるように拡張したい。
cell.bg_color
pywin32経由でのExcel操作のコードです。
冒頭のpywin32をインポートする部分は書き換えてくださいね。
コードの中身は基本的にはこんなかんじ↓(流石わかりやすい!)
http://flame-blaze.net/archives/4887
ただし、今回の自分の目的の様にMayaから標準のpywin32モジュールを利用しようとしてもできません。。。
オー トデスク帝国によると「Maya2013以降のPythonは、通常配布されているPythonとは違うバージョンのVisual Studioでコンパルされているため外部モジュールが利用できません。 」とのことで、Mayaが使っている同じバージョンのVisual Studioで再コンパイルする必要があるそうです(メンドクサイ…)
https://knowledge.autodesk.com/ja/support/maya/troubleshooting/caas/sfdcarticles/sfdcarticles/kA230000000tsRX.html
少し前まで、ネットでMaya各バージョンのpywin32モジュールを配ってくれていた方がいたのですが、今はなにやらページが消えております…。
メンドクサイですが、それぞれご用意を…。 (;´A`)
pythonでのExcel操作モジュールは「python-excel」や「openpyxl」があるのですが、
python-excelは書き込みと読込モジュールに分かれていて、かつ2007以降の*.xlsxは非対応ですし
もう一つのopenpyxlは逆に*.xlsxにしか対応してない…(´・ω・`)
新旧のフォーマットが混在した状態の環境だとどちらも使えなさそう…ってことに。
書いたはいいけど、自分でもまだ使ってないので機能不足状態ですが
もし何かのお役にたてれば幸いですーヾ(*´∀`*)ノ゛