ply_viewer 0.0.2
ply_viewer: ^0.0.2 copied to clipboard
A highly customizable 3D PLY point cloud & mesh viewer widget for Flutter, supporting local files, assets, raw bytes, and network URLs using WebGL and Three.js.
import 'package:flutter/material.dart';
import 'package:ply_viewer/ply_viewer.dart';
void main() {
runApp(const PlyViewerExampleApp());
}
class PlyViewerExampleApp extends StatelessWidget {
const PlyViewerExampleApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: '3D Multi-Format Viewer Example',
theme: ThemeData.dark(useMaterial3: true),
home: const PlyViewerHomePage(),
);
}
}
class SampleModelItem {
final String name;
final String url;
final Model3DFormat format;
final double initialRotationX;
final double initialRotationZ;
const SampleModelItem({
required this.name,
required this.url,
required this.format,
this.initialRotationX = 0.0,
this.initialRotationZ = 0.0,
});
}
class PlyViewerHomePage extends StatefulWidget {
const PlyViewerHomePage({super.key});
@override
State<PlyViewerHomePage> createState() => _PlyViewerHomePageState();
}
class _PlyViewerHomePageState extends State<PlyViewerHomePage> {
final PlyViewerController _controller = PlyViewerController();
static const List<SampleModelItem> _sampleModels = [
SampleModelItem(
name: '02site.ply (GitHub PLY)',
url: 'https://raw.githubusercontent.com/rithamto/ply_viewer/main/02site.ply',
format: Model3DFormat.ply,
initialRotationZ: -90.0,
),
SampleModelItem(
name: 'Astronaut (GLB)',
url: 'https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Models/main/2.0/Astronaut/glTF-Binary/Astronaut.glb',
format: Model3DFormat.glb,
initialRotationX: 0.0,
),
SampleModelItem(
name: 'Damaged Helmet (glTF)',
url: 'https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Models/main/2.0/DamagedHelmet/glTF/DamagedHelmet.gltf',
format: Model3DFormat.gltf,
initialRotationX: 0.0,
),
];
late SampleModelItem _selectedModel;
@override
void initState() {
super.initState();
_selectedModel = _sampleModels.first;
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(_selectedModel.name),
actions: [
PopupMenuButton<SampleModelItem>(
icon: const Icon(Icons.folder_open),
tooltip: 'Switch 3D Model',
onSelected: (model) {
setState(() {
_selectedModel = model;
});
},
itemBuilder: (context) => _sampleModels
.map(
(m) => PopupMenuItem<SampleModelItem>(
value: m,
child: Text(m.name),
),
)
.toList(),
),
IconButton(
icon: const Icon(Icons.rotate_90_degrees_cw),
tooltip: 'Rotate Axis 90°',
onPressed: () => _controller.rotateAxis90(),
),
IconButton(
icon: const Icon(Icons.refresh),
tooltip: 'Reset View',
onPressed: () => _controller.resetView(),
),
IconButton(
icon: const Icon(Icons.grid_on),
tooltip: 'Toggle Grid',
onPressed: () => _controller.toggleGrid(true),
),
],
),
body: PlyViewer(
key: ValueKey(_selectedModel.url),
source: PlySource.network(
_selectedModel.url,
format: _selectedModel.format,
),
controller: _controller,
config: PlyViewerConfig(
backgroundColor: const Color(0xFF141414),
accentColor: Colors.orange,
showGrid: true,
showToolbar: true,
showDPad: true,
showElevationControls: true,
showViewToggle: true,
showOrientationToggle: true,
initialRotationX: _selectedModel.initialRotationX,
initialRotationZ: _selectedModel.initialRotationZ,
),
onLoadProgress: (progress) {
debugPrint('3D Load progress: ${(progress * 100).toStringAsFixed(1)}%');
},
onLoaded: () {
debugPrint('3D Model (${_selectedModel.format.name}) Loaded Successfully!');
},
onError: (error) {
debugPrint('3D Model Load Error: $error');
},
),
);
}
}