Skip to content

Add Material write support and Mesh texture coordinates round-trip#1084

Open
ilCosmico wants to merge 7 commits into
DomCR:masterfrom
ilCosmico:material-write
Open

Add Material write support and Mesh texture coordinates round-trip#1084
ilCosmico wants to merge 7 commits into
DomCR:masterfrom
ilCosmico:material-write

Conversation

@ilCosmico
Copy link
Copy Markdown
Contributor

Summary

Adds Material write support and Mesh texture coordinate round-trip in DWG.

  • DwgWriter emits Material objects (writeMaterial body, dispatch, dropped from skipEntry).
  • Entities persist their Material reference via the common entity data material flag bits, and CadEntityTemplate binds the handle on read.
  • The override-color RGB packing in writeMaterial uses the 0xC2 tag in the high byte so AutoCAD reads it as a true-color override instead of falling back to ByLayer.
  • Material render-mode hints are now strongly-typed enums: MaterialChannelFlags, MaterialIlluminationModel, MaterialMode replace the raw int fields.
  • New Mesh.TextureCoordinates property persists per-vertex UVs through the ADSK_XREC_SUBDVERTEXTEXCOORDS XRecord that AcDbSubDMesh stores on the mesh extension dictionary. DwgWriter materializes the XRecord in prepareDocument before writeHeader so HandleSeed stays accurate.
  • CadDictionary.Add(string key, NonGraphicalObject value) syncs value.Name with the supplied key when the value has no name yet. Without this, callers using the 2-arg overload lost the entry key on write because writers serialize item.Name as the dictionary entry name.
  • Material.ChannelFlags default lowered from UseAll to UseDiffuse. UseAll caused DWG TrueView to drop texture rendering on basic materials because the renderer sampled channels (bump, reflection, refraction, normal) the material had no data for.

Test plan

  • Diagnostic project writes 3 cubes with distinct Materials and reads them back: 3 materials, 3 meshes with material references, identical color/method/factor fields on read.
  • DWG TrueView 2025 opens a mesh exported via this stack in Realistic mode with the diffuse texture rendered correctly (textured house from an OBJ import).
  • Existing read path still parses files produced by the reference writer (verified by re-reading the same TrueView-friendly file through DwgReader after each change).

ilCosmico added 7 commits May 19, 2026 12:20
Materials are currently dropped on the floor by DwgWriter: skipEntry
returns true for Material so the entries created via MaterialCollection
never reach the file. Round-tripping a CadDocument through DwgWriter and
DwgReader reports zero materials regardless of what was in memory.

This adds a writeMaterial method mirroring DwgObjectReader.readMaterial:
name and description, then Ambient/Diffuse/Specular color blocks, then
Reflection/Opacity/Bump/Refraction maps with their transform matrices,
gloss and refraction scalars in the same positions the reader expects.
The R2007a tail block (translucence, self_illumination, reflectivity,
illumination_model, channel_flags, mode) is also emitted with defaults
sampled from an AutoCAD-produced AC1027 reference. The reader does not
consume those bytes but they are part of the body length AutoCAD writes,
so omitting them produces a recover-mode prompt in DWG TrueView.

Material is removed from skipEntry and dispatched alongside the other
NonGraphicalObject writers. Verified end to end via RepoMaterialMissing:
ACadSharp roundtrip now reports 6 materials matching the reference file,
and DWG TrueView opens the output and switches to Conceptual without
crashing.
The Material flag pair in writeCommonEntityData was hard-coded to
00 (ByLayer), so the per-entity Material handle was never written out.
On read, MaterialHandle was captured in CadEntityTemplate but never
applied to the entity's Material property during the build pass; only
Layer, LineType and BookColor were resolved.

writeCommonEntityData now checks entity.Material: when set, it writes
the 11 flag pair and the hard-pointer handle to the material, matching
what the reader expects. CadEntityTemplate.build resolves the captured
MaterialHandle against the document builder and assigns the Material
back to the entity.

