1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Claudeと一緒に3Dプリンター用フィラメント管理アプリを作った話

Posted at

3D.jpg

フィラメントブログ_画像.png

はじめに

3Dプリンターで色々なものを作っていると、「あのプロジェクトでフィラメントをどれだけ使ったっけ?」「残りのフィラメントはあとどれくらい?」といった疑問が出てきます。

そこで、フィラメントの使用量を記録・管理できるWebアプリをClaude(AI)に相談しながら作ってみました。Google Apps ScriptとGoogle Spreadsheetで動く、スマホからでも使えるシンプルなアプリです。

この記事では、アプリの概要から、最近追加した「過去データ自動反映機能」の実装、そして遭遇した罠までをまとめています。

どんなアプリを作ったか

基本的な機能

このアプリでできることは:

  1. フィラメント使用量の記録

    • 日付
    • プロジェクト名
    • フィラメント名
    • 使用量(グラム)
    • 処理時間(任意)
  2. 現在使用中のフィラメント情報の表示

    • 現在使っているフィラメント名
    • 残量
    • 計算範囲(どの行からどの行まで集計しているか)
  3. フィラメント交換機能

    • 新しいフィラメントに交換したとき
    • 初期量の設定
    • 計算範囲の更新
  4. 過去データからの選択入力

    • 過去に使ったプロジェクト名を選択リストから選べる
    • 過去に使ったフィラメント名も選択リストから選べる
    • もちろん新規入力もできる

技術構成

  • バックエンド: Google Apps Script
  • データベース: Google Spreadsheet
  • フロントエンド: HTML/JavaScript
  • デプロイ: Google Apps ScriptのWebアプリとして公開

スマホでの使用を想定して、UIは大きめのボタンとフォントにしています。

なぜGoogle Apps Scriptを選んだか

  • 無料で使える
  • Google Spreadsheetをデータベースとして使える
  • デプロイが簡単
  • スマホからもアクセスできる
  • データがGoogleアカウントに紐付いているので安心

Claudeとの開発プロセス

最初は「こんなアプリが欲しい」という要望をClaudeに伝えて、基本的なコードを書いてもらいました。

その後、実際に使ってみながら:

  • 「このUIはスマホだと押しにくい」→ フォントサイズとボタンサイズを調整
  • 「毎回フィラメント名を入力するの面倒」→ 過去のデータから選択できる機能を追加
  • 「フィラメント交換のときの設定が分かりにくい」→ モーダルを追加

といった感じで、少しずつ改善してきました。

実際に記録しているプロジェクト例:

  • ケーブルホルダー
  • スマホスタンド
  • 小物入れ
  • フックやクリップ類
  • ガジェット系のパーツ

など、日常で使う小物が中心です。

AIに相談しながら開発する良いところは:

  • 実装方法が分からなくてもとりあえず動くものが作れる
  • エラーが出たらログを見せて相談できる
  • 「こうしたい」という要望を伝えればコードを書いてくれる

最近追加した機能:過去データの自動反映

機能追加の背景

アプリを使い始めてしばらくして、「同じプロジェクトを何度も印刷することが多い」ことに気づきました。

例えば:

  • テスト印刷で同じモデルを何度も印刷
  • 複数個必要なパーツを繰り返し印刷
  • 設定を変えて再印刷

こういうとき、プロジェクト名は過去のリストから選べるのですが、使用量と処理時間は毎回手入力でした。でも実際には、前回と同じ値を入力することがほとんどです。

そこで、「プロジェクト名を選択したら、前回そのプロジェクトで使った使用量と処理時間を自動で入力してくれたら便利だな」と思い、Claudeに相談して実装してもらいました。もちろん、変更したい場合は手動で書き直せるようにします。

実装方針

実装はシンプルで:

  1. GAS側で「指定されたプロジェクト名の最新データを取得する関数」を作る
  2. HTML側で「プロジェクト選択時に上記の関数を呼び出してフォームに反映する」

ただ、この実装中にいくつか罠がありました。後で詳しく書きます。

実装方法

GAS側:過去データを取得する関数

スプレッドシートから指定されたプロジェクト名の最新データを取得する関数を追加しました。

/**
 * 指定されたプロジェクトの最新データを取得
 */
function getProjectLatestData(projectName) {
  try {
    const spreadsheet = SpreadsheetApp.openById(SHEET_ID);
    const sheet = spreadsheet.getSheetByName('フィラメント');
    
    if (!sheet) {
      return { amount: '', time: '' };
    }
    
    const lastRow = sheet.getLastRow();
    if (lastRow <= 1) {
      return { amount: '', time: '' };
    }
    
    // getDisplayValues()で表示されている文字列をそのまま取得
    const displayData = sheet.getRange(2, 1, lastRow - 1, 5).getDisplayValues();
    
    // 後ろから検索して最新のデータを取得
    for (let i = displayData.length - 1; i >= 0; i--) {
      if (displayData[i][1] === projectName) {
        return {
          amount: displayData[i][3] || '',  // 使用量
          time: displayData[i][4] || ''      // 処理時間
        };
      }
    }
    
    return { amount: '', time: '' };
    
  } catch (error) {
    Logger.log('Error: ' + error.message);
    return { amount: '', time: '' };
  }
}

HTML側:プロジェクト選択時のイベントリスナー

プロジェクトを選択したときに、上記の関数を呼び出してデータを取得し、各フィールドに反映します。

