forked from 1b-agent/opencadstudio-upstream
IMPORT: 3D Studio files
A .3ds file opens the 3D Studio Import window: the file's objects with their kind and material count, filtered by kind or name, checked one by one or all at once, where to put them on layers and what a mesh with several materials becomes. Checked meshes come in as polyface meshes on the chosen layers with materials made from the file's colours, cameras as named views; a mesh with several materials is split, given its first material, given none, or asked about on the command line. Automation drives the window with tds_import_dialog. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
9ddc103bcc
commit
78e429732e
35 files changed
+1893
-4
No files matched your search
@@ -6687,6 +6687,41 @@ dgn-import =
|
||||
.help-import = Imports a file of the chosen format with the format's importer.
|
||||
.blank = Filename cannot be blank
|
||||
.invalid-dgn = Invalid or unsupported DGN file.
|
||||
import-3ds =
|
||||
.title = 3D Studio Import
|
||||
.summary = { $objects } objects, { $materials } materials
|
||||
.change = Change...
|
||||
.mesh = Mesh
|
||||
.light = Light
|
||||
.camera = Camera
|
||||
.general = General
|
||||
.global = Global Settings
|
||||
.all = All
|
||||
.name = Name
|
||||
.type = Type
|
||||
.materials = { $n } materials
|
||||
.objects = Objects
|
||||
.search = Search by name...
|
||||
.select-all = Select all
|
||||
.clear = Clear
|
||||
.layers = Place on layers
|
||||
.by-object = By object
|
||||
.by-material = By material
|
||||
.by-color = By object color
|
||||
.single = Single layer
|
||||
.note-object = Each object goes on a layer named after it.
|
||||
.note-material = Each object goes on a layer named after its material.
|
||||
.note-color = Each object goes on a layer named after its color.
|
||||
.note-single = Every object goes on one layer.
|
||||
.multi = Objects with several materials
|
||||
.ask = Ask each time
|
||||
.split = Split by material
|
||||
.first = Assign the first material
|
||||
.none = Don't assign a material
|
||||
.reason = Check at least one object
|
||||
.cancel = Cancel
|
||||
.import = Import
|
||||
.help = Imports the checked objects of a 3D Studio file: meshes as polyface meshes with their materials, cameras as named views.
|
||||
import-panel =
|
||||
.dgn-import = استيراد DGN
|
||||
.recognize-shx = التعرف على نص SHX
|
||||
|
||||
@@ -6675,6 +6675,41 @@ dgn-import =
|
||||
.help-import = Imports a file of the chosen format with the format's importer.
|
||||
.blank = Filename cannot be blank
|
||||
.invalid-dgn = Invalid or unsupported DGN file.
|
||||
import-3ds =
|
||||
.title = 3D Studio Import
|
||||
.summary = { $objects } objects, { $materials } materials
|
||||
.change = Change...
|
||||
.mesh = Mesh
|
||||
.light = Light
|
||||
.camera = Camera
|
||||
.general = General
|
||||
.global = Global Settings
|
||||
.all = All
|
||||
.name = Name
|
||||
.type = Type
|
||||
.materials = { $n } materials
|
||||
.objects = Objects
|
||||
.search = Search by name...
|
||||
.select-all = Select all
|
||||
.clear = Clear
|
||||
.layers = Place on layers
|
||||
.by-object = By object
|
||||
.by-material = By material
|
||||
.by-color = By object color
|
||||
.single = Single layer
|
||||
.note-object = Each object goes on a layer named after it.
|
||||
.note-material = Each object goes on a layer named after its material.
|
||||
.note-color = Each object goes on a layer named after its color.
|
||||
.note-single = Every object goes on one layer.
|
||||
.multi = Objects with several materials
|
||||
.ask = Ask each time
|
||||
.split = Split by material
|
||||
.first = Assign the first material
|
||||
.none = Don't assign a material
|
||||
.reason = Check at least one object
|
||||
.cancel = Cancel
|
||||
.import = Import
|
||||
.help = Imports the checked objects of a 3D Studio file: meshes as polyface meshes with their materials, cameras as named views.
|
||||
import-panel =
|
||||
.dgn-import = Импортиране на DGN
|
||||
.recognize-shx = Разпознаване на SHX текст
|
||||
|
||||
@@ -6672,6 +6672,41 @@ dgn-import =
|
||||
.help-import = Imports a file of the chosen format with the format's importer.
|
||||
.blank = Filename cannot be blank
|
||||
.invalid-dgn = Invalid or unsupported DGN file.
|
||||
import-3ds =
|
||||
.title = 3D Studio Import
|
||||
.summary = { $objects } objects, { $materials } materials
|
||||
.change = Change...
|
||||
.mesh = Mesh
|
||||
.light = Light
|
||||
.camera = Camera
|
||||
.general = General
|
||||
.global = Global Settings
|
||||
.all = All
|
||||
.name = Name
|
||||
.type = Type
|
||||
.materials = { $n } materials
|
||||
.objects = Objects
|
||||
.search = Search by name...
|
||||
.select-all = Select all
|
||||
.clear = Clear
|
||||
.layers = Place on layers
|
||||
.by-object = By object
|
||||
.by-material = By material
|
||||
.by-color = By object color
|
||||
.single = Single layer
|
||||
.note-object = Each object goes on a layer named after it.
|
||||
.note-material = Each object goes on a layer named after its material.
|
||||
.note-color = Each object goes on a layer named after its color.
|
||||
.note-single = Every object goes on one layer.
|
||||
.multi = Objects with several materials
|
||||
.ask = Ask each time
|
||||
.split = Split by material
|
||||
.first = Assign the first material
|
||||
.none = Don't assign a material
|
||||
.reason = Check at least one object
|
||||
.cancel = Cancel
|
||||
.import = Import
|
||||
.help = Imports the checked objects of a 3D Studio file: meshes as polyface meshes with their materials, cameras as named views.
|
||||
import-panel =
|
||||
.dgn-import = Import DGN
|
||||
.recognize-shx = Rozpoznat text SHX
|
||||
|
||||
@@ -6673,6 +6673,41 @@ dgn-import =
|
||||
.help-import = Imports a file of the chosen format with the format's importer.
|
||||
.blank = Filename cannot be blank
|
||||
.invalid-dgn = Invalid or unsupported DGN file.
|
||||
import-3ds =
|
||||
.title = 3D Studio Import
|
||||
.summary = { $objects } objects, { $materials } materials
|
||||
.change = Change...
|
||||
.mesh = Mesh
|
||||
.light = Light
|
||||
.camera = Camera
|
||||
.general = General
|
||||
.global = Global Settings
|
||||
.all = All
|
||||
.name = Name
|
||||
.type = Type
|
||||
.materials = { $n } materials
|
||||
.objects = Objects
|
||||
.search = Search by name...
|
||||
.select-all = Select all
|
||||
.clear = Clear
|
||||
.layers = Place on layers
|
||||
.by-object = By object
|
||||
.by-material = By material
|
||||
.by-color = By object color
|
||||
.single = Single layer
|
||||
.note-object = Each object goes on a layer named after it.
|
||||
.note-material = Each object goes on a layer named after its material.
|
||||
.note-color = Each object goes on a layer named after its color.
|
||||
.note-single = Every object goes on one layer.
|
||||
.multi = Objects with several materials
|
||||
.ask = Ask each time
|
||||
.split = Split by material
|
||||
.first = Assign the first material
|
||||
.none = Don't assign a material
|
||||
.reason = Check at least one object
|
||||
.cancel = Cancel
|
||||
.import = Import
|
||||
.help = Imports the checked objects of a 3D Studio file: meshes as polyface meshes with their materials, cameras as named views.
|
||||
import-panel =
|
||||
.dgn-import = DGN-Import
|
||||
.recognize-shx = SHX-Text erkennen
|
||||
|
||||
@@ -6675,6 +6675,41 @@ dgn-import =
|
||||
.help-import = Imports a file of the chosen format with the format's importer.
|
||||
.blank = Filename cannot be blank
|
||||
.invalid-dgn = Invalid or unsupported DGN file.
|
||||
import-3ds =
|
||||
.title = 3D Studio Import
|
||||
.summary = { $objects } objects, { $materials } materials
|
||||
.change = Change...
|
||||
.mesh = Mesh
|
||||
.light = Light
|
||||
.camera = Camera
|
||||
.general = General
|
||||
.global = Global Settings
|
||||
.all = All
|
||||
.name = Name
|
||||
.type = Type
|
||||
.materials = { $n } materials
|
||||
.objects = Objects
|
||||
.search = Search by name...
|
||||
.select-all = Select all
|
||||
.clear = Clear
|
||||
.layers = Place on layers
|
||||
.by-object = By object
|
||||
.by-material = By material
|
||||
.by-color = By object color
|
||||
.single = Single layer
|
||||
.note-object = Each object goes on a layer named after it.
|
||||
.note-material = Each object goes on a layer named after its material.
|
||||
.note-color = Each object goes on a layer named after its color.
|
||||
.note-single = Every object goes on one layer.
|
||||
.multi = Objects with several materials
|
||||
.ask = Ask each time
|
||||
.split = Split by material
|
||||
.first = Assign the first material
|
||||
.none = Don't assign a material
|
||||
.reason = Check at least one object
|
||||
.cancel = Cancel
|
||||
.import = Import
|
||||
.help = Imports the checked objects of a 3D Studio file: meshes as polyface meshes with their materials, cameras as named views.
|
||||
import-panel =
|
||||
.dgn-import = Εισαγωγή DGN
|
||||
.recognize-shx = Αναγνώριση κειμένου SHX
|
||||
|
||||
@@ -6674,6 +6674,41 @@ dgn-import =
|
||||
.help-import = Imports a file of the chosen format with the format's importer.
|
||||
.blank = Filename cannot be blank
|
||||
.invalid-dgn = Invalid or unsupported DGN file.
|
||||
import-3ds =
|
||||
.title = 3D Studio Import
|
||||
.summary = { $objects } objects, { $materials } materials
|
||||
.change = Change...
|
||||
.mesh = Mesh
|
||||
.light = Light
|
||||
.camera = Camera
|
||||
.general = General
|
||||
.global = Global Settings
|
||||
.all = All
|
||||
.name = Name
|
||||
.type = Type
|
||||
.materials = { $n } materials
|
||||
.objects = Objects
|
||||
.search = Search by name...
|
||||
.select-all = Select all
|
||||
.clear = Clear
|
||||
.layers = Place on layers
|
||||
.by-object = By object
|
||||
.by-material = By material
|
||||
.by-color = By object color
|
||||
.single = Single layer
|
||||
.note-object = Each object goes on a layer named after it.
|
||||
.note-material = Each object goes on a layer named after its material.
|
||||
.note-color = Each object goes on a layer named after its color.
|
||||
.note-single = Every object goes on one layer.
|
||||
.multi = Objects with several materials
|
||||
.ask = Ask each time
|
||||
.split = Split by material
|
||||
.first = Assign the first material
|
||||
.none = Don't assign a material
|
||||
.reason = Check at least one object
|
||||
.cancel = Cancel
|
||||
.import = Import
|
||||
.help = Imports the checked objects of a 3D Studio file: meshes as polyface meshes with their materials, cameras as named views.
|
||||
import-panel =
|
||||
.dgn-import = DGN Import
|
||||
.recognize-shx = Recognize SHX Text
|
||||
|
||||
@@ -6675,6 +6675,41 @@ dgn-import =
|
||||
.help-import = Imports a file of the chosen format with the format's importer.
|
||||
.blank = Filename cannot be blank
|
||||
.invalid-dgn = Invalid or unsupported DGN file.
|
||||
import-3ds =
|
||||
.title = 3D Studio Import
|
||||
.summary = { $objects } objects, { $materials } materials
|
||||
.change = Change...
|
||||
.mesh = Mesh
|
||||
.light = Light
|
||||
.camera = Camera
|
||||
.general = General
|
||||
.global = Global Settings
|
||||
.all = All
|
||||
.name = Name
|
||||
.type = Type
|
||||
.materials = { $n } materials
|
||||
.objects = Objects
|
||||
.search = Search by name...
|
||||
.select-all = Select all
|
||||
.clear = Clear
|
||||
.layers = Place on layers
|
||||
.by-object = By object
|
||||
.by-material = By material
|
||||
.by-color = By object color
|
||||
.single = Single layer
|
||||
.note-object = Each object goes on a layer named after it.
|
||||
.note-material = Each object goes on a layer named after its material.
|
||||
.note-color = Each object goes on a layer named after its color.
|
||||
.note-single = Every object goes on one layer.
|
||||
.multi = Objects with several materials
|
||||
.ask = Ask each time
|
||||
.split = Split by material
|
||||
.first = Assign the first material
|
||||
.none = Don't assign a material
|
||||
.reason = Check at least one object
|
||||
.cancel = Cancel
|
||||
.import = Import
|
||||
.help = Imports the checked objects of a 3D Studio file: meshes as polyface meshes with their materials, cameras as named views.
|
||||
import-panel =
|
||||
.dgn-import = Importar DGN
|
||||
.recognize-shx = Reconocer texto SHX
|
||||
|
||||
@@ -6672,6 +6672,41 @@ dgn-import =
|
||||
.help-import = Imports a file of the chosen format with the format's importer.
|
||||
.blank = Filename cannot be blank
|
||||
.invalid-dgn = Invalid or unsupported DGN file.
|
||||
import-3ds =
|
||||
.title = 3D Studio Import
|
||||
.summary = { $objects } objects, { $materials } materials
|
||||
.change = Change...
|
||||
.mesh = Mesh
|
||||
.light = Light
|
||||
.camera = Camera
|
||||
.general = General
|
||||
.global = Global Settings
|
||||
.all = All
|
||||
.name = Name
|
||||
.type = Type
|
||||
.materials = { $n } materials
|
||||
.objects = Objects
|
||||
.search = Search by name...
|
||||
.select-all = Select all
|
||||
.clear = Clear
|
||||
.layers = Place on layers
|
||||
.by-object = By object
|
||||
.by-material = By material
|
||||
.by-color = By object color
|
||||
.single = Single layer
|
||||
.note-object = Each object goes on a layer named after it.
|
||||
.note-material = Each object goes on a layer named after its material.
|
||||
.note-color = Each object goes on a layer named after its color.
|
||||
.note-single = Every object goes on one layer.
|
||||
.multi = Objects with several materials
|
||||
.ask = Ask each time
|
||||
.split = Split by material
|
||||
.first = Assign the first material
|
||||
.none = Don't assign a material
|
||||
.reason = Check at least one object
|
||||
.cancel = Cancel
|
||||
.import = Import
|
||||
.help = Imports the checked objects of a 3D Studio file: meshes as polyface meshes with their materials, cameras as named views.
|
||||
import-panel =
|
||||
.dgn-import = DGN-tuonti
|
||||
.recognize-shx = Tunnista SHX-teksti
|
||||
|
||||
@@ -6675,6 +6675,41 @@ dgn-import =
|
||||
.help-import = Imports a file of the chosen format with the format's importer.
|
||||
.blank = Filename cannot be blank
|
||||
.invalid-dgn = Invalid or unsupported DGN file.
|
||||
import-3ds =
|
||||
.title = 3D Studio Import
|
||||
.summary = { $objects } objects, { $materials } materials
|
||||
.change = Change...
|
||||
.mesh = Mesh
|
||||
.light = Light
|
||||
.camera = Camera
|
||||
.general = General
|
||||
.global = Global Settings
|
||||
.all = All
|
||||
.name = Name
|
||||
.type = Type
|
||||
.materials = { $n } materials
|
||||
.objects = Objects
|
||||
.search = Search by name...
|
||||
.select-all = Select all
|
||||
.clear = Clear
|
||||
.layers = Place on layers
|
||||
.by-object = By object
|
||||
.by-material = By material
|
||||
.by-color = By object color
|
||||
.single = Single layer
|
||||
.note-object = Each object goes on a layer named after it.
|
||||
.note-material = Each object goes on a layer named after its material.
|
||||
.note-color = Each object goes on a layer named after its color.
|
||||
.note-single = Every object goes on one layer.
|
||||
.multi = Objects with several materials
|
||||
.ask = Ask each time
|
||||
.split = Split by material
|
||||
.first = Assign the first material
|
||||
.none = Don't assign a material
|
||||
.reason = Check at least one object
|
||||
.cancel = Cancel
|
||||
.import = Import
|
||||
.help = Imports the checked objects of a 3D Studio file: meshes as polyface meshes with their materials, cameras as named views.
|
||||
import-panel =
|
||||
.dgn-import = Import DGN
|
||||
.recognize-shx = Reconnaître le texte SHX
|
||||
|
||||
@@ -6663,6 +6663,41 @@ dgn-import =
|
||||
.help-import = Imports a file of the chosen format with the format's importer.
|
||||
.blank = Filename cannot be blank
|
||||
.invalid-dgn = Invalid or unsupported DGN file.
|
||||
import-3ds =
|
||||
.title = 3D Studio Import
|
||||
.summary = { $objects } objects, { $materials } materials
|
||||
.change = Change...
|
||||
.mesh = Mesh
|
||||
.light = Light
|
||||
.camera = Camera
|
||||
.general = General
|
||||
.global = Global Settings
|
||||
.all = All
|
||||
.name = Name
|
||||
.type = Type
|
||||
.materials = { $n } materials
|
||||
.objects = Objects
|
||||
.search = Search by name...
|
||||
.select-all = Select all
|
||||
.clear = Clear
|
||||
.layers = Place on layers
|
||||
.by-object = By object
|
||||
.by-material = By material
|
||||
.by-color = By object color
|
||||
.single = Single layer
|
||||
.note-object = Each object goes on a layer named after it.
|
||||
.note-material = Each object goes on a layer named after its material.
|
||||
.note-color = Each object goes on a layer named after its color.
|
||||
.note-single = Every object goes on one layer.
|
||||
.multi = Objects with several materials
|
||||
.ask = Ask each time
|
||||
.split = Split by material
|
||||
.first = Assign the first material
|
||||
.none = Don't assign a material
|
||||
.reason = Check at least one object
|
||||
.cancel = Cancel
|
||||
.import = Import
|
||||
.help = Imports the checked objects of a 3D Studio file: meshes as polyface meshes with their materials, cameras as named views.
|
||||
import-panel =
|
||||
.dgn-import = DGN आयात
|
||||
.recognize-shx = SHX टेक्स्ट पहचानें
|
||||
|
||||
@@ -6659,6 +6659,41 @@ dgn-import =
|
||||
.help-import = Imports a file of the chosen format with the format's importer.
|
||||
.blank = Filename cannot be blank
|
||||
.invalid-dgn = Invalid or unsupported DGN file.
|
||||
import-3ds =
|
||||
.title = 3D Studio Import
|
||||
.summary = { $objects } objects, { $materials } materials
|
||||
.change = Change...
|
||||
.mesh = Mesh
|
||||
.light = Light
|
||||
.camera = Camera
|
||||
.general = General
|
||||
.global = Global Settings
|
||||
.all = All
|
||||
.name = Name
|
||||
.type = Type
|
||||
.materials = { $n } materials
|
||||
.objects = Objects
|
||||
.search = Search by name...
|
||||
.select-all = Select all
|
||||
.clear = Clear
|
||||
.layers = Place on layers
|
||||
.by-object = By object
|
||||
.by-material = By material
|
||||
.by-color = By object color
|
||||
.single = Single layer
|
||||
.note-object = Each object goes on a layer named after it.
|
||||
.note-material = Each object goes on a layer named after its material.
|
||||
.note-color = Each object goes on a layer named after its color.
|
||||
.note-single = Every object goes on one layer.
|
||||
.multi = Objects with several materials
|
||||
.ask = Ask each time
|
||||
.split = Split by material
|
||||
.first = Assign the first material
|
||||
.none = Don't assign a material
|
||||
.reason = Check at least one object
|
||||
.cancel = Cancel
|
||||
.import = Import
|
||||
.help = Imports the checked objects of a 3D Studio file: meshes as polyface meshes with their materials, cameras as named views.
|
||||
import-panel =
|
||||
.dgn-import = DGN-importálás
|
||||
.recognize-shx = SHX-szöveg felismerése
|
||||
|
||||
@@ -6674,6 +6674,41 @@ dgn-import =
|
||||
.help-import = Imports a file of the chosen format with the format's importer.
|
||||
.blank = Filename cannot be blank
|
||||
.invalid-dgn = Invalid or unsupported DGN file.
|
||||
import-3ds =
|
||||
.title = 3D Studio Import
|
||||
.summary = { $objects } objects, { $materials } materials
|
||||
.change = Change...
|
||||
.mesh = Mesh
|
||||
.light = Light
|
||||
.camera = Camera
|
||||
.general = General
|
||||
.global = Global Settings
|
||||
.all = All
|
||||
.name = Name
|
||||
.type = Type
|
||||
.materials = { $n } materials
|
||||
.objects = Objects
|
||||
.search = Search by name...
|
||||
.select-all = Select all
|
||||
.clear = Clear
|
||||
.layers = Place on layers
|
||||
.by-object = By object
|
||||
.by-material = By material
|
||||
.by-color = By object color
|
||||
.single = Single layer
|
||||
.note-object = Each object goes on a layer named after it.
|
||||
.note-material = Each object goes on a layer named after its material.
|
||||
.note-color = Each object goes on a layer named after its color.
|
||||
.note-single = Every object goes on one layer.
|
||||
.multi = Objects with several materials
|
||||
.ask = Ask each time
|
||||
.split = Split by material
|
||||
.first = Assign the first material
|
||||
.none = Don't assign a material
|
||||
.reason = Check at least one object
|
||||
.cancel = Cancel
|
||||
.import = Import
|
||||
.help = Imports the checked objects of a 3D Studio file: meshes as polyface meshes with their materials, cameras as named views.
|
||||
import-panel =
|
||||
.dgn-import = Importa DGN
|
||||
.recognize-shx = Riconosci testo SHX
|
||||
|
||||
@@ -6663,6 +6663,41 @@ dgn-import =
|
||||
.help-import = Imports a file of the chosen format with the format's importer.
|
||||
.blank = Filename cannot be blank
|
||||
.invalid-dgn = Invalid or unsupported DGN file.
|
||||
import-3ds =
|
||||
.title = 3D Studio Import
|
||||
.summary = { $objects } objects, { $materials } materials
|
||||
.change = Change...
|
||||
.mesh = Mesh
|
||||
.light = Light
|
||||
.camera = Camera
|
||||
.general = General
|
||||
.global = Global Settings
|
||||
.all = All
|
||||
.name = Name
|
||||
.type = Type
|
||||
.materials = { $n } materials
|
||||
.objects = Objects
|
||||
.search = Search by name...
|
||||
.select-all = Select all
|
||||
.clear = Clear
|
||||
.layers = Place on layers
|
||||
.by-object = By object
|
||||
.by-material = By material
|
||||
.by-color = By object color
|
||||
.single = Single layer
|
||||
.note-object = Each object goes on a layer named after it.
|
||||
.note-material = Each object goes on a layer named after its material.
|
||||
.note-color = Each object goes on a layer named after its color.
|
||||
.note-single = Every object goes on one layer.
|
||||
.multi = Objects with several materials
|
||||
.ask = Ask each time
|
||||
.split = Split by material
|
||||
.first = Assign the first material
|
||||
.none = Don't assign a material
|
||||
.reason = Check at least one object
|
||||
.cancel = Cancel
|
||||
.import = Import
|
||||
.help = Imports the checked objects of a 3D Studio file: meshes as polyface meshes with their materials, cameras as named views.
|
||||
import-panel =
|
||||
.dgn-import = DGN 読み込み
|
||||
.recognize-shx = SHX 文字を認識
|
||||
|
||||
@@ -6674,6 +6674,41 @@ dgn-import =
|
||||
.help-import = Imports a file of the chosen format with the format's importer.
|
||||
.blank = Filename cannot be blank
|
||||
.invalid-dgn = Invalid or unsupported DGN file.
|
||||
import-3ds =
|
||||
.title = 3D Studio Import
|
||||
.summary = { $objects } objects, { $materials } materials
|
||||
.change = Change...
|
||||
.mesh = Mesh
|
||||
.light = Light
|
||||
.camera = Camera
|
||||
.general = General
|
||||
.global = Global Settings
|
||||
.all = All
|
||||
.name = Name
|
||||
.type = Type
|
||||
.materials = { $n } materials
|
||||
.objects = Objects
|
||||
.search = Search by name...
|
||||
.select-all = Select all
|
||||
.clear = Clear
|
||||
.layers = Place on layers
|
||||
.by-object = By object
|
||||
.by-material = By material
|
||||
.by-color = By object color
|
||||
.single = Single layer
|
||||
.note-object = Each object goes on a layer named after it.
|
||||
.note-material = Each object goes on a layer named after its material.
|
||||
.note-color = Each object goes on a layer named after its color.
|
||||
.note-single = Every object goes on one layer.
|
||||
.multi = Objects with several materials
|
||||
.ask = Ask each time
|
||||
.split = Split by material
|
||||
.first = Assign the first material
|
||||
.none = Don't assign a material
|
||||
.reason = Check at least one object
|
||||
.cancel = Cancel
|
||||
.import = Import
|
||||
.help = Imports the checked objects of a 3D Studio file: meshes as polyface meshes with their materials, cameras as named views.
|
||||
import-panel =
|
||||
.dgn-import = DGN 가져오기
|
||||
.recognize-shx = SHX 문자 인식
|
||||
|
||||
@@ -6675,6 +6675,41 @@ dgn-import =
|
||||
.help-import = Imports a file of the chosen format with the format's importer.
|
||||
.blank = Filename cannot be blank
|
||||
.invalid-dgn = Invalid or unsupported DGN file.
|
||||
import-3ds =
|
||||
.title = 3D Studio Import
|
||||
.summary = { $objects } objects, { $materials } materials
|
||||
.change = Change...
|
||||
.mesh = Mesh
|
||||
.light = Light
|
||||
.camera = Camera
|
||||
.general = General
|
||||
.global = Global Settings
|
||||
.all = All
|
||||
.name = Name
|
||||
.type = Type
|
||||
.materials = { $n } materials
|
||||
.objects = Objects
|
||||
.search = Search by name...
|
||||
.select-all = Select all
|
||||
.clear = Clear
|
||||
.layers = Place on layers
|
||||
.by-object = By object
|
||||
.by-material = By material
|
||||
.by-color = By object color
|
||||
.single = Single layer
|
||||
.note-object = Each object goes on a layer named after it.
|
||||
.note-material = Each object goes on a layer named after its material.
|
||||
.note-color = Each object goes on a layer named after its color.
|
||||
.note-single = Every object goes on one layer.
|
||||
.multi = Objects with several materials
|
||||
.ask = Ask each time
|
||||
.split = Split by material
|
||||
.first = Assign the first material
|
||||
.none = Don't assign a material
|
||||
.reason = Check at least one object
|
||||
.cancel = Cancel
|
||||
.import = Import
|
||||
.help = Imports the checked objects of a 3D Studio file: meshes as polyface meshes with their materials, cameras as named views.
|
||||
import-panel =
|
||||
.dgn-import = ນໍາເຂົ້າ DGN
|
||||
.recognize-shx = ຮັບຮູ້ຂໍ້ຄວາມ SHX
|
||||
|
||||
@@ -6673,6 +6673,41 @@ dgn-import =
|
||||
.help-import = Imports a file of the chosen format with the format's importer.
|
||||
.blank = Filename cannot be blank
|
||||
.invalid-dgn = Invalid or unsupported DGN file.
|
||||
import-3ds =
|
||||
.title = 3D Studio Import
|
||||
.summary = { $objects } objects, { $materials } materials
|
||||
.change = Change...
|
||||
.mesh = Mesh
|
||||
.light = Light
|
||||
.camera = Camera
|
||||
.general = General
|
||||
.global = Global Settings
|
||||
.all = All
|
||||
.name = Name
|
||||
.type = Type
|
||||
.materials = { $n } materials
|
||||
.objects = Objects
|
||||
.search = Search by name...
|
||||
.select-all = Select all
|
||||
.clear = Clear
|
||||
.layers = Place on layers
|
||||
.by-object = By object
|
||||
.by-material = By material
|
||||
.by-color = By object color
|
||||
.single = Single layer
|
||||
.note-object = Each object goes on a layer named after it.
|
||||
.note-material = Each object goes on a layer named after its material.
|
||||
.note-color = Each object goes on a layer named after its color.
|
||||
.note-single = Every object goes on one layer.
|
||||
.multi = Objects with several materials
|
||||
.ask = Ask each time
|
||||
.split = Split by material
|
||||
.first = Assign the first material
|
||||
.none = Don't assign a material
|
||||
.reason = Check at least one object
|
||||
.cancel = Cancel
|
||||
.import = Import
|
||||
.help = Imports the checked objects of a 3D Studio file: meshes as polyface meshes with their materials, cameras as named views.
|
||||
import-panel =
|
||||
.dgn-import = DGN importeren
|
||||
.recognize-shx = SHX-tekst herkennen
|
||||
|
||||
@@ -6674,6 +6674,41 @@ dgn-import =
|
||||
.help-import = Imports a file of the chosen format with the format's importer.
|
||||
.blank = Filename cannot be blank
|
||||
.invalid-dgn = Invalid or unsupported DGN file.
|
||||
import-3ds =
|
||||
.title = 3D Studio Import
|
||||
.summary = { $objects } objects, { $materials } materials
|
||||
.change = Change...
|
||||
.mesh = Mesh
|
||||
.light = Light
|
||||
.camera = Camera
|
||||
.general = General
|
||||
.global = Global Settings
|
||||
.all = All
|
||||
.name = Name
|
||||
.type = Type
|
||||
.materials = { $n } materials
|
||||
.objects = Objects
|
||||
.search = Search by name...
|
||||
.select-all = Select all
|
||||
.clear = Clear
|
||||
.layers = Place on layers
|
||||
.by-object = By object
|
||||
.by-material = By material
|
||||
.by-color = By object color
|
||||
.single = Single layer
|
||||
.note-object = Each object goes on a layer named after it.
|
||||
.note-material = Each object goes on a layer named after its material.
|
||||
.note-color = Each object goes on a layer named after its color.
|
||||
.note-single = Every object goes on one layer.
|
||||
.multi = Objects with several materials
|
||||
.ask = Ask each time
|
||||
.split = Split by material
|
||||
.first = Assign the first material
|
||||
.none = Don't assign a material
|
||||
.reason = Check at least one object
|
||||
.cancel = Cancel
|
||||
.import = Import
|
||||
.help = Imports the checked objects of a 3D Studio file: meshes as polyface meshes with their materials, cameras as named views.
|
||||
import-panel =
|
||||
.dgn-import = Import DGN
|
||||
.recognize-shx = Rozpoznaj tekst SHX
|
||||
|
||||
@@ -6675,6 +6675,41 @@ dgn-import =
|
||||
.help-import = Imports a file of the chosen format with the format's importer.
|
||||
.blank = Filename cannot be blank
|
||||
.invalid-dgn = Invalid or unsupported DGN file.
|
||||
import-3ds =
|
||||
.title = 3D Studio Import
|
||||
.summary = { $objects } objects, { $materials } materials
|
||||
.change = Change...
|
||||
.mesh = Mesh
|
||||
.light = Light
|
||||
.camera = Camera
|
||||
.general = General
|
||||
.global = Global Settings
|
||||
.all = All
|
||||
.name = Name
|
||||
.type = Type
|
||||
.materials = { $n } materials
|
||||
.objects = Objects
|
||||
.search = Search by name...
|
||||
.select-all = Select all
|
||||
.clear = Clear
|
||||
.layers = Place on layers
|
||||
.by-object = By object
|
||||
.by-material = By material
|
||||
.by-color = By object color
|
||||
.single = Single layer
|
||||
.note-object = Each object goes on a layer named after it.
|
||||
.note-material = Each object goes on a layer named after its material.
|
||||
.note-color = Each object goes on a layer named after its color.
|
||||
.note-single = Every object goes on one layer.
|
||||
.multi = Objects with several materials
|
||||
.ask = Ask each time
|
||||
.split = Split by material
|
||||
.first = Assign the first material
|
||||
.none = Don't assign a material
|
||||
.reason = Check at least one object
|
||||
.cancel = Cancel
|
||||
.import = Import
|
||||
.help = Imports the checked objects of a 3D Studio file: meshes as polyface meshes with their materials, cameras as named views.
|
||||
import-panel =
|
||||
.dgn-import = Importar DGN
|
||||
.recognize-shx = Reconhecer texto SHX
|
||||
|
||||
@@ -6676,6 +6676,41 @@ dgn-import =
|
||||
.help-import = Imports a file of the chosen format with the format's importer.
|
||||
.blank = Filename cannot be blank
|
||||
.invalid-dgn = Invalid or unsupported DGN file.
|
||||
import-3ds =
|
||||
.title = 3D Studio Import
|
||||
.summary = { $objects } objects, { $materials } materials
|
||||
.change = Change...
|
||||
.mesh = Mesh
|
||||
.light = Light
|
||||
.camera = Camera
|
||||
.general = General
|
||||
.global = Global Settings
|
||||
.all = All
|
||||
.name = Name
|
||||
.type = Type
|
||||
.materials = { $n } materials
|
||||
.objects = Objects
|
||||
.search = Search by name...
|
||||
.select-all = Select all
|
||||
.clear = Clear
|
||||
.layers = Place on layers
|
||||
.by-object = By object
|
||||
.by-material = By material
|
||||
.by-color = By object color
|
||||
.single = Single layer
|
||||
.note-object = Each object goes on a layer named after it.
|
||||
.note-material = Each object goes on a layer named after its material.
|
||||
.note-color = Each object goes on a layer named after its color.
|
||||
.note-single = Every object goes on one layer.
|
||||
.multi = Objects with several materials
|
||||
.ask = Ask each time
|
||||
.split = Split by material
|
||||
.first = Assign the first material
|
||||
.none = Don't assign a material
|
||||
.reason = Check at least one object
|
||||
.cancel = Cancel
|
||||
.import = Import
|
||||
.help = Imports the checked objects of a 3D Studio file: meshes as polyface meshes with their materials, cameras as named views.
|
||||
import-panel =
|
||||
.dgn-import = Импорт DGN
|
||||
.recognize-shx = Распознать текст SHX
|
||||
|
||||
@@ -6596,6 +6596,41 @@ dgn-import =
|
||||
.help-import = Seçilen biçimdeki dosyayı o biçimin içe aktarıcısıyla içe aktarır.
|
||||
.blank = Dosya adı boş olamaz
|
||||
.invalid-dgn = Geçersiz veya desteklenmeyen DGN dosyası.
|
||||
import-3ds =
|
||||
.title = 3D Studio İçe Aktar
|
||||
.summary = { $objects } nesne, { $materials } malzeme
|
||||
.change = Değiştir…
|
||||
.mesh = Ağ
|
||||
.light = Işık
|
||||
.camera = Kamera
|
||||
.general = Genel
|
||||
.global = Genel Ayarlar
|
||||
.all = Tümü
|
||||
.name = Ad
|
||||
.type = Tür
|
||||
.materials = { $n } malzeme
|
||||
.objects = Nesneler
|
||||
.search = Ada göre ara...
|
||||
.select-all = Tümünü seç
|
||||
.clear = Temizle
|
||||
.layers = Katmana yerleştir
|
||||
.by-object = Nesneye göre
|
||||
.by-material = Malzemeye göre
|
||||
.by-color = Nesne rengine göre
|
||||
.single = Tek katman
|
||||
.note-object = Her nesne kendi adını taşıyan katmana yerleşir.
|
||||
.note-material = Her nesne malzemesinin adını taşıyan katmana yerleşir.
|
||||
.note-color = Her nesne renginin adını taşıyan katmana yerleşir.
|
||||
.note-single = Tüm nesneler tek bir katmana yerleşir.
|
||||
.multi = Birden çok malzemeli nesne
|
||||
.ask = Her seferinde sor
|
||||
.split = Malzemeye göre böl
|
||||
.first = İlk malzemeyi ata
|
||||
.none = Malzeme atama
|
||||
.reason = En az bir nesne işaretleyin
|
||||
.cancel = İptal
|
||||
.import = İçe Aktar
|
||||
.help = İşaretli nesneleri içe aktarır: ağlar malzemeleriyle çokyüzlü ağ, kameralar adlandırılmış görünüm olur.
|
||||
import-panel =
|
||||
.dgn-import = DGN İçe Aktar
|
||||
.recognize-shx = SHX Metnini Tanı
|
||||
|
||||
@@ -6658,6 +6658,41 @@ dgn-import =
|
||||
.help-import = Imports a file of the chosen format with the format's importer.
|
||||
.blank = Filename cannot be blank
|
||||
.invalid-dgn = Invalid or unsupported DGN file.
|
||||
import-3ds =
|
||||
.title = 3D Studio Import
|
||||
.summary = { $objects } objects, { $materials } materials
|
||||
.change = Change...
|
||||
.mesh = Mesh
|
||||
.light = Light
|
||||
.camera = Camera
|
||||
.general = General
|
||||
.global = Global Settings
|
||||
.all = All
|
||||
.name = Name
|
||||
.type = Type
|
||||
.materials = { $n } materials
|
||||
.objects = Objects
|
||||
.search = Search by name...
|
||||
.select-all = Select all
|
||||
.clear = Clear
|
||||
.layers = Place on layers
|
||||
.by-object = By object
|
||||
.by-material = By material
|
||||
.by-color = By object color
|
||||
.single = Single layer
|
||||
.note-object = Each object goes on a layer named after it.
|
||||
.note-material = Each object goes on a layer named after its material.
|
||||
.note-color = Each object goes on a layer named after its color.
|
||||
.note-single = Every object goes on one layer.
|
||||
.multi = Objects with several materials
|
||||
.ask = Ask each time
|
||||
.split = Split by material
|
||||
.first = Assign the first material
|
||||
.none = Don't assign a material
|
||||
.reason = Check at least one object
|
||||
.cancel = Cancel
|
||||
.import = Import
|
||||
.help = Imports the checked objects of a 3D Studio file: meshes as polyface meshes with their materials, cameras as named views.
|
||||
import-panel =
|
||||
.dgn-import = DGN 输入
|
||||
.recognize-shx = 识别 SHX 文字
|
||||
|
||||
@@ -6670,6 +6670,41 @@ dgn-import =
|
||||
.help-import = Imports a file of the chosen format with the format's importer.
|
||||
.blank = Filename cannot be blank
|
||||
.invalid-dgn = Invalid or unsupported DGN file.
|
||||
import-3ds =
|
||||
.title = 3D Studio Import
|
||||
.summary = { $objects } objects, { $materials } materials
|
||||
.change = Change...
|
||||
.mesh = Mesh
|
||||
.light = Light
|
||||
.camera = Camera
|
||||
.general = General
|
||||
.global = Global Settings
|
||||
.all = All
|
||||
.name = Name
|
||||
.type = Type
|
||||
.materials = { $n } materials
|
||||
.objects = Objects
|
||||
.search = Search by name...
|
||||
.select-all = Select all
|
||||
.clear = Clear
|
||||
.layers = Place on layers
|
||||
.by-object = By object
|
||||
.by-material = By material
|
||||
.by-color = By object color
|
||||
.single = Single layer
|
||||
.note-object = Each object goes on a layer named after it.
|
||||
.note-material = Each object goes on a layer named after its material.
|
||||
.note-color = Each object goes on a layer named after its color.
|
||||
.note-single = Every object goes on one layer.
|
||||
.multi = Objects with several materials
|
||||
.ask = Ask each time
|
||||
.split = Split by material
|
||||
.first = Assign the first material
|
||||
.none = Don't assign a material
|
||||
.reason = Check at least one object
|
||||
.cancel = Cancel
|
||||
.import = Import
|
||||
.help = Imports the checked objects of a 3D Studio file: meshes as polyface meshes with their materials, cameras as named views.
|
||||
import-panel =
|
||||
.dgn-import = DGN 匯入
|
||||
.recognize-shx = 辨識 SHX 文字
|
||||
|
||||
@@ -654,6 +654,11 @@ impl OpenCADStudio {
|
||||
self.tabs[i].snap_result = None;
|
||||
return self.run_dgn_import(i, request);
|
||||
}
|
||||
CmdResult::TdsImport(request) => {
|
||||
self.tabs[i].active_cmd = None;
|
||||
self.tabs[i].snap_result = None;
|
||||
self.run_tds_import(i, request);
|
||||
}
|
||||
CmdResult::PdfImportFile(import) => {
|
||||
self.tabs[i].scene.clear_preview_wire();
|
||||
self.tabs[i].active_cmd = None;
|
||||
|
||||
@@ -65,6 +65,7 @@ enum Route {
|
||||
Dgn,
|
||||
Model(ModelFormat),
|
||||
Wmf,
|
||||
Tds,
|
||||
}
|
||||
|
||||
/// The importer for `path` chosen as `format` (an index of [`FORMATS`]; `None`
|
||||
@@ -91,6 +92,7 @@ fn route(path: &str, format: Option<usize>) -> Result<Route, String> {
|
||||
Some("STEP") => return Ok(Route::Model(ModelFormat::Step)),
|
||||
Some("IGES") => return Ok(Route::Model(ModelFormat::Iges)),
|
||||
Some("Metafile") => return Ok(Route::Wmf),
|
||||
Some("3D Studio") => return Ok(Route::Tds),
|
||||
_ => {}
|
||||
}
|
||||
let pdf = if by_extension { ext == "pdf" } else { f.is_some_and(|f| f.name == "PDF Files") };
|
||||
@@ -245,6 +247,14 @@ impl OpenCADStudio {
|
||||
let i = self.active_tab;
|
||||
self.import_wmf(i, path, false)
|
||||
}
|
||||
// The options window opens whatever FILEDIA is.
|
||||
Ok(Route::Tds) => {
|
||||
self.remember_import(path);
|
||||
if let Err(error) = self.open_tds_import_dialog(path) {
|
||||
self.command_line.push_error(&error);
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
Err(error) => {
|
||||
self.command_line.push_error(&error);
|
||||
Task::none()
|
||||
@@ -260,6 +270,7 @@ impl OpenCADStudio {
|
||||
"dgn" => crate::t!(
|
||||
"Imports a DGN model as drawing objects: levels as layers, cells as blocks."
|
||||
),
|
||||
"3ds" => std::borrow::Cow::Owned(crate::tr!("import-3ds", "help")),
|
||||
_ => crate::t!(
|
||||
"Imports a file of the chosen format with the format's importer."
|
||||
),
|
||||
@@ -362,8 +373,71 @@ impl OpenCADStudio {
|
||||
self.close_active_modal();
|
||||
return self.import_wmf(i, &path, false);
|
||||
}
|
||||
Ok(Route::Tds) => {
|
||||
self.remember_import(&path);
|
||||
self.close_active_modal();
|
||||
if let Err(error) = self.open_tds_import_dialog(&path) {
|
||||
self.command_line.push_error(&error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// ── 3D Studio Import ─────────────────────────────────────────
|
||||
ImportDialogMsg::TdsChange => {
|
||||
let title = crate::t!("Import File").into_owned();
|
||||
let filters = vec![("3D Studio (*.3ds)".to_string(), vec!["3ds".to_string()])];
|
||||
return Task::perform(pick(title, filters), |path| {
|
||||
Message::ImportDialog(ImportDialogMsg::TdsPicked(path))
|
||||
});
|
||||
}
|
||||
ImportDialogMsg::TdsPicked(Some(path)) => {
|
||||
if let Err(error) = self.open_tds_import_dialog(&path) {
|
||||
self.command_line.push_error(&error);
|
||||
}
|
||||
}
|
||||
ImportDialogMsg::TdsPicked(None) => {}
|
||||
ImportDialogMsg::TdsCheck(k, on) => {
|
||||
if let Some(c) = self.import_dialogs.tds.as_mut().and_then(|s| s.checked.get_mut(k)) {
|
||||
*c = on;
|
||||
}
|
||||
}
|
||||
ImportDialogMsg::TdsAll | ImportDialogMsg::TdsClear => {
|
||||
let on = matches!(message, ImportDialogMsg::TdsAll);
|
||||
if let Some(s) = self.import_dialogs.tds.as_mut() {
|
||||
s.checked.iter_mut().for_each(|c| *c = on);
|
||||
}
|
||||
}
|
||||
ImportDialogMsg::TdsFilter(k) => {
|
||||
if let Some(s) = self.import_dialogs.tds.as_mut() {
|
||||
s.filter = k.min(4);
|
||||
}
|
||||
}
|
||||
ImportDialogMsg::TdsSearch(text) => {
|
||||
if let Some(s) = self.import_dialogs.tds.as_mut() {
|
||||
s.search = text;
|
||||
}
|
||||
}
|
||||
ImportDialogMsg::TdsLayers(k) => {
|
||||
if let Some(s) = self.import_dialogs.tds.as_mut() {
|
||||
s.layers = k.min(3);
|
||||
}
|
||||
}
|
||||
ImportDialogMsg::TdsMulti(k) => {
|
||||
if let Some(s) = self.import_dialogs.tds.as_mut() {
|
||||
s.multi = k.min(3);
|
||||
}
|
||||
}
|
||||
ImportDialogMsg::TdsOk => {
|
||||
let Some(state) = self.import_dialogs.tds.take() else {
|
||||
return Task::none();
|
||||
};
|
||||
if !state.checked.iter().any(|c| *c) {
|
||||
self.import_dialogs.tds = Some(state);
|
||||
return Task::none();
|
||||
}
|
||||
self.close_active_modal();
|
||||
return self.start_tds_import(i, state);
|
||||
}
|
||||
// ── DGN Import ───────────────────────────────────────────────
|
||||
ImportDialogMsg::DgnChange => return self.pick_dgn_file(),
|
||||
ImportDialogMsg::DgnPicked(Some(path)) => {
|
||||
|
||||
@@ -203,3 +203,217 @@ impl OpenCADStudio {
|
||||
Task::none()
|
||||
}
|
||||
}
|
||||
|
||||
impl OpenCADStudio {
|
||||
/// The 3D Studio Import window for a file; an error when it is no 3D
|
||||
/// Studio file.
|
||||
pub(in crate::app) fn open_tds_import_dialog(&mut self, path: &str) -> Result<(), String> {
|
||||
let scene = crate::scene::model::pdf_raster::source_bytes(path)
|
||||
.and_then(|bytes| crate::io::tds_import::read(&bytes))
|
||||
.ok_or_else(|| format!("{} is not a valid 3D Studio file.", path.replace('/', "\\")))?;
|
||||
let mut objects: Vec<(String, &'static str, usize)> = scene
|
||||
.objects
|
||||
.iter()
|
||||
.map(|(name, object)| {
|
||||
let materials = match object {
|
||||
crate::io::tds_import::TdsObject::Mesh(mesh) => scene.mesh_materials(mesh).len(),
|
||||
_ => 0,
|
||||
};
|
||||
(name.clone(), object.kind(), materials)
|
||||
})
|
||||
.collect();
|
||||
objects.sort_by_key(|o| o.0.to_lowercase());
|
||||
objects.insert(0, ("Global Settings".to_string(), "Global", 0));
|
||||
// The layer and material choices made last stay; nothing starts
|
||||
// checked.
|
||||
let previous = self.import_dialogs.tds.take();
|
||||
self.import_dialogs.tds = Some(crate::ui::window::import_dialogs::TdsImportState {
|
||||
path: path.to_string(),
|
||||
checked: vec![false; objects.len()],
|
||||
objects,
|
||||
materials: scene.materials.len(),
|
||||
filter: 0,
|
||||
search: String::new(),
|
||||
layers: previous.as_ref().map_or(0, |s| s.layers),
|
||||
multi: previous.as_ref().map_or(0, |s| s.multi),
|
||||
});
|
||||
self.active_modal = Some(crate::app::ModalKind::TdsImport);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The window's choices as an import: asked mesh by mesh first when set
|
||||
/// to ask and some checked mesh has several materials.
|
||||
pub(in crate::app) fn start_tds_import(
|
||||
&mut self,
|
||||
i: usize,
|
||||
state: crate::ui::window::import_dialogs::TdsImportState,
|
||||
) -> Task<Message> {
|
||||
use crate::io::tds_import::{LayerMode, MultiMaterial, TdsObject};
|
||||
use crate::modules::insert::tds_import::{TdsImportRequest, TdsMaterialCommand};
|
||||
let Some(scene) = crate::scene::model::pdf_raster::source_bytes(&state.path)
|
||||
.and_then(|bytes| crate::io::tds_import::read(&bytes))
|
||||
else {
|
||||
return Task::none();
|
||||
};
|
||||
let selected: Vec<String> = state
|
||||
.objects
|
||||
.iter()
|
||||
.zip(&state.checked)
|
||||
.filter(|(_, on)| **on)
|
||||
.map(|((name, _, _), _)| name.clone())
|
||||
.collect();
|
||||
let several: Vec<(String, Vec<String>)> = scene
|
||||
.objects
|
||||
.iter()
|
||||
.filter(|(name, _)| selected.contains(name))
|
||||
.filter_map(|(name, object)| match object {
|
||||
TdsObject::Mesh(mesh) => {
|
||||
let materials = scene.mesh_materials(mesh);
|
||||
(materials.len() > 1).then(|| (name.clone(), materials))
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
let choice = match state.multi {
|
||||
1 => Some(MultiMaterial::Split),
|
||||
2 => Some(MultiMaterial::First),
|
||||
3 => Some(MultiMaterial::Material(None)),
|
||||
_ => None,
|
||||
};
|
||||
let mut request = TdsImportRequest {
|
||||
path: state.path.clone(),
|
||||
selected,
|
||||
layers: match state.layers {
|
||||
1 => LayerMode::ByMaterial,
|
||||
2 => LayerMode::ByObjectColor,
|
||||
3 => LayerMode::Single,
|
||||
_ => LayerMode::ByObject,
|
||||
},
|
||||
multi: Default::default(),
|
||||
compact: matches!(state.multi, 0 | 1),
|
||||
};
|
||||
match choice {
|
||||
Some(choice) => {
|
||||
for (name, _) in several {
|
||||
request.multi.insert(name, choice.clone());
|
||||
}
|
||||
}
|
||||
None if !several.is_empty() => {
|
||||
use crate::command::CadCommand;
|
||||
let command = TdsMaterialCommand::new(request, several);
|
||||
self.command_line.push_info(&command.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(command));
|
||||
return Task::none();
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
self.run_tds_import(i, request);
|
||||
Task::none()
|
||||
}
|
||||
|
||||
/// The import itself, one undo step: the meshes on their layers with
|
||||
/// their materials, the cameras as named views.
|
||||
pub(in crate::app) fn run_tds_import(
|
||||
&mut self,
|
||||
i: usize,
|
||||
request: crate::modules::insert::tds_import::TdsImportRequest,
|
||||
) {
|
||||
use codec::objects::ObjectType;
|
||||
let Some(scene) = crate::scene::model::pdf_raster::source_bytes(&request.path)
|
||||
.and_then(|bytes| crate::io::tds_import::read(&bytes))
|
||||
else {
|
||||
self.command_line.push_error(&format!(
|
||||
"{} is not a valid 3D Studio file.",
|
||||
request.path.replace('/', "\\")
|
||||
));
|
||||
return;
|
||||
};
|
||||
let import = crate::io::tds_import::convert(
|
||||
&scene,
|
||||
&crate::io::tds_import::TdsOptions {
|
||||
selected: request.selected.clone(),
|
||||
layers: request.layers,
|
||||
multi: request.multi.clone(),
|
||||
compact: request.compact,
|
||||
},
|
||||
);
|
||||
self.push_undo_snapshot(i, "IMPORT");
|
||||
let document = &mut self.tabs[i].scene.document;
|
||||
// Materials by name: the drawing's own when it has one, else one
|
||||
// made from the file's colours.
|
||||
let mut materials: std::collections::HashMap<String, codec::Handle> = Default::default();
|
||||
for name in &import.materials {
|
||||
let existing = document.objects.iter().find_map(|(h, o)| match o {
|
||||
ObjectType::Material(m) if m.name.eq_ignore_ascii_case(name) => Some(*h),
|
||||
_ => None,
|
||||
});
|
||||
let handle = match existing {
|
||||
Some(h) => h,
|
||||
None => {
|
||||
let source = scene.materials.iter().find(|m| m.name == *name);
|
||||
let mut material = codec::objects::Material::new();
|
||||
material.handle = document.allocate_handle();
|
||||
material.owner = document.header.acad_material_dict_handle;
|
||||
material.name = name.clone();
|
||||
let color = |rgb: Option<[u8; 3]>| match rgb {
|
||||
Some([r, g, b]) => codec::objects::MaterialColor {
|
||||
flag: 1,
|
||||
factor: 1.0,
|
||||
rgb: Some(
|
||||
((0xC2u32 << 24) | (u32::from(r) << 16) | (u32::from(g) << 8) | u32::from(b))
|
||||
as i32,
|
||||
),
|
||||
},
|
||||
None => codec::objects::MaterialColor::default(),
|
||||
};
|
||||
material.ambient_color = color(source.and_then(|m| m.ambient));
|
||||
material.diffuse_color = color(source.and_then(|m| m.diffuse));
|
||||
material.specular_color = color(source.and_then(|m| m.specular));
|
||||
let handle = material.handle;
|
||||
let dictionary = document.header.acad_material_dict_handle;
|
||||
if let Some(ObjectType::Dictionary(dictionary)) = document.objects.get_mut(&dictionary) {
|
||||
dictionary.add_entry(name.clone(), handle);
|
||||
}
|
||||
document.objects.insert(handle, ObjectType::Material(material));
|
||||
handle
|
||||
}
|
||||
};
|
||||
materials.insert(name.clone(), handle);
|
||||
}
|
||||
for part in &import.parts {
|
||||
if !document.layers.contains(&part.layer) {
|
||||
let mut layer = codec::tables::Layer::new(part.layer.as_str());
|
||||
layer.handle = document.allocate_handle();
|
||||
let _ = document.layers.add(layer);
|
||||
}
|
||||
}
|
||||
for view in &import.views {
|
||||
let mut named = codec::tables::View::new(view.name.as_str());
|
||||
named.target = codec::types::Vector3::new(view.target[0], view.target[1], view.target[2]);
|
||||
named.direction =
|
||||
codec::types::Vector3::new(view.direction[0], view.direction[1], view.direction[2]);
|
||||
named.height = view.height;
|
||||
named.width = view.height;
|
||||
named.lens_length = view.lens;
|
||||
named.handle = match document.views.get(&view.name) {
|
||||
Some(old) => old.handle,
|
||||
None => document.allocate_handle(),
|
||||
};
|
||||
document.views.add_or_replace(named);
|
||||
}
|
||||
for part in import.parts {
|
||||
let mut mesh = part.mesh;
|
||||
mesh.common.layer = part.layer.clone();
|
||||
mesh.common.material_handle = part.material.as_ref().and_then(|m| materials.get(m).copied());
|
||||
if mesh.common.material_handle.is_some() {
|
||||
mesh.common.material_flags = 0b11;
|
||||
}
|
||||
self.tabs[i].scene.add_entity(codec::EntityType::PolyfaceMesh(mesh));
|
||||
}
|
||||
for line in &import.messages {
|
||||
self.command_line.push_output(line);
|
||||
}
|
||||
self.tabs[i].dirty = true;
|
||||
self.refresh_properties();
|
||||
}
|
||||
}
|
||||
@@ -110,6 +110,7 @@ pub(super) const NAMES: &[&str] = &[
|
||||
"image_adjust",
|
||||
"import_file_dialog",
|
||||
"dgn_import_dialog",
|
||||
"tds_import_dialog",
|
||||
"refedit_dialog",
|
||||
"pdf_shx_settings",
|
||||
"pdf_layer_toggle",
|
||||
@@ -2198,6 +2199,45 @@ impl OpenCADStudio {
|
||||
_ => return Err(bad()),
|
||||
})
|
||||
}
|
||||
// The 3D Studio Import window, one field per call: "check=<name>"
|
||||
// and "uncheck=<name>" (the object's name, or "Global Settings"),
|
||||
// "all", "clear", "filter=all|mesh|light|camera|general",
|
||||
// "search=<text>", "layers=object|material|color|single",
|
||||
// "multi=ask|split|first|none", "path=<file>" (as Change… picks
|
||||
// it), "ok" or "cancel".
|
||||
"tds_import_dialog" => {
|
||||
use crate::ui::window::import_dialogs::ImportDialogMsg as I;
|
||||
if self.active_modal != Some(crate::app::ModalKind::TdsImport) {
|
||||
return Err(failure("no_dialog", "The 3D Studio Import window is not open"));
|
||||
}
|
||||
let spec = string(req, "value")?;
|
||||
let (key, value) = spec.split_once('=').unwrap_or((spec, ""));
|
||||
let bad = || failure("invalid_value", spec);
|
||||
if key == "cancel" {
|
||||
return Ok(Task::done(Message::CloseModal));
|
||||
}
|
||||
let pick = |options: &[&str]| options.iter().position(|o| *o == value).ok_or_else(bad);
|
||||
Message::ImportDialog(match key {
|
||||
"ok" => I::TdsOk,
|
||||
"all" => I::TdsAll,
|
||||
"clear" => I::TdsClear,
|
||||
"check" | "uncheck" => {
|
||||
let k = self
|
||||
.import_dialogs
|
||||
.tds
|
||||
.as_ref()
|
||||
.and_then(|s| s.objects.iter().position(|(name, _, _)| name.eq_ignore_ascii_case(value)))
|
||||
.ok_or_else(bad)?;
|
||||
I::TdsCheck(k, key == "check")
|
||||
}
|
||||
"filter" => I::TdsFilter(pick(&["all", "mesh", "light", "camera", "general"])?),
|
||||
"search" => I::TdsSearch(value.to_string()),
|
||||
"layers" => I::TdsLayers(pick(&["object", "material", "color", "single"])?),
|
||||
"multi" => I::TdsMulti(pick(&["ask", "split", "first", "none"])?),
|
||||
"path" => I::TdsPicked(Some(value.to_string())),
|
||||
_ => return Err(bad()),
|
||||
})
|
||||
}
|
||||
// The DGN Import window, one field per call: "path=<file>" (as
|
||||
// Change… picks it), "model=<n>", "target=new|current",
|
||||
// "conflict=prefix|ignore", "units=master|sub", "explode=on|off",
|
||||
|
||||
@@ -2335,6 +2335,8 @@ pub enum ModalKind {
|
||||
ImportFile,
|
||||
/// DGNIMPORT's DGN Import window.
|
||||
DgnImport,
|
||||
/// IMPORT's 3D Studio Import window.
|
||||
TdsImport,
|
||||
/// Web: a crash-recovery autosave snapshot newer than the drawing's last
|
||||
/// save awaits a restore/discard decision. Distinct from the corrupt-file
|
||||
/// `RecoveryPrompt` — nothing is wrong with the drawing itself.
|
||||
|
||||
@@ -254,6 +254,7 @@ impl OpenCADStudio {
|
||||
Some(PdfImportFile) => self.pdf_import_file = None,
|
||||
Some(ImportFile) => self.import_dialogs.file = None,
|
||||
Some(DgnImport) => self.import_dialogs.dgn = None,
|
||||
Some(TdsImport) => self.import_dialogs.tds = None,
|
||||
Some(XrefAttach) => self.xref_attach = None,
|
||||
Some(WriteBlock) => self.wblock = None,
|
||||
Some(Hyperlink) => {
|
||||
|
||||
@@ -51,6 +51,7 @@ impl OpenCADStudio {
|
||||
Some(K::PdfImportFile) => crate::t!("Import PDF").into_owned(),
|
||||
Some(K::ImportFile) => crate::t!("Import File").into_owned(),
|
||||
Some(K::DgnImport) => crate::t!("DGN Import").into_owned(),
|
||||
Some(K::TdsImport) => crate::tr!("import-3ds", "title"),
|
||||
Some(K::XrefAttach) => crate::t!("Attach External Reference").into_owned(),
|
||||
Some(K::WriteBlock) => crate::t!("Write Block").into_owned(),
|
||||
Some(K::GeometricTolerance) => crate::t!("Geometric Tolerance").into_owned(),
|
||||
@@ -613,6 +614,12 @@ impl OpenCADStudio {
|
||||
crate::ui::window::import_dialogs::view_import_file(state, recent, flow)
|
||||
})
|
||||
}
|
||||
super::super::ModalKind::TdsImport => {
|
||||
let state = self.import_dialogs.tds.as_ref()?;
|
||||
sized_flow(ex, 860, 560, |flow| {
|
||||
crate::ui::window::import_dialogs::view_tds_import(state, flow)
|
||||
})
|
||||
}
|
||||
super::super::ModalKind::DgnImport => {
|
||||
let state = self.import_dialogs.dgn.as_ref()?;
|
||||
sized_flow(ex, 900, 680, |flow| {
|
||||
|
||||
@@ -2305,6 +2305,8 @@ pub enum CmdResult {
|
||||
ReturnPoint(DVec3),
|
||||
/// Import a DGN model (DGNIMPORT); ends the command.
|
||||
DgnImport(crate::modules::insert::dgn_import::DgnImportRequest),
|
||||
/// Import a 3D Studio file with every question answered; ends the command.
|
||||
TdsImport(crate::modules::insert::tds_import::TdsImportRequest),
|
||||
}
|
||||
|
||||
/// What kind of value the active command is currently asking for. Drives
|
||||
|
||||
@@ -33,6 +33,7 @@ pub mod step;
|
||||
pub mod step_import;
|
||||
pub mod stl;
|
||||
pub mod svg_export;
|
||||
pub mod tds_import;
|
||||
pub mod thumbnail;
|
||||
pub mod update_check;
|
||||
pub mod visual_styles;
|
||||
|
||||
@@ -0,0 +1,403 @@
|
||||
//! 3D Studio (.3ds) reading: the materials, meshes, lights and cameras of a
|
||||
//! file's editor section, and their conversion to drawing objects — each
|
||||
//! mesh a polyface mesh on a layer chosen by object, material, object colour
|
||||
//! or a single layer, with its material; each camera a named view.
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use codec::entities::{PolyfaceFace, PolyfaceMesh, PolyfaceVertex};
|
||||
use codec::types::Vector3;
|
||||
|
||||
pub struct TdsMaterial {
|
||||
pub name: String,
|
||||
pub ambient: Option<[u8; 3]>,
|
||||
pub diffuse: Option<[u8; 3]>,
|
||||
pub specular: Option<[u8; 3]>,
|
||||
}
|
||||
|
||||
pub struct TdsMesh {
|
||||
pub vertices: Vec<[f64; 3]>,
|
||||
pub faces: Vec<[usize; 3]>,
|
||||
/// Each material the mesh uses and the faces it covers, in file order.
|
||||
pub groups: Vec<(String, Vec<usize>)>,
|
||||
/// The object colour, a palette index, when the file gives one.
|
||||
pub color: Option<u8>,
|
||||
}
|
||||
|
||||
pub struct TdsCamera {
|
||||
pub position: [f64; 3],
|
||||
pub target: [f64; 3],
|
||||
pub lens: f64,
|
||||
}
|
||||
|
||||
pub enum TdsObject {
|
||||
Mesh(TdsMesh),
|
||||
Light,
|
||||
Camera(TdsCamera),
|
||||
}
|
||||
|
||||
impl TdsObject {
|
||||
/// The kind as the import window lists it.
|
||||
pub fn kind(&self) -> &'static str {
|
||||
match self {
|
||||
TdsObject::Mesh(_) => "Mesh",
|
||||
TdsObject::Light => "Light",
|
||||
TdsObject::Camera(_) => "Camera",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TdsScene {
|
||||
pub materials: Vec<TdsMaterial>,
|
||||
pub objects: Vec<(String, TdsObject)>,
|
||||
}
|
||||
|
||||
impl TdsScene {
|
||||
/// The materials a mesh uses, in the order the file defines them.
|
||||
pub fn mesh_materials(&self, mesh: &TdsMesh) -> Vec<String> {
|
||||
let mut used: Vec<String> = Vec::new();
|
||||
for m in &self.materials {
|
||||
if mesh.groups.iter().any(|(name, faces)| !faces.is_empty() && *name == m.name) {
|
||||
used.push(m.name.clone());
|
||||
}
|
||||
}
|
||||
for (name, faces) in &mesh.groups {
|
||||
if !faces.is_empty() && !used.contains(name) {
|
||||
used.push(name.clone());
|
||||
}
|
||||
}
|
||||
used
|
||||
}
|
||||
}
|
||||
|
||||
struct Reader<'a> {
|
||||
b: &'a [u8],
|
||||
}
|
||||
|
||||
impl Reader<'_> {
|
||||
fn u16(&self, at: usize) -> Option<u16> {
|
||||
Some(u16::from_le_bytes([*self.b.get(at)?, *self.b.get(at + 1)?]))
|
||||
}
|
||||
fn u32(&self, at: usize) -> Option<u32> {
|
||||
Some(u32::from_le_bytes(self.b.get(at..at + 4)?.try_into().ok()?))
|
||||
}
|
||||
fn f32(&self, at: usize) -> Option<f64> {
|
||||
Some(f64::from(f32::from_le_bytes(self.b.get(at..at + 4)?.try_into().ok()?)))
|
||||
}
|
||||
fn point(&self, at: usize) -> Option<[f64; 3]> {
|
||||
Some([self.f32(at)?, self.f32(at + 4)?, self.f32(at + 8)?])
|
||||
}
|
||||
/// A zero-terminated string and the offset after it.
|
||||
fn cstr(&self, at: usize, end: usize) -> (String, usize) {
|
||||
let stop = self.b[at.min(end)..end]
|
||||
.iter()
|
||||
.position(|c| *c == 0)
|
||||
.map_or(end, |k| at + k);
|
||||
(
|
||||
String::from_utf8_lossy(&self.b[at.min(stop)..stop]).into_owned(),
|
||||
(stop + 1).min(end),
|
||||
)
|
||||
}
|
||||
/// The chunks between `from` and `to`: id, body start, body end.
|
||||
fn chunks(&self, from: usize, to: usize) -> Vec<(u16, usize, usize)> {
|
||||
let mut out = Vec::new();
|
||||
let mut at = from;
|
||||
while at + 6 <= to {
|
||||
let (Some(id), Some(len)) = (self.u16(at), self.u32(at + 2)) else { break };
|
||||
let len = len as usize;
|
||||
if len < 6 || at + len > to {
|
||||
break;
|
||||
}
|
||||
out.push((id, at + 6, at + len));
|
||||
at += len;
|
||||
}
|
||||
out
|
||||
}
|
||||
/// A colour chunk's 8-bit RGB: 24-bit or float, plain before linear.
|
||||
fn color(&self, from: usize, to: usize) -> Option<[u8; 3]> {
|
||||
let chunks = self.chunks(from, to);
|
||||
for want in [0x0011, 0x0010, 0x0012, 0x0013] {
|
||||
for &(id, start, _) in &chunks {
|
||||
if id != want {
|
||||
continue;
|
||||
}
|
||||
return Some(match id {
|
||||
0x0011 | 0x0012 => [*self.b.get(start)?, *self.b.get(start + 1)?, *self.b.get(start + 2)?],
|
||||
_ => self.point(start)?.map(|c| (c.clamp(0.0, 1.0) * 255.0).round() as u8),
|
||||
});
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// `None` when the bytes are no 3D Studio file.
|
||||
pub fn read(bytes: &[u8]) -> Option<TdsScene> {
|
||||
let r = Reader { b: bytes };
|
||||
if r.u16(0)? != 0x4D4D {
|
||||
return None;
|
||||
}
|
||||
let end = (r.u32(2)? as usize).min(bytes.len());
|
||||
let mut scene = TdsScene {
|
||||
materials: Vec::new(),
|
||||
objects: Vec::new(),
|
||||
};
|
||||
for (id, start, stop) in r.chunks(6, end) {
|
||||
if id != 0x3D3D {
|
||||
continue;
|
||||
}
|
||||
for (id, start, stop) in r.chunks(start, stop) {
|
||||
match id {
|
||||
0xAFFF => {
|
||||
let mut material = TdsMaterial {
|
||||
name: String::new(),
|
||||
ambient: None,
|
||||
diffuse: None,
|
||||
specular: None,
|
||||
};
|
||||
for (id, s, e) in r.chunks(start, stop) {
|
||||
match id {
|
||||
0xA000 => material.name = r.cstr(s, e).0,
|
||||
0xA010 => material.ambient = r.color(s, e),
|
||||
0xA020 => material.diffuse = r.color(s, e),
|
||||
0xA030 => material.specular = r.color(s, e),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
scene.materials.push(material);
|
||||
}
|
||||
0x4000 => {
|
||||
let (name, body) = r.cstr(start, stop);
|
||||
for (id, s, e) in r.chunks(body, stop) {
|
||||
let object = match id {
|
||||
0x4100 => TdsObject::Mesh(read_mesh(&r, s, e)),
|
||||
0x4600 => TdsObject::Light,
|
||||
0x4700 => TdsObject::Camera(TdsCamera {
|
||||
position: r.point(s)?,
|
||||
target: r.point(s + 12)?,
|
||||
lens: r.f32(s + 28).filter(|l| *l > 0.0).unwrap_or(50.0),
|
||||
}),
|
||||
_ => continue,
|
||||
};
|
||||
scene.objects.push((name.clone(), object));
|
||||
break;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(scene)
|
||||
}
|
||||
|
||||
fn read_mesh(r: &Reader<'_>, from: usize, to: usize) -> TdsMesh {
|
||||
let mut mesh = TdsMesh {
|
||||
vertices: Vec::new(),
|
||||
faces: Vec::new(),
|
||||
groups: Vec::new(),
|
||||
color: None,
|
||||
};
|
||||
for (id, s, e) in r.chunks(from, to) {
|
||||
match id {
|
||||
0x4110 => {
|
||||
let n = r.u16(s).unwrap_or(0) as usize;
|
||||
mesh.vertices = (0..n).filter_map(|k| r.point(s + 2 + 12 * k)).collect();
|
||||
}
|
||||
0x4120 => {
|
||||
let n = r.u16(s).unwrap_or(0) as usize;
|
||||
mesh.faces = (0..n)
|
||||
.filter_map(|k| {
|
||||
let at = s + 2 + 8 * k;
|
||||
Some([r.u16(at)? as usize, r.u16(at + 2)? as usize, r.u16(at + 4)? as usize])
|
||||
})
|
||||
.collect();
|
||||
for (id, gs, ge) in r.chunks(s + 2 + 8 * n, e) {
|
||||
if id == 0x4130 {
|
||||
let (name, at) = r.cstr(gs, ge);
|
||||
let count = r.u16(at).unwrap_or(0) as usize;
|
||||
let faces = (0..count).filter_map(|k| r.u16(at + 2 + 2 * k).map(usize::from)).collect();
|
||||
mesh.groups.push((name, faces));
|
||||
}
|
||||
}
|
||||
}
|
||||
0x4165 => mesh.color = r.b.get(s).copied(),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
mesh
|
||||
}
|
||||
|
||||
/// Where imported objects go.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
pub enum LayerMode {
|
||||
#[default]
|
||||
ByObject,
|
||||
ByMaterial,
|
||||
ByObjectColor,
|
||||
Single,
|
||||
}
|
||||
|
||||
/// What a mesh with several materials becomes.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum MultiMaterial {
|
||||
/// One mesh per material.
|
||||
Split,
|
||||
/// One mesh with the first material the file defines.
|
||||
First,
|
||||
/// One mesh with this material, or none.
|
||||
Material(Option<String>),
|
||||
}
|
||||
|
||||
pub struct TdsOptions {
|
||||
/// The objects to import, by name ("Global Settings" included).
|
||||
pub selected: Vec<String>,
|
||||
pub layers: LayerMode,
|
||||
/// The choice for each mesh with several materials, by name.
|
||||
pub multi: HashMap<String, MultiMaterial>,
|
||||
/// Whether meshes keep only the vertices their faces use, in the order
|
||||
/// they first use them — as they do when the import splits by
|
||||
/// material or asks — rather than the file's vertex list.
|
||||
pub compact: bool,
|
||||
}
|
||||
|
||||
/// One imported mesh.
|
||||
pub struct TdsPart {
|
||||
pub layer: String,
|
||||
pub material: Option<String>,
|
||||
pub mesh: PolyfaceMesh,
|
||||
}
|
||||
|
||||
pub struct TdsCameraView {
|
||||
pub name: String,
|
||||
pub target: [f64; 3],
|
||||
pub direction: [f64; 3],
|
||||
/// The view height at the target, from the lens.
|
||||
pub height: f64,
|
||||
pub lens: f64,
|
||||
}
|
||||
|
||||
pub struct TdsImport {
|
||||
pub parts: Vec<TdsPart>,
|
||||
/// The materials the parts use, in the order they were first used.
|
||||
pub materials: Vec<String>,
|
||||
pub views: Vec<TdsCameraView>,
|
||||
pub messages: Vec<String>,
|
||||
}
|
||||
|
||||
/// A polyface mesh of `faces` of `mesh`: its vertices in file order, or
|
||||
/// (`compact`) only those the faces use, in the order they first use them.
|
||||
fn polyface(mesh: &TdsMesh, faces: &[usize], compact: bool) -> PolyfaceMesh {
|
||||
let mut out = PolyfaceMesh::new();
|
||||
let mut index: HashMap<usize, i16> = HashMap::new();
|
||||
if !compact {
|
||||
for (k, v) in mesh.vertices.iter().enumerate() {
|
||||
index.insert(k, out.add_vertex(PolyfaceVertex::new(Vector3::new(v[0], v[1], v[2]))));
|
||||
}
|
||||
}
|
||||
for &f in faces {
|
||||
let Some(face) = mesh.faces.get(f) else { continue };
|
||||
if face.iter().any(|v| *v >= mesh.vertices.len()) {
|
||||
continue;
|
||||
}
|
||||
let ids = face.map(|v| {
|
||||
*index.entry(v).or_insert_with(|| {
|
||||
let p = mesh.vertices[v];
|
||||
out.add_vertex(PolyfaceVertex::new(Vector3::new(p[0], p[1], p[2])))
|
||||
})
|
||||
});
|
||||
out.add_face(PolyfaceFace::triangle(ids[0], ids[1], ids[2]));
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// The view height a camera's lens sees at the target distance.
|
||||
// ponytail: one measured camera (50 mm at 86.46 units gives 30.457); the
|
||||
// height is taken as proportional to distance over lens.
|
||||
fn view_height(distance: f64, lens: f64) -> f64 {
|
||||
distance * 17.6141 / lens
|
||||
}
|
||||
|
||||
pub fn convert(scene: &TdsScene, options: &TdsOptions) -> TdsImport {
|
||||
let mut out = TdsImport {
|
||||
parts: Vec::new(),
|
||||
materials: Vec::new(),
|
||||
views: Vec::new(),
|
||||
messages: Vec::new(),
|
||||
};
|
||||
let chosen = |name: &str| options.selected.iter().any(|s| s == name);
|
||||
// The objects are taken in the order the window lists them, by name.
|
||||
let mut objects: Vec<&(String, TdsObject)> = scene.objects.iter().collect();
|
||||
objects.sort_by_key(|(name, _)| name.to_lowercase());
|
||||
for (name, object) in objects {
|
||||
if !chosen(name) {
|
||||
continue;
|
||||
}
|
||||
match object {
|
||||
TdsObject::Mesh(mesh) => {
|
||||
out.messages.push(format!("Processing object {name}"));
|
||||
let materials = scene.mesh_materials(mesh);
|
||||
let all: Vec<usize> = (0..mesh.faces.len()).collect();
|
||||
let pieces: Vec<(Option<String>, Vec<usize>)> = if materials.len() > 1 {
|
||||
match options.multi.get(name).cloned().unwrap_or(MultiMaterial::Split) {
|
||||
MultiMaterial::Split => materials
|
||||
.iter()
|
||||
.map(|m| {
|
||||
let faces = mesh
|
||||
.groups
|
||||
.iter()
|
||||
.filter(|(g, _)| g == m)
|
||||
.flat_map(|(_, f)| f.iter().copied())
|
||||
.collect();
|
||||
(Some(m.clone()), faces)
|
||||
})
|
||||
.collect(),
|
||||
MultiMaterial::First => vec![(materials.first().cloned(), all)],
|
||||
MultiMaterial::Material(m) => vec![(m, all)],
|
||||
}
|
||||
} else {
|
||||
vec![(materials.first().cloned(), all)]
|
||||
};
|
||||
let compact = options.compact || pieces.len() > 1;
|
||||
for (material, faces) in pieces {
|
||||
if let Some(m) = &material {
|
||||
if !out.materials.contains(m) {
|
||||
out.messages.push(format!("Converting material {m}"));
|
||||
out.materials.push(m.clone());
|
||||
}
|
||||
}
|
||||
let layer = match options.layers {
|
||||
LayerMode::ByObject => name.to_uppercase(),
|
||||
LayerMode::ByMaterial => material.as_deref().unwrap_or("0").to_uppercase(),
|
||||
LayerMode::ByObjectColor => match mesh.color {
|
||||
Some(c) => format!("COLOR{c:02}"),
|
||||
None => "COLORNONE".to_string(),
|
||||
},
|
||||
LayerMode::Single => "AVLAYER".to_string(),
|
||||
};
|
||||
out.parts.push(TdsPart {
|
||||
layer,
|
||||
material,
|
||||
mesh: polyface(mesh, &faces, compact),
|
||||
});
|
||||
}
|
||||
}
|
||||
TdsObject::Camera(camera) => {
|
||||
let name = name.to_uppercase();
|
||||
out.messages.push(format!("Creating camera {name}"));
|
||||
let direction = [0, 1, 2].map(|k| camera.position[k] - camera.target[k]);
|
||||
let distance = direction.iter().map(|d| d * d).sum::<f64>().sqrt();
|
||||
out.views.push(TdsCameraView {
|
||||
name,
|
||||
target: camera.target,
|
||||
direction,
|
||||
height: view_height(distance, camera.lens),
|
||||
lens: camera.lens,
|
||||
});
|
||||
}
|
||||
TdsObject::Light => {}
|
||||
}
|
||||
}
|
||||
out.messages.push("3D Studio file import completed".to_string());
|
||||
out
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ mod content_browser;
|
||||
pub(crate) mod create_block;
|
||||
mod design_center;
|
||||
pub(crate) mod dgn_import;
|
||||
pub(crate) mod tds_import;
|
||||
pub(crate) mod edit_block;
|
||||
pub(crate) mod image_transparency;
|
||||
pub(crate) mod insert_block;
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
// 3D Studio import: when the import is set to ask what a mesh with several
|
||||
// materials becomes, this asks for each such mesh in turn.
|
||||
//
|
||||
// <name> has multiple materials assigned. [Split/First/Material] <Split>:
|
||||
// Select a material or [None] <None>:
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::command::{CadCommand, CmdOption, CmdResult, InputKind};
|
||||
use crate::io::tds_import::{LayerMode, MultiMaterial};
|
||||
|
||||
/// What one 3D Studio import reads and how.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct TdsImportRequest {
|
||||
pub path: String,
|
||||
/// The objects to import, by name ("Global Settings" included).
|
||||
pub selected: Vec<String>,
|
||||
pub layers: LayerMode,
|
||||
/// The choice for each mesh with several materials, by name.
|
||||
pub multi: HashMap<String, MultiMaterial>,
|
||||
/// Meshes keep only the vertices their faces use (the import splits or
|
||||
/// asks), else the file's vertex list.
|
||||
pub compact: bool,
|
||||
}
|
||||
|
||||
pub struct TdsMaterialCommand {
|
||||
request: TdsImportRequest,
|
||||
/// The meshes still to ask about, with their materials.
|
||||
queue: Vec<(String, Vec<String>)>,
|
||||
choosing: bool,
|
||||
}
|
||||
|
||||
impl TdsMaterialCommand {
|
||||
pub fn new(request: TdsImportRequest, queue: Vec<(String, Vec<String>)>) -> Self {
|
||||
Self {
|
||||
request,
|
||||
queue,
|
||||
choosing: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn answer(&mut self, choice: MultiMaterial) -> CmdResult {
|
||||
if self.queue.is_empty() {
|
||||
return CmdResult::Cancel;
|
||||
}
|
||||
let (name, _) = self.queue.remove(0);
|
||||
self.request.multi.insert(name, choice);
|
||||
self.choosing = false;
|
||||
if self.queue.is_empty() {
|
||||
CmdResult::TdsImport(self.request.clone())
|
||||
} else {
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CadCommand for TdsMaterialCommand {
|
||||
fn name(&self) -> &'static str {
|
||||
"IMPORT"
|
||||
}
|
||||
|
||||
fn prompt(&self) -> String {
|
||||
match (self.choosing, self.queue.first()) {
|
||||
(true, _) => "Select a material or [None] <None>:".to_string(),
|
||||
(false, Some((name, _))) => {
|
||||
format!("{name} has multiple materials assigned. [Split/First/Material] <Split>:")
|
||||
}
|
||||
(false, None) => String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn options(&self) -> Vec<CmdOption> {
|
||||
if self.choosing {
|
||||
let mut options = vec![CmdOption::new("None", "N")];
|
||||
if let Some((_, materials)) = self.queue.first() {
|
||||
options.extend(materials.iter().map(|m| CmdOption::new(m, m)));
|
||||
}
|
||||
options
|
||||
} else {
|
||||
vec![
|
||||
CmdOption::new("Split", "S"),
|
||||
CmdOption::new("First", "F"),
|
||||
CmdOption::new("Material", "M"),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn input_kind(&self) -> InputKind {
|
||||
if self.choosing {
|
||||
InputKind::FreeText
|
||||
} else {
|
||||
InputKind::SingleToken
|
||||
}
|
||||
}
|
||||
|
||||
fn on_point(&mut self, _pt: glam::DVec3) -> CmdResult {
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
|
||||
fn on_text_input(&mut self, text: &str) -> Option<CmdResult> {
|
||||
let text = text.trim();
|
||||
if self.choosing {
|
||||
if text.is_empty() || text.eq_ignore_ascii_case("None") || text.eq_ignore_ascii_case("N") {
|
||||
return Some(self.answer(MultiMaterial::Material(None)));
|
||||
}
|
||||
let found = self
|
||||
.queue
|
||||
.first()
|
||||
.and_then(|(_, materials)| materials.iter().find(|m| m.eq_ignore_ascii_case(text)))
|
||||
.cloned();
|
||||
return Some(match found {
|
||||
Some(material) => self.answer(MultiMaterial::Material(Some(material))),
|
||||
None => CmdResult::ReportError("Invalid option keyword.".to_string()),
|
||||
});
|
||||
}
|
||||
let t = text.to_ascii_uppercase();
|
||||
Some(match t.as_str() {
|
||||
"" => self.answer(MultiMaterial::Split),
|
||||
_ if "SPLIT".starts_with(&t) => self.answer(MultiMaterial::Split),
|
||||
_ if "FIRST".starts_with(&t) => self.answer(MultiMaterial::First),
|
||||
_ if "MATERIAL".starts_with(&t) => {
|
||||
self.choosing = true;
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
_ => CmdResult::ReportError("Invalid option keyword.".to_string()),
|
||||
})
|
||||
}
|
||||
|
||||
fn on_enter(&mut self) -> CmdResult {
|
||||
self.on_text_input("").unwrap_or(CmdResult::NeedPoint)
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
//! Import File (IMPORT: the format first, then the file, routed to the
|
||||
//! format's importer) and DGN Import (DGNIMPORT: model, target drawing,
|
||||
//! conversion units and the mapping setup's effect on the file).
|
||||
//! format's importer), DGN Import (DGNIMPORT: model, target drawing,
|
||||
//! conversion units and the mapping setup's effect on the file) and 3D Studio
|
||||
//! Import (the objects of a .3ds file to bring in, their layers and what a
|
||||
//! mesh with several materials becomes).
|
||||
|
||||
use iced::widget::{button, column, container, image, row, scrollable, text, text_input, Space};
|
||||
use iced::{Background, Border, Element, Fill, Length, Theme};
|
||||
@@ -34,6 +36,17 @@ pub enum ImportDialogMsg {
|
||||
DgnExplode(bool),
|
||||
DgnMappingTab(usize),
|
||||
DgnOk,
|
||||
// 3D Studio Import
|
||||
TdsChange,
|
||||
TdsPicked(Option<String>),
|
||||
TdsCheck(usize, bool),
|
||||
TdsAll,
|
||||
TdsClear,
|
||||
TdsFilter(usize),
|
||||
TdsSearch(String),
|
||||
TdsLayers(usize),
|
||||
TdsMulti(usize),
|
||||
TdsOk,
|
||||
Help(&'static str),
|
||||
}
|
||||
|
||||
@@ -84,7 +97,7 @@ pub const FORMATS: [ImportFormat; 21] = [
|
||||
format("MicroStation DGN", "*.dgn", &["dgn"], 0, Support::Yes),
|
||||
format("All DGN Files", "*.*", &["*"], 0, Support::Yes),
|
||||
format("Metafile", "*.wmf", &["wmf"], 0, Support::Yes),
|
||||
format("3D Studio", "*.3ds", &["3ds"], 1, Support::No),
|
||||
format("3D Studio", "*.3ds", &["3ds"], 1, Support::Yes),
|
||||
format("ACIS", "*.sat", &["sat"], 1, Support::Yes),
|
||||
format(
|
||||
"CATIA V4",
|
||||
@@ -146,11 +159,29 @@ pub struct DgnImportState {
|
||||
pub mapping: [Vec<(String, String)>; 4],
|
||||
}
|
||||
|
||||
/// Both windows' working copies and the files imported this session.
|
||||
/// The objects of a 3D Studio file and the import's choices.
|
||||
pub struct TdsImportState {
|
||||
pub path: String,
|
||||
/// "Global Settings" first, then the file's objects by name: each name,
|
||||
/// its kind (Mesh, Light, Camera or Global) and the materials it uses.
|
||||
pub objects: Vec<(String, &'static str, usize)>,
|
||||
pub materials: usize,
|
||||
pub checked: Vec<bool>,
|
||||
/// 0 all, 1 meshes, 2 lights, 3 cameras, 4 global.
|
||||
pub filter: usize,
|
||||
pub search: String,
|
||||
/// 0 by object, 1 by material, 2 by object colour, 3 single layer.
|
||||
pub layers: usize,
|
||||
/// 0 always ask, 1 split by material, 2 first material, 3 no material.
|
||||
pub multi: usize,
|
||||
}
|
||||
|
||||
/// The windows' working copies and the files imported this session.
|
||||
#[derive(Default)]
|
||||
pub struct ImportDialogs {
|
||||
pub file: Option<ImportFileState>,
|
||||
pub dgn: Option<DgnImportState>,
|
||||
pub tds: Option<TdsImportState>,
|
||||
pub recent: Vec<String>,
|
||||
}
|
||||
|
||||
@@ -414,6 +445,212 @@ pub fn view_import_file<'a>(
|
||||
.into()
|
||||
}
|
||||
|
||||
/// One choice out of a few, as buttons stacked one above the other.
|
||||
fn stacked<'a>(options: Vec<(String, bool, Message)>) -> Element<'a, Message> {
|
||||
let buttons = options.into_iter().map(|(label, on, message)| {
|
||||
button(text(label).size(11).width(Fill))
|
||||
.on_press(message)
|
||||
.style(button_style(on))
|
||||
.padding([5, 10])
|
||||
.width(Fill)
|
||||
.into()
|
||||
});
|
||||
container(iced::widget::Column::with_children(buttons.collect::<Vec<_>>()).spacing(2))
|
||||
.padding(2)
|
||||
.width(Fill)
|
||||
.style(well_style)
|
||||
.into()
|
||||
}
|
||||
|
||||
/// The kind of a 3D Studio object in the window's words.
|
||||
fn tds_kind(kind: &str) -> String {
|
||||
match kind {
|
||||
"Mesh" => crate::tr!("import-3ds", "mesh"),
|
||||
"Light" => crate::tr!("import-3ds", "light"),
|
||||
"Camera" => crate::tr!("import-3ds", "camera"),
|
||||
_ => crate::tr!("import-3ds", "general"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn view_tds_import<'a>(
|
||||
state: &'a TdsImportState,
|
||||
sizing: crate::ui::modal::ModalSizing,
|
||||
) -> Element<'a, Message> {
|
||||
let top = container(
|
||||
row![
|
||||
container(text(file_name(&state.path)).size(12))
|
||||
.padding([4, 10])
|
||||
.style(|theme: &Theme| container::Style {
|
||||
border: Border {
|
||||
width: 1.0,
|
||||
radius: 12.0.into(),
|
||||
color: theme.palette().primary.base.color,
|
||||
},
|
||||
..Default::default()
|
||||
}),
|
||||
text(crate::tr!("import-3ds", "summary", objects = (state.objects.len().saturating_sub(1) as i64), materials = (state.materials as i64)))
|
||||
.size(11)
|
||||
.style(muted_style)
|
||||
.width(Fill),
|
||||
button(text(crate::tr!("import-3ds", "change")).size(11))
|
||||
.on_press(msg(ImportDialogMsg::TdsChange))
|
||||
.style(button::text)
|
||||
.padding([4, 6]),
|
||||
]
|
||||
.spacing(10)
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
.padding([8, 12])
|
||||
.width(Fill)
|
||||
.style(crate::ui::window::pdf_dialogs::card_style);
|
||||
|
||||
let kinds = ["", "Mesh", "Light", "Camera", "Global"];
|
||||
let mut filters = row![].spacing(4).align_y(iced::Center);
|
||||
for (k, label) in [crate::tr!("import-3ds", "all"), crate::tr!("import-3ds", "mesh"), crate::tr!("import-3ds", "light"), crate::tr!("import-3ds", "camera"), crate::tr!("import-3ds", "general")]
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
{
|
||||
filters = filters.push(chip(label, state.filter == k, msg(ImportDialogMsg::TdsFilter(k))));
|
||||
}
|
||||
let search = state.search.trim().to_lowercase();
|
||||
let mut rows = column![row![
|
||||
Space::new().width(Length::Fixed(22.0)),
|
||||
text(crate::tr!("import-3ds", "name")).size(11).style(muted_style).width(Fill),
|
||||
text(crate::tr!("import-3ds", "type")).size(11).style(muted_style).width(Length::Fixed(150.0)),
|
||||
]]
|
||||
.spacing(2);
|
||||
for (k, (name, kind, materials)) in state.objects.iter().enumerate() {
|
||||
let global = *kind == "Global";
|
||||
if state.filter != 0 && kinds[state.filter] != *kind {
|
||||
continue;
|
||||
}
|
||||
if !search.is_empty() && !name.to_lowercase().contains(&search) {
|
||||
continue;
|
||||
}
|
||||
let shown_name = if global { crate::tr!("import-3ds", "global") } else { name.clone() };
|
||||
let shown_kind = if global {
|
||||
String::new()
|
||||
} else if *kind == "Mesh" {
|
||||
format!("{} · {}", tds_kind(kind), crate::tr!("import-3ds", "materials", n = (*materials as i64)))
|
||||
} else {
|
||||
tds_kind(kind)
|
||||
};
|
||||
let on = state.checked.get(k).copied().unwrap_or(false);
|
||||
rows = rows.push(
|
||||
row![
|
||||
iced::widget::checkbox(on)
|
||||
.on_toggle(move |v| msg(ImportDialogMsg::TdsCheck(k, v)))
|
||||
.size(14),
|
||||
text(shown_name).size(12).width(Fill),
|
||||
text(shown_kind).size(11).style(muted_style).width(Length::Fixed(150.0)),
|
||||
]
|
||||
.spacing(8)
|
||||
.align_y(iced::Center),
|
||||
);
|
||||
}
|
||||
let objects = card(
|
||||
crate::tr!("import-3ds", "objects"),
|
||||
column![
|
||||
row![
|
||||
filters,
|
||||
Space::new().width(Fill),
|
||||
text_input(crate::tr!("import-3ds", "search").as_ref(), &state.search)
|
||||
.on_input(|s| msg(ImportDialogMsg::TdsSearch(s)))
|
||||
.size(11)
|
||||
.padding([4, 8])
|
||||
.width(Length::Fixed(170.0))
|
||||
.style(field_style),
|
||||
]
|
||||
.spacing(8)
|
||||
.align_y(iced::Center),
|
||||
container(scrollable(rows).height(Length::Fixed(300.0)))
|
||||
.padding(6)
|
||||
.width(Fill)
|
||||
.style(well_style),
|
||||
row![
|
||||
button(text(crate::tr!("import-3ds", "select-all")).size(11))
|
||||
.on_press(msg(ImportDialogMsg::TdsAll))
|
||||
.style(button_style(false))
|
||||
.padding([4, 10]),
|
||||
button(text(crate::tr!("import-3ds", "clear")).size(11))
|
||||
.on_press(msg(ImportDialogMsg::TdsClear))
|
||||
.style(button_style(false))
|
||||
.padding([4, 10]),
|
||||
]
|
||||
.spacing(6),
|
||||
]
|
||||
.spacing(8),
|
||||
);
|
||||
let layer_notes = [
|
||||
crate::tr!("import-3ds", "note-object"),
|
||||
crate::tr!("import-3ds", "note-material"),
|
||||
crate::tr!("import-3ds", "note-color"),
|
||||
crate::tr!("import-3ds", "note-single"),
|
||||
];
|
||||
let layers = card(
|
||||
crate::tr!("import-3ds", "layers"),
|
||||
column![
|
||||
stacked(
|
||||
[crate::tr!("import-3ds", "by-object"), crate::tr!("import-3ds", "by-material"), crate::tr!("import-3ds", "by-color"), crate::tr!("import-3ds", "single")]
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(k, label)| (label, state.layers == k, msg(ImportDialogMsg::TdsLayers(k))))
|
||||
.collect()
|
||||
),
|
||||
text(layer_notes[state.layers.min(3)].clone()).size(11).style(muted_style),
|
||||
]
|
||||
.spacing(8),
|
||||
);
|
||||
let multi = card(
|
||||
crate::tr!("import-3ds", "multi"),
|
||||
stacked(
|
||||
[
|
||||
crate::tr!("import-3ds", "ask"),
|
||||
crate::tr!("import-3ds", "split"),
|
||||
crate::tr!("import-3ds", "first"),
|
||||
crate::tr!("import-3ds", "none"),
|
||||
]
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(k, label)| (label, state.multi == k, msg(ImportDialogMsg::TdsMulti(k))))
|
||||
.collect(),
|
||||
),
|
||||
);
|
||||
let any = state.checked.iter().any(|c| *c);
|
||||
let mut actions = row![
|
||||
button(text("?").size(12))
|
||||
.on_press(msg(ImportDialogMsg::Help("3ds")))
|
||||
.style(button_style(false))
|
||||
.padding([5, 11]),
|
||||
Space::new().width(Fill),
|
||||
]
|
||||
.spacing(6)
|
||||
.align_y(iced::Center);
|
||||
if !any {
|
||||
actions = actions.push(text(crate::tr!("import-3ds", "reason")).size(11).style(muted_style));
|
||||
}
|
||||
actions = actions
|
||||
.push(dialog_button(crate::tr!("import-3ds", "cancel"), Message::CloseModal, false))
|
||||
.push(dialog_button_styled_opt(
|
||||
crate::tr!("import-3ds", "import"),
|
||||
any.then(|| msg(ImportDialogMsg::TdsOk)),
|
||||
button::primary,
|
||||
));
|
||||
column![
|
||||
top,
|
||||
row![
|
||||
container(objects).width(Length::FillPortion(3)),
|
||||
column![layers, multi].spacing(10).width(Length::FillPortion(2)),
|
||||
]
|
||||
.spacing(10),
|
||||
actions,
|
||||
]
|
||||
.spacing(10)
|
||||
.padding([10, 12])
|
||||
.width(sizing.width)
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn view_dgn_import<'a>(
|
||||
state: &'a DgnImportState,
|
||||
sizing: crate::ui::modal::ModalSizing,
|
||||
|
||||
Reference in new issue
Block a user