Verified end to end with RepoMaterialMissing: three Mesh cubes carrying
distinct materials round-trip cleanly (meshesWithMaterial=3) and the
resulting DWG still opens in DWG TrueView and switches to Conceptual
without crashing.
The reader (readMaterial) constructs the color from the low three bytes
of the BitLong and discards the high byte. The encoder was leaving the
high byte at 0, which round-tripped through ACadSharp itself but caused
AutoCAD and DWG TrueView to render any material whose method was set to
Override as solid black: the high-byte tag tells the reader to treat the
remaining three bytes as a 24-bit RGB true color rather than fall back
to an indexed color lookup, and the indexed-color path was resolving to
black with the diffuse RGB stored inline.

Setting the high byte to 0xc2, the same true-color marker the BookColor
writer already uses, fixes the rendered output. Verified end to end in
DWG TrueView via RepoMaterialMissing: three meshes carrying Red, Green
and Blue override-color materials now render in their assigned colors
under the Realistic and Conceptual visual styles instead of all three
appearing black.
Surface the three render-mode hints AutoCAD writes in the SINCE R_2007a
Material tail block as proper enums on the Material API instead of raw
ints. The three new types mirror the ODA-side OdGiMaterialTraits enums
(ChannelFlags, IlluminationModel and Mode); their numeric values match
the constants in GiMaterial.h so the bit patterns stay identical on the
wire.

Material.ChannelFlags goes from int to MaterialChannelFlags ([Flags]),
defaulting to UseAll (0x7F = every channel enabled), which matches the
value AutoCAD reference DWGs emit unconditionally and removes the
"== 0 ? 127" fallback the writer used to need.

Material.IlluminationModel goes from int to MaterialIlluminationModel,
defaulting to BlinnShader (0) just like OdGiMaterialTraitsData.

Material.Mode is new; it was previously hard-coded to 0 in the writer
and never read. It now defaults to Realistic and is consumed by both
sides.

readMaterial now consumes the full tail block (translucence,
self_illumination, reflectivity, illumination model, channel flags and
mode) instead of stopping after the refraction map. AutoCAD always
writes these bytes, so consuming them keeps the in-memory model in sync
with the file and lets non-default values (e.g. kMetalShader, a custom
channel mask) survive a DwgWriter / DwgReader round trip.
Add Mesh.TextureCoordinates and persist them as the
ADSK_XREC_SUBDVERTEXTEXCOORDS XRecord that AutoCAD/AcDbSubDMesh stores
on a mesh extension dictionary. DwgWriter materializes the XRecord
before writeHeader so the file's HandleSeed stays accurate; the reader
template populates the property when the XRecord is present.

Also fix CadDictionary.Add(string key, NonGraphicalObject value) so it
syncs value.Name with the supplied key when the value has no name yet.
Without this, calling Add("MY_KEY", new XRecord()) would persist with
an empty dictionary entry name. And lower Material.ChannelFlags default
from UseAll to UseDiffuse: UseAll caused TrueView to sample channels
the material had no data for and dropped texture rendering.
- MeshTests covers bounding-box, default TextureCoordinates state,
  clone independence, and DWG round-trip with the
  ADSK_XREC_SUBDVERTEXTEXCOORDS XRecord.
- MaterialTests covers default property values and DWG round-trip for
  color methods, the strongly-typed enums, and diffuse-map metadata.
- WriterSingleObjectTests registers SingleMeshWithTextureCoordinates
  and SingleMaterial so the writer cases run across the supported
  ACadVersion matrix.
The render-mode hints (Translucence, self_illumination, Reflectivity,
IlluminationModel, ChannelFlags, Mode) are only present in MATERIAL
objects from R2007a onward. Reading or writing them unconditionally
runs past the end of older MATERIAL objects, corrupting the stream
position for every subsequent object and tripping ReadBitLong in
unrelated tables. Gate both sides on R2007Plus.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant