【テクニカル・上級編】【上級プロ】アセンブリ図面(Drawing)と3Dモデルの同期維持:VBAによる自動バルーン付与とBOM番号順序の完全一致保証システム – SolidWorks VBA解析バイブル

スポンサーリンク

【上級プロ向け】SolidWorks VBAで図面と3Dモデルの同期を保証する:バルーンとBOM番号順序の完全一致システム構築

長年、SolidWorks VBAの世界で数々の自動化プロジェクトを牽引してきた経験から、皆さんに共有したい「真実」があります。それは、表層的なAPIの利用にとどまらず、オブジェクトのライフサイクル、メモリ管理、そしてシステム全体の整合性までを深く理解することの重要性です。特に、アセンブリ図面と3Dモデルの同期維持は、多くの現場で頭を悩ませる課題であり、その解決策は単なるスクリプト作成を超えた、システム設計の領域に踏み込む必要があります。

本稿では、アセンブリツリーの変更や部品の入れ替えに起因する図面側バルーンのズレを、SolidWorks APIを駆使して検知・修正し、さらにはBOM(部品表)番号の順序まで完全に一致させる、エンタープライズレベルの自動化システム構築について、その深淵なる知見を淡々と、しかし圧倒的な正確さで解説していきます。Windows APIの呼び出し、メモリ最適化、レガシー環境の保守、そしてシステム間連携といった、我々が直面する現実的な課題にも触れながら、技術の真髄を掘り下げていきましょう。

1. なぜ「同期維持」が重要なのか? 現場が抱える「見えないコスト」

多くのエンジニアは、3Dモデルの変更が図面に反映されない、あるいはバルーン番号がBOMと一致しないといった事象に遭遇した経験があるでしょう。しかし、これらの「小さなミス」が、積み重なると無視できない「見えないコスト」を生み出します。

  • 手戻り工数: 図面修正、部品表再作成、関係部署への連絡など、単純なミスを修正するために膨大な時間が浪費されます。
  • 品質低下: 不正確な図面や部品表は、製造ミス、組み立てミス、さらには顧客からのクレームに直結します。
  • 信頼性低下: 設計部門や製造部門間の信頼関係を損ない、コミュニケーションの障壁となります。

これらの問題は、設計変更の頻度が高い現代の製造業においては、避けては通れない課題です。VBA、いやSolidWorks APIを駆使して、この「同期維持」をシステムレベルで保証することは、我々エンジニアに課せられた責務と言えるでしょう。

2. アセンブリと図面の「関係性」をAPIで理解する

このシステム構築の根幹をなすのは、SolidWorks APIが提供するアセンブリと図面の間の「関係性」を正確に把握することです。

2.1. アセンブリ構造の把握:`AssemblyDoc` と `Component2`

アセンブリドキュメント (`AssemblyDoc`) は、その構造を階層的に管理しています。各部品やサブアセンブリは `Component2` オブジェクトとして表現され、その親子関係や配置情報が定義されています。

‘ アセンブリドキュメントのルートコンポーネントを取得
Dim swAssembly As SldWorks.AssemblyDoc
Set swAssembly = swApp.ActiveDoc

Dim swRootComponent As SldWorks.Component2
Set swRootComponent = swAssembly.GetComponentByName(“”) ‘ ルートコンポーネントは名前が空文字列

‘ ルートコンポーネントから子コンポーネントを再帰的に取得する関数例
Sub TraverseAssembly(parentComponent As SldWorks.Component2)
Dim children As Variant
children = parentComponent.GetChildren

If Not IsEmpty(children) Then
Dim child As SldWorks.Component2
For Each child In children
‘ ここで各子コンポーネントの情報を取得・処理する
Debug.Print “Component Name: ” & child.Name2 & “, Type: ” & child.GetType ‘ Typeは 1:Part, 2:Assembly, 3:Virtual Part

‘ 必要に応じて、子コンポーネントのさらに子を探索
TraverseAssembly child
Next
End If
End Sub

‘ 呼び出し例
TraverseAssembly swRootComponent

2.2. 図面とモデルの紐付け:`DrawingDoc` と `ModelDoc2`

図面ドキュメント (`DrawingDoc`) は、複数のビュー (`View`) を持ち、各ビューは特定の3Dモデル (`ModelDoc2`) のインスタンスを参照しています。この紐付け情報をAPIで取得することが、同期の起点となります。

‘ アクティブな図面ドキュメントを取得
Dim swDrawing As SldWorks.DrawingDoc
Set swDrawing = swApp.ActiveDoc

If Not swDrawing Is Nothing Then
Dim swViews As Variant
swViews = swDrawing.enumViews

If Not IsEmpty(swViews) Then
Dim swView As SldWorks.View
For Each swView In swViews
‘ ビューが有効か確認
If swView.IsVisible Then
Dim swModel As SldWorks.ModelDoc2
Set swModel = swView.ReferencedModel

If Not swModel Is Nothing Then
‘ 参照されているモデルのパスや名前を取得
Debug.Print “View: ” & swView.Name & “, Referenced Model: ” & swModel.GetPathName

‘ ビュータイプ(例: 断面図、投影図など)も取得可能
‘ Debug.Print “View Type: ” & swView.Type

‘ ここで、参照モデルとアセンブリ構造との関連性をチェックする処理を実装
End If
End If
Next
End If
Else
MsgBox “アクティブなドキュメントは図面ではありません。”, vbExclamation
End If

3. バルーンのズレを検知・修正するアルゴリズム

3.1. バルーンの「状態」をAPIで取得する

図面上のバルーン (`Balloon`) オブジェクトは、参照しているアセンブリコンポーネント (`Component2`) の情報を持っています。この参照関係が崩れた場合に、ズレが発生します。

‘ 図面ドキュメントとアセンブリドキュメントをそれぞれ取得
Dim swDrawing As SldWorks.DrawingDoc
Set swDrawing = swApp.ActiveDoc ‘ 現在アクティブなドキュメント

Dim swAssembly As SldWorks.AssemblyDoc
‘ 図面がアセンブリ図面であることを確認
If swDrawing Is Nothing Or swDrawing.GetType <> swDocumentTypes_e.swDocASSEMBLY Then
MsgBox “アクティブなドキュメントはアセンブリ図面ではありません。”, vbExclamation
Exit Sub
End If

‘ 図面が参照しているモデル(通常はアセンブリ)を取得
Dim swModelDoc As SldWorks.ModelDoc2
Set swModelDoc = swDrawing.GetModelDoc2

If swModelDoc Is Nothing Then
MsgBox “図面が参照するモデルが見つかりません。”, vbExclamation
Exit Sub
End If

‘ アセンブリドキュメントオブジェクトを取得
Dim swAssemblyDoc As SldWorks.AssemblyDoc
Set swAssemblyDoc = swModelDoc

‘ 図面内のすべてのバルーンを取得
Dim swBalloon As SldWorks.Balloon
Dim swBalloons As Variant
swBalloons = swDrawing.GetBalloons

If IsEmpty(swBalloons) Then
Debug.Print “この図面にはバルーンが設定されていません。”
Exit Sub
End If

Dim inconsistentBalloons As New Collection ‘ 不整合なバルーンを格納するコレクション

‘ 参照しているアセンブリコンポーネントの情報を保持するDictionary
‘ キー: バルーンの参照コンポーネント名, 値: Component2オブジェクト
Dim componentMap As Object
Set componentMap = CreateObject(“Scripting.Dictionary”)

‘ まず、アセンブリ内のすべてのコンポーネントをマップに格納
Dim rootComponent As SldWorks.Component2
Set rootComponent = swAssemblyDoc.GetComponentByName(“”)
Dim components As Variant
components = rootComponent.GetChildren

Dim swComp As SldWorks.Component2
For Each swComp In components
‘ コンポーネント名(一意)をキーとしてDictionaryに追加
If Not componentMap.Exists(swComp.Name2) Then
componentMap.Add swComp.Name2, swComp
End If
Next

‘ 各バルーンをチェック
Dim balloon As SldWorks.Balloon
For Each balloon In swBalloons
Dim referencedComponentName As String
referencedComponentName = balloon.GetComponentName ‘ バルーンが参照しているコンポーネント名

‘ バルーンが参照しているコンポーネントがアセンブリ内に存在するか確認
If componentMap.Exists(referencedComponentName) Then
‘ コンポーネントが存在する場合、そのコンポーネントの配置(Transform)が
‘ バルーンの配置と一致するかどうかのチェックも可能だが、
‘ ここでは単純に存在チェックに留める(より高度なチェックは後述)

‘ バルーンの表示状態やプロパティを取得・更新することも可能
‘ Debug.Print “Balloon: ” & balloon.Name & “, Ref Component: ” & referencedComponentName & “, Quantity: ” & balloon.Quantity
Else
‘ アセンブリ内に存在しないコンポーネントを参照しているバルーンは不整合
Debug.Print “Inconsistent Balloon Found: ” & balloon.Name & ” – References non-existent component: ” & referencedComponentName
inconsistentBalloons.Add balloon ‘ 不整合なバルーンをリストに追加
End If
Next

‘ — 不整合なバルーンの修正処理 —
If inconsistentBalloons.Count > 0 Then
MsgBox inconsistentBalloons.Count & ” 個の不整合なバルーンが見つかりました。修正処理を開始します。”, vbInformation

‘ TODO: ここで、不整合なバルーンの削除、または正しいコンポーネントへの再割り当て処理を実装
‘ 例: 削除する場合
‘ For Each balloon In inconsistentBalloons
‘ swDrawing.DeleteBalloon balloon.Name ‘ バルーンの名前で削除
‘ Next

‘ 例: 正しいコンポーネントへの再割り当て(より複雑なロジックが必要)
‘ For Each balloon In inconsistentBalloons
‘ ‘ 新しい参照コンポーネントを特定するロジック
‘ Dim newComponentName As String = FindCorrectComponent(balloon.OtherData) ‘ 仮の関数
‘ If Not newComponentName = “” Then
‘ ‘ バルーンの参照先を変更するAPIがあれば使用。なければ削除して再作成。
‘ ‘ swDrawing.ModifyBalloonReference balloon.Name, newComponentName ‘ 仮のAPI
‘ Else
‘ ‘ 削除
‘ swDrawing.DeleteBalloon balloon.Name
‘ End If
‘ Next

swDrawing.EditRebuild ‘ 図面を再構築
MsgBox “不整合なバルーンの修正処理(または削除)が完了しました。”, vbInformation
Else
Debug.Print “すべてのバルーンはアセンブリ構造と整合しています。”
End If

‘ オブジェクトの解放 (重要!)
Set componentMap = Nothing
Set swRootComponent = Nothing
Set swAssemblyDoc = Nothing
Set swModelDoc = Nothing
Set swDrawing = Nothing

3.2. バルーンの「配置」と「モデルの配置」の同期

単純なコンポーネントの存在チェックだけでなく、バルーンが指し示す位置と、実際にアセンブリ内でコンポーネントが配置されている位置が一致しているかどうかのチェックも重要です。これは、コンポーネントの移動や回転によってバルーンが「ズレる」ケースに対応します。

このチェックには、Windows APIの呼び出しが必要になる場合があります。SolidWorks APIは、ビュー内の要素の「画面座標」を直接取得する機能が限定的であるため、COMオブジェクトの `GetTransform` メソッドで取得したアセンブリ座標系での位置情報を、ビューの変換行列と組み合わせて画面座標に変換する必要があります。この変換計算は複雑であり、場合によってはWindows API(GDIなど)を利用して、ビューのクライアント領域における3D座標の投影計算を補助する必要が出てきます。

‘ — より高度なバルーン配置チェックの概念 —
‘ このコードは概念を示すもので、完全な実装には多くの計算とAPI連携が必要です。

‘ 1. アセンブリドキュメントから、バルーンが参照するコンポーネントの3D空間上の「代表点」を取得する
‘ (例: コンポーネントの原点、外接球の中心など)
‘ swComp.GetOrigin() や BoundingBox を利用

‘ 2. 図面ドキュメントの各ビュー (`View` オブジェクト) について:
‘ a. ビューの「3Dから2Dへの変換行列」を取得する。
‘ これは、ビューのカメラ位置、方向、投影タイプ(平行/パース)、ビューのスケールなどから計算される。
‘ SolidWorks APIでは直接的な関数がないため、ビューのプロパティを組み合わせたり、
‘ 場合によってはWindows APIやDirectX/OpenGLの概念を援用して計算する必要がある。
‘ (swView.IGetCameraTransform, swView.IGetModelTransform などを利用し、行列演算を行う)

