forked from 1b-agent/opencadstudio-upstream
Point clouds: POINTCLOUDCOLORMAP dialog, out-of-range colouring
POINTCLOUDCOLORMAP asks for a cloud (or None for the drawing's default schemes) and opens the Point Cloud Color Map dialog: intensity and elevation tabs with the scheme list, New / Delete / Rename, number of colours (2-25, resampled along the scheme), blend between the ends, reverse, gradient or bands, the colorized range, the out-of-range choice and, for elevation, the cloud's extents or a fixed range with its band height. OK / Apply write the drawing's colour map (created under ACAD_POINTCLOUD_COLORMAP_DICT when missing) and the cloud's colouring in one undo step; Make stylization current sets the tab's stylization. Values outside a scheme's range take the end colours, the scan colours or are hidden. Automation action pc_colormap edits the dialog. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
7761652a9f
commit
2eaae1ecab
32 files changed
+1449
-21
No files matched your search
@@ -3984,6 +3984,25 @@ insert =
|
||||
.pc-rcs = Point Cloud Scan
|
||||
.pc-clouds = Point Clouds
|
||||
.pc-help = Attaches a point cloud scan or project.
|
||||
.pcm-title = Point Cloud Color Map
|
||||
.pcm-ramp = Color ramp
|
||||
.pcm-count = Number of colors
|
||||
.pcm-gradient = Display as gradient
|
||||
.pcm-range = Range of colorized points
|
||||
.pcm-max-int = Maximum intensity
|
||||
.pcm-min-int = Minimum intensity
|
||||
.pcm-max-elev = Maximum elevation
|
||||
.pcm-min-elev = Minimum elevation
|
||||
.pcm-interval = Interval height
|
||||
.pcm-extents = Apply to extents of point cloud
|
||||
.pcm-oor = Out of range points
|
||||
.pcm-oor-minmax = Use min/max colors
|
||||
.pcm-oor-scan = Use RGB scan colors
|
||||
.pcm-oor-hide = Hide points
|
||||
.pcm-current = Make stylization current
|
||||
.pcm-name-taken = A color scheme with this name already exists.
|
||||
.pcm-bad-range = Invalid range of colorized points.
|
||||
.pcm-help = Sets the color schemes and ranges a point cloud is colored by intensity or elevation.
|
||||
model =
|
||||
.create = إنشاء
|
||||
.boolean = عمليات منطقية
|
||||
|
||||
@@ -3971,6 +3971,25 @@ insert =
|
||||
.pc-rcs = Point Cloud Scan
|
||||
.pc-clouds = Point Clouds
|
||||
.pc-help = Attaches a point cloud scan or project.
|
||||
.pcm-title = Point Cloud Color Map
|
||||
.pcm-ramp = Color ramp
|
||||
.pcm-count = Number of colors
|
||||
.pcm-gradient = Display as gradient
|
||||
.pcm-range = Range of colorized points
|
||||
.pcm-max-int = Maximum intensity
|
||||
.pcm-min-int = Minimum intensity
|
||||
.pcm-max-elev = Maximum elevation
|
||||
.pcm-min-elev = Minimum elevation
|
||||
.pcm-interval = Interval height
|
||||
.pcm-extents = Apply to extents of point cloud
|
||||
.pcm-oor = Out of range points
|
||||
.pcm-oor-minmax = Use min/max colors
|
||||
.pcm-oor-scan = Use RGB scan colors
|
||||
.pcm-oor-hide = Hide points
|
||||
.pcm-current = Make stylization current
|
||||
.pcm-name-taken = A color scheme with this name already exists.
|
||||
.pcm-bad-range = Invalid range of colorized points.
|
||||
.pcm-help = Sets the color schemes and ranges a point cloud is colored by intensity or elevation.
|
||||
model =
|
||||
.create = Създаване
|
||||
.boolean = Булеви операции
|
||||
|
||||
@@ -3969,6 +3969,25 @@ insert =
|
||||
.pc-rcs = Point Cloud Scan
|
||||
.pc-clouds = Point Clouds
|
||||
.pc-help = Attaches a point cloud scan or project.
|
||||
.pcm-title = Point Cloud Color Map
|
||||
.pcm-ramp = Color ramp
|
||||
.pcm-count = Number of colors
|
||||
.pcm-gradient = Display as gradient
|
||||
.pcm-range = Range of colorized points
|
||||
.pcm-max-int = Maximum intensity
|
||||
.pcm-min-int = Minimum intensity
|
||||
.pcm-max-elev = Maximum elevation
|
||||
.pcm-min-elev = Minimum elevation
|
||||
.pcm-interval = Interval height
|
||||
.pcm-extents = Apply to extents of point cloud
|
||||
.pcm-oor = Out of range points
|
||||
.pcm-oor-minmax = Use min/max colors
|
||||
.pcm-oor-scan = Use RGB scan colors
|
||||
.pcm-oor-hide = Hide points
|
||||
.pcm-current = Make stylization current
|
||||
.pcm-name-taken = A color scheme with this name already exists.
|
||||
.pcm-bad-range = Invalid range of colorized points.
|
||||
.pcm-help = Sets the color schemes and ranges a point cloud is colored by intensity or elevation.
|
||||
model =
|
||||
.create = Vytvořit
|
||||
.boolean = Booleovské operace
|
||||
|
||||
@@ -3970,6 +3970,25 @@ insert =
|
||||
.pc-rcs = Point Cloud Scan
|
||||
.pc-clouds = Point Clouds
|
||||
.pc-help = Attaches a point cloud scan or project.
|
||||
.pcm-title = Point Cloud Color Map
|
||||
.pcm-ramp = Color ramp
|
||||
.pcm-count = Number of colors
|
||||
.pcm-gradient = Display as gradient
|
||||
.pcm-range = Range of colorized points
|
||||
.pcm-max-int = Maximum intensity
|
||||
.pcm-min-int = Minimum intensity
|
||||
.pcm-max-elev = Maximum elevation
|
||||
.pcm-min-elev = Minimum elevation
|
||||
.pcm-interval = Interval height
|
||||
.pcm-extents = Apply to extents of point cloud
|
||||
.pcm-oor = Out of range points
|
||||
.pcm-oor-minmax = Use min/max colors
|
||||
.pcm-oor-scan = Use RGB scan colors
|
||||
.pcm-oor-hide = Hide points
|
||||
.pcm-current = Make stylization current
|
||||
.pcm-name-taken = A color scheme with this name already exists.
|
||||
.pcm-bad-range = Invalid range of colorized points.
|
||||
.pcm-help = Sets the color schemes and ranges a point cloud is colored by intensity or elevation.
|
||||
model =
|
||||
.create = Erstellen
|
||||
.boolean = Boolesche Operationen
|
||||
|
||||
@@ -3972,6 +3972,25 @@ insert =
|
||||
.pc-rcs = Point Cloud Scan
|
||||
.pc-clouds = Point Clouds
|
||||
.pc-help = Attaches a point cloud scan or project.
|
||||
.pcm-title = Point Cloud Color Map
|
||||
.pcm-ramp = Color ramp
|
||||
.pcm-count = Number of colors
|
||||
.pcm-gradient = Display as gradient
|
||||
.pcm-range = Range of colorized points
|
||||
.pcm-max-int = Maximum intensity
|
||||
.pcm-min-int = Minimum intensity
|
||||
.pcm-max-elev = Maximum elevation
|
||||
.pcm-min-elev = Minimum elevation
|
||||
.pcm-interval = Interval height
|
||||
.pcm-extents = Apply to extents of point cloud
|
||||
.pcm-oor = Out of range points
|
||||
.pcm-oor-minmax = Use min/max colors
|
||||
.pcm-oor-scan = Use RGB scan colors
|
||||
.pcm-oor-hide = Hide points
|
||||
.pcm-current = Make stylization current
|
||||
.pcm-name-taken = A color scheme with this name already exists.
|
||||
.pcm-bad-range = Invalid range of colorized points.
|
||||
.pcm-help = Sets the color schemes and ranges a point cloud is colored by intensity or elevation.
|
||||
model =
|
||||
.create = Δημιουργία
|
||||
.boolean = Δυαδική πράξη
|
||||
|
||||
@@ -3973,6 +3973,25 @@ insert =
|
||||
.pc-rcs = Point Cloud Scan
|
||||
.pc-clouds = Point Clouds
|
||||
.pc-help = Attaches a point cloud scan or project.
|
||||
.pcm-title = Point Cloud Color Map
|
||||
.pcm-ramp = Color ramp
|
||||
.pcm-count = Number of colors
|
||||
.pcm-gradient = Display as gradient
|
||||
.pcm-range = Range of colorized points
|
||||
.pcm-max-int = Maximum intensity
|
||||
.pcm-min-int = Minimum intensity
|
||||
.pcm-max-elev = Maximum elevation
|
||||
.pcm-min-elev = Minimum elevation
|
||||
.pcm-interval = Interval height
|
||||
.pcm-extents = Apply to extents of point cloud
|
||||
.pcm-oor = Out of range points
|
||||
.pcm-oor-minmax = Use min/max colors
|
||||
.pcm-oor-scan = Use RGB scan colors
|
||||
.pcm-oor-hide = Hide points
|
||||
.pcm-current = Make stylization current
|
||||
.pcm-name-taken = A color scheme with this name already exists.
|
||||
.pcm-bad-range = Invalid range of colorized points.
|
||||
.pcm-help = Sets the color schemes and ranges a point cloud is colored by intensity or elevation.
|
||||
model =
|
||||
.create = Create
|
||||
.boolean = Boolean
|
||||
|
||||
@@ -3972,6 +3972,25 @@ insert =
|
||||
.pc-rcs = Point Cloud Scan
|
||||
.pc-clouds = Point Clouds
|
||||
.pc-help = Attaches a point cloud scan or project.
|
||||
.pcm-title = Point Cloud Color Map
|
||||
.pcm-ramp = Color ramp
|
||||
.pcm-count = Number of colors
|
||||
.pcm-gradient = Display as gradient
|
||||
.pcm-range = Range of colorized points
|
||||
.pcm-max-int = Maximum intensity
|
||||
.pcm-min-int = Minimum intensity
|
||||
.pcm-max-elev = Maximum elevation
|
||||
.pcm-min-elev = Minimum elevation
|
||||
.pcm-interval = Interval height
|
||||
.pcm-extents = Apply to extents of point cloud
|
||||
.pcm-oor = Out of range points
|
||||
.pcm-oor-minmax = Use min/max colors
|
||||
.pcm-oor-scan = Use RGB scan colors
|
||||
.pcm-oor-hide = Hide points
|
||||
.pcm-current = Make stylization current
|
||||
.pcm-name-taken = A color scheme with this name already exists.
|
||||
.pcm-bad-range = Invalid range of colorized points.
|
||||
.pcm-help = Sets the color schemes and ranges a point cloud is colored by intensity or elevation.
|
||||
model =
|
||||
.create = Crear
|
||||
.boolean = Booleanas
|
||||
|
||||
@@ -3971,6 +3971,25 @@ insert =
|
||||
.pc-rcs = Point Cloud Scan
|
||||
.pc-clouds = Point Clouds
|
||||
.pc-help = Attaches a point cloud scan or project.
|
||||
.pcm-title = Point Cloud Color Map
|
||||
.pcm-ramp = Color ramp
|
||||
.pcm-count = Number of colors
|
||||
.pcm-gradient = Display as gradient
|
||||
.pcm-range = Range of colorized points
|
||||
.pcm-max-int = Maximum intensity
|
||||
.pcm-min-int = Minimum intensity
|
||||
.pcm-max-elev = Maximum elevation
|
||||
.pcm-min-elev = Minimum elevation
|
||||
.pcm-interval = Interval height
|
||||
.pcm-extents = Apply to extents of point cloud
|
||||
.pcm-oor = Out of range points
|
||||
.pcm-oor-minmax = Use min/max colors
|
||||
.pcm-oor-scan = Use RGB scan colors
|
||||
.pcm-oor-hide = Hide points
|
||||
.pcm-current = Make stylization current
|
||||
.pcm-name-taken = A color scheme with this name already exists.
|
||||
.pcm-bad-range = Invalid range of colorized points.
|
||||
.pcm-help = Sets the color schemes and ranges a point cloud is colored by intensity or elevation.
|
||||
model =
|
||||
.create = Luo
|
||||
.boolean = Boolen operaatiot
|
||||
|
||||
@@ -3972,6 +3972,25 @@ insert =
|
||||
.pc-rcs = Point Cloud Scan
|
||||
.pc-clouds = Point Clouds
|
||||
.pc-help = Attaches a point cloud scan or project.
|
||||
.pcm-title = Point Cloud Color Map
|
||||
.pcm-ramp = Color ramp
|
||||
.pcm-count = Number of colors
|
||||
.pcm-gradient = Display as gradient
|
||||
.pcm-range = Range of colorized points
|
||||
.pcm-max-int = Maximum intensity
|
||||
.pcm-min-int = Minimum intensity
|
||||
.pcm-max-elev = Maximum elevation
|
||||
.pcm-min-elev = Minimum elevation
|
||||
.pcm-interval = Interval height
|
||||
.pcm-extents = Apply to extents of point cloud
|
||||
.pcm-oor = Out of range points
|
||||
.pcm-oor-minmax = Use min/max colors
|
||||
.pcm-oor-scan = Use RGB scan colors
|
||||
.pcm-oor-hide = Hide points
|
||||
.pcm-current = Make stylization current
|
||||
.pcm-name-taken = A color scheme with this name already exists.
|
||||
.pcm-bad-range = Invalid range of colorized points.
|
||||
.pcm-help = Sets the color schemes and ranges a point cloud is colored by intensity or elevation.
|
||||
model =
|
||||
.create = Créer
|
||||
.boolean = Opérations booléennes
|
||||
|
||||
@@ -3960,6 +3960,25 @@ insert =
|
||||
.pc-rcs = Point Cloud Scan
|
||||
.pc-clouds = Point Clouds
|
||||
.pc-help = Attaches a point cloud scan or project.
|
||||
.pcm-title = Point Cloud Color Map
|
||||
.pcm-ramp = Color ramp
|
||||
.pcm-count = Number of colors
|
||||
.pcm-gradient = Display as gradient
|
||||
.pcm-range = Range of colorized points
|
||||
.pcm-max-int = Maximum intensity
|
||||
.pcm-min-int = Minimum intensity
|
||||
.pcm-max-elev = Maximum elevation
|
||||
.pcm-min-elev = Minimum elevation
|
||||
.pcm-interval = Interval height
|
||||
.pcm-extents = Apply to extents of point cloud
|
||||
.pcm-oor = Out of range points
|
||||
.pcm-oor-minmax = Use min/max colors
|
||||
.pcm-oor-scan = Use RGB scan colors
|
||||
.pcm-oor-hide = Hide points
|
||||
.pcm-current = Make stylization current
|
||||
.pcm-name-taken = A color scheme with this name already exists.
|
||||
.pcm-bad-range = Invalid range of colorized points.
|
||||
.pcm-help = Sets the color schemes and ranges a point cloud is colored by intensity or elevation.
|
||||
model =
|
||||
.create = बनाएँ
|
||||
.boolean = बूलियन संक्रियाएँ
|
||||
|
||||
@@ -3971,6 +3971,25 @@ insert =
|
||||
.pc-rcs = Point Cloud Scan
|
||||
.pc-clouds = Point Clouds
|
||||
.pc-help = Attaches a point cloud scan or project.
|
||||
.pcm-title = Point Cloud Color Map
|
||||
.pcm-ramp = Color ramp
|
||||
.pcm-count = Number of colors
|
||||
.pcm-gradient = Display as gradient
|
||||
.pcm-range = Range of colorized points
|
||||
.pcm-max-int = Maximum intensity
|
||||
.pcm-min-int = Minimum intensity
|
||||
.pcm-max-elev = Maximum elevation
|
||||
.pcm-min-elev = Minimum elevation
|
||||
.pcm-interval = Interval height
|
||||
.pcm-extents = Apply to extents of point cloud
|
||||
.pcm-oor = Out of range points
|
||||
.pcm-oor-minmax = Use min/max colors
|
||||
.pcm-oor-scan = Use RGB scan colors
|
||||
.pcm-oor-hide = Hide points
|
||||
.pcm-current = Make stylization current
|
||||
.pcm-name-taken = A color scheme with this name already exists.
|
||||
.pcm-bad-range = Invalid range of colorized points.
|
||||
.pcm-help = Sets the color schemes and ranges a point cloud is colored by intensity or elevation.
|
||||
model =
|
||||
.create = Létrehozás
|
||||
.boolean = Logikai műveletek
|
||||
|
||||
@@ -3971,6 +3971,25 @@ insert =
|
||||
.pc-rcs = Point Cloud Scan
|
||||
.pc-clouds = Point Clouds
|
||||
.pc-help = Attaches a point cloud scan or project.
|
||||
.pcm-title = Point Cloud Color Map
|
||||
.pcm-ramp = Color ramp
|
||||
.pcm-count = Number of colors
|
||||
.pcm-gradient = Display as gradient
|
||||
.pcm-range = Range of colorized points
|
||||
.pcm-max-int = Maximum intensity
|
||||
.pcm-min-int = Minimum intensity
|
||||
.pcm-max-elev = Maximum elevation
|
||||
.pcm-min-elev = Minimum elevation
|
||||
.pcm-interval = Interval height
|
||||
.pcm-extents = Apply to extents of point cloud
|
||||
.pcm-oor = Out of range points
|
||||
.pcm-oor-minmax = Use min/max colors
|
||||
.pcm-oor-scan = Use RGB scan colors
|
||||
.pcm-oor-hide = Hide points
|
||||
.pcm-current = Make stylization current
|
||||
.pcm-name-taken = A color scheme with this name already exists.
|
||||
.pcm-bad-range = Invalid range of colorized points.
|
||||
.pcm-help = Sets the color schemes and ranges a point cloud is colored by intensity or elevation.
|
||||
model =
|
||||
.create = Crea
|
||||
.boolean = Operazioni booleane
|
||||
|
||||
@@ -3960,6 +3960,25 @@ insert =
|
||||
.pc-rcs = Point Cloud Scan
|
||||
.pc-clouds = Point Clouds
|
||||
.pc-help = Attaches a point cloud scan or project.
|
||||
.pcm-title = Point Cloud Color Map
|
||||
.pcm-ramp = Color ramp
|
||||
.pcm-count = Number of colors
|
||||
.pcm-gradient = Display as gradient
|
||||
.pcm-range = Range of colorized points
|
||||
.pcm-max-int = Maximum intensity
|
||||
.pcm-min-int = Minimum intensity
|
||||
.pcm-max-elev = Maximum elevation
|
||||
.pcm-min-elev = Minimum elevation
|
||||
.pcm-interval = Interval height
|
||||
.pcm-extents = Apply to extents of point cloud
|
||||
.pcm-oor = Out of range points
|
||||
.pcm-oor-minmax = Use min/max colors
|
||||
.pcm-oor-scan = Use RGB scan colors
|
||||
.pcm-oor-hide = Hide points
|
||||
.pcm-current = Make stylization current
|
||||
.pcm-name-taken = A color scheme with this name already exists.
|
||||
.pcm-bad-range = Invalid range of colorized points.
|
||||
.pcm-help = Sets the color schemes and ranges a point cloud is colored by intensity or elevation.
|
||||
model =
|
||||
.create = 作成
|
||||
.boolean = ブール演算
|
||||
|
||||
@@ -3971,6 +3971,25 @@ insert =
|
||||
.pc-rcs = Point Cloud Scan
|
||||
.pc-clouds = Point Clouds
|
||||
.pc-help = Attaches a point cloud scan or project.
|
||||
.pcm-title = Point Cloud Color Map
|
||||
.pcm-ramp = Color ramp
|
||||
.pcm-count = Number of colors
|
||||
.pcm-gradient = Display as gradient
|
||||
.pcm-range = Range of colorized points
|
||||
.pcm-max-int = Maximum intensity
|
||||
.pcm-min-int = Minimum intensity
|
||||
.pcm-max-elev = Maximum elevation
|
||||
.pcm-min-elev = Minimum elevation
|
||||
.pcm-interval = Interval height
|
||||
.pcm-extents = Apply to extents of point cloud
|
||||
.pcm-oor = Out of range points
|
||||
.pcm-oor-minmax = Use min/max colors
|
||||
.pcm-oor-scan = Use RGB scan colors
|
||||
.pcm-oor-hide = Hide points
|
||||
.pcm-current = Make stylization current
|
||||
.pcm-name-taken = A color scheme with this name already exists.
|
||||
.pcm-bad-range = Invalid range of colorized points.
|
||||
.pcm-help = Sets the color schemes and ranges a point cloud is colored by intensity or elevation.
|
||||
model =
|
||||
.create = 생성
|
||||
.boolean = 불리언 연산
|
||||
|
||||
@@ -3970,6 +3970,25 @@ insert =
|
||||
.pc-rcs = Point Cloud Scan
|
||||
.pc-clouds = Point Clouds
|
||||
.pc-help = Attaches a point cloud scan or project.
|
||||
.pcm-title = Point Cloud Color Map
|
||||
.pcm-ramp = Color ramp
|
||||
.pcm-count = Number of colors
|
||||
.pcm-gradient = Display as gradient
|
||||
.pcm-range = Range of colorized points
|
||||
.pcm-max-int = Maximum intensity
|
||||
.pcm-min-int = Minimum intensity
|
||||
.pcm-max-elev = Maximum elevation
|
||||
.pcm-min-elev = Minimum elevation
|
||||
.pcm-interval = Interval height
|
||||
.pcm-extents = Apply to extents of point cloud
|
||||
.pcm-oor = Out of range points
|
||||
.pcm-oor-minmax = Use min/max colors
|
||||
.pcm-oor-scan = Use RGB scan colors
|
||||
.pcm-oor-hide = Hide points
|
||||
.pcm-current = Make stylization current
|
||||
.pcm-name-taken = A color scheme with this name already exists.
|
||||
.pcm-bad-range = Invalid range of colorized points.
|
||||
.pcm-help = Sets the color schemes and ranges a point cloud is colored by intensity or elevation.
|
||||
model =
|
||||
.create = Maken
|
||||
.boolean = Booleaanse bewerkingen
|
||||
|
||||
@@ -3971,6 +3971,25 @@ insert =
|
||||
.pc-rcs = Point Cloud Scan
|
||||
.pc-clouds = Point Clouds
|
||||
.pc-help = Attaches a point cloud scan or project.
|
||||
.pcm-title = Point Cloud Color Map
|
||||
.pcm-ramp = Color ramp
|
||||
.pcm-count = Number of colors
|
||||
.pcm-gradient = Display as gradient
|
||||
.pcm-range = Range of colorized points
|
||||
.pcm-max-int = Maximum intensity
|
||||
.pcm-min-int = Minimum intensity
|
||||
.pcm-max-elev = Maximum elevation
|
||||
.pcm-min-elev = Minimum elevation
|
||||
.pcm-interval = Interval height
|
||||
.pcm-extents = Apply to extents of point cloud
|
||||
.pcm-oor = Out of range points
|
||||
.pcm-oor-minmax = Use min/max colors
|
||||
.pcm-oor-scan = Use RGB scan colors
|
||||
.pcm-oor-hide = Hide points
|
||||
.pcm-current = Make stylization current
|
||||
.pcm-name-taken = A color scheme with this name already exists.
|
||||
.pcm-bad-range = Invalid range of colorized points.
|
||||
.pcm-help = Sets the color schemes and ranges a point cloud is colored by intensity or elevation.
|
||||
model =
|
||||
.create = Utwórz
|
||||
.boolean = Operacje boolowskie
|
||||
|
||||
@@ -3972,6 +3972,25 @@ insert =
|
||||
.pc-rcs = Point Cloud Scan
|
||||
.pc-clouds = Point Clouds
|
||||
.pc-help = Attaches a point cloud scan or project.
|
||||
.pcm-title = Point Cloud Color Map
|
||||
.pcm-ramp = Color ramp
|
||||
.pcm-count = Number of colors
|
||||
.pcm-gradient = Display as gradient
|
||||
.pcm-range = Range of colorized points
|
||||
.pcm-max-int = Maximum intensity
|
||||
.pcm-min-int = Minimum intensity
|
||||
.pcm-max-elev = Maximum elevation
|
||||
.pcm-min-elev = Minimum elevation
|
||||
.pcm-interval = Interval height
|
||||
.pcm-extents = Apply to extents of point cloud
|
||||
.pcm-oor = Out of range points
|
||||
.pcm-oor-minmax = Use min/max colors
|
||||
.pcm-oor-scan = Use RGB scan colors
|
||||
.pcm-oor-hide = Hide points
|
||||
.pcm-current = Make stylization current
|
||||
.pcm-name-taken = A color scheme with this name already exists.
|
||||
.pcm-bad-range = Invalid range of colorized points.
|
||||
.pcm-help = Sets the color schemes and ranges a point cloud is colored by intensity or elevation.
|
||||
model =
|
||||
.create = Criar
|
||||
.boolean = Operações booleanas
|
||||
|
||||
@@ -3973,6 +3973,25 @@ insert =
|
||||
.pc-rcs = Point Cloud Scan
|
||||
.pc-clouds = Point Clouds
|
||||
.pc-help = Attaches a point cloud scan or project.
|
||||
.pcm-title = Point Cloud Color Map
|
||||
.pcm-ramp = Color ramp
|
||||
.pcm-count = Number of colors
|
||||
.pcm-gradient = Display as gradient
|
||||
.pcm-range = Range of colorized points
|
||||
.pcm-max-int = Maximum intensity
|
||||
.pcm-min-int = Minimum intensity
|
||||
.pcm-max-elev = Maximum elevation
|
||||
.pcm-min-elev = Minimum elevation
|
||||
.pcm-interval = Interval height
|
||||
.pcm-extents = Apply to extents of point cloud
|
||||
.pcm-oor = Out of range points
|
||||
.pcm-oor-minmax = Use min/max colors
|
||||
.pcm-oor-scan = Use RGB scan colors
|
||||
.pcm-oor-hide = Hide points
|
||||
.pcm-current = Make stylization current
|
||||
.pcm-name-taken = A color scheme with this name already exists.
|
||||
.pcm-bad-range = Invalid range of colorized points.
|
||||
.pcm-help = Sets the color schemes and ranges a point cloud is colored by intensity or elevation.
|
||||
model =
|
||||
.create = Создание
|
||||
.boolean = Булевы операции
|
||||
|
||||
@@ -3932,6 +3932,25 @@ insert =
|
||||
.pc-rcs = Nokta Bulutu Taraması
|
||||
.pc-clouds = Nokta Bulutları
|
||||
.pc-help = Bir nokta bulutu taramasını veya projesini bağlar.
|
||||
.pcm-title = Nokta Bulutu Renk Haritası
|
||||
.pcm-ramp = Renk rampası
|
||||
.pcm-count = Renk sayısı
|
||||
.pcm-gradient = Geçişli göster
|
||||
.pcm-range = Renklendirilen nokta aralığı
|
||||
.pcm-max-int = En büyük yoğunluk (%)
|
||||
.pcm-min-int = En küçük yoğunluk (%)
|
||||
.pcm-max-elev = En büyük yükseklik
|
||||
.pcm-min-elev = En küçük yükseklik
|
||||
.pcm-interval = Aralık yüksekliği
|
||||
.pcm-extents = Nokta bulutu kapsamına uygula
|
||||
.pcm-oor = Aralık dışı noktalar
|
||||
.pcm-oor-minmax = En küçük/en büyük renkleri kullan
|
||||
.pcm-oor-scan = Tarama RGB renklerini kullan
|
||||
.pcm-oor-hide = Noktaları gizle
|
||||
.pcm-current = Stilizasyonu geçerli yap
|
||||
.pcm-name-taken = Bu adda bir renk şeması zaten var.
|
||||
.pcm-bad-range = Renklendirilen nokta aralığı geçersiz.
|
||||
.pcm-help = Nokta bulutunun yoğunluğa veya yüksekliğe göre renklendirildiği renk şemalarını ve aralıkları ayarlar.
|
||||
model =
|
||||
.create = Oluştur
|
||||
.boolean = Boole işlemleri
|
||||
|
||||
@@ -3955,6 +3955,25 @@ insert =
|
||||
.pc-rcs = Point Cloud Scan
|
||||
.pc-clouds = Point Clouds
|
||||
.pc-help = Attaches a point cloud scan or project.
|
||||
.pcm-title = Point Cloud Color Map
|
||||
.pcm-ramp = Color ramp
|
||||
.pcm-count = Number of colors
|
||||
.pcm-gradient = Display as gradient
|
||||
.pcm-range = Range of colorized points
|
||||
.pcm-max-int = Maximum intensity
|
||||
.pcm-min-int = Minimum intensity
|
||||
.pcm-max-elev = Maximum elevation
|
||||
.pcm-min-elev = Minimum elevation
|
||||
.pcm-interval = Interval height
|
||||
.pcm-extents = Apply to extents of point cloud
|
||||
.pcm-oor = Out of range points
|
||||
.pcm-oor-minmax = Use min/max colors
|
||||
.pcm-oor-scan = Use RGB scan colors
|
||||
.pcm-oor-hide = Hide points
|
||||
.pcm-current = Make stylization current
|
||||
.pcm-name-taken = A color scheme with this name already exists.
|
||||
.pcm-bad-range = Invalid range of colorized points.
|
||||
.pcm-help = Sets the color schemes and ranges a point cloud is colored by intensity or elevation.
|
||||
model =
|
||||
.create = 创建
|
||||
.boolean = 布尔运算
|
||||
|
||||
@@ -3967,6 +3967,25 @@ insert =
|
||||
.pc-rcs = Point Cloud Scan
|
||||
.pc-clouds = Point Clouds
|
||||
.pc-help = Attaches a point cloud scan or project.
|
||||
.pcm-title = Point Cloud Color Map
|
||||
.pcm-ramp = Color ramp
|
||||
.pcm-count = Number of colors
|
||||
.pcm-gradient = Display as gradient
|
||||
.pcm-range = Range of colorized points
|
||||
.pcm-max-int = Maximum intensity
|
||||
.pcm-min-int = Minimum intensity
|
||||
.pcm-max-elev = Maximum elevation
|
||||
.pcm-min-elev = Minimum elevation
|
||||
.pcm-interval = Interval height
|
||||
.pcm-extents = Apply to extents of point cloud
|
||||
.pcm-oor = Out of range points
|
||||
.pcm-oor-minmax = Use min/max colors
|
||||
.pcm-oor-scan = Use RGB scan colors
|
||||
.pcm-oor-hide = Hide points
|
||||
.pcm-current = Make stylization current
|
||||
.pcm-name-taken = A color scheme with this name already exists.
|
||||
.pcm-bad-range = Invalid range of colorized points.
|
||||
.pcm-help = Sets the color schemes and ranges a point cloud is colored by intensity or elevation.
|
||||
model =
|
||||
.create = 建立
|
||||
.boolean = 布林運算
|
||||
|
||||
@@ -15,6 +15,7 @@ mod layerprops;
|
||||
mod layers;
|
||||
pub(crate) mod pdf_import;
|
||||
mod point_cloud;
|
||||
mod pc_colormap;
|
||||
mod pc_extract;
|
||||
mod pdf_underlay;
|
||||
mod pdf_dialogs;
|
||||
@@ -271,6 +272,9 @@ impl OpenCADStudio {
|
||||
if let Some(t) = self.dispatch_pdf_underlay(cmd, i) {
|
||||
return Some(t);
|
||||
}
|
||||
if let Some(t) = self.dispatch_pc_colormap(cmd, i) {
|
||||
return Some(t);
|
||||
}
|
||||
if let Some(t) = self.dispatch_pc_extract(cmd, i) {
|
||||
return Some(t);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,405 @@
|
||||
//! POINTCLOUDCOLORMAP: the Point Cloud Color Map dialog for a chosen cloud
|
||||
//! (or the drawing's default schemes), and what OK / Apply write — the
|
||||
//! drawing's colour map (its schemes) and the cloud's intensity and
|
||||
//! elevation colouring — as one undo step.
|
||||
|
||||
use super::*;
|
||||
use codec::entities::{ExtendedEntityData, PointCloudExData};
|
||||
use codec::objects::{ClassObject, ClassObjectData, Dictionary, ObjectType, PointCloudColorMap, PointCloudColorRamp, PointCloudRampColor};
|
||||
use codec::types::Handle;
|
||||
|
||||
use crate::ui::window::pdf_dialogs::{short, MapRamp, MapSlot, OutOfRange, PdfDialogMsg, PointCloudColorMapState};
|
||||
|
||||
const MAP_DICTIONARY: &str = "ACAD_POINTCLOUD_COLORMAP_DICT";
|
||||
const MAP_ENTRY: &str = "ACAD_POINTCLOUD_COLORMAP";
|
||||
/// The classification scheme the colour map names by default.
|
||||
const CLASSIFICATION_SCHEME: &str = "8AF59545-D22F-46B7-923C-912F71ED57DE";
|
||||
|
||||
fn cloud_data(document: &codec::CadDocument, handle: Handle) -> Option<&PointCloudExData> {
|
||||
match document.get_entity(handle)? {
|
||||
codec::EntityType::Extended(extended) => match &extended.data {
|
||||
ExtendedEntityData::PointCloudEx(data) => Some(data),
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// The drawing's colour map object, created (with the built-in schemes)
|
||||
/// under its dictionary when the drawing has none.
|
||||
fn ensure_color_map(document: &mut codec::CadDocument) -> Option<Handle> {
|
||||
let existing = document.objects.iter().find_map(|(handle, object)| match object {
|
||||
ObjectType::ClassObject(object) if matches!(object.data, ClassObjectData::PointCloudColorMap(_)) => Some(*handle),
|
||||
_ => None,
|
||||
});
|
||||
if existing.is_some() {
|
||||
return existing;
|
||||
}
|
||||
let root = document.header.named_objects_dict_handle;
|
||||
let ObjectType::Dictionary(root_dictionary) = document.objects.get(&root)? else {
|
||||
return None;
|
||||
};
|
||||
let dictionary = match root_dictionary.get(MAP_DICTIONARY) {
|
||||
Some(handle) if matches!(document.objects.get(&handle), Some(ObjectType::Dictionary(_))) => handle,
|
||||
_ => {
|
||||
let handle = document.allocate_handle();
|
||||
let mut dictionary = Dictionary::new();
|
||||
dictionary.handle = handle;
|
||||
dictionary.owner = root;
|
||||
document.objects.insert(handle, ObjectType::Dictionary(dictionary));
|
||||
if let Some(ObjectType::Dictionary(root_dictionary)) = document.objects.get_mut(&root) {
|
||||
root_dictionary.add_entry(MAP_DICTIONARY, handle);
|
||||
}
|
||||
handle
|
||||
}
|
||||
};
|
||||
let handle = document.allocate_handle();
|
||||
let ramps = crate::scene::model::point_cloud::schemes(document)
|
||||
.into_iter()
|
||||
.map(|(id, name, colors)| to_codec_ramp(&MapRamp { id, name, colors }))
|
||||
.collect();
|
||||
let mut map = ClassObject::new(ClassObjectData::PointCloudColorMap(PointCloudColorMap {
|
||||
class_version: 4,
|
||||
default_intensity_scheme: crate::entities::extended::point_cloud_ramp_id("Spectrum").to_string(),
|
||||
default_elevation_scheme: crate::entities::extended::point_cloud_ramp_id("Earth").to_string(),
|
||||
default_classification_scheme: CLASSIFICATION_SCHEME.to_string(),
|
||||
color_ramps: ramps,
|
||||
classification_color_ramps: Vec::new(),
|
||||
}));
|
||||
map.handle = handle;
|
||||
map.owner = dictionary;
|
||||
document.objects.insert(handle, ObjectType::ClassObject(map));
|
||||
if let Some(ObjectType::Dictionary(dictionary)) = document.objects.get_mut(&dictionary) {
|
||||
dictionary.add_entry(MAP_ENTRY, handle);
|
||||
}
|
||||
Some(handle)
|
||||
}
|
||||
|
||||
/// A scheme as the colour map stores it: true colours, all shown.
|
||||
fn to_codec_ramp(ramp: &MapRamp) -> PointCloudColorRamp {
|
||||
PointCloudColorRamp {
|
||||
id: ramp.id.clone(),
|
||||
class_version: 1,
|
||||
colors: ramp
|
||||
.colors
|
||||
.iter()
|
||||
.map(|[r, g, b]| PointCloudRampColor {
|
||||
color: (0xC2u32 << 24 | u32::from(*r) << 16 | u32::from(*g) << 8 | u32::from(*b)) as i32,
|
||||
visible: true,
|
||||
})
|
||||
.collect(),
|
||||
name: ramp.name.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// A new scheme identifier: a random GUID in the stored form.
|
||||
fn new_scheme_id() -> String {
|
||||
let mut bytes = [0u8; 16];
|
||||
getrandom::fill(&mut bytes).expect("OS random source");
|
||||
let hex: String = bytes.iter().map(|b| format!("{b:02X}")).collect();
|
||||
format!("{}-{}-{}-{}-{}", &hex[0..8], &hex[8..12], &hex[12..16], &hex[16..20], &hex[20..32])
|
||||
}
|
||||
|
||||
/// `count` colours taken evenly along a scheme's blend.
|
||||
fn resample(colors: &[[u8; 3]], count: usize) -> Vec<[u8; 3]> {
|
||||
let count = count.clamp(2, 25);
|
||||
(0..count)
|
||||
.map(|k| crate::ui::window::pdf_dialogs::blend(colors, k as f64 / (count - 1) as f64))
|
||||
.collect()
|
||||
}
|
||||
|
||||
impl OpenCADStudio {
|
||||
/// POINTCLOUDCOLORMAP after the prompt: the dialog for `cloud`, or for
|
||||
/// the drawing's default schemes.
|
||||
pub(in crate::app) fn open_point_cloud_color_map(&mut self, i: usize, cloud: Option<Handle>) {
|
||||
let document = &self.tabs[i].scene.document;
|
||||
let ramps: Vec<MapRamp> = crate::scene::model::point_cloud::schemes(document)
|
||||
.into_iter()
|
||||
.map(|(id, name, colors)| MapRamp { id, name, colors })
|
||||
.collect();
|
||||
let defaults = crate::scene::model::point_cloud::default_schemes(document);
|
||||
let data = cloud.and_then(|handle| cloud_data(document, handle));
|
||||
let height = data
|
||||
.map(|data| {
|
||||
let corners = codec::entities::point_cloud_ex_corners(data);
|
||||
let low = corners.iter().map(|c| c.z).fold(f64::MAX, f64::min);
|
||||
let high = corners.iter().map(|c| c.z).fold(f64::MIN, f64::max);
|
||||
(low, high)
|
||||
})
|
||||
.unwrap_or((0.0, 0.0));
|
||||
let slot = |scheme: &str, fallback: &str, gradient: bool, max: String, min: String, out: i16| MapSlot {
|
||||
scheme: if scheme.is_empty() { fallback.to_string() } else { scheme.to_string() },
|
||||
gradient,
|
||||
max,
|
||||
min,
|
||||
out_of_range: OutOfRange(out),
|
||||
};
|
||||
let (intensity, elevation, extents, elevation_tab) = match data {
|
||||
Some(data) => (
|
||||
slot(
|
||||
&data.intensity_color_scheme,
|
||||
&defaults.0,
|
||||
data.intensity_as_gradient,
|
||||
data.intensity_max.to_string(),
|
||||
data.intensity_min.to_string(),
|
||||
data.intensity_out_of_range_behavior,
|
||||
),
|
||||
slot(
|
||||
&data.current_color_scheme,
|
||||
&defaults.1,
|
||||
data.elevation_as_gradient,
|
||||
short(if data.elevation_apply_to_fixed_range { data.elevation_max } else { height.1 }),
|
||||
short(if data.elevation_apply_to_fixed_range { data.elevation_min } else { height.0 }),
|
||||
data.elevation_out_of_range_behavior,
|
||||
),
|
||||
!data.elevation_apply_to_fixed_range,
|
||||
data.stylization_type == 4,
|
||||
),
|
||||
None => (
|
||||
slot("", &defaults.0, true, String::new(), String::new(), 1),
|
||||
slot("", &defaults.1, false, String::new(), String::new(), 1),
|
||||
true,
|
||||
false,
|
||||
),
|
||||
};
|
||||
let mut state = PointCloudColorMapState {
|
||||
cloud: cloud.filter(|_| data.is_some()),
|
||||
elevation_tab,
|
||||
ramps,
|
||||
intensity,
|
||||
elevation,
|
||||
extents,
|
||||
height: (clean(height.0), clean(height.1)),
|
||||
make_current: true,
|
||||
interval: String::new(),
|
||||
naming: None,
|
||||
};
|
||||
refresh_interval(&mut state);
|
||||
self.point_cloud_color_map = Some(state);
|
||||
self.active_modal = Some(crate::app::ModalKind::PointCloudColorMap);
|
||||
}
|
||||
|
||||
pub(in crate::app) fn update_point_cloud_color_map(&mut self, message: PdfDialogMsg) -> Task<Message> {
|
||||
let i = self.active_tab;
|
||||
let Some(state) = self.point_cloud_color_map.as_mut() else {
|
||||
return Task::none();
|
||||
};
|
||||
match message {
|
||||
PdfDialogMsg::MapTab(elevation) => {
|
||||
state.elevation_tab = elevation;
|
||||
state.naming = None;
|
||||
refresh_interval(state);
|
||||
}
|
||||
PdfDialogMsg::MapScheme(name) => {
|
||||
if let Some(ramp) = state.ramps.iter().find(|r| r.name == name) {
|
||||
state.slot_mut().scheme = ramp.id.clone();
|
||||
refresh_interval(state);
|
||||
}
|
||||
}
|
||||
PdfDialogMsg::MapCount(count) => {
|
||||
if let Some(ramp) = state.ramp_mut() {
|
||||
ramp.colors = resample(&ramp.colors, count);
|
||||
}
|
||||
refresh_interval(state);
|
||||
}
|
||||
// The colours between the two ends, blended evenly.
|
||||
PdfDialogMsg::MapEven => {
|
||||
if let Some(ramp) = state.ramp_mut() {
|
||||
let (first, last) = (ramp.colors[0], ramp.colors[ramp.colors.len() - 1]);
|
||||
ramp.colors = resample(&[first, last], ramp.colors.len());
|
||||
}
|
||||
}
|
||||
PdfDialogMsg::MapReverse => {
|
||||
if let Some(ramp) = state.ramp_mut() {
|
||||
ramp.colors.reverse();
|
||||
}
|
||||
}
|
||||
PdfDialogMsg::MapGradient(on) => state.slot_mut().gradient = on,
|
||||
PdfDialogMsg::MapNew => {
|
||||
let name = state.ramp().map(|r| format!("{} (2)", r.name)).unwrap_or_default();
|
||||
state.naming = Some((true, name));
|
||||
}
|
||||
PdfDialogMsg::MapRename => {
|
||||
let name = state.ramp().map(|r| r.name.clone()).unwrap_or_default();
|
||||
state.naming = Some((false, name));
|
||||
}
|
||||
PdfDialogMsg::MapNameInput(value) => {
|
||||
if let Some((_, name)) = state.naming.as_mut() {
|
||||
*name = value;
|
||||
}
|
||||
}
|
||||
PdfDialogMsg::MapNameCancel => state.naming = None,
|
||||
PdfDialogMsg::MapNameOk => {
|
||||
let Some((new, name)) = state.naming.take() else {
|
||||
return Task::none();
|
||||
};
|
||||
let name = name.trim().to_string();
|
||||
let taken = state.ramps.iter().any(|r| r.name.eq_ignore_ascii_case(&name))
|
||||
&& !(!new && state.ramp().is_some_and(|r| r.name == name));
|
||||
if name.is_empty() || taken {
|
||||
self.command_line.push_error(crate::t!("A color scheme with this name already exists.").as_ref());
|
||||
self.point_cloud_color_map.as_mut().map(|s| s.naming = Some((new, name)));
|
||||
return Task::none();
|
||||
}
|
||||
if new {
|
||||
let colors = state.ramp().map(|r| r.colors.clone()).unwrap_or_else(|| vec![[255; 3], [0; 3]]);
|
||||
let id = new_scheme_id();
|
||||
state.ramps.push(MapRamp { id: id.clone(), name, colors });
|
||||
state.slot_mut().scheme = id;
|
||||
} else if let Some(ramp) = state.ramp_mut() {
|
||||
ramp.name = name;
|
||||
}
|
||||
}
|
||||
// The last scheme stays; a slot naming a deleted one takes the first.
|
||||
PdfDialogMsg::MapDelete => {
|
||||
if state.ramps.len() > 1 {
|
||||
let id = state.ramp().map(|r| r.id.clone()).unwrap_or_default();
|
||||
state.ramps.retain(|r| r.id != id);
|
||||
let first = state.ramps[0].id.clone();
|
||||
for slot in [&mut state.intensity, &mut state.elevation] {
|
||||
if slot.scheme.eq_ignore_ascii_case(&id) {
|
||||
slot.scheme = first.clone();
|
||||
}
|
||||
}
|
||||
refresh_interval(state);
|
||||
}
|
||||
}
|
||||
PdfDialogMsg::MapMax(value) => {
|
||||
state.slot_mut().max = value;
|
||||
refresh_interval(state);
|
||||
}
|
||||
PdfDialogMsg::MapMin(value) => {
|
||||
state.slot_mut().min = value;
|
||||
refresh_interval(state);
|
||||
}
|
||||
// A band height sets the maximum from the minimum.
|
||||
PdfDialogMsg::MapInterval(value) => {
|
||||
let count = state.ramp().map_or(1, |r| r.colors.len()) as f64;
|
||||
if let (Ok(step), Ok(min)) = (value.trim().parse::<f64>(), state.elevation.min.trim().parse::<f64>()) {
|
||||
state.elevation.max = short(min + step * count);
|
||||
}
|
||||
state.interval = value;
|
||||
}
|
||||
PdfDialogMsg::MapExtents(on) => {
|
||||
state.extents = on;
|
||||
if on {
|
||||
state.elevation.max = short(state.height.1);
|
||||
state.elevation.min = short(state.height.0);
|
||||
}
|
||||
refresh_interval(state);
|
||||
}
|
||||
PdfDialogMsg::MapOutOfRange(choice) => state.slot_mut().out_of_range = choice,
|
||||
PdfDialogMsg::MapCurrent(on) => state.make_current = on,
|
||||
PdfDialogMsg::MapApply => {
|
||||
self.apply_point_cloud_color_map(i);
|
||||
}
|
||||
PdfDialogMsg::MapOk => {
|
||||
if self.apply_point_cloud_color_map(i) {
|
||||
self.point_cloud_color_map = None;
|
||||
self.active_modal = None;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
|
||||
/// Writes the dialog's schemes and the cloud's colouring as one undo
|
||||
/// step; `false` (with a message) when a range value is not a number.
|
||||
fn apply_point_cloud_color_map(&mut self, i: usize) -> bool {
|
||||
let Some(state) = self.point_cloud_color_map.as_ref() else {
|
||||
return false;
|
||||
};
|
||||
let intensity = (state.intensity.min.trim().parse::<i32>(), state.intensity.max.trim().parse::<i32>());
|
||||
let elevation = (state.elevation.min.trim().parse::<f64>(), state.elevation.max.trim().parse::<f64>());
|
||||
let cloud = state.cloud;
|
||||
if cloud.is_some() {
|
||||
let ok_intensity = matches!(intensity, (Ok(lo), Ok(hi)) if (0..=100).contains(&lo) && (0..=100).contains(&hi) && lo < hi);
|
||||
let ok_elevation = state.extents || matches!(elevation, (Ok(lo), Ok(hi)) if lo < hi);
|
||||
if !ok_intensity || !ok_elevation {
|
||||
self.command_line.push_error(crate::t!("Invalid range of colorized points.").as_ref());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
let ramps: Vec<PointCloudColorRamp> = state.ramps.iter().map(to_codec_ramp).collect();
|
||||
let (intensity_slot, elevation_slot) = (state.intensity.clone(), state.elevation.clone());
|
||||
let (extents, make_current, elevation_tab) = (state.extents, state.make_current, state.elevation_tab);
|
||||
self.push_undo_snapshot(i, "POINTCLOUDCOLORMAP");
|
||||
let document = &mut self.tabs[i].scene.document;
|
||||
if let Some(handle) = ensure_color_map(document) {
|
||||
if let Some(ObjectType::ClassObject(object)) = document.objects.get_mut(&handle) {
|
||||
if let ClassObjectData::PointCloudColorMap(map) = &mut object.data {
|
||||
map.color_ramps = ramps;
|
||||
if cloud.is_none() {
|
||||
map.default_intensity_scheme = intensity_slot.scheme.clone();
|
||||
map.default_elevation_scheme = elevation_slot.scheme.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(handle) = cloud {
|
||||
if let Some(codec::EntityType::Extended(extended)) = document.get_entity_mut(handle) {
|
||||
if let ExtendedEntityData::PointCloudEx(data) = &mut extended.data {
|
||||
data.intensity_color_scheme = intensity_slot.scheme;
|
||||
data.intensity_as_gradient = intensity_slot.gradient;
|
||||
data.intensity_out_of_range_behavior = intensity_slot.out_of_range.0;
|
||||
if let (Ok(lo), Ok(hi)) = intensity {
|
||||
(data.intensity_min, data.intensity_max) = (lo, hi);
|
||||
}
|
||||
data.current_color_scheme = elevation_slot.scheme;
|
||||
data.elevation_as_gradient = elevation_slot.gradient;
|
||||
data.elevation_out_of_range_behavior = elevation_slot.out_of_range.0;
|
||||
data.elevation_apply_to_fixed_range = !extents;
|
||||
if let (false, Ok(lo), Ok(hi)) = (extents, elevation.0, elevation.1) {
|
||||
(data.elevation_min, data.elevation_max) = (lo, hi);
|
||||
}
|
||||
if make_current {
|
||||
data.stylization_type = if elevation_tab { 4 } else { 5 };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Every cloud colours from the schemes: redraw them all.
|
||||
let changes: Vec<_> = self.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.entities()
|
||||
.filter(|entity| crate::scene::is_point_cloud(entity))
|
||||
.map(|entity| (entity.common().handle, crate::scene::ChangeKind::Modified))
|
||||
.collect();
|
||||
self.tabs[i].scene.bump_entities(&changes);
|
||||
self.tabs[i].dirty = true;
|
||||
self.refresh_properties();
|
||||
true
|
||||
}
|
||||
|
||||
pub(super) fn dispatch_pc_colormap(&mut self, cmd: &str, i: usize) -> Option<Task<Message>> {
|
||||
use crate::command::CadCommand;
|
||||
if cmd == "POINTCLOUDCOLORMAP" {
|
||||
let command = crate::modules::insert::pc_stylize::PointCloudColorMapCommand::new();
|
||||
self.command_line.push_info(&command.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(command));
|
||||
return Some(self.finish_dispatch(cmd));
|
||||
}
|
||||
let rest = cmd.strip_prefix("_PCCOLORMAP")?;
|
||||
let cloud = u64::from_str_radix(rest.trim(), 16).ok().map(Handle::new);
|
||||
let task = self.finish_dispatch(cmd);
|
||||
self.open_point_cloud_color_map(i, cloud);
|
||||
Some(task)
|
||||
}
|
||||
}
|
||||
|
||||
/// A height with rounding noise dropped (the extents box's -0.0016 reads 0).
|
||||
fn clean(value: f64) -> f64 {
|
||||
(value * 100.0).round() / 100.0
|
||||
}
|
||||
|
||||
fn refresh_interval(state: &mut PointCloudColorMapState) {
|
||||
let count = state.ramp().map_or(1, |r| r.colors.len()).max(1) as f64;
|
||||
let lo = state.elevation.min.trim().parse::<f64>();
|
||||
let hi = state.elevation.max.trim().parse::<f64>();
|
||||
state.interval = match (lo, hi) {
|
||||
(Ok(lo), Ok(hi)) => short((hi - lo) / count),
|
||||
_ => String::new(),
|
||||
};
|
||||
}
|
||||
@@ -378,6 +378,9 @@ impl OpenCADStudio {
|
||||
}
|
||||
"attach" => crate::t!("Attaches a PDF file as an underlay."),
|
||||
"pointcloud" => crate::t!("Attaches a point cloud scan or project."),
|
||||
"pointcloudcolormap" => {
|
||||
crate::t!("Sets the color schemes and ranges a point cloud is colored by intensity or elevation.")
|
||||
}
|
||||
"layers" => crate::t!("Turns the layers of a PDF underlay on or off."),
|
||||
_ => crate::t!("Imports the geometry, fills, raster images and text of a PDF file as drawing objects."),
|
||||
};
|
||||
@@ -480,6 +483,26 @@ impl OpenCADStudio {
|
||||
// ── Attach Point Cloud ────────────────────────────────────────
|
||||
PdfDialogMsg::CloudBrowse => return Task::done(Message::PointCloudAttachPick),
|
||||
PdfDialogMsg::CloudOk => return self.point_cloud_attach_ok(i),
|
||||
message @ (PdfDialogMsg::MapTab(_)
|
||||
| PdfDialogMsg::MapScheme(_)
|
||||
| PdfDialogMsg::MapCount(_)
|
||||
| PdfDialogMsg::MapEven
|
||||
| PdfDialogMsg::MapReverse
|
||||
| PdfDialogMsg::MapGradient(_)
|
||||
| PdfDialogMsg::MapNew
|
||||
| PdfDialogMsg::MapDelete
|
||||
| PdfDialogMsg::MapRename
|
||||
| PdfDialogMsg::MapNameInput(_)
|
||||
| PdfDialogMsg::MapNameOk
|
||||
| PdfDialogMsg::MapNameCancel
|
||||
| PdfDialogMsg::MapMax(_)
|
||||
| PdfDialogMsg::MapMin(_)
|
||||
| PdfDialogMsg::MapInterval(_)
|
||||
| PdfDialogMsg::MapExtents(_)
|
||||
| PdfDialogMsg::MapOutOfRange(_)
|
||||
| PdfDialogMsg::MapCurrent(_)
|
||||
| PdfDialogMsg::MapApply
|
||||
| PdfDialogMsg::MapOk) => return self.update_point_cloud_color_map(message),
|
||||
PdfDialogMsg::CloudPathType(choice) => {
|
||||
let stored = self.point_cloud_attach.as_ref().map(|s| self.pdf_stored_path(&s.path, choice.0));
|
||||
if let (Some(s), Some(stored)) = (self.point_cloud_attach.as_mut(), stored) {
|
||||
|
||||
@@ -72,6 +72,7 @@ pub(super) const NAMES: &[&str] = &[
|
||||
"pdf_dialog_ok",
|
||||
"pdf_layer_toggle",
|
||||
"pdf_page_select",
|
||||
"pc_colormap",
|
||||
"ribbon_tab",
|
||||
"ribbon_dropdown",
|
||||
"dialog_ok",
|
||||
@@ -688,6 +689,36 @@ impl OpenCADStudio {
|
||||
"pdf_layer_toggle" => Message::PdfDialog(
|
||||
crate::ui::window::pdf_dialogs::PdfDialogMsg::LayersToggle(string(req, "value")?.into()),
|
||||
),
|
||||
// Point Cloud Color Map: one edit, "tab=elevation", "count=3",
|
||||
// "even", "reverse", "scheme=Earth", "gradient=0", "max=90",
|
||||
// "min=10", "interval=0.1", "extents=0", "range=2", "current=0".
|
||||
"pc_colormap" => {
|
||||
use crate::ui::window::pdf_dialogs::{OutOfRange, PdfDialogMsg as M};
|
||||
let value = string(req, "value")?;
|
||||
let (key, arg) = value.split_once('=').unwrap_or((value, ""));
|
||||
let on = arg != "0";
|
||||
Message::PdfDialog(match key {
|
||||
"tab" => M::MapTab(arg == "elevation"),
|
||||
"scheme" => M::MapScheme(arg.into()),
|
||||
"count" => M::MapCount(arg.parse().map_err(|_| failure("invalid_value", "count"))?),
|
||||
"even" => M::MapEven,
|
||||
"reverse" => M::MapReverse,
|
||||
"gradient" => M::MapGradient(on),
|
||||
"max" => M::MapMax(arg.into()),
|
||||
"min" => M::MapMin(arg.into()),
|
||||
"interval" => M::MapInterval(arg.into()),
|
||||
"extents" => M::MapExtents(on),
|
||||
"range" => M::MapOutOfRange(OutOfRange(arg.parse().unwrap_or(1))),
|
||||
"current" => M::MapCurrent(on),
|
||||
"new" => M::MapNew,
|
||||
"rename" => M::MapRename,
|
||||
"name" => M::MapNameInput(arg.into()),
|
||||
"name_ok" => M::MapNameOk,
|
||||
"delete" => M::MapDelete,
|
||||
"apply" => M::MapApply,
|
||||
_ => return Err(failure("invalid_value", "Unknown color map edit")),
|
||||
})
|
||||
}
|
||||
// Attach dialog: choose pages by index ("0,2").
|
||||
"pdf_page_select" => {
|
||||
let pages: Vec<usize> = string(req, "value")?
|
||||
@@ -714,6 +745,7 @@ impl OpenCADStudio {
|
||||
Message::PdfDialog(match self.active_modal {
|
||||
Some(crate::app::ModalKind::PdfAttach) => PdfDialogMsg::AttachOk,
|
||||
Some(crate::app::ModalKind::PointCloudAttach) => PdfDialogMsg::CloudOk,
|
||||
Some(crate::app::ModalKind::PointCloudColorMap) => PdfDialogMsg::MapOk,
|
||||
Some(crate::app::ModalKind::UnderlayLayers) => PdfDialogMsg::LayersOk,
|
||||
Some(crate::app::ModalKind::PdfImportSettings) => PdfDialogMsg::SettingsOk,
|
||||
Some(crate::app::ModalKind::PdfImportFile) => PdfDialogMsg::ImportOk,
|
||||
|
||||
@@ -498,6 +498,7 @@ pub(super) struct OpenCADStudio {
|
||||
/// PDF dialogs' working copies; `None` while closed.
|
||||
pdf_attach: Option<crate::ui::window::pdf_dialogs::PdfAttachState>,
|
||||
point_cloud_attach: Option<crate::ui::window::pdf_dialogs::PointCloudAttachState>,
|
||||
point_cloud_color_map: Option<crate::ui::window::pdf_dialogs::PointCloudColorMapState>,
|
||||
underlay_layers: Option<crate::ui::window::pdf_dialogs::UnderlayLayersState>,
|
||||
pdf_import_settings: Option<crate::modules::insert::pdf_import::PdfImportSettings>,
|
||||
pdf_import_file: Option<crate::ui::window::pdf_dialogs::PdfImportFileState>,
|
||||
@@ -1903,6 +1904,7 @@ pub enum ModalKind {
|
||||
BlockDefinition,
|
||||
PdfAttach,
|
||||
PointCloudAttach,
|
||||
PointCloudColorMap,
|
||||
UnderlayLayers,
|
||||
PdfImportSettings,
|
||||
PdfImportFile,
|
||||
@@ -4034,6 +4036,7 @@ impl OpenCADStudio {
|
||||
block_definition: None,
|
||||
pdf_attach: None,
|
||||
point_cloud_attach: None,
|
||||
point_cloud_color_map: None,
|
||||
underlay_layers: None,
|
||||
pdf_import_settings: None,
|
||||
pdf_import_file: None,
|
||||
|
||||
@@ -30,6 +30,7 @@ impl OpenCADStudio {
|
||||
_ => crate::t!("Attach PDF Underlay").into_owned(),
|
||||
},
|
||||
Some(K::PointCloudAttach) => crate::t!("Attach Point Cloud").into_owned(),
|
||||
Some(K::PointCloudColorMap) => crate::t!("Point Cloud Color Map").into_owned(),
|
||||
Some(K::UnderlayLayers) => crate::t!("Underlay Layers").into_owned(),
|
||||
Some(K::PdfImportSettings) => crate::t!("PDF Import Settings").into_owned(),
|
||||
Some(K::PdfImportFile) => crate::t!("Import PDF").into_owned(),
|
||||
@@ -529,6 +530,12 @@ impl OpenCADStudio {
|
||||
crate::ui::window::pdf_dialogs::view_point_cloud_attach(state, flow)
|
||||
})
|
||||
}
|
||||
super::super::ModalKind::PointCloudColorMap => {
|
||||
let state = self.point_cloud_color_map.as_ref()?;
|
||||
sized_flow(ex, 760, 560, |flow| {
|
||||
crate::ui::window::pdf_dialogs::view_point_cloud_color_map(state, flow)
|
||||
})
|
||||
}
|
||||
super::super::ModalKind::UnderlayLayers => {
|
||||
let state = self.underlay_layers.as_ref()?;
|
||||
sized_flow(ex, 460, 520, |flow| {
|
||||
|
||||
@@ -1130,6 +1130,11 @@ pub(crate) const POINT_CLOUD_RAMPS: [(&str, &str); 7] = [
|
||||
("Spectrum", "C0A3838B-81E3-4506-B908-13ECF523DB68"),
|
||||
];
|
||||
|
||||
/// A built-in scheme's identifier by name (Blues when unknown).
|
||||
pub(crate) fn point_cloud_ramp_id(name: &str) -> &'static str {
|
||||
POINT_CLOUD_RAMPS.iter().find(|(n, _)| *n == name).unwrap_or(&POINT_CLOUD_RAMPS[0]).1
|
||||
}
|
||||
|
||||
/// Stylization choices in the order the reference lists them, with the
|
||||
/// stored values: scan colours 1, object colour 2, normals 3, intensity 5,
|
||||
/// elevation 4.
|
||||
|
||||
@@ -115,6 +115,25 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static
|
||||
"Point Cloud Scan" => Some(("insert", "pc-rcs")),
|
||||
"Point Clouds" => Some(("insert", "pc-clouds")),
|
||||
"Attaches a point cloud scan or project." => Some(("insert", "pc-help")),
|
||||
"Point Cloud Color Map" => Some(("insert", "pcm-title")),
|
||||
"Color ramp" => Some(("insert", "pcm-ramp")),
|
||||
"Number of colors" => Some(("insert", "pcm-count")),
|
||||
"Display as gradient" => Some(("insert", "pcm-gradient")),
|
||||
"Range of colorized points" => Some(("insert", "pcm-range")),
|
||||
"Maximum intensity" => Some(("insert", "pcm-max-int")),
|
||||
"Minimum intensity" => Some(("insert", "pcm-min-int")),
|
||||
"Maximum elevation" => Some(("insert", "pcm-max-elev")),
|
||||
"Minimum elevation" => Some(("insert", "pcm-min-elev")),
|
||||
"Interval height" => Some(("insert", "pcm-interval")),
|
||||
"Apply to extents of point cloud" => Some(("insert", "pcm-extents")),
|
||||
"Out of range points" => Some(("insert", "pcm-oor")),
|
||||
"Use min/max colors" => Some(("insert", "pcm-oor-minmax")),
|
||||
"Use RGB scan colors" => Some(("insert", "pcm-oor-scan")),
|
||||
"Hide points" => Some(("insert", "pcm-oor-hide")),
|
||||
"Make stylization current" => Some(("insert", "pcm-current")),
|
||||
"A color scheme with this name already exists." => Some(("insert", "pcm-name-taken")),
|
||||
"Invalid range of colorized points." => Some(("insert", "pcm-bad-range")),
|
||||
"Sets the color schemes and ranges a point cloud is colored by intensity or elevation." => Some(("insert", "pcm-help")),
|
||||
"Stylization" => Some(("properties", "stylization")),
|
||||
"Color scheme" => Some(("properties", "color-scheme")),
|
||||
"Scan Colors" => Some(("properties", "scan-colors")),
|
||||
|
||||
@@ -86,3 +86,80 @@ impl CadCommand for PointCloudStylizeCommand {
|
||||
}
|
||||
|
||||
inventory::submit!(crate::command::CommandRegistration { names: &["POINTCLOUDSTYLIZE"] });
|
||||
|
||||
// POINTCLOUDCOLORMAP — the Point Cloud Color Map dialog.
|
||||
//
|
||||
// Select point cloud or [None] <None>:
|
||||
//
|
||||
// A picked cloud opens the dialog for it; None (or Enter) for the
|
||||
// drawing's default schemes.
|
||||
pub struct PointCloudColorMapCommand {
|
||||
picked: Option<codec::EntityType>,
|
||||
}
|
||||
|
||||
impl PointCloudColorMapCommand {
|
||||
pub fn new() -> Self {
|
||||
Self { picked: None }
|
||||
}
|
||||
}
|
||||
|
||||
impl CadCommand for PointCloudColorMapCommand {
|
||||
fn name(&self) -> &'static str {
|
||||
"POINTCLOUDCOLORMAP"
|
||||
}
|
||||
|
||||
fn prompt(&self) -> String {
|
||||
"Select point cloud or [None] <None>:".to_string()
|
||||
}
|
||||
|
||||
fn options(&self) -> Vec<CmdOption> {
|
||||
vec![CmdOption::new("None", "N")]
|
||||
}
|
||||
|
||||
fn input_kind(&self) -> InputKind {
|
||||
InputKind::Point
|
||||
}
|
||||
|
||||
fn point_step_accepts_keywords(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn needs_entity_pick(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn inject_before_entity_pick(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn inject_picked_entity(&mut self, entity: codec::EntityType) {
|
||||
self.picked = Some(entity);
|
||||
}
|
||||
|
||||
fn on_entity_pick(&mut self, handle: Handle, _pt: glam::DVec3) -> CmdResult {
|
||||
match self.picked.take() {
|
||||
Some(entity) if crate::scene::is_point_cloud(&entity) => {
|
||||
CmdResult::Dispatch(format!("_PCCOLORMAP {:X}", handle.value()))
|
||||
}
|
||||
// Anything else: the prompt again.
|
||||
_ => CmdResult::NeedPoint,
|
||||
}
|
||||
}
|
||||
|
||||
fn on_text_input(&mut self, text: &str) -> Option<CmdResult> {
|
||||
match text.trim().to_ascii_uppercase().as_str() {
|
||||
"" | "N" | "NONE" => Some(CmdResult::Dispatch("_PCCOLORMAP".to_string())),
|
||||
_ => Some(CmdResult::ReportError("Invalid option keyword.".to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
fn on_point(&mut self, _pt: glam::DVec3) -> CmdResult {
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
|
||||
fn on_enter(&mut self) -> CmdResult {
|
||||
CmdResult::Dispatch("_PCCOLORMAP".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
inventory::submit!(crate::command::CommandRegistration { names: &["POINTCLOUDCOLORMAP"] });
|
||||
@@ -318,7 +318,28 @@ pub(crate) fn placed(
|
||||
(lo.min(z), hi.max(z))
|
||||
})
|
||||
};
|
||||
let color = |i: usize, z: f64| -> [u8; 4] {
|
||||
let scan_color = |i: usize| {
|
||||
if cloud.has_rgb {
|
||||
cloud.colors[i]
|
||||
} else if cloud.has_intensity {
|
||||
[cloud.intensity[i]; 3]
|
||||
} else {
|
||||
object_color
|
||||
}
|
||||
};
|
||||
// A value outside a scheme's range takes the end colour, the scan
|
||||
// colour, or is hidden (`None`), as the cloud's out-of-range choice says.
|
||||
let along = |colors: &[[u8; 3]], t: f64, gradient: bool, out_of_range: i16, fixed: bool, i: usize| {
|
||||
if fixed && !(0.0..=1.0).contains(&t) {
|
||||
return match out_of_range {
|
||||
0 => Some(ramp(colors, t, gradient)),
|
||||
2 => None,
|
||||
_ => Some(scan_color(i)),
|
||||
};
|
||||
}
|
||||
Some(ramp(colors, t, gradient))
|
||||
};
|
||||
let color = |i: usize, z: f64| -> Option<[u8; 4]> {
|
||||
let [r, g, b] = match &style {
|
||||
Stylization::Object(c) => *c,
|
||||
Stylization::Normal => match cloud.normals.get(i) {
|
||||
@@ -329,25 +350,22 @@ pub(crate) fn placed(
|
||||
}
|
||||
None => object_color,
|
||||
},
|
||||
Stylization::Ramp { colors, gradient, intensity: true, range } => {
|
||||
Stylization::Ramp { colors, gradient, intensity: true, range, out_of_range } => {
|
||||
let percent = f64::from(cloud.intensity.get(i).copied().unwrap_or(0)) * 100.0 / 255.0;
|
||||
let (from, to) = (range.0 as f64, range.1 as f64);
|
||||
ramp(colors, (percent - from) / (to - from).max(1e-9), *gradient)
|
||||
}
|
||||
Stylization::Ramp { colors, gradient, .. } => {
|
||||
ramp(colors, (z - low) / (high - low).max(1e-9), *gradient)
|
||||
}
|
||||
Stylization::Scan => {
|
||||
if cloud.has_rgb {
|
||||
cloud.colors[i]
|
||||
} else if cloud.has_intensity {
|
||||
[cloud.intensity[i]; 3]
|
||||
} else {
|
||||
object_color
|
||||
}
|
||||
along(colors, (percent - from) / (to - from).max(1e-9), *gradient, *out_of_range, true, i)?
|
||||
}
|
||||
Stylization::Ramp { colors, gradient, out_of_range, .. } => along(
|
||||
colors,
|
||||
(z - low) / (high - low).max(1e-9),
|
||||
*gradient,
|
||||
*out_of_range,
|
||||
data.elevation_apply_to_fixed_range,
|
||||
i,
|
||||
)?,
|
||||
Stylization::Scan => scan_color(i),
|
||||
};
|
||||
[r, g, b, 255]
|
||||
Some([r, g, b, 255])
|
||||
};
|
||||
let mut instances = Vec::with_capacity(cloud.positions.len() / step + 1);
|
||||
for (i, p) in cloud.positions.iter().enumerate().step_by(step) {
|
||||
@@ -355,6 +373,9 @@ pub(crate) fn placed(
|
||||
continue;
|
||||
}
|
||||
let w = world(p);
|
||||
let Some(color) = color(i, w.z) else {
|
||||
continue;
|
||||
};
|
||||
let pos = [w.x as f32, w.y as f32, w.z as f32];
|
||||
instances.push(PointInstance {
|
||||
pos,
|
||||
@@ -363,7 +384,7 @@ pub(crate) fn placed(
|
||||
(w.y - pos[1] as f64) as f32,
|
||||
(w.z - pos[2] as f64) as f32,
|
||||
],
|
||||
color: color(i, w.z),
|
||||
color,
|
||||
});
|
||||
}
|
||||
crate::perf::record(format_args!(
|
||||
@@ -419,8 +440,9 @@ enum Stylization {
|
||||
/// Each point's world normal as a colour, (n + 1) / 2 per component.
|
||||
Normal,
|
||||
/// A colour scheme over intensity (per cent, in the range given) or
|
||||
/// elevation, blended or in bands.
|
||||
Ramp { colors: Vec<[u8; 3]>, gradient: bool, intensity: bool, range: (i64, i64) },
|
||||
/// elevation, blended or in bands; values out of range per
|
||||
/// `out_of_range` (0 end colours, 1 scan colours, 2 hidden).
|
||||
Ramp { colors: Vec<[u8; 3]>, gradient: bool, intensity: bool, range: (i64, i64), out_of_range: i16 },
|
||||
}
|
||||
|
||||
impl Stylization {
|
||||
@@ -439,7 +461,9 @@ impl Stylization {
|
||||
} else {
|
||||
(0, 100)
|
||||
};
|
||||
Self::Ramp { colors, gradient, intensity, range: (range.0, range.1) }
|
||||
let out_of_range =
|
||||
if intensity { data.intensity_out_of_range_behavior } else { data.elevation_out_of_range_behavior };
|
||||
Self::Ramp { colors, gradient, intensity, range: (range.0, range.1), out_of_range }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -458,6 +482,58 @@ fn rgb(color: u32) -> [u8; 3] {
|
||||
[(color >> 16) as u8, (color >> 8) as u8, color as u8]
|
||||
}
|
||||
|
||||
/// The colour schemes the drawing offers (identifier, name, colours high end
|
||||
/// first): its colour map's, or the built-in ones.
|
||||
pub(crate) fn schemes(document: &CadDocument) -> Vec<(String, String, Vec<[u8; 3]>)> {
|
||||
for object in document.objects.values() {
|
||||
let ObjectType::ClassObject(object) = object else {
|
||||
continue;
|
||||
};
|
||||
let ClassObjectData::PointCloudColorMap(map) = &object.data else {
|
||||
continue;
|
||||
};
|
||||
if !map.color_ramps.is_empty() {
|
||||
return map
|
||||
.color_ramps
|
||||
.iter()
|
||||
.map(|r| {
|
||||
let colors = r.colors.iter().filter(|c| c.visible).map(|c| rgb(c.color as u32)).collect();
|
||||
(r.id.clone(), r.name.clone(), colors)
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
}
|
||||
BUILTIN_RAMPS
|
||||
.iter()
|
||||
.map(|(name, colors)| {
|
||||
let id = crate::entities::extended::point_cloud_ramp_id(name).to_string();
|
||||
(id, name.to_string(), colors.iter().map(|c| rgb(*c)).collect())
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// The schemes new intensity and elevation colouring starts from: the
|
||||
/// colour map's defaults, else Spectrum and Earth.
|
||||
pub(crate) fn default_schemes(document: &CadDocument) -> (String, String) {
|
||||
let map = document.objects.values().find_map(|object| match object {
|
||||
ObjectType::ClassObject(object) => match &object.data {
|
||||
ClassObjectData::PointCloudColorMap(map) => Some(map),
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
});
|
||||
let pick = |stored: Option<&String>, name: &str| {
|
||||
stored
|
||||
.filter(|s| !s.is_empty())
|
||||
.cloned()
|
||||
.unwrap_or_else(|| crate::entities::extended::point_cloud_ramp_id(name).to_string())
|
||||
};
|
||||
(
|
||||
pick(map.map(|m| &m.default_intensity_scheme), "Spectrum"),
|
||||
pick(map.map(|m| &m.default_elevation_scheme), "Earth"),
|
||||
)
|
||||
}
|
||||
|
||||
/// A scheme's colours: from the drawing's colour map when it has the scheme,
|
||||
/// else the built-in one of that name (or `fallback` for an unset scheme).
|
||||
fn ramp_colors(document: &CadDocument, id: &str, fallback: &str) -> Vec<[u8; 3]> {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
//! PDF dialogs: Attach PDF Underlay (pages, path type, insertion, scale,
|
||||
//! rotation), Underlay Layers, PDF Import Settings and Import PDF, built
|
||||
//! from cards, segmented choices and toggle chips.
|
||||
//! from cards, segmented choices and toggle chips; the point cloud Attach
|
||||
//! and Color Map dialogs share them.
|
||||
|
||||
use std::fmt;
|
||||
|
||||
@@ -47,6 +48,28 @@ pub enum PdfDialogMsg {
|
||||
CloudLock(bool),
|
||||
CloudZoom(bool),
|
||||
CloudOk,
|
||||
// Point Cloud Color Map
|
||||
/// Elevation tab (else intensity).
|
||||
MapTab(bool),
|
||||
MapScheme(String),
|
||||
MapCount(usize),
|
||||
MapEven,
|
||||
MapReverse,
|
||||
MapGradient(bool),
|
||||
MapNew,
|
||||
MapDelete,
|
||||
MapRename,
|
||||
MapNameInput(String),
|
||||
MapNameOk,
|
||||
MapNameCancel,
|
||||
MapMax(String),
|
||||
MapMin(String),
|
||||
MapInterval(String),
|
||||
MapExtents(bool),
|
||||
MapOutOfRange(OutOfRange),
|
||||
MapCurrent(bool),
|
||||
MapApply,
|
||||
MapOk,
|
||||
// Underlay Layers
|
||||
LayersUnderlay(String),
|
||||
LayersSearch(String),
|
||||
@@ -1108,3 +1131,358 @@ pub fn view_import_file<'a>(
|
||||
.width(sizing.width)
|
||||
.into()
|
||||
}
|
||||
|
||||
// ── Point Cloud Color Map ──────────────────────────────────────────────────
|
||||
|
||||
/// A colour scheme as the dialog edits it: identifier, name and colours
|
||||
/// (high end first).
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct MapRamp {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub colors: Vec<[u8; 3]>,
|
||||
}
|
||||
|
||||
/// What a scheme does with values outside its range.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct OutOfRange(pub i16);
|
||||
|
||||
impl OutOfRange {
|
||||
pub const ALL: [OutOfRange; 3] = [OutOfRange(0), OutOfRange(1), OutOfRange(2)];
|
||||
}
|
||||
|
||||
impl fmt::Display for OutOfRange {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let label = match self.0 {
|
||||
0 => t!("Use min/max colors"),
|
||||
2 => t!("Hide points"),
|
||||
_ => t!("Use RGB scan colors"),
|
||||
};
|
||||
f.write_str(label.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
/// One tab's settings: its scheme and range.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MapSlot {
|
||||
pub scheme: String,
|
||||
pub gradient: bool,
|
||||
pub max: String,
|
||||
pub min: String,
|
||||
pub out_of_range: OutOfRange,
|
||||
}
|
||||
|
||||
/// The Point Cloud Color Map dialog: the drawing's colour schemes and, for
|
||||
/// a chosen cloud, its intensity and elevation colouring.
|
||||
pub struct PointCloudColorMapState {
|
||||
/// The cloud edited; `None` edits the drawing's default schemes.
|
||||
pub cloud: Option<codec::Handle>,
|
||||
/// Elevation tab (else intensity).
|
||||
pub elevation_tab: bool,
|
||||
pub ramps: Vec<MapRamp>,
|
||||
pub intensity: MapSlot,
|
||||
pub elevation: MapSlot,
|
||||
/// Elevation over the cloud's own height (else the fixed range).
|
||||
pub extents: bool,
|
||||
/// The cloud's height range.
|
||||
pub height: (f64, f64),
|
||||
pub make_current: bool,
|
||||
/// The height of one band (elevation), as shown and typed.
|
||||
pub interval: String,
|
||||
/// A name being typed for a new scheme (`true`) or a rename.
|
||||
pub naming: Option<(bool, String)>,
|
||||
}
|
||||
|
||||
impl PointCloudColorMapState {
|
||||
pub fn slot(&self) -> &MapSlot {
|
||||
if self.elevation_tab { &self.elevation } else { &self.intensity }
|
||||
}
|
||||
|
||||
pub fn slot_mut(&mut self) -> &mut MapSlot {
|
||||
if self.elevation_tab { &mut self.elevation } else { &mut self.intensity }
|
||||
}
|
||||
|
||||
pub fn ramp(&self) -> Option<&MapRamp> {
|
||||
let id = &self.slot().scheme;
|
||||
self.ramps.iter().find(|r| r.id.eq_ignore_ascii_case(id)).or(self.ramps.first())
|
||||
}
|
||||
|
||||
pub fn ramp_mut(&mut self) -> Option<&mut MapRamp> {
|
||||
let id = self.slot().scheme.clone();
|
||||
let at = self.ramps.iter().position(|r| r.id.eq_ignore_ascii_case(&id)).unwrap_or(0);
|
||||
self.ramps.get_mut(at)
|
||||
}
|
||||
|
||||
/// The range the ramp spans on the current tab.
|
||||
pub fn range(&self) -> Option<(f64, f64)> {
|
||||
if self.elevation_tab && self.extents {
|
||||
return Some(self.height);
|
||||
}
|
||||
let slot = self.slot();
|
||||
Some((slot.min.trim().parse().ok()?, slot.max.trim().parse().ok()?))
|
||||
}
|
||||
}
|
||||
|
||||
/// `t` (0 at the high end) along colours, blended; channels are cut to
|
||||
/// whole values, as the reference stores resampled schemes.
|
||||
pub fn blend(colors: &[[u8; 3]], t: f64) -> [u8; 3] {
|
||||
if colors.len() < 2 {
|
||||
return colors.first().copied().unwrap_or([255; 3]);
|
||||
}
|
||||
let x = t.clamp(0.0, 1.0) * (colors.len() - 1) as f64;
|
||||
let k = (x as usize).min(colors.len() - 2);
|
||||
let f = x - k as f64;
|
||||
let (a, b) = (colors[k], colors[k + 1]);
|
||||
[0, 1, 2].map(|c| (f64::from(a[c]) + (f64::from(b[c]) - f64::from(a[c])) * f) as u8)
|
||||
}
|
||||
|
||||
/// A number as the dialog labels it: at most two decimals.
|
||||
pub fn short(value: f64) -> String {
|
||||
let value = if value.abs() < 0.005 { 0.0 } else { value };
|
||||
let text = format!("{:.2}", value);
|
||||
text.trim_end_matches('0').trim_end_matches('.').to_string()
|
||||
}
|
||||
|
||||
fn swatch<'a>(color: [u8; 3], width: f32, height: f32) -> Element<'a, Message> {
|
||||
let color = iced::Color::from_rgb8(color[0], color[1], color[2]);
|
||||
container(Space::new().width(width).height(height))
|
||||
.style(move |_: &Theme| container::Style {
|
||||
background: Some(Background::Color(color)),
|
||||
border: Border { width: 1.0, radius: 3.0.into(), color: iced::Color::from_rgb8(0x77, 0x77, 0x77) },
|
||||
..Default::default()
|
||||
})
|
||||
.into()
|
||||
}
|
||||
|
||||
/// The ramp as a bar, high end on top, blended or in bands, with the values
|
||||
/// where the bands meet (blended: the ends and the inner band centres).
|
||||
fn ramp_bar<'a>(colors: &[[u8; 3]], gradient: bool, range: Option<(f64, f64)>, percent: bool) -> Element<'a, Message> {
|
||||
const HEIGHT: f32 = 360.0;
|
||||
let n = colors.len().max(1);
|
||||
let strips: Vec<Element<'a, Message>> = if gradient {
|
||||
(0..72).map(|k| {
|
||||
let c = blend(colors, (k as f64 + 0.5) / 72.0);
|
||||
let color = iced::Color::from_rgb8(c[0], c[1], c[2]);
|
||||
container(Space::new().width(Fill).height(Fill))
|
||||
.height(Length::FillPortion(1))
|
||||
.style(move |_: &Theme| container::Style {
|
||||
background: Some(Background::Color(color)),
|
||||
..Default::default()
|
||||
})
|
||||
.into()
|
||||
}).collect()
|
||||
} else {
|
||||
colors.iter().map(|c| {
|
||||
let color = iced::Color::from_rgb8(c[0], c[1], c[2]);
|
||||
container(Space::new().width(Fill).height(Fill))
|
||||
.height(Length::FillPortion(1))
|
||||
.style(move |_: &Theme| container::Style {
|
||||
background: Some(Background::Color(color)),
|
||||
..Default::default()
|
||||
})
|
||||
.into()
|
||||
}).collect()
|
||||
};
|
||||
let bar = container(iced::widget::Column::with_children(strips))
|
||||
.width(Length::Fixed(56.0))
|
||||
.height(Length::Fixed(HEIGHT));
|
||||
// Label positions from the top (0) to the bottom (1).
|
||||
let marks: Vec<f64> = if gradient {
|
||||
let mut marks = vec![0.0];
|
||||
marks.extend((1..n.saturating_sub(1)).map(|k| (k as f64 + 0.5) / n as f64));
|
||||
marks.push(1.0);
|
||||
marks
|
||||
} else {
|
||||
(0..=n).map(|k| k as f64 / n as f64).collect()
|
||||
};
|
||||
let mut labels = column![];
|
||||
if let Some((low, high)) = range {
|
||||
// Each label is centred on its mark, kept inside the bar.
|
||||
let mut used = 0.0;
|
||||
for mark in marks {
|
||||
let value = high - (high - low) * mark;
|
||||
let label = if percent { format!("{}%", value.round()) } else { short(value) };
|
||||
let top = (mark * f64::from(HEIGHT) - 7.0).clamp(0.0, f64::from(HEIGHT) - 14.0);
|
||||
labels = labels.push(Space::new().height((top - used).max(0.0) as f32)).push(text(label).size(10).height(14.0));
|
||||
used = top.max(used) + 14.0;
|
||||
}
|
||||
}
|
||||
row![bar, labels.width(Length::Fixed(44.0))].spacing(4).into()
|
||||
}
|
||||
|
||||
pub fn view_point_cloud_color_map<'a>(
|
||||
state: &'a PointCloudColorMapState,
|
||||
sizing: crate::ui::modal::ModalSizing,
|
||||
) -> Element<'a, Message> {
|
||||
let tab = |label: std::borrow::Cow<'static, str>, on: bool, message: Option<Message>| -> Element<'a, Message> {
|
||||
let mut b = button(text(label).size(11).width(Fill).align_x(iced::Center))
|
||||
.style(button_style(on))
|
||||
.padding([5, 6])
|
||||
.width(Length::Fixed(118.0));
|
||||
if let Some(message) = message {
|
||||
b = b.on_press(message);
|
||||
}
|
||||
b.into()
|
||||
};
|
||||
let tabs = container(
|
||||
row![
|
||||
tab(t!("Intensity"), !state.elevation_tab, Some(msg(PdfDialogMsg::MapTab(false)))),
|
||||
tab(t!("Elevation"), state.elevation_tab, Some(msg(PdfDialogMsg::MapTab(true)))),
|
||||
tab(t!("Classification"), false, None),
|
||||
]
|
||||
.spacing(2),
|
||||
)
|
||||
.padding(2)
|
||||
.style(well_style);
|
||||
|
||||
let slot = state.slot();
|
||||
let ramp = state.ramp();
|
||||
let colors: Vec<[u8; 3]> = ramp.map(|r| r.colors.clone()).unwrap_or_default();
|
||||
let ramp_card = card(
|
||||
t!("Color ramp").into_owned(),
|
||||
ramp_bar(&colors, slot.gradient, state.cloud.and(state.range()), !state.elevation_tab),
|
||||
);
|
||||
|
||||
let names: Vec<String> = state.ramps.iter().map(|r| r.name.clone()).collect();
|
||||
let scheme = pick_list(ramp.map(|r| r.name.clone()), names, |n: &String| n.clone())
|
||||
.on_select(|n| msg(PdfDialogMsg::MapScheme(n)))
|
||||
.text_size(12)
|
||||
.padding([5, 8])
|
||||
.width(Fill);
|
||||
let small = |label: std::borrow::Cow<'static, str>, message: PdfDialogMsg| {
|
||||
button(text(label).size(11).width(Fill).align_x(iced::Center))
|
||||
.on_press(msg(message))
|
||||
.style(button_style(false))
|
||||
.padding([5, 8])
|
||||
.width(Length::Fixed(118.0))
|
||||
};
|
||||
let counts: Vec<usize> = (2..=25).collect();
|
||||
let count = pick_list(Some(colors.len().max(2)), counts, |n: &usize| n.to_string())
|
||||
.on_select(|n| msg(PdfDialogMsg::MapCount(n)))
|
||||
.text_size(12)
|
||||
.padding([4, 8])
|
||||
.width(Length::Fixed(70.0));
|
||||
// Blend: a strip from the first colour to the last.
|
||||
let ends = [colors.first().copied().unwrap_or([255; 3]), colors.last().copied().unwrap_or([255; 3])];
|
||||
let blend_strip = row((0..6).map(|k| swatch(blend(&ends, k as f64 / 5.0), 3.0, 14.0)));
|
||||
let even = button(blend_strip).on_press(msg(PdfDialogMsg::MapEven)).style(button_style(false)).padding([5, 6]);
|
||||
let reverse = button(
|
||||
iced::widget::svg(crate::ui::icons::themed_handle(include_bytes!("../../../assets/icons/modify_reverse.svg")))
|
||||
.width(16.0)
|
||||
.height(16.0),
|
||||
)
|
||||
.on_press(msg(PdfDialogMsg::MapReverse))
|
||||
.style(button_style(false))
|
||||
.padding([4, 6]);
|
||||
let mut scheme_rows = column![
|
||||
row![scheme, small(t!("New"), PdfDialogMsg::MapNew)].spacing(8).align_y(iced::Center),
|
||||
row![
|
||||
text(t!("Number of colors")).size(11).width(Length::Fixed(110.0)),
|
||||
count,
|
||||
even,
|
||||
reverse,
|
||||
Space::new().width(Fill),
|
||||
small(t!("Delete"), PdfDialogMsg::MapDelete)
|
||||
]
|
||||
.spacing(8)
|
||||
.align_y(iced::Center),
|
||||
row![
|
||||
chip(t!("Display as gradient").into_owned(), slot.gradient, msg(PdfDialogMsg::MapGradient(!slot.gradient))),
|
||||
Space::new().width(Fill),
|
||||
small(t!("Rename"), PdfDialogMsg::MapRename)
|
||||
]
|
||||
.align_y(iced::Center),
|
||||
]
|
||||
.spacing(8);
|
||||
if let Some((_, name)) = &state.naming {
|
||||
scheme_rows = scheme_rows.push(
|
||||
row![
|
||||
text(t!("Name")).size(11).style(muted_style).width(Length::Fixed(110.0)),
|
||||
text_input("", name)
|
||||
.on_input(|v| msg(PdfDialogMsg::MapNameInput(v)))
|
||||
.on_submit(msg(PdfDialogMsg::MapNameOk))
|
||||
.size(11)
|
||||
.padding([4, 8])
|
||||
.width(Fill)
|
||||
.style(field_style),
|
||||
dialog_button(t!("Cancel"), msg(PdfDialogMsg::MapNameCancel), false),
|
||||
dialog_button(t!("OK"), msg(PdfDialogMsg::MapNameOk), true),
|
||||
]
|
||||
.spacing(6)
|
||||
.align_y(iced::Center),
|
||||
);
|
||||
}
|
||||
let scheme_card = card(t!("Color scheme").into_owned(), scheme_rows);
|
||||
|
||||
let has_cloud = state.cloud.is_some();
|
||||
let top = colors.first().copied().unwrap_or([255; 3]);
|
||||
let bottom = colors.last().copied().unwrap_or([255; 3]);
|
||||
let editable = has_cloud && !(state.elevation_tab && state.extents);
|
||||
let with_swatch = |field: Element<'a, Message>, color: [u8; 3]| -> Element<'a, Message> {
|
||||
row![field, swatch(color, 22.0, 22.0)].spacing(8).align_y(iced::Center).into()
|
||||
};
|
||||
let out_of_range = {
|
||||
let list = pick_list(Some(slot.out_of_range), OutOfRange::ALL.to_vec(), |o: &OutOfRange| o.to_string())
|
||||
.text_size(12)
|
||||
.padding([4, 8])
|
||||
.width(Fill);
|
||||
let list = if editable { list.on_select(|o| msg(PdfDialogMsg::MapOutOfRange(o))) } else { list };
|
||||
row![text(t!("Out of range points")).size(11).style(muted_style).width(Length::Fixed(150.0)), list]
|
||||
.spacing(6)
|
||||
.align_y(iced::Center)
|
||||
};
|
||||
let range_rows = if state.elevation_tab {
|
||||
let (low, high) = state.height;
|
||||
let interval = state.interval.as_str();
|
||||
column![
|
||||
chip(
|
||||
format!("{} ({} ~ {})", t!("Apply to extents of point cloud"), short(low), short(high)),
|
||||
state.extents,
|
||||
msg(PdfDialogMsg::MapExtents(!state.extents))
|
||||
),
|
||||
field(t!("Interval height").into_owned(), interval, editable, 150.0, |v| msg(PdfDialogMsg::MapInterval(v))),
|
||||
with_swatch(field(t!("Maximum elevation").into_owned(), &slot.max, editable, 150.0, |v| msg(PdfDialogMsg::MapMax(v))), top),
|
||||
with_swatch(field(t!("Minimum elevation").into_owned(), &slot.min, editable, 150.0, |v| msg(PdfDialogMsg::MapMin(v))), bottom),
|
||||
out_of_range,
|
||||
]
|
||||
.spacing(8)
|
||||
} else {
|
||||
column![
|
||||
with_swatch(field(t!("Maximum intensity").into_owned(), &slot.max, editable, 150.0, |v| msg(PdfDialogMsg::MapMax(v))), top),
|
||||
with_swatch(field(t!("Minimum intensity").into_owned(), &slot.min, editable, 150.0, |v| msg(PdfDialogMsg::MapMin(v))), bottom),
|
||||
out_of_range,
|
||||
]
|
||||
.spacing(8)
|
||||
};
|
||||
let range_card = card(t!("Range of colorized points").into_owned(), range_rows);
|
||||
|
||||
let body = row![
|
||||
container(ramp_card).width(Length::Fixed(150.0)),
|
||||
column![scheme_card, range_card].spacing(8).width(Fill),
|
||||
]
|
||||
.spacing(10);
|
||||
|
||||
let mut left = row![button(text("?").size(12))
|
||||
.on_press(msg(PdfDialogMsg::Help("pointcloudcolormap")))
|
||||
.style(button_style(false))
|
||||
.padding([5, 11])]
|
||||
.spacing(8)
|
||||
.align_y(iced::Center);
|
||||
if has_cloud {
|
||||
left = left.push(chip(
|
||||
t!("Make stylization current").into_owned(),
|
||||
state.make_current,
|
||||
msg(PdfDialogMsg::MapCurrent(!state.make_current)),
|
||||
));
|
||||
}
|
||||
let footer = row![
|
||||
left,
|
||||
Space::new().width(Fill),
|
||||
dialog_button(t!("Cancel"), Message::CloseModal, false),
|
||||
dialog_button(t!("Apply"), msg(PdfDialogMsg::MapApply), false),
|
||||
dialog_button(t!("OK"), msg(PdfDialogMsg::MapOk), true),
|
||||
]
|
||||
.spacing(6)
|
||||
.align_y(iced::Center);
|
||||
column![tabs, body, footer].spacing(10).padding([10, 12]).width(sizing.width).into()
|
||||
}
|
||||
Reference in new issue
Block a user