dominus-cli 2.2.1 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +58 -0
- package/README.md +32 -3
- package/dist/bridge-daemon.js.map +1 -1
- package/dist/install-plugin.js.map +1 -1
- package/dist/mcp.js +658 -20
- package/dist/mcp.js.map +1 -1
- package/package.json +5 -5
- package/plugin/Dominus.rbxmx +1064 -12
- package/plugin/src/Building.lua +427 -0
- package/plugin/src/CommandRouter.lua +41 -0
- package/plugin/src/Metadata.lua +411 -0
- package/plugin/src/Mutation.lua +45 -0
- package/plugin/src/ValueCodec.lua +116 -0
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
local ChangeHistoryService = game:GetService("ChangeHistoryService")
|
|
2
|
+
local Selection = game:GetService("Selection")
|
|
3
|
+
|
|
4
|
+
local InstanceRegistry = require(script.Parent.InstanceRegistry)
|
|
5
|
+
local ValueCodec = require(script.Parent.ValueCodec)
|
|
6
|
+
|
|
7
|
+
local Building = {}
|
|
8
|
+
|
|
9
|
+
local MAX_ITEMS = 200
|
|
10
|
+
local MAX_PROPERTIES = 100
|
|
11
|
+
|
|
12
|
+
local ALLOWED_PART_CLASSES = {
|
|
13
|
+
Part = true,
|
|
14
|
+
WedgePart = true,
|
|
15
|
+
CornerWedgePart = true,
|
|
16
|
+
TrussPart = true,
|
|
17
|
+
MeshPart = true,
|
|
18
|
+
SpawnLocation = true,
|
|
19
|
+
Seat = true,
|
|
20
|
+
VehicleSeat = true,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
local function resolve(target, label)
|
|
24
|
+
local instance, err = InstanceRegistry.resolve(target)
|
|
25
|
+
if not instance then
|
|
26
|
+
error(label .. ": " .. tostring(err))
|
|
27
|
+
end
|
|
28
|
+
return instance
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
local function cleanup(instances)
|
|
32
|
+
for _, instance in instances do
|
|
33
|
+
pcall(function()
|
|
34
|
+
if instance.Parent == nil then
|
|
35
|
+
instance:Destroy()
|
|
36
|
+
end
|
|
37
|
+
end)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
local function record(name, callback, created)
|
|
42
|
+
local recording = ChangeHistoryService:TryBeginRecording(name)
|
|
43
|
+
if not recording then
|
|
44
|
+
return { success = false, error = "Another plugin recording is already active" }
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
local ok, result = pcall(callback)
|
|
48
|
+
if not ok then
|
|
49
|
+
ChangeHistoryService:FinishRecording(recording, Enum.FinishRecordingOperation.Cancel)
|
|
50
|
+
cleanup(created or {})
|
|
51
|
+
return { success = false, error = tostring(result), rolledBack = true }
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
ChangeHistoryService:FinishRecording(recording, Enum.FinishRecordingOperation.Commit)
|
|
55
|
+
result.success = true
|
|
56
|
+
return result
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
local function finiteNumber(value, label)
|
|
60
|
+
assert(type(value) == "number" and value == value and math.abs(value) < math.huge, label .. " must be finite")
|
|
61
|
+
return value
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
local function vector3(value, label, default)
|
|
65
|
+
if value == nil then
|
|
66
|
+
return default
|
|
67
|
+
end
|
|
68
|
+
assert(type(value) == "table", label .. " must be a Vector3 object")
|
|
69
|
+
return Vector3.new(
|
|
70
|
+
finiteNumber(value.X or value.x or value[1], label .. ".x"),
|
|
71
|
+
finiteNumber(value.Y or value.y or value[2], label .. ".y"),
|
|
72
|
+
finiteNumber(value.Z or value.z or value[3], label .. ".z")
|
|
73
|
+
)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
local function rotation(value, label)
|
|
77
|
+
local degrees = vector3(value, label, Vector3.zero)
|
|
78
|
+
return CFrame.fromOrientation(math.rad(degrees.X), math.rad(degrees.Y), math.rad(degrees.Z))
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
local function setProperties(instance, properties)
|
|
82
|
+
if properties == nil then
|
|
83
|
+
return {}
|
|
84
|
+
end
|
|
85
|
+
assert(type(properties) == "table", "properties must be an object")
|
|
86
|
+
local count = 0
|
|
87
|
+
local applied = {}
|
|
88
|
+
for propertyName, value in properties do
|
|
89
|
+
count += 1
|
|
90
|
+
if count > MAX_PROPERTIES then
|
|
91
|
+
error("At most " .. MAX_PROPERTIES .. " properties are allowed per item")
|
|
92
|
+
end
|
|
93
|
+
applied[propertyName] = ValueCodec.setProperty(instance, propertyName, value)
|
|
94
|
+
end
|
|
95
|
+
return applied
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
local function validateItemCount(items, label, maximum)
|
|
99
|
+
assert(type(items) == "table" and #items > 0, label .. " must contain at least one item")
|
|
100
|
+
assert(#items <= maximum, label .. " is limited to " .. maximum .. " items")
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
local function assertMovable(instance, label)
|
|
104
|
+
assert(instance ~= game and instance.Parent ~= game, label .. " cannot be a DataModel service")
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
function Building.createParts(spec)
|
|
108
|
+
validateItemCount(spec.parts, "parts", MAX_ITEMS)
|
|
109
|
+
local parent = spec.parent and resolve(spec.parent, "parent") or workspace
|
|
110
|
+
local group = nil
|
|
111
|
+
local created = {}
|
|
112
|
+
|
|
113
|
+
return record("Dominus 2: Build parts", function()
|
|
114
|
+
if spec.groupName ~= nil then
|
|
115
|
+
assert(type(spec.groupName) == "string" and #spec.groupName > 0, "groupName must be a non-empty string")
|
|
116
|
+
local groupClass = spec.groupClass or "Model"
|
|
117
|
+
assert(groupClass == "Model" or groupClass == "Folder", "groupClass must be Model or Folder")
|
|
118
|
+
group = Instance.new(groupClass)
|
|
119
|
+
group.Name = spec.groupName
|
|
120
|
+
table.insert(created, group)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
local parts = {}
|
|
124
|
+
for index, item in spec.parts do
|
|
125
|
+
assert(type(item) == "table", "Part " .. index .. " must be an object")
|
|
126
|
+
assert(type(item.name) == "string" and #item.name > 0, "Part " .. index .. ".name must be a non-empty string")
|
|
127
|
+
local className = item.className or "Part"
|
|
128
|
+
if item.shape == "Wedge" then
|
|
129
|
+
className = "WedgePart"
|
|
130
|
+
elseif item.shape == "CornerWedge" then
|
|
131
|
+
className = "CornerWedgePart"
|
|
132
|
+
end
|
|
133
|
+
assert(ALLOWED_PART_CLASSES[className] == true, "Part " .. index .. " uses an unsupported class: " .. tostring(className))
|
|
134
|
+
|
|
135
|
+
local part = Instance.new(className)
|
|
136
|
+
table.insert(created, part)
|
|
137
|
+
part.Name = item.name
|
|
138
|
+
if part:IsA("BasePart") then
|
|
139
|
+
part.Anchored = item.anchored ~= false
|
|
140
|
+
end
|
|
141
|
+
if item.position then
|
|
142
|
+
part.Position = vector3(item.position, "Part " .. index .. ".position")
|
|
143
|
+
end
|
|
144
|
+
if item.orientation then
|
|
145
|
+
part.Orientation = vector3(item.orientation, "Part " .. index .. ".orientation")
|
|
146
|
+
end
|
|
147
|
+
if item.size then
|
|
148
|
+
part.Size = vector3(item.size, "Part " .. index .. ".size")
|
|
149
|
+
end
|
|
150
|
+
if item.color then
|
|
151
|
+
ValueCodec.setProperty(part, "Color", item.color)
|
|
152
|
+
end
|
|
153
|
+
if item.material then
|
|
154
|
+
ValueCodec.setProperty(part, "Material", item.material)
|
|
155
|
+
end
|
|
156
|
+
if item.transparency ~= nil then
|
|
157
|
+
part.Transparency = finiteNumber(item.transparency, "Part " .. index .. ".transparency")
|
|
158
|
+
end
|
|
159
|
+
if item.canCollide ~= nil then
|
|
160
|
+
assert(type(item.canCollide) == "boolean", "Part " .. index .. ".canCollide must be a boolean")
|
|
161
|
+
part.CanCollide = item.canCollide
|
|
162
|
+
end
|
|
163
|
+
if item.shape and className == "Part" then
|
|
164
|
+
ValueCodec.setProperty(part, "Shape", item.shape)
|
|
165
|
+
end
|
|
166
|
+
setProperties(part, item.properties)
|
|
167
|
+
part.Parent = group or parent
|
|
168
|
+
table.insert(parts, part)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
if group then
|
|
172
|
+
group.Parent = parent
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
local refs = {}
|
|
176
|
+
for _, part in parts do
|
|
177
|
+
table.insert(refs, InstanceRegistry.toRef(part))
|
|
178
|
+
end
|
|
179
|
+
return {
|
|
180
|
+
created = refs,
|
|
181
|
+
group = group and InstanceRegistry.toRef(group) or nil,
|
|
182
|
+
count = #refs,
|
|
183
|
+
}
|
|
184
|
+
end, created)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
function Building.cloneInstances(spec)
|
|
188
|
+
validateItemCount(spec.requests, "requests", 100)
|
|
189
|
+
local created = {}
|
|
190
|
+
|
|
191
|
+
return record("Dominus 2: Clone instances", function()
|
|
192
|
+
local clones = {}
|
|
193
|
+
local totalCopies = 0
|
|
194
|
+
for requestIndex, request in spec.requests do
|
|
195
|
+
assert(type(request) == "table", "Clone request " .. requestIndex .. " must be an object")
|
|
196
|
+
local source = resolve(request.target, "Clone request " .. requestIndex .. " target")
|
|
197
|
+
assertMovable(source, "Clone source")
|
|
198
|
+
local parent = request.parent and resolve(request.parent, "Clone request " .. requestIndex .. " parent") or source.Parent
|
|
199
|
+
assert(parent ~= nil, "Clone request " .. requestIndex .. " source has no parent")
|
|
200
|
+
local count = request.count or 1
|
|
201
|
+
assert(type(count) == "number" and count % 1 == 0 and count >= 1 and count <= 50, "count must be an integer from 1 to 50")
|
|
202
|
+
totalCopies += count
|
|
203
|
+
assert(totalCopies <= MAX_ITEMS, "A clone batch is limited to " .. MAX_ITEMS .. " copies")
|
|
204
|
+
local offset = vector3(request.offset, "Clone request " .. requestIndex .. ".offset", Vector3.zero)
|
|
205
|
+
local stepOffset = vector3(request.stepOffset, "Clone request " .. requestIndex .. ".stepOffset", Vector3.zero)
|
|
206
|
+
|
|
207
|
+
for copyIndex = 1, count do
|
|
208
|
+
local clone = source:Clone()
|
|
209
|
+
assert(clone ~= nil, "Clone request " .. requestIndex .. " target is not Archivable")
|
|
210
|
+
table.insert(created, clone)
|
|
211
|
+
if request.name then
|
|
212
|
+
clone.Name = count == 1 and request.name or (request.name .. " " .. copyIndex)
|
|
213
|
+
end
|
|
214
|
+
setProperties(clone, request.properties)
|
|
215
|
+
if clone:IsA("PVInstance") then
|
|
216
|
+
local totalOffset = offset + stepOffset * (copyIndex - 1)
|
|
217
|
+
clone:PivotTo(clone:GetPivot() + totalOffset)
|
|
218
|
+
elseif offset.Magnitude > 0 or stepOffset.Magnitude > 0 then
|
|
219
|
+
error("Clone request " .. requestIndex .. " cannot offset a non-PVInstance")
|
|
220
|
+
end
|
|
221
|
+
clone.Parent = parent
|
|
222
|
+
table.insert(clones, InstanceRegistry.toRef(clone))
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
return { clones = clones, count = #clones }
|
|
226
|
+
end, created)
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
local function validateIndependentTargets(targets)
|
|
230
|
+
for leftIndex, left in targets do
|
|
231
|
+
for rightIndex = leftIndex + 1, #targets do
|
|
232
|
+
local right = targets[rightIndex]
|
|
233
|
+
assert(not left:IsAncestorOf(right) and not right:IsAncestorOf(left), "Targets " .. leftIndex .. " and " .. rightIndex .. " overlap")
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
function Building.groupInstances(spec)
|
|
239
|
+
validateItemCount(spec.targets, "targets", 100)
|
|
240
|
+
assert(type(spec.name) == "string" and #spec.name > 0, "name must be a non-empty string")
|
|
241
|
+
local className = spec.className or "Model"
|
|
242
|
+
assert(className == "Model" or className == "Folder", "className must be Model or Folder")
|
|
243
|
+
local targets = {}
|
|
244
|
+
for index, targetRef in spec.targets do
|
|
245
|
+
local target = resolve(targetRef, "Target " .. index)
|
|
246
|
+
assertMovable(target, "Target " .. index)
|
|
247
|
+
table.insert(targets, target)
|
|
248
|
+
end
|
|
249
|
+
validateIndependentTargets(targets)
|
|
250
|
+
local parent = spec.parent and resolve(spec.parent, "parent") or targets[1].Parent
|
|
251
|
+
assert(parent ~= nil, "The first target has no parent")
|
|
252
|
+
local created = {}
|
|
253
|
+
|
|
254
|
+
return record("Dominus 2: Group instances", function()
|
|
255
|
+
local group = Instance.new(className)
|
|
256
|
+
table.insert(created, group)
|
|
257
|
+
group.Name = spec.name
|
|
258
|
+
group.Parent = parent
|
|
259
|
+
for _, target in targets do
|
|
260
|
+
assert(not target:IsAncestorOf(group), "Cannot group an ancestor into its descendant")
|
|
261
|
+
target.Parent = group
|
|
262
|
+
end
|
|
263
|
+
return { group = InstanceRegistry.toRef(group), count = #targets }
|
|
264
|
+
end, created)
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
function Building.ungroupInstances(spec)
|
|
268
|
+
assert(spec.confirm == true, "confirm must be true")
|
|
269
|
+
validateItemCount(spec.targets, "targets", 20)
|
|
270
|
+
|
|
271
|
+
return record("Dominus 2: Ungroup instances", function()
|
|
272
|
+
local moved = {}
|
|
273
|
+
local removed = {}
|
|
274
|
+
for index, targetRef in spec.targets do
|
|
275
|
+
local group = resolve(targetRef, "Group " .. index)
|
|
276
|
+
assert(group:IsA("Model") or group:IsA("Folder"), "Group " .. index .. " must be a Model or Folder")
|
|
277
|
+
assertMovable(group, "Group " .. index)
|
|
278
|
+
local parent = spec.parent and resolve(spec.parent, "parent") or group.Parent
|
|
279
|
+
assert(parent ~= nil, "Group " .. index .. " has no parent")
|
|
280
|
+
for _, child in group:GetChildren() do
|
|
281
|
+
child.Parent = parent
|
|
282
|
+
table.insert(moved, InstanceRegistry.toRef(child))
|
|
283
|
+
end
|
|
284
|
+
table.insert(removed, { name = group.Name, className = group.ClassName })
|
|
285
|
+
group:Destroy()
|
|
286
|
+
end
|
|
287
|
+
return { moved = moved, removedGroups = removed, count = #moved }
|
|
288
|
+
end)
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
local function absolutePivot(current, operation)
|
|
292
|
+
if operation.cframe then
|
|
293
|
+
return ValueCodec.decodeForCurrent(current, operation.cframe)
|
|
294
|
+
end
|
|
295
|
+
local position = vector3(operation.position, "position", current.Position)
|
|
296
|
+
local x, y, z = current:ToOrientation()
|
|
297
|
+
if operation.orientation then
|
|
298
|
+
local degrees = vector3(operation.orientation, "orientation")
|
|
299
|
+
x, y, z = math.rad(degrees.X), math.rad(degrees.Y), math.rad(degrees.Z)
|
|
300
|
+
end
|
|
301
|
+
return CFrame.new(position) * CFrame.fromOrientation(x, y, z)
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
function Building.transformInstances(spec)
|
|
305
|
+
validateItemCount(spec.operations, "operations", 100)
|
|
306
|
+
|
|
307
|
+
return record("Dominus 2: Transform instances", function()
|
|
308
|
+
local results = {}
|
|
309
|
+
for index, operation in spec.operations do
|
|
310
|
+
local target = resolve(operation.target, "Operation " .. index .. " target")
|
|
311
|
+
assert(target:IsA("PVInstance"), "Operation " .. index .. " target must be a Model or BasePart")
|
|
312
|
+
local current = target:GetPivot()
|
|
313
|
+
local mode = operation.mode or "absolute"
|
|
314
|
+
local nextPivot
|
|
315
|
+
if mode == "absolute" then
|
|
316
|
+
nextPivot = absolutePivot(current, operation)
|
|
317
|
+
elseif mode == "relative" then
|
|
318
|
+
local offset = vector3(operation.offset, "Operation " .. index .. ".offset", Vector3.zero)
|
|
319
|
+
local rotate = rotation(operation.rotationOffset, "Operation " .. index .. ".rotationOffset")
|
|
320
|
+
if operation.space == "local" then
|
|
321
|
+
nextPivot = current * CFrame.new(offset) * rotate
|
|
322
|
+
else
|
|
323
|
+
nextPivot = CFrame.new(offset) * current * rotate
|
|
324
|
+
end
|
|
325
|
+
else
|
|
326
|
+
error("Operation " .. index .. ".mode must be absolute or relative")
|
|
327
|
+
end
|
|
328
|
+
target:PivotTo(nextPivot)
|
|
329
|
+
table.insert(results, { target = InstanceRegistry.toRef(target), pivot = ValueCodec.encode(target:GetPivot()) })
|
|
330
|
+
end
|
|
331
|
+
return { transformed = results, count = #results }
|
|
332
|
+
end)
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
function Building.createWelds(spec)
|
|
336
|
+
validateItemCount(spec.welds, "welds", MAX_ITEMS)
|
|
337
|
+
local created = {}
|
|
338
|
+
|
|
339
|
+
return record("Dominus 2: Create welds", function()
|
|
340
|
+
local welds = {}
|
|
341
|
+
for index, item in spec.welds do
|
|
342
|
+
local part0 = resolve(item.part0, "Weld " .. index .. " part0")
|
|
343
|
+
local part1 = resolve(item.part1, "Weld " .. index .. " part1")
|
|
344
|
+
assert(part0:IsA("BasePart") and part1:IsA("BasePart"), "Weld " .. index .. " endpoints must be BaseParts")
|
|
345
|
+
assert(part0 ~= part1, "Weld " .. index .. " endpoints must be different")
|
|
346
|
+
local parent = item.parent and resolve(item.parent, "Weld " .. index .. " parent") or part0
|
|
347
|
+
local weld = Instance.new("WeldConstraint")
|
|
348
|
+
table.insert(created, weld)
|
|
349
|
+
weld.Name = item.name or (part0.Name .. "_to_" .. part1.Name)
|
|
350
|
+
weld.Part0 = part0
|
|
351
|
+
weld.Part1 = part1
|
|
352
|
+
weld.Parent = parent
|
|
353
|
+
table.insert(welds, InstanceRegistry.toRef(weld))
|
|
354
|
+
end
|
|
355
|
+
return { welds = welds, count = #welds }
|
|
356
|
+
end, created)
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
local function overlapParams(spec)
|
|
360
|
+
local params = OverlapParams.new()
|
|
361
|
+
local maxParts = spec.maxParts or 200
|
|
362
|
+
assert(type(maxParts) == "number" and maxParts % 1 == 0 and maxParts >= 1 and maxParts <= 500, "maxParts must be an integer from 1 to 500")
|
|
363
|
+
params.MaxParts = maxParts
|
|
364
|
+
params.RespectCanCollide = spec.respectCanCollide == true
|
|
365
|
+
if spec.filter and #spec.filter > 0 then
|
|
366
|
+
local instances = {}
|
|
367
|
+
for index, targetRef in spec.filter do
|
|
368
|
+
table.insert(instances, resolve(targetRef, "Filter " .. index))
|
|
369
|
+
end
|
|
370
|
+
params.FilterDescendantsInstances = instances
|
|
371
|
+
params.FilterType = spec.filterType == "Include" and Enum.RaycastFilterType.Include or Enum.RaycastFilterType.Exclude
|
|
372
|
+
end
|
|
373
|
+
return params
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
function Building.queryParts(spec)
|
|
377
|
+
local root = spec.root and resolve(spec.root, "root") or workspace
|
|
378
|
+
assert(root:IsA("WorldRoot"), "root must be Workspace or a WorldModel")
|
|
379
|
+
local mode = spec.mode or "box"
|
|
380
|
+
local found
|
|
381
|
+
if mode == "box" then
|
|
382
|
+
local position = vector3(spec.position, "position")
|
|
383
|
+
local size = vector3(spec.size, "size")
|
|
384
|
+
assert(size.X > 0 and size.Y > 0 and size.Z > 0, "size components must be greater than zero")
|
|
385
|
+
local box = CFrame.new(position) * rotation(spec.orientation, "orientation")
|
|
386
|
+
found = root:GetPartBoundsInBox(box, size, overlapParams(spec))
|
|
387
|
+
elseif mode == "radius" then
|
|
388
|
+
local position = vector3(spec.position, "position")
|
|
389
|
+
local radius = finiteNumber(spec.radius, "radius")
|
|
390
|
+
assert(radius > 0, "radius must be greater than zero")
|
|
391
|
+
found = root:GetPartBoundsInRadius(position, radius, overlapParams(spec))
|
|
392
|
+
else
|
|
393
|
+
error("mode must be box or radius")
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
table.sort(found, function(left, right)
|
|
397
|
+
return InstanceRegistry.getDisplayPath(left) < InstanceRegistry.getDisplayPath(right)
|
|
398
|
+
end)
|
|
399
|
+
local parts = {}
|
|
400
|
+
for _, part in found do
|
|
401
|
+
table.insert(parts, {
|
|
402
|
+
name = part.Name,
|
|
403
|
+
className = part.ClassName,
|
|
404
|
+
ref = InstanceRegistry.toRef(part),
|
|
405
|
+
position = ValueCodec.encode(part.Position),
|
|
406
|
+
size = ValueCodec.encode(part.Size),
|
|
407
|
+
})
|
|
408
|
+
end
|
|
409
|
+
return { success = true, parts = parts, count = #parts, mode = mode }
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
function Building.setSelection(spec)
|
|
413
|
+
assert(type(spec.targets) == "table", "targets must be an array")
|
|
414
|
+
assert(#spec.targets <= 200, "At most 200 instances can be selected")
|
|
415
|
+
local targets = {}
|
|
416
|
+
for index, targetRef in spec.targets do
|
|
417
|
+
table.insert(targets, resolve(targetRef, "Target " .. index))
|
|
418
|
+
end
|
|
419
|
+
Selection:Set(targets)
|
|
420
|
+
local selected = {}
|
|
421
|
+
for _, target in targets do
|
|
422
|
+
table.insert(selected, InstanceRegistry.toRef(target))
|
|
423
|
+
end
|
|
424
|
+
return { success = true, selection = selected, count = #selected }
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
return Building
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
local Building = require(script.Parent.Building)
|
|
1
2
|
local Explorer = require(script.Parent.Explorer)
|
|
2
3
|
local InstanceRegistry = require(script.Parent.InstanceRegistry)
|
|
4
|
+
local Metadata = require(script.Parent.Metadata)
|
|
3
5
|
local Mutation = require(script.Parent.Mutation)
|
|
4
6
|
local Properties = require(script.Parent.Properties)
|
|
5
7
|
local Reflection = require(script.Parent.Reflection)
|
|
@@ -37,6 +39,45 @@ local handlers = {
|
|
|
37
39
|
["studio:v2:apply"] = function(payload)
|
|
38
40
|
return Mutation.apply(payload)
|
|
39
41
|
end,
|
|
42
|
+
["studio:v2:create_parts"] = function(payload)
|
|
43
|
+
return Building.createParts(payload)
|
|
44
|
+
end,
|
|
45
|
+
["studio:v2:clone_instances"] = function(payload)
|
|
46
|
+
return Building.cloneInstances(payload)
|
|
47
|
+
end,
|
|
48
|
+
["studio:v2:group_instances"] = function(payload)
|
|
49
|
+
return Building.groupInstances(payload)
|
|
50
|
+
end,
|
|
51
|
+
["studio:v2:ungroup_instances"] = function(payload)
|
|
52
|
+
return Building.ungroupInstances(payload)
|
|
53
|
+
end,
|
|
54
|
+
["studio:v2:transform_instances"] = function(payload)
|
|
55
|
+
return Building.transformInstances(payload)
|
|
56
|
+
end,
|
|
57
|
+
["studio:v2:create_welds"] = function(payload)
|
|
58
|
+
return Building.createWelds(payload)
|
|
59
|
+
end,
|
|
60
|
+
["studio:v2:query_parts"] = function(payload)
|
|
61
|
+
return Building.queryParts(payload)
|
|
62
|
+
end,
|
|
63
|
+
["studio:v2:set_selection"] = function(payload)
|
|
64
|
+
return Building.setSelection(payload)
|
|
65
|
+
end,
|
|
66
|
+
["studio:v2:get_metadata"] = function(payload)
|
|
67
|
+
return Metadata.get(payload)
|
|
68
|
+
end,
|
|
69
|
+
["studio:v2:set_attributes"] = function(payload)
|
|
70
|
+
return Metadata.setAttributes(payload)
|
|
71
|
+
end,
|
|
72
|
+
["studio:v2:update_tags"] = function(payload)
|
|
73
|
+
return Metadata.updateTags(payload)
|
|
74
|
+
end,
|
|
75
|
+
["studio:v2:list_callable_methods"] = function(payload)
|
|
76
|
+
return Metadata.listCallableMethods(payload)
|
|
77
|
+
end,
|
|
78
|
+
["studio:v2:invoke_methods"] = function(payload)
|
|
79
|
+
return Metadata.invoke(payload)
|
|
80
|
+
end,
|
|
40
81
|
["studio:v2:delete"] = function(payload)
|
|
41
82
|
return Mutation.delete(payload)
|
|
42
83
|
end,
|