‘ b. 取得したコンポーネントの3D代表点を、上記計算した変換行列で2D平面上に投影する。
‘ これにより、3D空間上の点が、図面上のどこに「本来」表示されるべきかの座標が得られる。

‘ 3. 図面上のバルーン (`Balloon` オブジェクト) の「画面上の位置」を取得する。
‘ これは、COMオブジェクトの `GetDisplayTransformation` などを利用して、
‘ ビューのクライアント領域におけるピクセル座標を取得する。

‘ 4. 比較:
‘ 計算された「本来の位置」と、バルーンの「実際の画面上の位置」を比較する。
‘ 許容誤差(例: 数ピクセル)を超えていれば、バルーンのズレと判断する。

‘ — 修正処理の概念 —
‘ ズレが検出された場合:
‘ a. バルーンを削除し、正しい位置に再配置する。
‘ `swDrawing.AddBalloon` を使用して、正しいコンポーネントと、計算された正しい位置を指定して追加する。
‘ b. コンポーネントの表示設定(例: `Component2.SetDisplayState`)や、
‘ ビューのプロパティ(例: `View.SetScale`, `View.SetPosition`)を調整して、
‘ バルーンが適切に表示されるように試みる。これは非常に高度で、ケースバイケースの対応となる。

‘ — メモリ最適化の観点 —
‘ 上記のような複雑な計算を行う際、大量のオブジェクトやデータが一時的に生成されます。
‘ 不要になったオブジェクトは、明示的に `Set obj = Nothing` を実行して解放することが、
‘ メモリリークを防ぎ、パフォーマンスを維持するために不可欠です。
‘ 特に、ループ処理内で `swApp.GetModelDoc2` や `swDrawing.enumViews` のような
‘ ドキュメントオブジェクトを繰り返し取得・解放する際には注意が必要です。

4. BOM番号順序の完全一致保証システム

バルーンの同期に加えて、BOM番号の順序を完全に一致させることは、部品の識別と管理において極めて重要です。

4.1. BOMの構造とAPIによる取得

SolidWorks APIでは、アセンブリの `ComponentSuppression` プロパティや、`IGetConfigurationByName`、`IGetDefaultConfiguration`、`IGetBomTableAnnotation` などを通じて、BOM情報を取得・操作できます。BOMの「順序」は、通常、アセンブリツリーの階層構造や、部品の追加順、あるいはBOMテーブルで明示的にソートされた結果に基づきます。

‘ アセンブリドキュメントを取得
Dim swAssembly As SldWorks.AssemblyDoc
Set swAssembly = swApp.ActiveDoc

If swAssembly Is Nothing Then
MsgBox “アクティブなドキュメントはアセンブリではありません。”, vbExclamation
Exit Sub
End If

‘ アクティブな設定を取得
Dim swConfig As SldWorks.Configuration
Set swConfig = swAssembly.GetActiveConfiguration

‘ 設定からBOMテーブルアノテーションを取得(もし存在すれば)
Dim swBomTable As SldWorks.BomTableAnnotation
Set swBomTable = swConfig.GetBomTableAnnotation

Dim bomRows As Variant
If Not swBomTable Is Nothing Then
bomRows = swBomTable.GetTableRows ‘ BOMテーブルの各行を取得
Else
‘ BOMテーブルがない場合、アセンブリコンポーネントからBOM情報を構築する
Debug.Print “BOMテーブルアノテーションが見つかりません。アセンブリ構造からBOMを構築します。”
‘ ここで、TraverseAssembly関数などを利用して、
‘ コンポーネントのリスト、数量、部品番号などを取得し、
‘ 独自のBOMデータ構造を構築するロジックを実装する。
‘ この構築されたデータ構造が、図面バルーンの参照コンポーネントとの比較に使われる。
Exit Sub ‘ 例として、ここでは終了
End If

‘ BOMの順序を保持する配列
Dim bomOrder As New Collection

‘ BOMテーブルの各行を処理
Dim row As Variant
For Each row In bomRows
‘ 各行は、セルオブジェクトの配列で構成されている
Dim cells As Variant
cells = row ‘ row自体がセルオブジェクトの配列とみなせる場合がある

‘ BOM番号(通常は最初の列)を取得
‘ GetValue3 メソッドは、セルの値とその種類(文字列、数値など)を返す
Dim cellValue As Variant
Dim valueType As Long
cellValue = cells(0).GetValue3(valueType) ‘ 最初のセル(BOM番号)を取得