// プロジェクト選択時に過去のデータを自動反映
projectSelect.addEventListener('change', () => {
  const selectedProject = projectSelect.value;
  if (selectedProject) {
    google.script.run
      .withSuccessHandler((data) => {
        if (data && data.amount) {
          document.getElementById('amount').value = data.amount;
        }
        if (data && data.time) {
          // 処理時間を分解して各フィールドに設定
          const timeParts = data.time.split(':');
          if (timeParts.length === 3) {
            document.getElementById('timeHours').value = parseInt(timeParts[0]) || 0;
            document.getElementById('timeMinutes').value = parseInt(timeParts[1]) || 0;
            document.getElementById('timeSeconds').value = parseInt(timeParts[2]) || 0;
          }
        }
      })
      .withFailureHandler((error) => {
        console.error('データ取得エラー:', error);
      })
      .getProjectLatestData(selectedProject);
  }
});

遭遇した罠

罠1: getValues() では時間データが日付オブジェクトになる

最初は getValues() でデータを取得していました。

const data = sheet.getRange(2, 1, lastRow - 1, 5).getValues();

しかし、スプレッドシートで「5:23:45」のように入力されている処理時間が、取得すると日付オブジェクト 1899-12-29T16:00:00.000Z のような形式になっていました。

これは、スプレッドシートが時間を内部的に日付オブジェクトとして扱っているためです。

罠2: 日付オブジェクトをそのままJSONで返すとnullになる

日付オブジェクトをそのままWebアプリから返そうとすると、JSONシリアライズに失敗して null が返ってきてしまいます。

最初はテスト関数では正常に動作しているのに、Webアプリから呼び出すと null になる、という現象に悩まされました。

実行ログを確認すると:

Found data: {"amount":18.79,"time":"1899-12-29T16:00:00.000Z"}

テスト関数ではデータが取れているのに、HTML側では:

Received data: null

罠3: 日付オブジェクトを手動で変換しても秒と分が消える

そこで、日付オブジェクトを手動で「時:分:秒」形式に変換するコードを書きました。

if (data[i][4] instanceof Date) {
  const hours = data[i][4].getHours();
  const minutes = data[i][4].getMinutes();
  const seconds = data[i][4].getSeconds();
  timeValue = hours + ':' + String(minutes).padStart(2, '0') + ':' + String(seconds).padStart(2, '0');
}

しかし、これでもスプレッドシートに「5:23:45」と入力されているのに「5:00:00」しか取得できませんでした。

日付オブジェクトとして取得した時点で、分と秒の情報が失われているようでした。

解決方法: getDisplayValues() を使う

結論として、getValues() ではなく getDisplayValues() を使うことで解決しました。

// getDisplayValues()で表示されている文字列をそのまま取得
const displayData = sheet.getRange(2, 1, lastRow - 1, 5).getDisplayValues();

違い:

  • getValues(): セルの内部データ(数値、日付オブジェクトなど)を取得
  • getDisplayValues(): スプレッドシートに表示されている通りの文字列を取得

getDisplayValues() を使えば、「5:23:45」と表示されているセルから「5:23:45」という文字列がそのまま取得できます。

これにより:

  1. 時間データを文字列として取得
  2. JSONで問題なく返せる
  3. HTML側で : で分割して時・分・秒に分解できる

実装後の動作

プロジェクトを選択すると:

// コンソールログ
Selected project: スマホスタンド
Received data: {amount: '125.5', time: '6:30:00'}
Setting amount: 125.5
Setting time: 6:30:00
Time parts: (3) ['6', '30', '00']
Setting hours: 6
Hours field value after set: 6
Setting minutes: 30
Minutes field value after set: 30
Setting seconds: 00
Seconds field value after set: 0

使用量「125.5g」と処理時間「6時間30分0秒」が自動で入力されます。

まとめ

学んだこと

Google Apps Scriptでスプレッドシートのデータを扱う際:

  • 時間や日付のセルデータを扱うときは getDisplayValues() を検討する
  • getValues() だと内部データ形式(日付オブジェクトなど)になる
  • 日付オブジェクトをWebアプリで返すときは注意が必要
  • 表示されている文字列をそのまま取得したいなら getDisplayValues() が便利

Claudeと開発した感想

今回の機能追加も含めて、Claudeに相談しながら開発してきましたが:

良かったこと:

  • 実装方法が分からなくてもとりあえず動くものが作れる
  • エラーが出たら原因を一緒に調べてもらえる
  • コードの説明をしてもらえるので勉強になる
  • 「こうしたい」という要望を伝えれば実装してくれる

注意すること:

  • 生成されたコードが常に正しいとは限らない(今回みたいに罠がある)
  • エラーログやコンソールログをちゃんと見せて相談する
  • 一度に大きな機能を追加するより、小さく試しながら進める

シンプルな機能追加のつもりが、意外と罠が多くて勉強になりました。でもClaudeと一緒にデバッグして解決できたので、一人で悩むより効率的でした。

同じような実装をする方、特にGoogle Apps Scriptで時間データを扱う方の参考になれば幸いです。

余談:3Dプリンターのモデルはどこから?

このアプリで記録しているプロジェクトの多くは、Printablesからダウンロードしたモデルを印刷したものです。

Printablesは無料で使える3Dモデル共有サイトで:

  • 高品質なモデルが豊富
  • 検索しやすい
  • 印刷設定やサポート材の情報も充実
  • コミュニティが活発

「こんなのが欲しいな」と思ったら、だいたいPrintablesで見つかります。日常の小物からガジェット系のパーツまで、本当に何でもあります。

3Dプリンターを持っている方、これから始める方にはおすすめのサイトです!

開発環境

  • Google Apps Script
  • Google Spreadsheet
  • HTML/JavaScript(フロントエンド)
  • Claude Sonnet 4.5(開発パートナー)

以上、フィラメントログアプリ開発記でした。次は何を追加しようかな。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?