Dim componentTag As String
‘ BOM番号に対応するコンポーネントの「タグ」や「一意ID」を取得するロジックが必要
‘ これは、BOMテーブルの列設定や、カスタムプロパティに依存する
‘ 例: componentTag = cells(1).GetValue3(valueType) ‘ 仮に2列目がコンポーネント名とする

‘ 実際には、BOMテーブルの行から、参照しているアセンブリコンポーネントを特定する
‘ 複雑な場合、BOMテーブルの行とComponent2オブジェクトを紐付けるための
‘ マッピング処理が必要になる。

‘ ここで、部品番号やコンポーネント名を取得し、
‘ `bomOrder` コレクションに追加する
‘ 例: bomOrder.Add componentName ‘ 部品名でソートする場合
‘ 例: bomOrder.Add bomNumber ‘ BOM番号でソートする場合
Debug.Print “BOM Item: ” & valueType & “, Value: ” & cellValue
Next

‘ — 図面バルーンの番号順序との比較 —
‘ 1. 図面上のバルーンをすべて取得し、参照しているコンポーネント名と
‘ バルーン番号(Quantityとは異なる、BOM上の番号)をマッピングする。
‘ (バルーン自体がBOM番号を持つとは限らないため、このマッピングが鍵となる)
‘
‘ 2. 図面バルーンから得られたコンポーネントリストと、BOMから得られたコンポーネントリストの
‘ 順序を比較する。
‘
‘ 3. もし順序が異なる場合、図面バルーンの「表示順序」を修正する。
‘ これは、バルーンの削除と再作成、またはバルーンの「配置」を微調整することで実現する。
‘ SolidWorks APIでバルーンの「順序」を直接変更する機能は限定的であるため、
‘ 多くの場合、バルーンを一度削除し、BOMの順序に従って再配置(再作成)することになる。
‘ `swDrawing.AddBalloon` を使用する際に、配置座標を指定することで、
‘ 意図した順序で表示させることができる。

‘ — レガシー環境での考慮事項 —
‘ 古いバージョンのSolidWorksでは、APIの機能が限定的であったり、
‘ 動作が不安定な場合があります。
‘ その場合、以下のようなアプローチを検討します。
‘ – Windows APIをより積極的に利用して、UI要素の操作や座標取得を行う。
‘ – VBAではなく、VB.NETやC#などのよりモダンな言語で開発し、
‘ COM Interopを通じてSolidWorks APIを利用する。
‘ – UI AutomationなどのUI自動化ツールとの連携を検討する。
‘ – 頻繁な図面変更が発生しないように、設計プロセスの見直しを提案する。

‘ オブジェクトの解放
If Not swBomTable Is Nothing Then Set swBomTable = Nothing
Set swConfig = Nothing
Set swAssembly = Nothing

5. Windows API、メモリ最適化、システム間連携の極意

5.1. Windows APIの賢明な利用

SolidWorks APIだけでは実現できない、あるいは非常に煩雑になる処理(例:画面座標の取得、特定ウィンドウの操作、ファイルシステムの詳細な制御など)において、Windows APIは強力な味方となります。

  • `Declare` ステートメント: VBAからWindows API関数を呼び出すための宣言は、正確性が命です。データ型(`Long` vs `Integer`、`String` vs `ByVal As String` など)を誤ると、クラッシュや予期せぬ動作を引き起こします。
  • ポインタとメモリ管理: API関数はしばしばポインタを介してデータをやり取りします。`ByRef` と `ByVal` の使い分け、`GlobalAlloc` や `GlobalLock` といったメモリ管理APIの理解が、安全なAPI呼び出しには不可欠です。
  • エラーハンドリング: API呼び出しでエラーが発生した場合 (`GetLastError` 関数など)、その原因を特定し、適切に対処する仕組みが必要です。

5.2. メモリ最適化:オブジェクトのライフサイクル管理

VBAにおけるメモリ管理の基本は、「不要になったオブジェクトは、速やかに解放する」ことです。特に、`ModelDoc2` や `DrawingDoc` といったドキュメントオブジェクト、`Component2` などのコレクション、そしてAPI呼び出しで一時的に生成される多数のオブジェクトは、メモリを消費します。

  • `Set obj = Nothing` の徹底: ループ処理の終了後や、オブジェクトが不要になった箇所では、必ず `Set obj = Nothing` を実行します。これにより、オブジェクトが保持していたメモリが解放されます。
  • `With` ブロックの活用: `With…End With` ブロックは、コードの可読性を向上させるだけでなく、オブジェクトへの参照を一時的に保持し、不要になった時点で解放されるため、メモリ管理の観点からも有効です。
  • グローバル変数・静的変数の管理: グローバル変数やモジュールレベルの変数は、プロシージャの実行が終わっても解放されない場合があります。これらの変数にドキュメントオブジェクトなどを格納し続けると、メモリリークの原因となります。必要な時だけオブジェクトを格納し、不要になったら解放する、という厳格なルールを設けるべきです。
  • COMオブジェクトの解放: SolidWorks APIはCOMオブジェクトを返します。これらのオブジェクトも、不要になったら明示的に解放する必要があります。

5.3. レガシー環境の保守とモダナイゼーション

長年運用されているシステムには、古いバージョンのSolidWorksやWindows、あるいは独自のVBAコードベースが存在します。

  • 互換性の維持: レガシーシステムを保守する際には、まずその環境での動作を理解することが重要です。APIのバージョンによる違い、OSのサービスパックによる影響などを把握し、可能な限り後方互換性を維持します。
  • コードの可読性と構造化: レガシーコードは、しばしば「スパゲッティコード」化しがちです。定期的なリファクタリング(コードの再構築)を行い、モジュール分割、コメントの追加、変数名の統一などを進めることで、保守性を向上させます。
  • モダナイゼーションの検討: VBAは強力ですが、機能やパフォーマンスの限界もあります。必要に応じて、VB.NETやC#によるデスクトップアプリケーション開発、あるいはWeb APIを介したクラウド連携など、よりモダンな技術スタックへの移行を検討することも、長期的な視点では重要です。

5.4. システム間連携の極限:APIと外部ツールの協調

SolidWorks VBAシステムは、単独で存在するのではなく、PLM(製品ライフサイクル管理)、ERP(統合基幹業務)、MES(製造実行システム)などの他のシステムと連携することが多いです。

  • ファイルベース連携: CSV、XML、JSONなどの標準的なファイルフォーマットを介したデータ交換は、最も一般的で堅牢な方法です。VBAからファイルへの書き出し・読み込みを行い、他のシステムと連携します。
  • データベース連携: ODBCやADO(ActiveX Data Objects)を利用して、データベースに直接アクセスし、データの読み書きを行うことも可能です。これにより、リアルタイムに近いデータ連携が実現できます。
  • Web API連携: 近年では、RESTful APIなどを介したWebサービス連携が主流になっています。VBAから `MSXML2.XMLHTTP` オブジェクトなどを使用してHTTPリクエストを送信し、JSONやXML形式でデータを送受信します。これは、より高度で柔軟な連携を可能にしますが、VBA単体での実装は複雑になるため、VB.NETなどの言語でヘルパーモジュールを作成することも有効です。
  • COM Automation: 他のWindowsアプリケーション(Excelなど)もCOM Automationを通じて制御できます。SolidWorks APIもCOMベースであるため、これらの技術を組み合わせることで、より高度なシステム連携が実現可能です。

6. まとめ:技術の真髄を追求するエンジニアへ

本稿では、SolidWorks VBAを用いたアセンブリ図面と3Dモデルの同期維持、特にバルーンとBOM番号順序の完全一致保証システム構築について、その深遠なる技術的側面を解説しました。Windows APIの呼び出し、メモリ最適化、レガシー環境の保守、そしてシステム間連携といった、我々が現場で直面する現実的な課題に触れながら、技術の真髄を掘り下げてきました。

このレベルのシステム開発は、単にAPIのドキュメントをなぞるだけでは到達できません。オブジェクトのライフサイクル、メモリの挙動、そしてシステム全体の整合性に対する深い洞察が求められます。我々エンジニアは、常に技術の最前線に立ち、これらの課題に真摯に向き合い続ける必要があります。

この知識が、皆さんの開発現場における「見えないコスト」を削減し、より堅牢で信頼性の高いシステム構築の一助となれば幸いです。技術の探求に終わりはありません。

タイトルとURLをコピーしました